556 lines
31 KiB
HTML
556 lines
31 KiB
HTML
{% extends "base.html" %}
|
|
{% block content %}
|
|
<section class="section">
|
|
<div class="container tasks-settings-page">
|
|
<h1 class="title is-4">Task Settings</h1>
|
|
<p class="subtitle is-6">Project defaults flow into channel overrides. Use Quick Setup for normal operation; open Advanced Setup for full controls.</p>
|
|
|
|
<div class="notification is-light">
|
|
<div class="content is-size-7">
|
|
<p><strong>How Matching Works</strong></p>
|
|
<p><strong>Safe default behavior</strong>: strict matching, required prefixes, completion parsing enabled, and task-id announcements disabled.</p>
|
|
<p><strong>Hierarchy</strong>: <strong>Project</strong> flags are defaults. A mapped <strong>channel</strong> can override those defaults without changing project-wide behavior.</p>
|
|
<p><strong>Matching modes</strong>: <code>strict</code> (prefix only), <code>balanced</code> (prefix + limited hints), <code>broad</code> (more permissive, higher false-positive risk).</p>
|
|
</div>
|
|
</div>
|
|
|
|
<section class="block box">
|
|
<h2 class="title is-6">Quick Setup</h2>
|
|
<p class="help">Creates or updates project + optional epic + channel mapping in one submission.</p>
|
|
<p class="help">After setup, view tasks in <a href="{% url 'tasks_hub' %}">Tasks Hub</a>{% if prefill_service and prefill_identifier %} or <a href="{% url 'tasks_group' service=prefill_service identifier=prefill_identifier %}">this group task view</a>{% endif %}.</p>
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="quick_setup">
|
|
<div class="columns tasks-settings-inline-columns">
|
|
<div class="column">
|
|
<label class="label is-size-7">Service</label>
|
|
<div class="select is-small is-fullwidth">
|
|
<select name="service">
|
|
<option value="web" {% if prefill_service == 'web' %}selected{% endif %}>web</option>
|
|
<option value="xmpp" {% if prefill_service == 'xmpp' %}selected{% endif %}>xmpp</option>
|
|
<option value="signal" {% if prefill_service == 'signal' %}selected{% endif %}>signal</option>
|
|
<option value="whatsapp" {% if prefill_service == 'whatsapp' or not prefill_service %}selected{% endif %}>whatsapp</option>
|
|
</select>
|
|
</div>
|
|
<p class="help">Platform to watch for task extraction.</p>
|
|
</div>
|
|
<div class="column">
|
|
<label class="label is-size-7">Channel Identifier</label>
|
|
<input class="input is-small" name="channel_identifier" value="{{ prefill_identifier }}" placeholder="120...@g.us">
|
|
<p class="help">Exact chat/group id where messages are monitored.</p>
|
|
</div>
|
|
<div class="column">
|
|
<label class="label is-size-7">Project</label>
|
|
<input class="input is-small" name="project_name" placeholder="Project name">
|
|
<p class="help">Top-level container for derived tasks.</p>
|
|
</div>
|
|
<div class="column">
|
|
<label class="label is-size-7">Epic (optional)</label>
|
|
<input class="input is-small" name="epic_name" placeholder="Epic name">
|
|
<p class="help">Optional sub-container within a project.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="columns tasks-settings-inline-columns">
|
|
<div class="column">
|
|
<label class="label is-size-7">Match Mode</label>
|
|
<div class="select is-small is-fullwidth">
|
|
<select name="source_match_mode">
|
|
<option value="strict" selected>strict</option>
|
|
<option value="balanced">balanced</option>
|
|
<option value="broad">broad</option>
|
|
</select>
|
|
</div>
|
|
<p class="help">strict = safest, balanced = moderate, broad = permissive.</p>
|
|
</div>
|
|
<div class="column">
|
|
<label class="label is-size-7">Allowed Prefixes</label>
|
|
<input id="quick-prefixes" class="input is-small" name="source_allowed_prefixes" value="task:,todo:">
|
|
<p class="help">Click to add:
|
|
<button type="button" class="button is-small is-light prefix-chip" data-target="quick-prefixes" data-prefix="task:">task:</button>
|
|
<button type="button" class="button is-small is-light prefix-chip" data-target="quick-prefixes" data-prefix="todo:">todo:</button>
|
|
<button type="button" class="button is-small is-light prefix-chip" data-target="quick-prefixes" data-prefix="action:">action:</button>
|
|
</p>
|
|
</div>
|
|
<div class="column is-narrow">
|
|
<label class="label is-size-7">Min Chars</label>
|
|
<input class="input is-small" name="source_min_chars" value="3">
|
|
<p class="help">Minimum length after prefix.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field is-grouped is-grouped-multiline">
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="source_require_prefix" value="1" checked> Require Prefix</label>
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="source_derive_enabled" value="1" checked> Derivation Enabled</label>
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="source_completion_enabled" value="1" checked> Completion Enabled</label>
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="source_ai_title_enabled" value="1" checked> AI Title Enabled</label>
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="source_announce_task_id" value="1"> Announce Task ID</label>
|
|
</div>
|
|
<p class="help">
|
|
<strong>Require Prefix</strong>: only prefixed messages can create tasks.
|
|
<strong>Derivation Enabled</strong>: master on/off for extraction.
|
|
<strong>Completion Enabled</strong>: parse completion phrases like <code>done #12</code>.
|
|
<strong>AI Title Enabled</strong>: normalize task titles using AI.
|
|
<strong>Announce Task ID</strong>: send bot confirmation on creation.
|
|
</p>
|
|
|
|
<button class="button is-link is-small mt-3" type="submit">Apply Quick Setup</button>
|
|
</form>
|
|
</section>
|
|
|
|
<details class="tasks-advanced box" {% if not prefill_identifier %}open{% endif %}>
|
|
<summary class="title is-6">Advanced Setup</summary>
|
|
<p class="help">Manual controls for creating hierarchy entities, mapping channels, and overriding behavior.</p>
|
|
|
|
<div class="columns is-multiline tasks-settings-grid">
|
|
<div class="column is-6">
|
|
<section class="tasks-panel">
|
|
<h3 class="title is-7">Projects</h3>
|
|
<p class="help">Create projects and review their effective defaults.</p>
|
|
<form method="post" class="block">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="project_create">
|
|
<div class="field has-addons">
|
|
<div class="control is-expanded"><input class="input is-small" name="name" placeholder="Project name"></div>
|
|
<div class="control"><button class="button is-small is-link" type="submit">Add Project</button></div>
|
|
</div>
|
|
<p class="help">Project names should describe a long-running stream of work.</p>
|
|
</form>
|
|
<table class="table is-fullwidth is-striped is-size-7">
|
|
<thead><tr><th>Project</th><th>Defaults</th></tr></thead>
|
|
<tbody>
|
|
{% for row in projects %}
|
|
<tr>
|
|
<td>{{ row.name }}</td>
|
|
<td>mode={{ row.settings_effective.match_mode }}, prefixes={{ row.allowed_prefixes_csv }}, announce_id={{ row.settings_effective.announce_task_id }}</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="2">No projects.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="column is-6">
|
|
<section class="tasks-panel">
|
|
<h3 class="title is-7">Epics</h3>
|
|
<p class="help">Epics are optional subdivisions under a project.</p>
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="epic_create">
|
|
<div class="field">
|
|
<label class="label is-size-7">Project + Epic</label>
|
|
</div>
|
|
<div class="field has-addons">
|
|
<div class="control">
|
|
<div class="select is-small">
|
|
<select name="project_id">{% for p in projects %}<option value="{{ p.id }}">{{ p.name }}</option>{% endfor %}</select>
|
|
</div>
|
|
</div>
|
|
<div class="control is-expanded"><input class="input is-small" name="name" placeholder="Epic name"></div>
|
|
<div class="control"><button class="button is-small is-link" type="submit">Add Epic</button></div>
|
|
</div>
|
|
<p class="help">Choose the parent project first, then add the epic name.</p>
|
|
</form>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="column is-12">
|
|
<section class="tasks-panel">
|
|
<h3 class="title is-7">Group Mapping</h3>
|
|
<p class="help">Map a channel to a project/epic. Channel flags can later override project defaults.</p>
|
|
<form method="post" class="block">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="source_create">
|
|
<div class="columns tasks-settings-inline-columns">
|
|
<div class="column">
|
|
<div class="field">
|
|
<label class="label is-size-7">Service</label>
|
|
<div class="control">
|
|
<div class="select is-small is-fullwidth"><select name="service"><option>web</option><option>xmpp</option><option>signal</option><option>whatsapp</option></select></div>
|
|
</div>
|
|
<p class="help">Service/platform for this mapping.</p>
|
|
</div>
|
|
</div>
|
|
<div class="column">
|
|
<div class="field">
|
|
<label class="label is-size-7">Channel Identifier</label>
|
|
<div class="control">
|
|
<input class="input is-small" name="channel_identifier" value="{{ prefill_identifier }}" placeholder="channel">
|
|
</div>
|
|
<p class="help">Exact identifier for the chat/group.</p>
|
|
</div>
|
|
</div>
|
|
<div class="column">
|
|
<div class="field">
|
|
<label class="label is-size-7">Project</label>
|
|
<div class="control">
|
|
<div class="select is-small is-fullwidth"><select name="project_id">{% for p in projects %}<option value="{{ p.id }}">{{ p.name }}</option>{% endfor %}</select></div>
|
|
</div>
|
|
<p class="help">Project receiving derived tasks.</p>
|
|
</div>
|
|
</div>
|
|
<div class="column">
|
|
<div class="field">
|
|
<label class="label is-size-7">Epic (optional)</label>
|
|
<div class="control">
|
|
<div class="select is-small is-fullwidth"><select name="epic_id"><option value="">-</option>{% for e in epics %}<option value="{{ e.id }}">{{ e.project.name }} / {{ e.name }}</option>{% endfor %}</select></div>
|
|
</div>
|
|
<p class="help">Optional epic within that project.</p>
|
|
</div>
|
|
</div>
|
|
<div class="column is-narrow"><button class="button is-small is-link" type="submit">Add</button></div>
|
|
</div>
|
|
</form>
|
|
<table class="table is-fullwidth is-striped is-size-7">
|
|
<thead><tr><th>Chat</th><th>Project</th><th>Epic</th><th>Match</th><th>Announce</th><th></th></tr></thead>
|
|
<tbody>
|
|
{% for row in sources %}
|
|
<tr>
|
|
<td>{{ row.service }} · {{ row.channel_identifier }}</td>
|
|
<td>{{ row.project.name }}</td>
|
|
<td>{{ row.epic.name }}</td>
|
|
<td>{{ row.settings_effective.match_mode }}{% if row.settings_effective.require_prefix %} +prefix{% endif %}</td>
|
|
<td>{{ row.settings_effective.announce_task_id }}</td>
|
|
<td>
|
|
<form method="post" aria-label="Delete mapping {{ row.service }} {{ row.channel_identifier }}">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="source_delete">
|
|
<input type="hidden" name="source_id" value="{{ row.id }}">
|
|
<button class="button is-danger is-light is-small" type="submit">Delete</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="6">No mappings.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="column is-6">
|
|
<section class="tasks-panel">
|
|
<h3 class="title is-7">Project Defaults (All Mapped Chats)</h3>
|
|
<p class="help">Set baseline extraction behavior for a project. Every mapped chat inherits this unless overridden below.</p>
|
|
<form method="post" class="tasks-flag-form">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="project_flags_update">
|
|
<div class="field"><label class="label is-size-7">Project</label><div class="select is-small is-fullwidth"><select name="project_id">{% for p in projects %}<option value="{{ p.id }}">{{ p.name }}</option>{% endfor %}</select></div></div>
|
|
<div class="field"><label class="label is-size-7">Match Mode</label><div class="select is-small is-fullwidth"><select name="match_mode"><option value="strict" selected>strict</option><option value="balanced">balanced</option><option value="broad">broad</option></select></div></div>
|
|
<div class="field"><label class="label is-size-7">Allowed Prefixes</label><input id="proj-prefixes" class="input is-small" name="allowed_prefixes" value="task:,todo:"></div>
|
|
<div class="field"><label class="label is-size-7">Min Chars</label><input class="input is-small" name="min_chars" value="3"></div>
|
|
<div class="field is-grouped is-grouped-multiline">
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="require_prefix" value="1" checked> Require Prefix</label>
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="derive_enabled" value="1" checked> Derivation Enabled</label>
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="completion_enabled" value="1" checked> Completion Enabled</label>
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="announce_task_id" value="1"> Announce Task ID</label>
|
|
</div>
|
|
<p class="help">
|
|
<button type="button" class="button is-small is-light prefix-chip" data-target="proj-prefixes" data-prefix="task:">task:</button>
|
|
<button type="button" class="button is-small is-light prefix-chip" data-target="proj-prefixes" data-prefix="todo:">todo:</button>
|
|
<button type="button" class="button is-small is-light prefix-chip" data-target="proj-prefixes" data-prefix="action:">action:</button>
|
|
</p>
|
|
<button class="button is-small is-link is-light" type="submit">Save Project Flags</button>
|
|
</form>
|
|
<p class="help">
|
|
<strong>Require Prefix</strong>: allow task creation only with configured prefixes.
|
|
<strong>Derivation Enabled</strong>: turn extraction on/off for this project.
|
|
<strong>Completion Enabled</strong>: enable completion phrase parser.
|
|
<strong>Announce Task ID</strong>: emit confirmation messages on task creation.
|
|
</p>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="column is-6">
|
|
<section class="tasks-panel">
|
|
<h3 class="title is-7">Channel Override Flags</h3>
|
|
<p class="help">Channel-level override. Use only where this chat should behave differently from the project default.</p>
|
|
<form method="post" class="tasks-flag-form">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="source_flags_update">
|
|
<div class="field"><label class="label is-size-7">Mapped Channel</label><div class="select is-small is-fullwidth"><select name="source_id">{% for s in sources %}<option value="{{ s.id }}">{{ s.service }} · {{ s.channel_identifier }} · {{ s.project.name }}</option>{% endfor %}</select></div></div>
|
|
<div class="field"><label class="label is-size-7">Match Mode</label><div class="select is-small is-fullwidth"><select name="source_match_mode"><option value="strict" selected>strict</option><option value="balanced">balanced</option><option value="broad">broad</option></select></div></div>
|
|
<div class="field"><label class="label is-size-7">Allowed Prefixes</label><input id="source-prefixes" class="input is-small" name="source_allowed_prefixes" value="task:,todo:"></div>
|
|
<div class="field"><label class="label is-size-7">Min Chars</label><input class="input is-small" name="source_min_chars" value="3"></div>
|
|
<div class="field is-grouped is-grouped-multiline">
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="source_require_prefix" value="1" checked> Require Prefix</label>
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="source_derive_enabled" value="1" checked> Derivation Enabled</label>
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="source_completion_enabled" value="1" checked> Completion Enabled</label>
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="source_ai_title_enabled" value="1" checked> AI Title Enabled</label>
|
|
<label class="checkbox is-size-7"><input type="checkbox" name="source_announce_task_id" value="1"> Announce Task ID</label>
|
|
</div>
|
|
<p class="help">
|
|
<button type="button" class="button is-small is-light prefix-chip" data-target="source-prefixes" data-prefix="task:">task:</button>
|
|
<button type="button" class="button is-small is-light prefix-chip" data-target="source-prefixes" data-prefix="todo:">todo:</button>
|
|
<button type="button" class="button is-small is-light prefix-chip" data-target="source-prefixes" data-prefix="action:">action:</button>
|
|
</p>
|
|
<button class="button is-small is-link is-light" type="submit">Save Channel Flags</button>
|
|
</form>
|
|
<p class="help">
|
|
<strong>Require Prefix</strong>: enforce prefixes in this channel.
|
|
<strong>Derivation Enabled</strong>: extraction on/off for this channel only.
|
|
<strong>Completion Enabled</strong>: completion phrase parser in this channel.
|
|
<strong>AI Title Enabled</strong>: AI title normalization in this channel.
|
|
<strong>Announce Task ID</strong>: confirmation message in this channel.
|
|
</p>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="column is-6">
|
|
<section class="tasks-panel">
|
|
<h3 class="title is-7">Completion Phrases</h3>
|
|
<p class="help">Add parser phrases for completion statements followed by a task reference, e.g. <code>done #12</code>.</p>
|
|
<form method="post" class="block">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="pattern_create">
|
|
<div class="field has-addons">
|
|
<div class="control is-expanded"><input class="input is-small" name="phrase" placeholder="done"></div>
|
|
<div class="control"><button class="button is-small is-link" type="submit">Add Phrase</button></div>
|
|
</div>
|
|
</form>
|
|
<ul class="tasks-settings-list">{% for row in patterns %}<li>{{ row.phrase }}</li>{% empty %}<li>No phrases.</li>{% endfor %}</ul>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="column is-6">
|
|
<section class="tasks-panel">
|
|
<h3 class="title is-7">Providers</h3>
|
|
<p class="help">Controls outbound sync to external tracking systems. If disabled, tasks are still derived and visible inside GIA only.</p>
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="provider_update">
|
|
<input type="hidden" name="provider" value="mock">
|
|
<label class="checkbox"><input type="checkbox" name="enabled" value="1" {% if mock_provider_config and mock_provider_config.enabled %}checked{% endif %}> Enable mock provider</label>
|
|
<p class="help">Mock provider logs sync events without writing to a real third-party system.</p>
|
|
<div style="margin-top:0.5rem;">
|
|
<button class="button is-small is-link is-light" type="submit">Save</button>
|
|
</div>
|
|
</form>
|
|
<hr>
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="provider_update">
|
|
<input type="hidden" name="provider" value="codex_cli">
|
|
<label class="checkbox"><input type="checkbox" name="enabled" value="1" {% if codex_provider_config and codex_provider_config.enabled %}checked{% endif %}> Enable Codex CLI provider</label>
|
|
<p class="help">Codex task-sync runs in a dedicated worker (<code>python manage.py codex_worker</code>).</p>
|
|
<p class="help">This provider syncs task updates to Codex; it does not mirror whole chat threads in this phase.</p>
|
|
<div class="field" style="margin-top:0.5rem;">
|
|
<label class="label is-size-7">Command</label>
|
|
<input class="input is-small" name="command" value="{{ codex_provider_settings.command }}" placeholder="codex">
|
|
</div>
|
|
<div class="field">
|
|
<label class="label is-size-7">Workspace Root</label>
|
|
<input class="input is-small" name="workspace_root" value="{{ codex_provider_settings.workspace_root }}" placeholder="/code/xf">
|
|
</div>
|
|
<div class="field">
|
|
<label class="label is-size-7">Default Profile</label>
|
|
<input class="input is-small" name="default_profile" value="{{ codex_provider_settings.default_profile }}" placeholder="default">
|
|
</div>
|
|
<div class="field">
|
|
<label class="label is-size-7">Timeout Seconds</label>
|
|
<input class="input is-small" type="number" min="1" name="timeout_seconds" value="{{ codex_provider_settings.timeout_seconds }}">
|
|
</div>
|
|
<div style="margin-top:0.5rem;">
|
|
<button class="button is-small is-link is-light" type="submit">Save Codex Provider</button>
|
|
</div>
|
|
</form>
|
|
<p class="help">Browse all derived tasks in <a href="{% url 'tasks_hub' %}">Tasks Hub</a>.</p>
|
|
</section>
|
|
</div>
|
|
<div class="column is-12">
|
|
<section class="tasks-panel">
|
|
<h3 class="title is-7">External Chat Links</h3>
|
|
<p class="help">Map a GIA contact identifier to one Codex conversation/session so task-sync updates are routed to the correct Codex thread.</p>
|
|
{% if external_link_scoped %}
|
|
<article class="message is-info is-light tasks-link-scope-note">
|
|
<div class="message-body">
|
|
Scoped to <strong>{{ external_link_scope_label }}</strong>. Only matching identifiers are available below.
|
|
</div>
|
|
</article>
|
|
{% endif %}
|
|
<form method="post" class="block">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="external_chat_link_upsert">
|
|
<input type="hidden" name="prefill_service" value="{{ prefill_service }}">
|
|
<input type="hidden" name="prefill_identifier" value="{{ prefill_identifier }}">
|
|
<div class="columns is-multiline is-variable is-2 tasks-external-link-columns">
|
|
<div class="column is-12-mobile is-4-tablet is-2-desktop">
|
|
<div class="field">
|
|
<label class="label is-size-7">Provider</label>
|
|
<div class="control">
|
|
<div class="select is-small is-fullwidth">
|
|
<select name="provider">
|
|
<option value="codex_cli" selected>codex_cli</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="column is-12-mobile is-8-tablet is-5-desktop">
|
|
<div class="field">
|
|
<label class="label is-size-7">Contact Identifier</label>
|
|
<div class="control">
|
|
<div class="select is-small is-fullwidth">
|
|
<select name="person_identifier_id">
|
|
<option value="">Unlinked</option>
|
|
{% for row in external_link_person_identifiers %}
|
|
<option value="{{ row.id }}">{{ row.person.name }} · {{ row.service }} · {{ row.identifier }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<p class="help">Choose which contact/group in GIA this Codex chat mapping belongs to.</p>
|
|
</div>
|
|
</div>
|
|
<div class="column is-12-mobile is-8-tablet is-3-desktop">
|
|
<div class="field">
|
|
<label class="label is-size-7">External Chat ID</label>
|
|
<div class="control">
|
|
<input class="input is-small" name="external_chat_id" placeholder="codex-chat-...">
|
|
</div>
|
|
<p class="help">Use the Codex conversation/session ID (the stable ID Codex worker should target for task updates).</p>
|
|
</div>
|
|
</div>
|
|
<div class="column is-6-mobile is-4-tablet is-2-desktop">
|
|
<div class="field">
|
|
<label class="label is-size-7">Enabled</label>
|
|
<label class="checkbox"><input type="checkbox" name="enabled" value="1" checked> Active</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<div class="control">
|
|
<button class="button is-small is-link is-light" type="submit">Save Link</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
<table class="table is-fullwidth is-striped is-size-7">
|
|
<thead><tr><th>Provider</th><th>Person</th><th>Identifier</th><th>External Chat</th><th>Enabled</th><th></th></tr></thead>
|
|
<tbody>
|
|
{% for row in external_chat_links %}
|
|
<tr>
|
|
<td>{{ row.provider }}</td>
|
|
<td>{% if row.person %}{{ row.person.name }}{% else %}-{% endif %}</td>
|
|
<td>{% if row.person_identifier %}{{ row.person_identifier.service }} · {{ row.person_identifier.identifier }}{% else %}-{% endif %}</td>
|
|
<td>{{ row.external_chat_id }}</td>
|
|
<td>{{ row.enabled }}</td>
|
|
<td>
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
<input type="hidden" name="action" value="external_chat_link_delete">
|
|
<input type="hidden" name="external_link_id" value="{{ row.id }}">
|
|
<button class="button is-danger is-light is-small" type="submit">Delete</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="6">No external chat links.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
</section>
|
|
|
|
<style>
|
|
.tasks-settings-page .tasks-settings-inline-columns {
|
|
margin-left: 0;
|
|
margin-right: 0;
|
|
margin-top: 0;
|
|
margin-bottom: 0.4rem;
|
|
}
|
|
.tasks-settings-page .tasks-settings-inline-columns > .column {
|
|
padding-left: 0;
|
|
padding-right: 0.75rem;
|
|
}
|
|
.tasks-settings-page .tasks-settings-inline-columns > .column:last-child {
|
|
padding-right: 0;
|
|
}
|
|
.tasks-settings-page .tasks-settings-inline-columns .help {
|
|
display: block;
|
|
margin-top: 0.3rem;
|
|
line-height: 1.25;
|
|
}
|
|
.tasks-settings-page .tasks-settings-inline-columns .field {
|
|
margin-bottom: 0;
|
|
}
|
|
.tasks-settings-page .tasks-settings-list {
|
|
margin-top: 0.75rem;
|
|
}
|
|
.tasks-settings-page .tasks-link-scope-note {
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
.tasks-settings-page .tasks-external-link-columns .field {
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
.tasks-settings-page .prefix-chip {
|
|
margin-right: 0.25rem;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
.tasks-settings-page .tasks-advanced {
|
|
margin-top: 1.25rem;
|
|
padding: 1rem;
|
|
}
|
|
.tasks-settings-page .tasks-advanced > summary {
|
|
cursor: pointer;
|
|
list-style: none;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
.tasks-settings-page .tasks-advanced > summary::-webkit-details-marker {
|
|
display: none;
|
|
}
|
|
.tasks-settings-page .tasks-panel {
|
|
height: 100%;
|
|
margin-bottom: 0;
|
|
border-top: 1px solid #ececec;
|
|
padding-top: 0.75rem;
|
|
padding-right: 0.1rem;
|
|
overflow-x: hidden;
|
|
}
|
|
.tasks-settings-page .tasks-settings-grid > .column {
|
|
display: flex;
|
|
}
|
|
.tasks-settings-page .tasks-settings-grid > .column > .tasks-panel {
|
|
width: 100%;
|
|
}
|
|
.tasks-settings-page .tasks-settings-grid .table {
|
|
table-layout: fixed;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
(function () {
|
|
const chips = Array.from(document.querySelectorAll('.tasks-settings-page .prefix-chip'));
|
|
if (!chips.length) {
|
|
return;
|
|
}
|
|
const addPrefix = function (targetId, prefix) {
|
|
const input = document.getElementById(targetId);
|
|
if (!input) {
|
|
return;
|
|
}
|
|
const current = String(input.value || '').split(',').map(function (v) { return String(v || '').trim(); }).filter(Boolean);
|
|
const value = String(prefix || '').trim();
|
|
if (!value) {
|
|
return;
|
|
}
|
|
if (!current.includes(value)) {
|
|
current.push(value);
|
|
}
|
|
input.value = current.join(',');
|
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
};
|
|
chips.forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
addPrefix(String(btn.dataset.target || ''), String(btn.dataset.prefix || ''));
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|