Reimplement compose and add tiling windows
This commit is contained in:
@@ -1,116 +1,228 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<div>
|
||||
<h1 class="title is-4">Traces</h1>
|
||||
<p class="subtitle is-6">Tracked model calls and usage metrics for this account.</p>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<div>
|
||||
<h1 class="title is-4">Traces</h1>
|
||||
<p class="subtitle is-6">Tracked model calls and usage metrics for this account.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
{% if stats.total_runs %}
|
||||
<span class="tag is-success is-light">Tracking Active</span>
|
||||
{% else %}
|
||||
<span class="tag is-warning is-light">No Runs Yet</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
{% if stats.total_runs %}
|
||||
<span class="tag is-success is-light">Tracking Active</span>
|
||||
{% else %}
|
||||
<span class="tag is-warning is-light">No Runs Yet</span>
|
||||
{% endif %}
|
||||
|
||||
<article class="notification is-light">
|
||||
<p class="is-size-7 has-text-grey-dark">Execution health at a glance</p>
|
||||
<div class="tags mt-2">
|
||||
<span class="tag is-light">Total {{ stats.total_runs }}</span>
|
||||
<span class="tag is-success is-light">OK {{ stats.total_ok }}</span>
|
||||
<span class="tag is-danger is-light">Failed {{ stats.total_failed }}</span>
|
||||
<span class="tag is-info is-light">24h {{ stats.last_24h_runs }}</span>
|
||||
<span class="tag is-warning is-light">24h Failed {{ stats.last_24h_failed }}</span>
|
||||
<span class="tag is-link is-light">7d {{ stats.last_7d_runs }}</span>
|
||||
</div>
|
||||
<p class="is-size-7 has-text-grey-dark mt-3">Success Rate</p>
|
||||
<progress class="progress is-link is-small" value="{{ stats.success_rate }}" max="100">{{ stats.success_rate }}%</progress>
|
||||
</article>
|
||||
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-12-tablet is-4-desktop">
|
||||
<article class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-size-6">Reliability</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<table class="table is-fullwidth is-narrow is-size-7">
|
||||
<tbody>
|
||||
<tr><th>Total Runs</th><td>{{ stats.total_runs }}</td></tr>
|
||||
<tr><th>OK</th><td class="has-text-success">{{ stats.total_ok }}</td></tr>
|
||||
<tr><th>Failed</th><td class="has-text-danger">{{ stats.total_failed }}</td></tr>
|
||||
<tr><th>Success Rate</th><td>{{ stats.success_rate }}%</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="column is-12-tablet is-4-desktop">
|
||||
<article class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-size-6">Throughput</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<table class="table is-fullwidth is-narrow is-size-7">
|
||||
<tbody>
|
||||
<tr><th>Runs (24h)</th><td>{{ stats.last_24h_runs }}</td></tr>
|
||||
<tr><th>Failed (24h)</th><td>{{ stats.last_24h_failed }}</td></tr>
|
||||
<tr><th>Runs (7d)</th><td>{{ stats.last_7d_runs }}</td></tr>
|
||||
<tr><th>Avg Duration</th><td>{{ stats.avg_duration_ms }}ms</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="column is-12-tablet is-4-desktop">
|
||||
<article class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-size-6">Token Proxy (Chars)</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<table class="table is-fullwidth is-narrow is-size-7">
|
||||
<tbody>
|
||||
<tr><th>Total Prompt</th><td>{{ stats.total_prompt_chars }}</td></tr>
|
||||
<tr><th>Total Response</th><td>{{ stats.total_response_chars }}</td></tr>
|
||||
<tr><th>Avg Prompt</th><td>{{ stats.avg_prompt_chars }}</td></tr>
|
||||
<tr><th>Avg Response</th><td>{{ stats.avg_response_chars }}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="notification is-light">
|
||||
<p class="is-size-7 has-text-grey-dark">Execution health at a glance</p>
|
||||
<div class="tags mt-2">
|
||||
<span class="tag is-light">Total {{ stats.total_runs }}</span>
|
||||
<span class="tag is-success is-light">OK {{ stats.total_ok }}</span>
|
||||
<span class="tag is-danger is-light">Failed {{ stats.total_failed }}</span>
|
||||
<span class="tag is-info is-light">24h {{ stats.last_24h_runs }}</span>
|
||||
<span class="tag is-warning is-light">24h Failed {{ stats.last_24h_failed }}</span>
|
||||
<span class="tag is-link is-light">7d {{ stats.last_7d_runs }}</span>
|
||||
</div>
|
||||
<p class="is-size-7 has-text-grey-dark mt-3">Success Rate</p>
|
||||
<progress class="progress is-link is-small" value="{{ stats.success_rate }}" max="100">{{ stats.success_rate }}%</progress>
|
||||
</article>
|
||||
<div class="columns">
|
||||
<div class="column is-6">
|
||||
<article class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-size-6">By Operation</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-size-7 is-striped is-hoverable">
|
||||
<thead>
|
||||
<tr><th>Operation</th><th>Total</th><th>OK</th><th>Failed</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in operation_breakdown %}
|
||||
<tr>
|
||||
<td>{{ row.operation|default:"(none)" }}</td>
|
||||
<td>{{ row.total }}</td>
|
||||
<td class="has-text-success">{{ row.ok }}</td>
|
||||
<td class="has-text-danger">{{ row.failed }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4">No runs yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<article class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-size-6">By Model</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-size-7 is-striped is-hoverable">
|
||||
<thead>
|
||||
<tr><th>Model</th><th>Total</th><th>OK</th><th>Failed</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in model_breakdown %}
|
||||
<tr>
|
||||
<td>{{ row.model|default:"(none)" }}</td>
|
||||
<td>{{ row.total }}</td>
|
||||
<td class="has-text-success">{{ row.ok }}</td>
|
||||
<td class="has-text-danger">{{ row.failed }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4">No runs yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-12-tablet is-4-desktop">
|
||||
<article class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-size-6">Reliability</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<table class="table is-fullwidth is-narrow is-size-7">
|
||||
<tbody>
|
||||
<tr><th>Total Runs</th><td>{{ stats.total_runs }}</td></tr>
|
||||
<tr><th>OK</th><td class="has-text-success">{{ stats.total_ok }}</td></tr>
|
||||
<tr><th>Failed</th><td class="has-text-danger">{{ stats.total_failed }}</td></tr>
|
||||
<tr><th>Success Rate</th><td>{{ stats.success_rate }}%</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="column is-12-tablet is-4-desktop">
|
||||
<article class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-size-6">Throughput</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<table class="table is-fullwidth is-narrow is-size-7">
|
||||
<tbody>
|
||||
<tr><th>Runs (24h)</th><td>{{ stats.last_24h_runs }}</td></tr>
|
||||
<tr><th>Failed (24h)</th><td>{{ stats.last_24h_failed }}</td></tr>
|
||||
<tr><th>Runs (7d)</th><td>{{ stats.last_7d_runs }}</td></tr>
|
||||
<tr><th>Avg Duration</th><td>{{ stats.avg_duration_ms }}ms</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="column is-12-tablet is-4-desktop">
|
||||
<article class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-size-6">Token Proxy (Chars)</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<table class="table is-fullwidth is-narrow is-size-7">
|
||||
<tbody>
|
||||
<tr><th>Total Prompt</th><td>{{ stats.total_prompt_chars }}</td></tr>
|
||||
<tr><th>Total Response</th><td>{{ stats.total_response_chars }}</td></tr>
|
||||
<tr><th>Avg Prompt</th><td>{{ stats.avg_prompt_chars }}</td></tr>
|
||||
<tr><th>Avg Response</th><td>{{ stats.avg_response_chars }}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column is-6">
|
||||
<article class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-size-6">By Operation</p>
|
||||
<p class="card-header-title is-size-6">Recent Runs</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-size-7 is-striped is-hoverable">
|
||||
<thead>
|
||||
<tr><th>Operation</th><th>Total</th><th>OK</th><th>Failed</th></tr>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Started</th>
|
||||
<th>Status</th>
|
||||
<th>Operation</th>
|
||||
<th>Model</th>
|
||||
<th>Messages</th>
|
||||
<th>Prompt</th>
|
||||
<th>Response</th>
|
||||
<th>Duration</th>
|
||||
<th>Error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in operation_breakdown %}
|
||||
{% for run in runs %}
|
||||
<tr>
|
||||
<td>{{ row.operation|default:"(none)" }}</td>
|
||||
<td>{{ row.total }}</td>
|
||||
<td class="has-text-success">{{ row.ok }}</td>
|
||||
<td class="has-text-danger">{{ row.failed }}</td>
|
||||
<td>
|
||||
<button
|
||||
class="button is-small is-light trace-run-expand"
|
||||
type="button"
|
||||
data-detail-row="trace-run-detail-{{ run.id }}"
|
||||
data-detail-content="trace-run-detail-content-{{ run.id }}"
|
||||
data-expanded-label="Hide"
|
||||
data-collapsed-label="Show"
|
||||
hx-get="{% url 'ai_execution_run_detail' run_id=run.id %}"
|
||||
hx-target="#trace-run-detail-content-{{ run.id }}"
|
||||
hx-swap="innerHTML"
|
||||
hx-trigger="click once"
|
||||
>
|
||||
Show
|
||||
</button>
|
||||
</td>
|
||||
<td>{{ run.started_at }}</td>
|
||||
<td>
|
||||
{% if run.status == "ok" %}
|
||||
<span class="tag is-success is-light">ok</span>
|
||||
{% elif run.status == "failed" %}
|
||||
<span class="tag is-danger is-light">failed</span>
|
||||
{% else %}
|
||||
<span class="tag is-light">{{ run.status }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ run.operation|default:"-" }}</td>
|
||||
<td>{{ run.model|default:"-" }}</td>
|
||||
<td>{{ run.message_count }}</td>
|
||||
<td>{{ run.prompt_chars }}</td>
|
||||
<td>{{ run.response_chars }}</td>
|
||||
<td>{% if run.duration_ms %}{{ run.duration_ms }}ms{% else %}-{% endif %}</td>
|
||||
<td>
|
||||
{% if run.error %}
|
||||
<span title="{{ run.error }}">{{ run.error|truncatechars:120 }}</span>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="trace-run-detail-{{ run.id }}" class="is-hidden">
|
||||
<td colspan="10">
|
||||
<div id="trace-run-detail-content-{{ run.id }}" class="trace-run-detail-shell is-size-7 has-text-grey">
|
||||
Click Show to load run details.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4">No runs yet.</td></tr>
|
||||
<tr><td colspan="10">No runs yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -118,115 +230,7 @@
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="column is-6">
|
||||
<article class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-size-6">By Model</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-size-7 is-striped is-hoverable">
|
||||
<thead>
|
||||
<tr><th>Model</th><th>Total</th><th>OK</th><th>Failed</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in model_breakdown %}
|
||||
<tr>
|
||||
<td>{{ row.model|default:"(none)" }}</td>
|
||||
<td>{{ row.total }}</td>
|
||||
<td class="has-text-success">{{ row.ok }}</td>
|
||||
<td class="has-text-danger">{{ row.failed }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4">No runs yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="card">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title is-size-6">Recent Runs</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-size-7 is-striped is-hoverable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Started</th>
|
||||
<th>Status</th>
|
||||
<th>Operation</th>
|
||||
<th>Model</th>
|
||||
<th>Messages</th>
|
||||
<th>Prompt</th>
|
||||
<th>Response</th>
|
||||
<th>Duration</th>
|
||||
<th>Error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for run in runs %}
|
||||
<tr>
|
||||
<td>
|
||||
<button
|
||||
class="button is-small is-light trace-run-expand"
|
||||
type="button"
|
||||
data-detail-row="trace-run-detail-{{ run.id }}"
|
||||
data-detail-content="trace-run-detail-content-{{ run.id }}"
|
||||
data-expanded-label="Hide"
|
||||
data-collapsed-label="Show"
|
||||
hx-get="{% url 'ai_execution_run_detail' run_id=run.id %}"
|
||||
hx-target="#trace-run-detail-content-{{ run.id }}"
|
||||
hx-swap="innerHTML"
|
||||
hx-trigger="click once"
|
||||
>
|
||||
Show
|
||||
</button>
|
||||
</td>
|
||||
<td>{{ run.started_at }}</td>
|
||||
<td>
|
||||
{% if run.status == "ok" %}
|
||||
<span class="tag is-success is-light">ok</span>
|
||||
{% elif run.status == "failed" %}
|
||||
<span class="tag is-danger is-light">failed</span>
|
||||
{% else %}
|
||||
<span class="tag is-light">{{ run.status }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ run.operation|default:"-" }}</td>
|
||||
<td>{{ run.model|default:"-" }}</td>
|
||||
<td>{{ run.message_count }}</td>
|
||||
<td>{{ run.prompt_chars }}</td>
|
||||
<td>{{ run.response_chars }}</td>
|
||||
<td>{% if run.duration_ms %}{{ run.duration_ms }}ms{% else %}-{% endif %}</td>
|
||||
<td>
|
||||
{% if run.error %}
|
||||
<span title="{{ run.error }}">{{ run.error|truncatechars:120 }}</span>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="trace-run-detail-{{ run.id }}" class="is-hidden">
|
||||
<td colspan="10">
|
||||
<div id="trace-run-detail-content-{{ run.id }}" class="trace-run-detail-shell is-size-7 has-text-grey">
|
||||
Click Show to load run details.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="10">No runs yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<script>
|
||||
(function () {
|
||||
document.querySelectorAll(".trace-run-expand").forEach(function (button) {
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
{% extends "index.html" %}
|
||||
|
||||
{% block load_widgets %}
|
||||
<div
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{% url 'ai_workspace_contacts' type='widget' %}"
|
||||
hx-target="#widgets-here"
|
||||
hx-trigger="load"
|
||||
hx-swap="afterend"
|
||||
style="display: none;"></div>
|
||||
{% url 'ai_workspace_contacts' type='widget' as contacts_widget_url %}
|
||||
{% include "partials/workspace-widget-loader.html" with widget_url=contacts_widget_url %}
|
||||
{% if selected_person_id %}
|
||||
<div
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{% url 'ai_workspace_person' type='widget' person_id=selected_person_id %}"
|
||||
hx-target="#widgets-here"
|
||||
hx-trigger="load delay:250ms"
|
||||
hx-swap="afterend"
|
||||
style="display: none;"></div>
|
||||
{% url 'ai_workspace_person' type='widget' person_id=selected_person_id as person_widget_url %}
|
||||
{% include "partials/workspace-widget-loader.html" with widget_url=person_widget_url trigger_delay="250ms" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container gia-page-shell">
|
||||
<div class="gia-page-header">
|
||||
<div>
|
||||
<h1 class="title is-4">Codex Status</h1>
|
||||
<p class="subtitle is-6">Worker-backed task sync status, runs, and approvals for the canonical GIA task store.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article class="box">
|
||||
<div class="codex-inline-stats">
|
||||
<span><strong>Provider</strong> codex_cli</span>
|
||||
<span><strong>Health</strong> <span class="{% if health and health.ok %}has-text-success{% else %}has-text-danger{% endif %}">{% if health and health.ok %}online{% else %}offline{% endif %}</span></span>
|
||||
<span><strong>Pending</strong> {{ queue_counts.pending }}</span>
|
||||
<span><strong>Waiting Approval</strong> {{ queue_counts.waiting_approval }}</span>
|
||||
</div>
|
||||
{% if health and health.error %}
|
||||
<p class="help">Healthcheck error: <code>{{ health.error }}</code></p>
|
||||
{% endif %}
|
||||
<p class="help">Config snapshot: command=<code>{{ provider_settings.command }}</code>, workspace=<code>{{ provider_settings.workspace_root|default:"-" }}</code>, profile=<code>{{ provider_settings.default_profile|default:"-" }}</code>, instance=<code>{{ provider_settings.instance_label }}</code>, approver=<code>{{ provider_settings.approver_service }} {{ provider_settings.approver_identifier }}</code>.</p>
|
||||
<p class="help"><a href="{% url 'tasks_settings' %}">Edit in Task Automation</a>.</p>
|
||||
</article>
|
||||
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Run Filters</h2>
|
||||
<form method="get">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-2">
|
||||
<label class="label is-size-7">Status</label>
|
||||
<input class="input is-small" name="status" value="{{ filters.status }}" placeholder="ok/failed/...">
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<label class="label is-size-7">Service</label>
|
||||
<input class="input is-small" name="service" value="{{ filters.service }}" placeholder="signal">
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<label class="label is-size-7">Channel</label>
|
||||
<input class="input is-small" name="channel" value="{{ filters.channel }}" placeholder="identifier">
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<label class="label is-size-7">Project</label>
|
||||
<div class="select is-small is-fullwidth">
|
||||
<select name="project">
|
||||
<option value="">All</option>
|
||||
{% for row in projects %}
|
||||
<option value="{{ row.id }}" {% if filters.project == row.id|stringformat:"s" %}selected{% endif %}>{{ row.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-2">
|
||||
<label class="label is-size-7">Date From</label>
|
||||
<input class="input is-small" type="date" name="date_from" value="{{ filters.date_from }}">
|
||||
</div>
|
||||
</div>
|
||||
<button class="button is-small is-link is-light" type="submit">Apply</button>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Runs</h2>
|
||||
<table class="table is-fullwidth is-size-7 is-striped">
|
||||
<thead><tr><th>When</th><th>Status</th><th>Service/Channel</th><th>Project</th><th>Task</th><th>Summary</th><th>Files</th><th>Links</th></tr></thead>
|
||||
<tbody>
|
||||
{% for run in runs %}
|
||||
<tr>
|
||||
<td>{{ run.created_at }}</td>
|
||||
<td>{{ run.status }}</td>
|
||||
<td>{{ run.source_service }} · <code>{{ run.source_channel }}</code></td>
|
||||
<td>{{ run.project.name|default:"-" }}</td>
|
||||
<td>{% if run.task %}<a href="{% url 'tasks_task' task_id=run.task.id %}">#{{ run.task.reference_code }}</a>{% else %}-{% endif %}</td>
|
||||
<td>{{ run.result_payload.summary|default:"-" }}</td>
|
||||
<td>{{ run.result_payload.files_modified_count|default:"0" }}</td>
|
||||
<td>
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<p><strong>Request</strong></p>
|
||||
<pre>{{ run.request_payload }}</pre>
|
||||
<p><strong>Result</strong></p>
|
||||
<pre>{{ run.result_payload }}</pre>
|
||||
<p><strong>Error</strong> {{ run.error|default:"-" }}</p>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="8">No runs.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Approvals Queue</h2>
|
||||
<table class="table is-fullwidth is-size-7 is-striped">
|
||||
<thead><tr><th>Requested</th><th>Approval Key</th><th>Status</th><th>Summary</th><th>Permissions</th><th>Run</th><th>Task</th><th>Actions</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in permission_requests %}
|
||||
<tr>
|
||||
<td>{{ row.requested_at }}</td>
|
||||
<td><code>{{ row.approval_key }}</code></td>
|
||||
<td>{{ row.status }}</td>
|
||||
<td>{{ row.summary|default:"-" }}</td>
|
||||
<td><pre>{{ row.requested_permissions }}</pre></td>
|
||||
<td><code>{{ row.codex_run_id }}</code></td>
|
||||
<td>{% if row.codex_run.task %}<a href="{% url 'tasks_task' task_id=row.codex_run.task.id %}">#{{ row.codex_run.task.reference_code }}</a>{% else %}-{% endif %}</td>
|
||||
<td>
|
||||
{% if row.status == 'pending' %}
|
||||
<form method="post" action="{% url 'codex_approval' %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="request_id" value="{{ row.id }}">
|
||||
<input type="hidden" name="decision" value="approve">
|
||||
<button class="button is-small is-success is-light" type="submit">Approve</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'codex_approval' %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="request_id" value="{{ row.id }}">
|
||||
<input type="hidden" name="decision" value="deny">
|
||||
<button class="button is-small is-danger is-light" type="submit">Deny</button>
|
||||
</form>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="8">No permission requests.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
<style>
|
||||
.codex-inline-stats {
|
||||
display: flex;
|
||||
gap: 0.95rem;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.92rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.codex-inline-stats span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -463,16 +463,8 @@
|
||||
return;
|
||||
}
|
||||
const applyDefaults = function () {
|
||||
const slug = String(commandSelect.value || "").trim().toLowerCase();
|
||||
if (slug === "codex") {
|
||||
triggerInput.value = ".codex";
|
||||
if (!nameInput.value || nameInput.value === "Business Plan") {
|
||||
nameInput.value = "Codex";
|
||||
}
|
||||
return;
|
||||
}
|
||||
triggerInput.value = ".bp";
|
||||
if (!nameInput.value || nameInput.value === "Codex") {
|
||||
if (!nameInput.value) {
|
||||
nameInput.value = "Business Plan";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
{% extends "index.html" %}
|
||||
|
||||
{% block load_widgets %}
|
||||
<div
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{{ contacts_widget_url }}"
|
||||
hx-target="#widgets-here"
|
||||
hx-trigger="load"
|
||||
hx-swap="afterend"
|
||||
style="display: none;"></div>
|
||||
{% include "partials/workspace-widget-loader.html" with widget_url=contacts_widget_url %}
|
||||
{% include "partials/workspace-widget-loader.html" with widget_url=history_widget_url trigger_delay="125ms" %}
|
||||
{% if initial_widget_url %}
|
||||
<div
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{{ initial_widget_url }}"
|
||||
hx-target="#widgets-here"
|
||||
hx-trigger="load delay:250ms"
|
||||
hx-swap="afterend"
|
||||
style="display: none;"></div>
|
||||
{% include "partials/workspace-widget-loader.html" with widget_url=initial_widget_url trigger_delay="250ms" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block extra_head_assets %}
|
||||
{% include "partials/compose-panel-assets.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section pt-5 pb-0">
|
||||
<div class="container">
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
{% extends "index.html" %}
|
||||
|
||||
{% block load_widgets %}
|
||||
<div
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{{ tabs_widget_url }}"
|
||||
hx-target="#widgets-here"
|
||||
hx-trigger="load"
|
||||
hx-swap="afterend"
|
||||
style="display: none;"></div>
|
||||
{% include "partials/workspace-widget-loader.html" with widget_url=tabs_widget_url %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
{% extends "index.html" %}
|
||||
|
||||
{% block load_widgets %}
|
||||
<div
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{% url accounts_url_name type='widget' %}"
|
||||
hx-target="#widgets-here"
|
||||
hx-trigger="load"
|
||||
hx-swap="afterend"
|
||||
style="display: none;"></div>
|
||||
{% url accounts_url_name type='widget' as accounts_widget_url %}
|
||||
{% include "partials/workspace-widget-loader.html" with widget_url=accounts_widget_url %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -15,12 +15,6 @@
|
||||
</p>
|
||||
<div class="buttons">
|
||||
<a class="button is-small is-light" href="{% url 'tasks_hub' %}">Back</a>
|
||||
<form method="post" action="{% url 'tasks_codex_submit' %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="task_id" value="{{ task.id }}">
|
||||
<input type="hidden" name="next" value="{% url 'tasks_task' task_id=task.id %}">
|
||||
<button class="button is-small is-link is-light" type="submit">Send to Codex</button>
|
||||
</form>
|
||||
</div>
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Events</h2>
|
||||
@@ -57,57 +51,6 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
<article class="box">
|
||||
<h2 class="title is-6">External Sync</h2>
|
||||
<table class="table is-fullwidth is-size-7">
|
||||
<thead><tr><th>When</th><th>Provider</th><th>Status</th><th>Error</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in sync_events %}
|
||||
<tr><td>{{ row.updated_at }}</td><td>{{ row.provider }}</td><td>{{ row.status }}</td><td>{{ row.error }}</td></tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4">No sync events.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Codex Runs</h2>
|
||||
<table class="table is-fullwidth is-size-7">
|
||||
<thead><tr><th>When</th><th>Status</th><th>Summary</th><th>Files</th><th>Error</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in codex_runs %}
|
||||
<tr>
|
||||
<td>{{ row.updated_at }}</td>
|
||||
<td>{{ row.status }}</td>
|
||||
<td>{{ row.result_payload.summary|default:"-" }}</td>
|
||||
<td>{{ row.result_payload.files_modified_count|default:"0" }}</td>
|
||||
<td>{{ row.error|default:"" }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5">No Codex runs.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Permission Requests</h2>
|
||||
<table class="table is-fullwidth is-size-7">
|
||||
<thead><tr><th>When</th><th>Approval Key</th><th>Status</th><th>Summary</th><th>Resolved</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in permission_requests %}
|
||||
<tr>
|
||||
<td>{{ row.requested_at }}</td>
|
||||
<td><code>{{ row.approval_key }}</code></td>
|
||||
<td>{{ row.status }}</td>
|
||||
<td>{{ row.summary|default:"-" }}</td>
|
||||
<td>{{ row.resolved_at|default:"-" }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5">No permission requests.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</div></section>
|
||||
<style>
|
||||
.task-event-payload {
|
||||
|
||||
@@ -216,37 +216,6 @@
|
||||
<td>{{ row.status_snapshot }}</td>
|
||||
<td>
|
||||
<a class="button is-small is-light" href="{% url 'tasks_task' task_id=row.id %}">Open</a>
|
||||
{% if enabled_providers|length == 1 %}
|
||||
<form method="post" action="{% url 'tasks_codex_submit' %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="task_id" value="{{ row.id }}">
|
||||
<input type="hidden" name="next" value="{% url 'tasks_hub' %}">
|
||||
<input type="hidden" name="provider" value="{{ enabled_providers.0 }}">
|
||||
<button class="button is-small is-link is-light" type="submit">
|
||||
Send to {% if enabled_providers.0 == "claude_cli" %}Claude{% else %}Codex{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
{% elif enabled_providers %}
|
||||
<form method="post" action="{% url 'tasks_codex_submit' %}" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="task_id" value="{{ row.id }}">
|
||||
<input type="hidden" name="next" value="{% url 'tasks_hub' %}">
|
||||
<div class="field has-addons" style="display:inline-flex;">
|
||||
<div class="control">
|
||||
<div class="select is-small">
|
||||
<select name="provider">
|
||||
{% for p in enabled_providers %}
|
||||
<option value="{{ p }}">{% if p == "claude_cli" %}Claude{% else %}Codex{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-small is-link is-light" type="submit">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
|
||||
@@ -322,7 +322,7 @@
|
||||
<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>
|
||||
<p class="help">Controls outbound sync from canonical GIA tasks. If disabled, tasks still work inside GIA but no sync event is emitted.</p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="provider_update">
|
||||
@@ -333,250 +333,9 @@
|
||||
<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 config is global per-user and shared across all projects/chats. This phase is task-sync only (no full transcript mirroring by default).</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 class="field">
|
||||
<label class="label is-size-7">Instance Label</label>
|
||||
<input class="input is-small" name="instance_label" value="{{ codex_provider_settings.instance_label }}" placeholder="default">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label is-size-7">Approver Service</label>
|
||||
<input class="input is-small" name="approver_service" value="{{ codex_provider_settings.approver_service }}" placeholder="signal">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label is-size-7">Approver Identifier</label>
|
||||
<input class="input is-small" name="approver_identifier" value="{{ codex_provider_settings.approver_identifier }}" placeholder="+15550000001">
|
||||
</div>
|
||||
<div style="margin-top:0.5rem;">
|
||||
<button class="button is-small is-link is-light" type="submit">Save Codex Provider</button>
|
||||
<a class="button is-small is-light" href="{% url 'codex_settings' %}">Open Codex Status</a>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<article class="box" style="margin-top:0.5rem;">
|
||||
<h4 class="title is-7">Codex Compact Summary</h4>
|
||||
<p class="help">
|
||||
Health:
|
||||
{% if codex_compact_summary.healthcheck_ok %}
|
||||
<span class="tag is-success is-light">online</span>
|
||||
{% else %}
|
||||
<span class="tag is-danger is-light">offline</span>
|
||||
{% endif %}
|
||||
{% if codex_compact_summary.healthcheck_error %}
|
||||
<code>{{ codex_compact_summary.healthcheck_error }}</code>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p class="help">
|
||||
Worker heartbeat:
|
||||
{% if codex_compact_summary.worker_heartbeat_at %}
|
||||
{{ codex_compact_summary.worker_heartbeat_at }} ({{ codex_compact_summary.worker_heartbeat_age }})
|
||||
{% else %}
|
||||
no worker activity yet
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="tags">
|
||||
<span class="tag is-light">pending {{ codex_compact_summary.queue_counts.pending }}</span>
|
||||
<span class="tag is-warning is-light">waiting_approval {{ codex_compact_summary.queue_counts.waiting_approval }}</span>
|
||||
<span class="tag is-danger is-light">failed {{ codex_compact_summary.queue_counts.failed }}</span>
|
||||
<span class="tag is-success is-light">ok {{ codex_compact_summary.queue_counts.ok }}</span>
|
||||
</div>
|
||||
<table class="table is-fullwidth is-size-7 is-striped" style="margin-top:0.5rem;">
|
||||
<thead><tr><th>When</th><th>Status</th><th>Task</th><th>Summary</th></tr></thead>
|
||||
<tbody>
|
||||
{% for run in codex_compact_summary.recent_runs %}
|
||||
<tr>
|
||||
<td>{{ run.created_at }}</td>
|
||||
<td>{{ run.status }}</td>
|
||||
<td>{% if run.task %}<a href="{% url 'tasks_task' task_id=run.task.id %}">#{{ run.task.reference_code }}</a>{% else %}-{% endif %}</td>
|
||||
<td>{{ run.result_payload.summary|default:"-" }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4">No runs yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
<hr>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="provider_update">
|
||||
<input type="hidden" name="provider" value="claude_cli">
|
||||
<label class="checkbox"><input type="checkbox" name="enabled" value="1" {% if claude_provider_config and claude_provider_config.enabled %}checked{% endif %}> Enable Claude CLI provider</label>
|
||||
<p class="help">Claude task-sync runs in the same dedicated worker (<code>python manage.py codex_worker</code>).</p>
|
||||
<p class="help">This provider config is global per-user and shared across all projects/chats.</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="{{ claude_provider_settings.command }}" placeholder="claude">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label is-size-7">Workspace Root</label>
|
||||
<input class="input is-small" name="workspace_root" value="{{ claude_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="{{ claude_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="{{ claude_provider_settings.timeout_seconds }}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label is-size-7">Approver Service</label>
|
||||
<input class="input is-small" name="approver_service" value="{{ claude_provider_settings.approver_service }}" placeholder="signal">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label is-size-7">Approver Identifier</label>
|
||||
<input class="input is-small" name="approver_identifier" value="{{ claude_provider_settings.approver_identifier }}" placeholder="+15550000001">
|
||||
</div>
|
||||
<div style="margin-top:0.5rem;">
|
||||
<button class="button is-small is-link is-light" type="submit">Save Claude Provider</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<article class="box" style="margin-top:0.5rem;">
|
||||
<h4 class="title is-7">Claude Compact Summary</h4>
|
||||
<p class="help">
|
||||
Health:
|
||||
{% if claude_compact_summary.healthcheck_ok %}
|
||||
<span class="tag is-success is-light">online</span>
|
||||
{% else %}
|
||||
<span class="tag is-danger is-light">offline</span>
|
||||
{% endif %}
|
||||
{% if claude_compact_summary.healthcheck_error %}
|
||||
<code>{{ claude_compact_summary.healthcheck_error }}</code>
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="tags">
|
||||
<span class="tag is-light">pending {{ claude_compact_summary.queue_counts.pending }}</span>
|
||||
<span class="tag is-warning is-light">waiting_approval {{ claude_compact_summary.queue_counts.waiting_approval }}</span>
|
||||
<span class="tag is-danger is-light">failed {{ claude_compact_summary.queue_counts.failed }}</span>
|
||||
<span class="tag is-success is-light">ok {{ claude_compact_summary.queue_counts.ok }}</span>
|
||||
</div>
|
||||
</article>
|
||||
<p class="help">Browse all derived tasks in <a href="{% url 'tasks_hub' %}">Task Inbox</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 one GIA contact to one Codex thread for task-sync routing.</p>
|
||||
<details class="tasks-external-help">
|
||||
<summary class="is-size-7">More info</summary>
|
||||
<p class="help">
|
||||
This is task-sync only. It does not mirror full chat history. The link tells the Codex worker which Codex conversation/session should receive updates for tasks from that contact/group.
|
||||
</p>
|
||||
</details>
|
||||
{% 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</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">Which GIA contact/group this link 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">Codex Chat ID</label>
|
||||
<div class="control">
|
||||
<input class="input is-small" name="external_chat_id" placeholder="codex-chat-...">
|
||||
</div>
|
||||
<p class="help">Stable Codex conversation/session ID.</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>
|
||||
@@ -607,20 +366,6 @@
|
||||
.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-help {
|
||||
margin-bottom: 0.55rem;
|
||||
}
|
||||
.tasks-settings-page .tasks-external-help > summary {
|
||||
cursor: pointer;
|
||||
color: #4a4a4a;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
.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;
|
||||
|
||||
Reference in New Issue
Block a user