Increase security and reformat
This commit is contained in:
@@ -342,7 +342,7 @@
|
||||
hx-trigger="click"
|
||||
hx-swap="innerHTML">
|
||||
<span class="icon is-small"><i class="fa-solid fa-paper-plane"></i></span>
|
||||
<span style="margin-left: 0.35rem;">Message</span>
|
||||
<span style="margin-left: 0.35rem;">Compose</span>
|
||||
</a>
|
||||
<div class="navbar-dropdown" id="nav-compose-contacts">
|
||||
<a
|
||||
@@ -350,55 +350,20 @@
|
||||
hx-get="{% url 'compose_contacts_dropdown' %}?all=1"
|
||||
hx-target="#nav-compose-contacts"
|
||||
hx-swap="innerHTML">
|
||||
Fetch Contacts
|
||||
Open Contacts
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a class="navbar-item" href="{% url 'tasks_hub' %}">
|
||||
Tasks
|
||||
Task Inbox
|
||||
</a>
|
||||
<a class="navbar-item" href="{% url 'ai_workspace' %}">
|
||||
AI
|
||||
</a>
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<a class="navbar-link">
|
||||
Security
|
||||
</a>
|
||||
<div class="navbar-dropdown">
|
||||
<a
|
||||
class="navbar-item{% if request.resolver_match.url_name == 'encryption_settings' or request.resolver_match.url_name == 'security_settings' %} is-current-route{% endif %}"
|
||||
href="{% url 'encryption_settings' %}"
|
||||
>
|
||||
Encryption
|
||||
</a>
|
||||
<a
|
||||
class="navbar-item{% if request.resolver_match.url_name == 'permission_settings' %} is-current-route{% endif %}"
|
||||
href="{% url 'permission_settings' %}"
|
||||
>
|
||||
Permission
|
||||
</a>
|
||||
<a
|
||||
class="navbar-item{% if request.resolver_match.url_name == 'security_2fa' or request.resolver_match.namespace == 'two_factor' %} is-current-route{% endif %}"
|
||||
href="{% url 'security_2fa' %}"
|
||||
>
|
||||
2FA
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a class="navbar-item" href="{% url 'osint_search' type='page' %}">
|
||||
Search
|
||||
</a>
|
||||
<a class="navbar-item" href="{% url 'queues' type='page' %}">
|
||||
Queue
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="{% url 'osint_workspace' %}">
|
||||
OSINT
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="navbar-item add-button">
|
||||
Install
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
@@ -423,15 +388,15 @@
|
||||
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<a class="navbar-link">
|
||||
Storage
|
||||
Data
|
||||
</a>
|
||||
|
||||
<div class="navbar-dropdown">
|
||||
<a class="navbar-item" href="{% url 'sessions' type='page' %}">
|
||||
Sessions
|
||||
</a>
|
||||
<a class="navbar-item" href="{% url 'command_routing' %}#bp-documents">
|
||||
Documents
|
||||
<a class="navbar-item{% if request.resolver_match.url_name == 'business_plan_inbox' or request.resolver_match.url_name == 'business_plan_editor' %} is-current-route{% endif %}" href="{% url 'business_plan_inbox' %}">
|
||||
Business Plans
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -454,6 +419,19 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
<hr class="navbar-divider">
|
||||
<div class="navbar-item has-text-weight-semibold is-size-7 has-text-grey">
|
||||
Security
|
||||
</div>
|
||||
<a class="navbar-item{% if request.resolver_match.url_name == 'encryption_settings' or request.resolver_match.url_name == 'security_settings' %} is-current-route{% endif %}" href="{% url 'encryption_settings' %}">
|
||||
Encryption
|
||||
</a>
|
||||
<a class="navbar-item{% if request.resolver_match.url_name == 'permission_settings' %} is-current-route{% endif %}" href="{% url 'permission_settings' %}">
|
||||
Permissions
|
||||
</a>
|
||||
<a class="navbar-item{% if request.resolver_match.url_name == 'security_2fa' or request.resolver_match.namespace == 'two_factor' %} is-current-route{% endif %}" href="{% url 'security_2fa' %}">
|
||||
2FA
|
||||
</a>
|
||||
<hr class="navbar-divider">
|
||||
<div class="navbar-item has-text-weight-semibold is-size-7 has-text-grey">
|
||||
AI
|
||||
</div>
|
||||
@@ -470,8 +448,11 @@
|
||||
<a class="navbar-item{% if request.resolver_match.url_name == 'command_routing' %} is-current-route{% endif %}" href="{% url 'command_routing' %}">
|
||||
Commands
|
||||
</a>
|
||||
<a class="navbar-item{% if request.resolver_match.url_name == 'business_plan_inbox' or request.resolver_match.url_name == 'business_plan_editor' %} is-current-route{% endif %}" href="{% url 'business_plan_inbox' %}">
|
||||
Business Plans
|
||||
</a>
|
||||
<a class="navbar-item{% if request.resolver_match.url_name == 'tasks_settings' %} is-current-route{% endif %}" href="{% url 'tasks_settings' %}">
|
||||
Tasks
|
||||
Task Automation
|
||||
</a>
|
||||
<a class="navbar-item{% if request.resolver_match.url_name == 'translation_settings' %} is-current-route{% endif %}" href="{% url 'translation_settings' %}">
|
||||
Translation
|
||||
@@ -480,6 +461,16 @@
|
||||
Availability
|
||||
</a>
|
||||
<hr class="navbar-divider">
|
||||
<div class="navbar-item has-text-weight-semibold is-size-7 has-text-grey">
|
||||
Automation
|
||||
</div>
|
||||
<a class="navbar-item{% if request.resolver_match.url_name == 'queues' %} is-current-route{% endif %}" href="{% url 'queues' type='page' %}">
|
||||
Approvals Queue
|
||||
</a>
|
||||
<a class="navbar-item{% if request.resolver_match.url_name == 'osint_workspace' %} is-current-route{% endif %}" href="{% url 'osint_workspace' %}">
|
||||
OSINT Workspace
|
||||
</a>
|
||||
<hr class="navbar-divider">
|
||||
<a class="navbar-item{% if request.resolver_match.url_name == 'accessibility_settings' %} is-current-route{% endif %}" href="{% url 'accessibility_settings' %}">
|
||||
Accessibility
|
||||
</a>
|
||||
@@ -499,6 +490,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<button class="button is-light add-button" type="button" style="display:none;">Install App</button>
|
||||
<a class="button is-dark" href="{% url 'logout' %}">Logout</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -510,8 +502,13 @@
|
||||
<script>
|
||||
let deferredPrompt;
|
||||
const addBtn = document.querySelector('.add-button');
|
||||
addBtn.style.display = 'none';
|
||||
if (addBtn) {
|
||||
addBtn.style.display = 'none';
|
||||
}
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
if (!addBtn) {
|
||||
return;
|
||||
}
|
||||
// Prevent Chrome 67 and earlier from automatically showing the prompt
|
||||
e.preventDefault();
|
||||
// Stash the event so it can be triggered later.
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">Accessibility</h1>
|
||||
<div class="box">
|
||||
<h2 class="title is-6">Motion</h2>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="disable_animations"{% if accessibility_settings.disable_animations %} checked{% endif %}>
|
||||
Disable animations
|
||||
</label>
|
||||
<p class="help is-size-7 has-text-grey mt-1">
|
||||
Reduces motion by disabling most transitions and animations across the interface.
|
||||
</p>
|
||||
</div>
|
||||
<button class="button is-link is-small" type="submit">Save</button>
|
||||
</form>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">Accessibility</h1>
|
||||
<div class="box">
|
||||
<h2 class="title is-6">Motion</h2>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="disable_animations"{% if accessibility_settings.disable_animations %} checked{% endif %}>
|
||||
Disable animations
|
||||
</label>
|
||||
<p class="help is-size-7 has-text-grey mt-1">
|
||||
Reduces motion by disabling most transitions and animations across the interface.
|
||||
</p>
|
||||
</div>
|
||||
<button class="button is-link is-small" type="submit">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,301 +1,301 @@
|
||||
{% 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>
|
||||
</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 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>
|
||||
|
||||
<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 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 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>
|
||||
<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">Recent Runs</p>
|
||||
<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>
|
||||
</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>
|
||||
<tr><th>Operation</th><th>Total</th><th>OK</th><th>Failed</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for run in runs %}
|
||||
{% for row in operation_breakdown %}
|
||||
<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>
|
||||
<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="10">No runs yet.</td></tr>
|
||||
<tr><td colspan="4">No runs yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<script>
|
||||
(function () {
|
||||
document.querySelectorAll(".trace-run-expand").forEach(function (button) {
|
||||
button.addEventListener("click", function () {
|
||||
const rowId = String(button.getAttribute("data-detail-row") || "");
|
||||
const row = rowId ? document.getElementById(rowId) : null;
|
||||
if (!row) {
|
||||
</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>
|
||||
<script>
|
||||
(function () {
|
||||
document.querySelectorAll(".trace-run-expand").forEach(function (button) {
|
||||
button.addEventListener("click", function () {
|
||||
const rowId = String(button.getAttribute("data-detail-row") || "");
|
||||
const row = rowId ? document.getElementById(rowId) : null;
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
const isHidden = row.classList.contains("is-hidden");
|
||||
row.classList.toggle("is-hidden", !isHidden);
|
||||
button.textContent = isHidden
|
||||
? String(button.getAttribute("data-expanded-label") || "Hide")
|
||||
: String(button.getAttribute("data-collapsed-label") || "Show");
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener("click", function (event) {
|
||||
const trigger = event.target.closest(".trace-run-tab-trigger");
|
||||
if (!trigger) {
|
||||
return;
|
||||
}
|
||||
const isHidden = row.classList.contains("is-hidden");
|
||||
row.classList.toggle("is-hidden", !isHidden);
|
||||
button.textContent = isHidden
|
||||
? String(button.getAttribute("data-expanded-label") || "Hide")
|
||||
: String(button.getAttribute("data-collapsed-label") || "Show");
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener("click", function (event) {
|
||||
const trigger = event.target.closest(".trace-run-tab-trigger");
|
||||
if (!trigger) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
const shell = trigger.closest(".trace-run-detail-tabs");
|
||||
if (!shell) {
|
||||
return;
|
||||
}
|
||||
const targetName = String(trigger.getAttribute("data-tab-target") || "");
|
||||
if (!targetName) {
|
||||
return;
|
||||
}
|
||||
shell.querySelectorAll(".trace-run-tab-trigger").forEach(function (item) {
|
||||
item.parentElement.classList.toggle("is-active", item === trigger);
|
||||
});
|
||||
shell.querySelectorAll(".trace-run-tab-panel").forEach(function (panel) {
|
||||
const isActive = panel.getAttribute("data-tab-panel") === targetName;
|
||||
panel.classList.toggle("is-hidden", !isActive);
|
||||
});
|
||||
|
||||
const lazyUrl = String(trigger.getAttribute("data-lazy-url") || "");
|
||||
if (!lazyUrl) {
|
||||
return;
|
||||
}
|
||||
const panel = shell.querySelector(
|
||||
'.trace-run-tab-panel[data-tab-panel="' + targetName + '"]'
|
||||
);
|
||||
if (!panel || panel.getAttribute("data-loaded") === "1") {
|
||||
return;
|
||||
}
|
||||
panel.setAttribute("data-loaded", "1");
|
||||
panel.classList.add("is-loading");
|
||||
fetch(lazyUrl, { credentials: "same-origin" })
|
||||
.then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error("tab load failed");
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(function (html) {
|
||||
panel.innerHTML = html;
|
||||
})
|
||||
.catch(function () {
|
||||
panel.innerHTML =
|
||||
'<p class="has-text-danger">Unable to load this tab.</p>';
|
||||
})
|
||||
.finally(function () {
|
||||
panel.classList.remove("is-loading");
|
||||
event.preventDefault();
|
||||
const shell = trigger.closest(".trace-run-detail-tabs");
|
||||
if (!shell) {
|
||||
return;
|
||||
}
|
||||
const targetName = String(trigger.getAttribute("data-tab-target") || "");
|
||||
if (!targetName) {
|
||||
return;
|
||||
}
|
||||
shell.querySelectorAll(".trace-run-tab-trigger").forEach(function (item) {
|
||||
item.parentElement.classList.toggle("is-active", item === trigger);
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
shell.querySelectorAll(".trace-run-tab-panel").forEach(function (panel) {
|
||||
const isActive = panel.getAttribute("data-tab-panel") === targetName;
|
||||
panel.classList.toggle("is-hidden", !isActive);
|
||||
});
|
||||
|
||||
const lazyUrl = String(trigger.getAttribute("data-lazy-url") || "");
|
||||
if (!lazyUrl) {
|
||||
return;
|
||||
}
|
||||
const panel = shell.querySelector(
|
||||
'.trace-run-tab-panel[data-tab-panel="' + targetName + '"]'
|
||||
);
|
||||
if (!panel || panel.getAttribute("data-loaded") === "1") {
|
||||
return;
|
||||
}
|
||||
panel.setAttribute("data-loaded", "1");
|
||||
panel.classList.add("is-loading");
|
||||
fetch(lazyUrl, { credentials: "same-origin" })
|
||||
.then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error("tab load failed");
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(function (html) {
|
||||
panel.innerHTML = html;
|
||||
})
|
||||
.catch(function () {
|
||||
panel.innerHTML =
|
||||
'<p class="has-text-danger">Unable to load this tab.</p>';
|
||||
})
|
||||
.finally(function () {
|
||||
panel.classList.remove("is-loading");
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">Availability Settings</h1>
|
||||
<form method="post" class="box">
|
||||
{% csrf_token %}
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-3"><label class="checkbox"><input type="checkbox" name="enabled" {% if settings_row.enabled %}checked{% endif %}> Enabled</label></div>
|
||||
<div class="column is-3"><label class="checkbox"><input type="checkbox" name="show_in_chat" {% if settings_row.show_in_chat %}checked{% endif %}> Show In Chat</label></div>
|
||||
<div class="column is-3"><label class="checkbox"><input type="checkbox" name="show_in_groups" {% if settings_row.show_in_groups %}checked{% endif %}> Show In Groups</label></div>
|
||||
<div class="column is-3"><label class="checkbox"><input type="checkbox" name="inference_enabled" {% if settings_row.inference_enabled %}checked{% endif %}> Inference Enabled</label></div>
|
||||
<div class="column is-3">
|
||||
<label class="label is-size-7">Retention Days</label>
|
||||
<input class="input is-small" type="number" min="1" name="retention_days" value="{{ settings_row.retention_days }}">
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">Availability Settings</h1>
|
||||
<form method="post" class="box">
|
||||
{% csrf_token %}
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-3"><label class="checkbox"><input type="checkbox" name="enabled" {% if settings_row.enabled %}checked{% endif %}> Enabled</label></div>
|
||||
<div class="column is-3"><label class="checkbox"><input type="checkbox" name="show_in_chat" {% if settings_row.show_in_chat %}checked{% endif %}> Show In Chat</label></div>
|
||||
<div class="column is-3"><label class="checkbox"><input type="checkbox" name="show_in_groups" {% if settings_row.show_in_groups %}checked{% endif %}> Show In Groups</label></div>
|
||||
<div class="column is-3"><label class="checkbox"><input type="checkbox" name="inference_enabled" {% if settings_row.inference_enabled %}checked{% endif %}> Inference Enabled</label></div>
|
||||
<div class="column is-3">
|
||||
<label class="label is-size-7">Retention Days</label>
|
||||
<input class="input is-small" type="number" min="1" name="retention_days" value="{{ settings_row.retention_days }}">
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<label class="label is-size-7">Fade Threshold (seconds)</label>
|
||||
<input class="input is-small" type="number" min="30" name="fade_threshold_seconds" value="{{ settings_row.fade_threshold_seconds }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<label class="label is-size-7">Fade Threshold (seconds)</label>
|
||||
<input class="input is-small" type="number" min="30" name="fade_threshold_seconds" value="{{ settings_row.fade_threshold_seconds }}">
|
||||
</div>
|
||||
</div>
|
||||
<button class="button is-link is-small" type="submit">Save</button>
|
||||
</form>
|
||||
<button class="button is-link is-small" type="submit">Save</button>
|
||||
</form>
|
||||
|
||||
<div class="box">
|
||||
<h2 class="title is-6">Availability Event Statistics Per Contact</h2>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Contact</th>
|
||||
<th>Service</th>
|
||||
<th>Total</th>
|
||||
<th>Available</th>
|
||||
<th>Fading</th>
|
||||
<th>Unavailable</th>
|
||||
<th>Unknown</th>
|
||||
<th>Native</th>
|
||||
<th>Read</th>
|
||||
<th>Typing</th>
|
||||
<th>Msg Activity</th>
|
||||
<th>Timeout</th>
|
||||
<th>Last Event TS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in contact_stats %}
|
||||
<div class="box">
|
||||
<h2 class="title is-6">Availability Event Statistics Per Contact</h2>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{{ row.person__name }}</td>
|
||||
<td>{{ row.service }}</td>
|
||||
<td>{{ row.total_events }}</td>
|
||||
<td>{{ row.available_events }}</td>
|
||||
<td>{{ row.fading_events }}</td>
|
||||
<td>{{ row.unavailable_events }}</td>
|
||||
<td>{{ row.unknown_events }}</td>
|
||||
<td>{{ row.native_presence_events }}</td>
|
||||
<td>{{ row.read_receipt_events }}</td>
|
||||
<td>{{ row.typing_events }}</td>
|
||||
<td>{{ row.message_activity_events }}</td>
|
||||
<td>{{ row.inferred_timeout_events }}</td>
|
||||
<td>{{ row.last_event_ts }}</td>
|
||||
<th>Contact</th>
|
||||
<th>Service</th>
|
||||
<th>Total</th>
|
||||
<th>Available</th>
|
||||
<th>Fading</th>
|
||||
<th>Unavailable</th>
|
||||
<th>Unknown</th>
|
||||
<th>Native</th>
|
||||
<th>Read</th>
|
||||
<th>Typing</th>
|
||||
<th>Msg Activity</th>
|
||||
<th>Timeout</th>
|
||||
<th>Last Event TS</th>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="13">No availability events found.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in contact_stats %}
|
||||
<tr>
|
||||
<td>{{ row.person__name }}</td>
|
||||
<td>{{ row.service }}</td>
|
||||
<td>{{ row.total_events }}</td>
|
||||
<td>{{ row.available_events }}</td>
|
||||
<td>{{ row.fading_events }}</td>
|
||||
<td>{{ row.unavailable_events }}</td>
|
||||
<td>{{ row.unknown_events }}</td>
|
||||
<td>{{ row.native_presence_events }}</td>
|
||||
<td>{{ row.read_receipt_events }}</td>
|
||||
<td>{{ row.typing_events }}</td>
|
||||
<td>{{ row.message_activity_events }}</td>
|
||||
<td>{{ row.inferred_timeout_events }}</td>
|
||||
<td>{{ row.last_event_ts }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="13">No availability events found.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<textarea class="textarea" name="content_markdown" rows="18">{{ document.content_markdown }}</textarea>
|
||||
<div class="buttons" style="margin-top: 0.75rem;">
|
||||
<button class="button is-link" type="submit">Save Revision</button>
|
||||
<a class="button is-light" href="{% url 'command_routing' %}">Back</a>
|
||||
<a class="button is-light" href="{% url 'business_plan_inbox' %}">Back To Inbox</a>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
99
core/templates/pages/business-plan-inbox.html
Normal file
99
core/templates/pages/business-plan-inbox.html
Normal file
@@ -0,0 +1,99 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">Business Plan Inbox</h1>
|
||||
<p class="subtitle is-6">Review, filter, and open generated business plan documents.</p>
|
||||
|
||||
<article class="notification is-light">
|
||||
<div class="tags mb-1">
|
||||
<span class="tag is-light">Total {{ stats.total|default:0 }}</span>
|
||||
<span class="tag is-warning is-light">Draft {{ stats.draft|default:0 }}</span>
|
||||
<span class="tag is-success is-light">Final {{ stats.final|default:0 }}</span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Filters</h2>
|
||||
<form method="get">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-4">
|
||||
<label class="label is-size-7">Search</label>
|
||||
<input class="input is-small" name="q" value="{{ filters.q }}" placeholder="Title, source channel, command profile">
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<label class="label is-size-7">Status</label>
|
||||
<div class="select is-small is-fullwidth">
|
||||
<select name="status">
|
||||
<option value="">All</option>
|
||||
<option value="draft" {% if filters.status == "draft" %}selected{% endif %}>Draft</option>
|
||||
<option value="final" {% if filters.status == "final" %}selected{% endif %}>Final</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3">
|
||||
<label class="label is-size-7">Service</label>
|
||||
<div class="select is-small is-fullwidth">
|
||||
<select name="service">
|
||||
<option value="">All</option>
|
||||
{% for service in service_choices %}
|
||||
<option value="{{ service }}" {% if filters.service == service %}selected{% endif %}>{{ service }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-2 is-flex is-align-items-flex-end">
|
||||
<button class="button is-small is-link is-light" type="submit">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<article class="box">
|
||||
<div class="is-flex is-justify-content-space-between is-align-items-center mb-2">
|
||||
<h2 class="title is-6 mb-0">Documents</h2>
|
||||
<a class="button is-small is-light" href="{% url 'command_routing' %}">Command Routing</a>
|
||||
</div>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Status</th>
|
||||
<th>Service</th>
|
||||
<th>Channel</th>
|
||||
<th>Profile</th>
|
||||
<th>Revisions</th>
|
||||
<th>Updated</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for doc in documents %}
|
||||
<tr>
|
||||
<td>{{ doc.title }}</td>
|
||||
<td>
|
||||
{% if doc.status == "final" %}
|
||||
<span class="tag is-success is-light">final</span>
|
||||
{% else %}
|
||||
<span class="tag is-warning is-light">draft</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ doc.source_service }}</td>
|
||||
<td><code>{{ doc.source_channel_identifier|default:"-" }}</code></td>
|
||||
<td>{% if doc.command_profile %}{{ doc.command_profile.name }}{% else %}-{% endif %}</td>
|
||||
<td>{{ doc.revision_count }}</td>
|
||||
<td>{{ doc.updated_at }}</td>
|
||||
<td>
|
||||
<a class="button is-small is-link is-light" href="{% url 'business_plan_editor' doc_id=doc.id %}">Open</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="8">No business plan documents yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -1,144 +1,144 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">Codex Status</h1>
|
||||
<p class="subtitle is-6">Global per-user Codex task-sync status, runs, and approvals.</p>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">Codex Status</h1>
|
||||
<p class="subtitle is-6">Global per-user Codex task-sync status, runs, and approvals.</p>
|
||||
|
||||
<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 Settings</a>.</p>
|
||||
</article>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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">Permission 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>
|
||||
<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 %}
|
||||
|
||||
@@ -393,7 +393,10 @@
|
||||
{% endfor %}
|
||||
|
||||
<article class="box" id="bp-documents">
|
||||
<h2 class="title is-6">Business Plan Documents</h2>
|
||||
<div class="is-flex is-justify-content-space-between is-align-items-center mb-2">
|
||||
<h2 class="title is-6 mb-0">Recent Business Plan Documents</h2>
|
||||
<a class="button is-small is-link is-light" href="{% url 'business_plan_inbox' %}">Open Business Plan Inbox</a>
|
||||
</div>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead>
|
||||
<tr><th scope="col">Title</th><th scope="col">Status</th><th scope="col">Source</th><th scope="col">Updated</th><th scope="col">Actions</th></tr>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,22 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">{{ category_title }}</h1>
|
||||
<p class="subtitle is-6">{{ category_description }}</p>
|
||||
<div class="tabs is-boxed is-small mb-4 security-page-tabs">
|
||||
<ul>
|
||||
{% for tab in category_tabs %}
|
||||
<li class="{% if tab.active %}is-active{% endif %}">
|
||||
<a href="{{ tab.href }}">{{ tab.label }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">{{ category_title }}</h1>
|
||||
<p class="subtitle is-6">{{ category_description }}</p>
|
||||
<div class="tabs is-boxed is-small mb-4 security-page-tabs">
|
||||
<ul>
|
||||
{% for tab in category_tabs %}
|
||||
<li class="{% if tab.active %}is-active{% endif %}">
|
||||
<a href="{{ tab.href }}">{{ tab.label }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="box">
|
||||
<p class="is-size-7 has-text-grey">Choose a tab above to open settings in this category.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
<p class="is-size-7 has-text-grey">Choose a tab above to open settings in this category.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,127 +1,127 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<section class="section"><div class="container">
|
||||
<h1 class="title is-4">Task #{{ task.reference_code }}: {{ task.title }}</h1>
|
||||
<p class="subtitle is-6">{{ task.project.name }}{% if task.epic %} / {{ task.epic.name }}{% endif %} · {{ task.status_snapshot }}</p>
|
||||
<p class="is-size-7 has-text-grey" style="margin-top:-0.65rem; margin-bottom: 0.65rem;">
|
||||
Created by {{ task.creator_label|default:"Unknown" }}
|
||||
{% if task.origin_message_id %}
|
||||
· Source message <code>{{ task.origin_message_id }}</code>
|
||||
{% endif %}
|
||||
</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>
|
||||
<table class="table is-fullwidth is-size-7">
|
||||
<thead><tr><th>When</th><th>Type</th><th>Actor</th><th>Payload</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in events %}
|
||||
<tr>
|
||||
<td>{{ row.created_at }}</td>
|
||||
<td>{{ row.event_type }}</td>
|
||||
<td>
|
||||
{{ row.actor_display|default:"Unknown" }}
|
||||
{% if row.actor_identifier and row.actor_identifier != row.actor_display %}
|
||||
<div class="has-text-grey"><code>{{ row.actor_identifier }}</code></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if row.payload_view.summary_items %}
|
||||
<div class="tags" style="margin-bottom: 0.35rem;">
|
||||
{% for item in row.payload_view.summary_items %}
|
||||
<span class="tag task-ui-badge"><strong>{{ item.0 }}</strong>: {{ item.1 }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<details>
|
||||
<summary class="is-size-7 has-text-link" style="cursor:pointer;">View payload JSON</summary>
|
||||
<section class="section"><div class="container">
|
||||
<h1 class="title is-4">Task #{{ task.reference_code }}: {{ task.title }}</h1>
|
||||
<p class="subtitle is-6">{{ task.project.name }}{% if task.epic %} / {{ task.epic.name }}{% endif %} · {{ task.status_snapshot }}</p>
|
||||
<p class="is-size-7 has-text-grey" style="margin-top:-0.65rem; margin-bottom: 0.65rem;">
|
||||
Created by {{ task.creator_label|default:"Unknown" }}
|
||||
{% if task.origin_message_id %}
|
||||
· Source message <code>{{ task.origin_message_id }}</code>
|
||||
{% endif %}
|
||||
</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>
|
||||
<table class="table is-fullwidth is-size-7">
|
||||
<thead><tr><th>When</th><th>Type</th><th>Actor</th><th>Payload</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in events %}
|
||||
<tr>
|
||||
<td>{{ row.created_at }}</td>
|
||||
<td>{{ row.event_type }}</td>
|
||||
<td>
|
||||
{{ row.actor_display|default:"Unknown" }}
|
||||
{% if row.actor_identifier and row.actor_identifier != row.actor_display %}
|
||||
<div class="has-text-grey"><code>{{ row.actor_identifier }}</code></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if row.payload_view.summary_items %}
|
||||
<div class="tags" style="margin-bottom: 0.35rem;">
|
||||
{% for item in row.payload_view.summary_items %}
|
||||
<span class="tag task-ui-badge"><strong>{{ item.0 }}</strong>: {{ item.1 }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<details>
|
||||
<summary class="is-size-7 has-text-link" style="cursor:pointer;">View payload JSON</summary>
|
||||
<pre class="task-event-payload">{{ row.payload_view.pretty_text }}</pre>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4">No events.</td></tr>
|
||||
{% endfor %}
|
||||
</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 {
|
||||
margin-top: 0.35rem;
|
||||
padding: 0.6rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(127, 127, 127, 0.25);
|
||||
background: rgba(245, 245, 245, 0.75);
|
||||
color: #1f1f1f;
|
||||
max-width: 72ch;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
[data-theme="dark"] .task-event-payload {
|
||||
background: rgba(35, 35, 35, 0.75);
|
||||
color: #f5f5f5;
|
||||
border-color: rgba(200, 200, 200, 0.35);
|
||||
}
|
||||
</style>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4">No events.</td></tr>
|
||||
{% endfor %}
|
||||
</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 {
|
||||
margin-top: 0.35rem;
|
||||
padding: 0.6rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(127, 127, 127, 0.25);
|
||||
background: rgba(245, 245, 245, 0.75);
|
||||
color: #1f1f1f;
|
||||
max-width: 72ch;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
[data-theme="dark"] .task-event-payload {
|
||||
background: rgba(35, 35, 35, 0.75);
|
||||
color: #f5f5f5;
|
||||
border-color: rgba(200, 200, 200, 0.35);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<section class="section"><div class="container">
|
||||
<h1 class="title is-4">Epic: {{ epic.name }}</h1>
|
||||
<div class="buttons"><a class="button is-small is-light" href="{% url 'tasks_project' project_id=epic.project_id %}">Back to project</a></div>
|
||||
<article class="box">
|
||||
<ul class="is-size-7">
|
||||
{% for row in tasks %}
|
||||
<li>
|
||||
<a href="{% url 'tasks_task' task_id=row.id %}">#{{ row.reference_code }} {{ row.title }}</a>
|
||||
<span class="has-text-grey">· by {{ row.creator_label|default:"Unknown" }}</span>
|
||||
{% if row.creator_identifier %}
|
||||
<code>{{ row.creator_identifier }}</code>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% empty %}
|
||||
<li>No tasks.</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</article>
|
||||
</div></section>
|
||||
<section class="section"><div class="container">
|
||||
<h1 class="title is-4">Epic: {{ epic.name }}</h1>
|
||||
<div class="buttons"><a class="button is-small is-light" href="{% url 'tasks_project' project_id=epic.project_id %}">Back to project</a></div>
|
||||
<article class="box">
|
||||
<ul class="is-size-7">
|
||||
{% for row in tasks %}
|
||||
<li>
|
||||
<a href="{% url 'tasks_task' task_id=row.id %}">#{{ row.reference_code }} {{ row.title }}</a>
|
||||
<span class="has-text-grey">· by {{ row.creator_label|default:"Unknown" }}</span>
|
||||
{% if row.creator_identifier %}
|
||||
<code>{{ row.creator_identifier }}</code>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% empty %}
|
||||
<li>No tasks.</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</article>
|
||||
</div></section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,84 +1,84 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<section class="section"><div class="container">
|
||||
<h1 class="title is-4">Group Tasks: {{ channel_display_name }}</h1>
|
||||
<p class="subtitle is-6">{{ service_label }}</p>
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Create Or Map Project</h2>
|
||||
{% if primary_project %}
|
||||
<section class="section"><div class="container">
|
||||
<h1 class="title is-4">Group Task Inbox: {{ channel_display_name }}</h1>
|
||||
<p class="subtitle is-6">{{ service_label }}</p>
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Create Or Map Project</h2>
|
||||
{% if primary_project %}
|
||||
<form method="post" style="margin-bottom: 0.7rem;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="group_project_rename">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-7">
|
||||
<label class="label is-size-7">Rename Current Chat Project</label>
|
||||
<input class="input is-small" name="project_name" value="{{ primary_project.name }}">
|
||||
</div>
|
||||
<div class="column is-5" style="display:flex; align-items:flex-end;">
|
||||
<button class="button is-small is-light" type="submit">Rename</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form method="post" style="margin-bottom: 0.7rem;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="group_project_rename">
|
||||
<input type="hidden" name="action" value="group_project_create">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-7">
|
||||
<label class="label is-size-7">Rename Current Chat Project</label>
|
||||
<input class="input is-small" name="project_name" value="{{ primary_project.name }}">
|
||||
<div class="column is-5">
|
||||
<label class="label is-size-7">Project Name</label>
|
||||
<input class="input is-small" name="project_name" placeholder="Project name">
|
||||
</div>
|
||||
<div class="column is-5" style="display:flex; align-items:flex-end;">
|
||||
<button class="button is-small is-light" type="submit">Rename</button>
|
||||
<div class="column is-5">
|
||||
<label class="label is-size-7">Initial Epic (optional)</label>
|
||||
<input class="input is-small" name="epic_name" placeholder="Epic name">
|
||||
</div>
|
||||
<div class="column is-2" style="display:flex; align-items:flex-end;">
|
||||
<button class="button is-small is-link is-light" type="submit">Create + Map</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form method="post" style="margin-bottom: 0.7rem;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="group_project_create">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-5">
|
||||
<label class="label is-size-7">Project Name</label>
|
||||
<input class="input is-small" name="project_name" placeholder="Project name">
|
||||
</div>
|
||||
<div class="column is-5">
|
||||
<label class="label is-size-7">Initial Epic (optional)</label>
|
||||
<input class="input is-small" name="epic_name" placeholder="Epic name">
|
||||
</div>
|
||||
<div class="column is-2" style="display:flex; align-items:flex-end;">
|
||||
<button class="button is-small is-link is-light" type="submit">Create + Map</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="group_map_existing_project">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-9">
|
||||
<label class="label is-size-7">Existing Project</label>
|
||||
<div class="select is-small is-fullwidth">
|
||||
<select name="project_id">
|
||||
{% for project in projects %}
|
||||
<option value="{{ project.id }}">{{ project.name }}</option>
|
||||
{% empty %}
|
||||
<option value="">No projects available</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="group_map_existing_project">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-9">
|
||||
<label class="label is-size-7">Existing Project</label>
|
||||
<div class="select is-small is-fullwidth">
|
||||
<select name="project_id">
|
||||
{% for project in projects %}
|
||||
<option value="{{ project.id }}">{{ project.name }}</option>
|
||||
{% empty %}
|
||||
<option value="">No projects available</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3" style="display:flex; align-items:flex-end;">
|
||||
<button class="button is-small is-light" type="submit">Map Existing</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-3" style="display:flex; align-items:flex-end;">
|
||||
<button class="button is-small is-light" type="submit">Map Existing</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
{% if not tasks %}
|
||||
<article class="box">
|
||||
<h2 class="title is-6">No Tasks Yet</h2>
|
||||
<div class="content is-size-7">
|
||||
<p>This group has no derived tasks yet. To start populating this view:</p>
|
||||
<ol>
|
||||
<li>Open <a href="{% url 'tasks_settings' %}?service={{ service }}&identifier={{ identifier|urlencode }}">Task Settings</a> and confirm this chat is mapped under <strong>Group Mapping</strong>.</li>
|
||||
<li>Send task-like messages in this group, for example: <code>task: ship v1</code>, <code>todo: write tests</code>, <code>please review PR</code>.</li>
|
||||
<li>Mark completion explicitly with a phrase + reference, for example: <code>done #12</code>, <code>completed #12</code>, <code>fixed #12</code>.</li>
|
||||
<li>Refresh this page; new derived tasks and events should appear automatically.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
{% endif %}
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Mappings</h2>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead><tr><th>Project</th><th>Epic</th><th>Channel</th><th>Enabled</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in mappings %}
|
||||
{% if not tasks %}
|
||||
<article class="box">
|
||||
<h2 class="title is-6">No Tasks Yet</h2>
|
||||
<div class="content is-size-7">
|
||||
<p>This group has no derived tasks yet. To start populating this view:</p>
|
||||
<ol>
|
||||
<li>Open <a href="{% url 'tasks_settings' %}?service={{ service }}&identifier={{ identifier|urlencode }}">Task Automation</a> and confirm this chat is mapped under <strong>Group Mapping</strong>.</li>
|
||||
<li>Send task-like messages in this group, for example: <code>task: ship v1</code>, <code>todo: write tests</code>, <code>please review PR</code>.</li>
|
||||
<li>Mark completion explicitly with a phrase + reference, for example: <code>done #12</code>, <code>completed #12</code>, <code>fixed #12</code>.</li>
|
||||
<li>Refresh this page; new derived tasks and events should appear automatically.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</article>
|
||||
{% endif %}
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Mappings</h2>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead><tr><th>Project</th><th>Epic</th><th>Channel</th><th>Enabled</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in mappings %}
|
||||
<tr>
|
||||
<td>{{ row.project.name }}</td>
|
||||
<td>{% if row.epic %}{{ row.epic.name }}{% else %}-{% endif %}</td>
|
||||
@@ -88,36 +88,36 @@
|
||||
<td>{{ row.enabled }}</td>
|
||||
<td><a class="button is-small is-light" href="{% url 'tasks_project' project_id=row.project_id %}">Open Project</a></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5">No mappings for this group.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Derived Tasks</h2>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead><tr><th>Ref</th><th>Title</th><th>Created By</th><th>Project</th><th>Status</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in tasks %}
|
||||
<tr>
|
||||
<td>#{{ row.reference_code }}</td>
|
||||
<td>{{ row.title }}</td>
|
||||
<td>
|
||||
{{ row.creator_label|default:"Unknown" }}
|
||||
{% if row.creator_identifier %}
|
||||
<div class="has-text-grey"><code>{{ row.creator_identifier }}</code></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ row.project.name }}{% if row.epic %} / {{ row.epic.name }}{% endif %}</td>
|
||||
<td>{{ row.status_snapshot }}</td>
|
||||
<td><a class="button is-small is-light" href="{% url 'tasks_task' task_id=row.id %}">Open</a></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="6">No tasks yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</div></section>
|
||||
{% empty %}
|
||||
<tr><td colspan="5">No mappings for this group.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Derived Tasks</h2>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead><tr><th>Ref</th><th>Title</th><th>Created By</th><th>Project</th><th>Status</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in tasks %}
|
||||
<tr>
|
||||
<td>#{{ row.reference_code }}</td>
|
||||
<td>{{ row.title }}</td>
|
||||
<td>
|
||||
{{ row.creator_label|default:"Unknown" }}
|
||||
{% if row.creator_identifier %}
|
||||
<div class="has-text-grey"><code>{{ row.creator_identifier }}</code></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ row.project.name }}{% if row.epic %} / {{ row.epic.name }}{% endif %}</td>
|
||||
<td>{{ row.status_snapshot }}</td>
|
||||
<td><a class="button is-small is-light" href="{% url 'tasks_task' task_id=row.id %}">Open</a></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="6">No tasks yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</div></section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,204 +1,204 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">Tasks</h1>
|
||||
<p class="subtitle is-6">Immutable tasks derived from chat activity.</p>
|
||||
<div class="buttons" style="margin-bottom: 0.75rem;">
|
||||
<a class="button is-small is-link is-light" href="{% url 'tasks_settings' %}{% if scope.person_id or scope.service or scope.identifier %}?{% if scope.person_id %}person={{ scope.person_id|urlencode }}{% endif %}{% if scope.service %}{% if scope.person_id %}&{% endif %}service={{ scope.service|urlencode }}{% endif %}{% if scope.identifier %}{% if scope.person_id or scope.service %}&{% endif %}identifier={{ scope.identifier|urlencode }}{% endif %}{% endif %}">Task Settings</a>
|
||||
</div>
|
||||
<div class="columns is-variable is-5">
|
||||
<div class="column is-4">
|
||||
<article class="box">
|
||||
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.5rem; margin-bottom: 0.6rem;">
|
||||
<h2 class="title is-6" style="margin: 0;">Projects</h2>
|
||||
<span class="tag task-ui-badge">{{ projects|length }}</span>
|
||||
</div>
|
||||
<p class="help" style="margin-bottom: 0.45rem;">Projects are created automatically from chat usage. Use this panel for manual cleanup and mapping.</p>
|
||||
<div class="buttons" style="margin-bottom:0.55rem;">
|
||||
{% if show_empty_projects %}
|
||||
<a class="button is-small is-light" href="{% url 'tasks_hub' %}">Hide empty projects</a>
|
||||
{% else %}
|
||||
<a class="button is-small is-light" href="{% url 'tasks_hub' %}?show_empty=1">Show empty projects</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<form method="post" style="margin-bottom: 0.75rem;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="project_create">
|
||||
<input type="hidden" name="person" value="{{ scope.person_id }}">
|
||||
<input type="hidden" name="service" value="{{ scope.service }}">
|
||||
<input type="hidden" name="identifier" value="{{ scope.identifier }}">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input class="input is-small" name="name" placeholder="New project name">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-small is-link is-light" type="submit">Add</button>
|
||||
</div>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">Task Inbox</h1>
|
||||
<p class="subtitle is-6">Immutable tasks derived from chat activity.</p>
|
||||
<div class="buttons" style="margin-bottom: 0.75rem;">
|
||||
<a class="button is-small is-link is-light" href="{% url 'tasks_settings' %}{% if scope.person_id or scope.service or scope.identifier %}?{% if scope.person_id %}person={{ scope.person_id|urlencode }}{% endif %}{% if scope.service %}{% if scope.person_id %}&{% endif %}service={{ scope.service|urlencode }}{% endif %}{% if scope.identifier %}{% if scope.person_id or scope.service %}&{% endif %}identifier={{ scope.identifier|urlencode }}{% endif %}{% endif %}">Task Automation</a>
|
||||
</div>
|
||||
<div class="columns is-variable is-5">
|
||||
<div class="column is-4">
|
||||
<article class="box">
|
||||
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.5rem; margin-bottom: 0.6rem;">
|
||||
<h2 class="title is-6" style="margin: 0;">Projects</h2>
|
||||
<span class="tag task-ui-badge">{{ projects|length }}</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if scope.person %}
|
||||
<article class="message is-light" style="margin-bottom: 0.75rem;">
|
||||
<div class="message-body is-size-7">
|
||||
Setup scope: <strong>{{ scope.person.name }}</strong>
|
||||
{% if scope.service and scope.identifier %}
|
||||
· {{ scope.service }} · {{ scope.identifier }}
|
||||
{% endif %}
|
||||
<p class="help" style="margin-bottom: 0.45rem;">Projects are created automatically from chat usage. Use this panel for manual cleanup and mapping.</p>
|
||||
<div class="buttons" style="margin-bottom:0.55rem;">
|
||||
{% if show_empty_projects %}
|
||||
<a class="button is-small is-light" href="{% url 'tasks_hub' %}">Hide empty projects</a>
|
||||
{% else %}
|
||||
<a class="button is-small is-light" href="{% url 'tasks_hub' %}?show_empty=1">Show empty projects</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<form method="post" style="margin-bottom: 0.75rem;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="project_create">
|
||||
<input type="hidden" name="person" value="{{ scope.person_id }}">
|
||||
<input type="hidden" name="service" value="{{ scope.service }}">
|
||||
<input type="hidden" name="identifier" value="{{ scope.identifier }}">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input class="input is-small" name="name" placeholder="New project name">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-small is-link is-light" type="submit">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<div style="margin-bottom: 0.75rem;">
|
||||
<label class="label is-size-7">Map Linked Identifiers To Project</label>
|
||||
<form method="get">
|
||||
<input type="hidden" name="person" value="{{ scope.person_id }}">
|
||||
<input type="hidden" name="service" value="{{ scope.service }}">
|
||||
<input type="hidden" name="identifier" value="{{ scope.identifier }}">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-small is-fullwidth">
|
||||
<select name="project">
|
||||
<option value="">Select project</option>
|
||||
{% for project in project_choices %}
|
||||
<option value="{{ project.id }}" {% if selected_project and selected_project.id == project.id %}selected{% endif %}>{{ project.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
|
||||
{% if scope.person %}
|
||||
<article class="message is-light" style="margin-bottom: 0.75rem;">
|
||||
<div class="message-body is-size-7">
|
||||
Setup scope: <strong>{{ scope.person.name }}</strong>
|
||||
{% if scope.service and scope.identifier %}
|
||||
· {{ scope.service }} · {{ scope.identifier }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
<div style="margin-bottom: 0.75rem;">
|
||||
<label class="label is-size-7">Map Linked Identifiers To Project</label>
|
||||
<form method="get">
|
||||
<input type="hidden" name="person" value="{{ scope.person_id }}">
|
||||
<input type="hidden" name="service" value="{{ scope.service }}">
|
||||
<input type="hidden" name="identifier" value="{{ scope.identifier }}">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-small is-fullwidth">
|
||||
<select name="project">
|
||||
<option value="">Select project</option>
|
||||
{% for project in project_choices %}
|
||||
<option value="{{ project.id }}" {% if selected_project and selected_project.id == project.id %}selected{% endif %}>{{ project.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-small is-light" type="submit">Select</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-small is-light" type="submit">Select</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<table class="table is-fullwidth is-striped is-size-7" style="margin-bottom:0.9rem;">
|
||||
<thead><tr><th>Identifier</th><th>Service</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in person_identifier_rows %}
|
||||
<tr>
|
||||
<td><code>{{ row.identifier }}</code></td>
|
||||
<td>{{ row.service }}</td>
|
||||
<td class="has-text-right">
|
||||
{% if selected_project %}
|
||||
{% if row.mapped %}
|
||||
<span class="tag task-ui-badge">Linked</span>
|
||||
</form>
|
||||
</div>
|
||||
<table class="table is-fullwidth is-striped is-size-7" style="margin-bottom:0.9rem;">
|
||||
<thead><tr><th>Identifier</th><th>Service</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in person_identifier_rows %}
|
||||
<tr>
|
||||
<td><code>{{ row.identifier }}</code></td>
|
||||
<td>{{ row.service }}</td>
|
||||
<td class="has-text-right">
|
||||
{% if selected_project %}
|
||||
{% if row.mapped %}
|
||||
<span class="tag task-ui-badge">Linked</span>
|
||||
{% else %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="project_map_identifier">
|
||||
<input type="hidden" name="project_id" value="{{ selected_project.id }}">
|
||||
<input type="hidden" name="person_identifier_id" value="{{ row.id }}">
|
||||
<input type="hidden" name="person" value="{{ scope.person_id }}">
|
||||
<input type="hidden" name="service" value="{{ scope.service }}">
|
||||
<input type="hidden" name="identifier" value="{{ scope.identifier }}">
|
||||
<button class="button is-small is-light" type="submit">Link</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="project_map_identifier">
|
||||
<input type="hidden" name="project_id" value="{{ selected_project.id }}">
|
||||
<input type="hidden" name="person_identifier_id" value="{{ row.id }}">
|
||||
<input type="hidden" name="person" value="{{ scope.person_id }}">
|
||||
<input type="hidden" name="service" value="{{ scope.service }}">
|
||||
<input type="hidden" name="identifier" value="{{ scope.identifier }}">
|
||||
<button class="button is-small is-light" type="submit">Link</button>
|
||||
</form>
|
||||
<span class="has-text-grey">Select project</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="has-text-grey">Select project</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3">No linked identifiers for this person yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="help" style="margin-bottom: 0.75rem;">
|
||||
Open this page from Compose to map a person’s linked identifiers in one click.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead><tr><th>Project</th><th>Stats</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for project in projects %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'tasks_project' project_id=project.id %}">{{ project.name }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tag task-ui-badge">{{ project.task_count }} task{{ project.task_count|pluralize }}</span>
|
||||
<span class="tag task-ui-badge">{{ project.epic_count }} epic{{ project.epic_count|pluralize }}</span>
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
<form method="post" onsubmit="const v=prompt('Type {{ project.name|escapejs }} to confirm delete'); if(v===null){return false;} this.confirm_name.value=v; return true;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="project_delete">
|
||||
<input type="hidden" name="project_id" value="{{ project.id }}">
|
||||
<input type="hidden" name="confirm_name" value="">
|
||||
<button class="button is-small is-danger is-light" type="submit">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3">No projects yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</div>
|
||||
<div class="column">
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Recent Derived Tasks</h2>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead><tr><th>Ref</th><th>Title</th><th>Created By</th><th>Project</th><th>Status</th><th>Actions</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in tasks %}
|
||||
<tr>
|
||||
<td>#{{ row.reference_code }}</td>
|
||||
<td>{{ row.title }}</td>
|
||||
<td>
|
||||
{{ row.creator_label|default:"Unknown" }}
|
||||
{% if row.creator_identifier %}
|
||||
<div class="has-text-grey"><code>{{ row.creator_identifier }}</code></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ row.project.name }}{% if row.epic %} / {{ row.epic.name }}{% endif %}</td>
|
||||
<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 %}
|
||||
<tr><td colspan="3">No linked identifiers for this person yet.</td></tr>
|
||||
<tr><td colspan="6">No derived tasks yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="help" style="margin-bottom: 0.75rem;">
|
||||
Open this page from Compose to map a person’s linked identifiers in one click.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead><tr><th>Project</th><th>Stats</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for project in projects %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'tasks_project' project_id=project.id %}">{{ project.name }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tag task-ui-badge">{{ project.task_count }} task{{ project.task_count|pluralize }}</span>
|
||||
<span class="tag task-ui-badge">{{ project.epic_count }} epic{{ project.epic_count|pluralize }}</span>
|
||||
</td>
|
||||
<td class="has-text-right">
|
||||
<form method="post" onsubmit="const v=prompt('Type {{ project.name|escapejs }} to confirm delete'); if(v===null){return false;} this.confirm_name.value=v; return true;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="project_delete">
|
||||
<input type="hidden" name="project_id" value="{{ project.id }}">
|
||||
<input type="hidden" name="confirm_name" value="">
|
||||
<button class="button is-small is-danger is-light" type="submit">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3">No projects yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</div>
|
||||
<div class="column">
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Recent Derived Tasks</h2>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead><tr><th>Ref</th><th>Title</th><th>Created By</th><th>Project</th><th>Status</th><th>Actions</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in tasks %}
|
||||
<tr>
|
||||
<td>#{{ row.reference_code }}</td>
|
||||
<td>{{ row.title }}</td>
|
||||
<td>
|
||||
{{ row.creator_label|default:"Unknown" }}
|
||||
{% if row.creator_identifier %}
|
||||
<div class="has-text-grey"><code>{{ row.creator_identifier }}</code></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ row.project.name }}{% if row.epic %} / {{ row.epic.name }}{% endif %}</td>
|
||||
<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 %}
|
||||
<tr><td colspan="6">No derived tasks yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,97 +1,97 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">Project: {{ project.name }}</h1>
|
||||
<div class="buttons" style="margin-bottom: 0.75rem;">
|
||||
<a class="button is-small is-light" href="{% url 'tasks_hub' %}">Back</a>
|
||||
<form method="post" onsubmit="const v=prompt('Type {{ project.name|escapejs }} to confirm delete'); if(v===null){return false;} this.confirm_name.value=v; return true;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="project_delete">
|
||||
<input type="hidden" name="confirm_name" value="">
|
||||
<button class="button is-small is-danger is-light" type="submit">Delete Project</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<article class="box">
|
||||
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.5rem; margin-bottom: 0.6rem;">
|
||||
<h2 class="title is-6" style="margin: 0;">Epics</h2>
|
||||
<span class="tag task-ui-badge">{{ epics|length }}</span>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title is-4">Project: {{ project.name }}</h1>
|
||||
<div class="buttons" style="margin-bottom: 0.75rem;">
|
||||
<a class="button is-small is-light" href="{% url 'tasks_hub' %}">Back</a>
|
||||
<form method="post" onsubmit="const v=prompt('Type {{ project.name|escapejs }} to confirm delete'); if(v===null){return false;} this.confirm_name.value=v; return true;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="project_delete">
|
||||
<input type="hidden" name="confirm_name" value="">
|
||||
<button class="button is-small is-danger is-light" type="submit">Delete Project</button>
|
||||
</form>
|
||||
</div>
|
||||
<form method="post" style="margin-bottom: 0.75rem;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="epic_create">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input class="input is-small" name="name" placeholder="New epic name">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-small is-link is-light" type="submit">Add Epic</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead><tr><th>Epic</th><th>Tasks</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for epic in epics %}
|
||||
<tr>
|
||||
<td><a href="{% url 'tasks_epic' epic_id=epic.id %}">{{ epic.name }}</a></td>
|
||||
<td><span class="tag task-ui-badge">{{ epic.task_count }}</span></td>
|
||||
<td class="has-text-right">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="epic_delete">
|
||||
<input type="hidden" name="epic_id" value="{{ epic.id }}">
|
||||
<button class="button is-small is-danger is-light" type="submit">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3">No epics.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Tasks</h2>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead><tr><th>Ref</th><th>Title</th><th>Created By</th><th>Epic</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in tasks %}
|
||||
<tr>
|
||||
<td>#{{ row.reference_code }}</td>
|
||||
<td>{{ row.title }}</td>
|
||||
<td>
|
||||
{{ row.creator_label|default:"Unknown" }}
|
||||
{% if row.creator_compose_href %}
|
||||
<div><a class="is-size-7" href="{{ row.creator_compose_href }}">Compose</a></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" class="is-flex" style="gap: 0.35rem; align-items: center;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="task_set_epic">
|
||||
<input type="hidden" name="task_id" value="{{ row.id }}">
|
||||
<div class="select is-small">
|
||||
<select name="epic_id">
|
||||
<option value="">No epic</option>
|
||||
{% for epic in epics %}
|
||||
<option value="{{ epic.id }}" {% if row.epic_id == epic.id %}selected{% endif %}>{{ epic.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button class="button is-small is-light" type="submit">Set</button>
|
||||
</form>
|
||||
</td>
|
||||
<td><a class="button is-small is-light" href="{% url 'tasks_task' task_id=row.id %}">Open</a></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5">No tasks.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
<article class="box">
|
||||
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.5rem; margin-bottom: 0.6rem;">
|
||||
<h2 class="title is-6" style="margin: 0;">Epics</h2>
|
||||
<span class="tag task-ui-badge">{{ epics|length }}</span>
|
||||
</div>
|
||||
<form method="post" style="margin-bottom: 0.75rem;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="epic_create">
|
||||
<div class="field has-addons">
|
||||
<div class="control is-expanded">
|
||||
<input class="input is-small" name="name" placeholder="New epic name">
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-small is-link is-light" type="submit">Add Epic</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead><tr><th>Epic</th><th>Tasks</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for epic in epics %}
|
||||
<tr>
|
||||
<td><a href="{% url 'tasks_epic' epic_id=epic.id %}">{{ epic.name }}</a></td>
|
||||
<td><span class="tag task-ui-badge">{{ epic.task_count }}</span></td>
|
||||
<td class="has-text-right">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="epic_delete">
|
||||
<input type="hidden" name="epic_id" value="{{ epic.id }}">
|
||||
<button class="button is-small is-danger is-light" type="submit">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3">No epics.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
|
||||
<article class="box">
|
||||
<h2 class="title is-6">Tasks</h2>
|
||||
<table class="table is-fullwidth is-striped is-size-7">
|
||||
<thead><tr><th>Ref</th><th>Title</th><th>Created By</th><th>Epic</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in tasks %}
|
||||
<tr>
|
||||
<td>#{{ row.reference_code }}</td>
|
||||
<td>{{ row.title }}</td>
|
||||
<td>
|
||||
{{ row.creator_label|default:"Unknown" }}
|
||||
{% if row.creator_compose_href %}
|
||||
<div><a class="is-size-7" href="{{ row.creator_compose_href }}">Compose</a></div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<form method="post" class="is-flex" style="gap: 0.35rem; align-items: center;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="task_set_epic">
|
||||
<input type="hidden" name="task_id" value="{{ row.id }}">
|
||||
<div class="select is-small">
|
||||
<select name="epic_id">
|
||||
<option value="">No epic</option>
|
||||
{% for epic in epics %}
|
||||
<option value="{{ epic.id }}" {% if row.epic_id == epic.id %}selected{% endif %}>{{ epic.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button class="button is-small is-light" type="submit">Set</button>
|
||||
</form>
|
||||
</td>
|
||||
<td><a class="button is-small is-light" href="{% url 'tasks_task' task_id=row.id %}">Open</a></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5">No tasks.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -129,7 +129,7 @@
|
||||
class="button is-small is-info is-light"
|
||||
onclick="giaWorkspaceQueueSelectedDraft('{{ person.id }}'); return false;">
|
||||
<span class="icon is-small"><i class="fa-solid fa-inbox-in"></i></span>
|
||||
<span>Add To Queue</span>
|
||||
<span>Queue For Approval</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -399,8 +399,8 @@
|
||||
return String(value || "")
|
||||
.split(",")
|
||||
.map(function (item) {
|
||||
return item.trim();
|
||||
})
|
||||
return item.trim();
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
|
||||
@@ -475,7 +475,7 @@
|
||||
</button>
|
||||
<button type="submit" class="button is-info is-light" onclick="giaEngageSetAction('{{ person.id }}', 'queue');">
|
||||
<span class="icon is-small"><i class="fa-solid fa-inbox-in"></i></span>
|
||||
<span>Add To Queue</span>
|
||||
<span>Queue For Approval</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -867,35 +867,35 @@
|
||||
};
|
||||
|
||||
defineGlobal("giaMitigationToggleEdit", function(button) {
|
||||
const form = button && button.closest ? button.closest("form") : null;
|
||||
if (!form) return;
|
||||
const editing = button.dataset.editState === "edit";
|
||||
if (!editing) {
|
||||
form.querySelectorAll('[data-editable="1"]').forEach(function(field) { field.removeAttribute("readonly"); });
|
||||
form.querySelectorAll('[data-editable-toggle="1"]').forEach(function(field) { field.removeAttribute("disabled"); });
|
||||
const card = form.closest(".mitigation-artifact-card");
|
||||
if (card) card.classList.add("is-editing");
|
||||
button.dataset.editState = "edit";
|
||||
button.classList.remove("is-light");
|
||||
button.title = "Save";
|
||||
button.innerHTML = '<span class="icon is-small"><i class="fa-solid fa-check"></i></span>';
|
||||
resizeEditableTextareas(form);
|
||||
return;
|
||||
}
|
||||
form.requestSubmit();
|
||||
const form = button && button.closest ? button.closest("form") : null;
|
||||
if (!form) return;
|
||||
const editing = button.dataset.editState === "edit";
|
||||
if (!editing) {
|
||||
form.querySelectorAll('[data-editable="1"]').forEach(function(field) { field.removeAttribute("readonly"); });
|
||||
form.querySelectorAll('[data-editable-toggle="1"]').forEach(function(field) { field.removeAttribute("disabled"); });
|
||||
const card = form.closest(".mitigation-artifact-card");
|
||||
if (card) card.classList.add("is-editing");
|
||||
button.dataset.editState = "edit";
|
||||
button.classList.remove("is-light");
|
||||
button.title = "Save";
|
||||
button.innerHTML = '<span class="icon is-small"><i class="fa-solid fa-check"></i></span>';
|
||||
resizeEditableTextareas(form);
|
||||
return;
|
||||
}
|
||||
form.requestSubmit();
|
||||
});
|
||||
|
||||
defineGlobal("giaEngageSetAction", function(pid, action) {
|
||||
const actionInput = document.getElementById("engage-action-input-" + pid);
|
||||
if (actionInput) actionInput.value = action;
|
||||
if (action === "send") window.giaEngageSyncSendOverride(pid);
|
||||
const actionInput = document.getElementById("engage-action-input-" + pid);
|
||||
if (actionInput) actionInput.value = action;
|
||||
if (action === "send") window.giaEngageSyncSendOverride(pid);
|
||||
});
|
||||
|
||||
defineGlobal("giaEngageAutoPreview", function(pid) {
|
||||
const form = document.getElementById("engage-form-" + pid);
|
||||
if (!form) return;
|
||||
window.giaEngageSetAction(pid, "preview");
|
||||
form.requestSubmit();
|
||||
const form = document.getElementById("engage-form-" + pid);
|
||||
if (!form) return;
|
||||
window.giaEngageSetAction(pid, "preview");
|
||||
form.requestSubmit();
|
||||
});
|
||||
|
||||
window.giaEngageSetTarget = function(pid, targetId) {
|
||||
@@ -908,14 +908,14 @@
|
||||
};
|
||||
|
||||
defineGlobal("giaEngageSelect", function(pid, kind, value, node) {
|
||||
const inputId = kind === "share" ? ("engage-share-input-" + pid) : (kind === "framing" ? ("engage-framing-input-" + pid) : "");
|
||||
const input = inputId ? document.getElementById(inputId) : null;
|
||||
if (input) input.value = value;
|
||||
const li = node && node.closest ? node.closest("li") : null;
|
||||
if (!li || !li.parentElement) return;
|
||||
Array.from(li.parentElement.children).forEach(function(child) { child.classList.remove("is-active"); });
|
||||
li.classList.add("is-active");
|
||||
window.giaEngageAutoPreview(pid);
|
||||
const inputId = kind === "share" ? ("engage-share-input-" + pid) : (kind === "framing" ? ("engage-framing-input-" + pid) : "");
|
||||
const input = inputId ? document.getElementById(inputId) : null;
|
||||
if (input) input.value = value;
|
||||
const li = node && node.closest ? node.closest("li") : null;
|
||||
if (!li || !li.parentElement) return;
|
||||
Array.from(li.parentElement.children).forEach(function(child) { child.classList.remove("is-active"); });
|
||||
li.classList.add("is-active");
|
||||
window.giaEngageAutoPreview(pid);
|
||||
});
|
||||
|
||||
window.giaMitigationShowTab(personId, "{{ active_tab|default:'plan_board' }}");
|
||||
|
||||
@@ -536,8 +536,8 @@
|
||||
showOperationPane(operation);
|
||||
const activeTab = tabKey || (
|
||||
operation === "artifacts"
|
||||
? ((window.giaWorkspaceState[personId] || {}).currentMitigationTab || "plan_board")
|
||||
: operation
|
||||
? ((window.giaWorkspaceState[personId] || {}).currentMitigationTab || "plan_board")
|
||||
: operation
|
||||
);
|
||||
setTopCapsuleActive(activeTab);
|
||||
const hydrated = hydrateCachedIfAvailable(operation);
|
||||
@@ -573,8 +573,8 @@
|
||||
const currentState = window.giaWorkspaceState[personId] || {};
|
||||
const targetTabKey = currentState.pendingTabKey || (
|
||||
operation === "artifacts"
|
||||
? (currentState.currentMitigationTab || "plan_board")
|
||||
: operation
|
||||
? (currentState.currentMitigationTab || "plan_board")
|
||||
: operation
|
||||
);
|
||||
if (!forceRefresh && currentState.current === operation && pane.dataset.loaded === "1") {
|
||||
window.giaWorkspaceShowTab(personId, operation, targetTabKey);
|
||||
@@ -622,38 +622,38 @@
|
||||
fetch(url, { method: "GET" })
|
||||
.then(function(resp) { return resp.text(); })
|
||||
.then(function(html) {
|
||||
pane.innerHTML = html;
|
||||
pane.dataset.loaded = "1";
|
||||
executeInlineScripts(pane);
|
||||
pane.classList.remove("ai-animate-in");
|
||||
void pane.offsetWidth;
|
||||
pane.classList.add("ai-animate-in");
|
||||
if (cacheAllowed) {
|
||||
window.giaWorkspaceCache[key] = {
|
||||
html: html,
|
||||
ts: Date.now(),
|
||||
};
|
||||
persistCache();
|
||||
setCachedIndicator(true, window.giaWorkspaceCache[key].ts);
|
||||
} else {
|
||||
setCachedIndicator(false, null);
|
||||
}
|
||||
if (window.htmx) {
|
||||
window.htmx.process(pane);
|
||||
}
|
||||
if (operation === "draft_reply" && typeof window.giaWorkspaceUseDraft === "function") {
|
||||
window.giaWorkspaceUseDraft(personId, operation, 0);
|
||||
}
|
||||
if (operation === "artifacts") {
|
||||
applyMitigationTabSelection();
|
||||
}
|
||||
if (window.giaWorkspaceState[personId]) {
|
||||
window.giaWorkspaceState[personId].pendingTabKey = "";
|
||||
}
|
||||
})
|
||||
pane.innerHTML = html;
|
||||
pane.dataset.loaded = "1";
|
||||
executeInlineScripts(pane);
|
||||
pane.classList.remove("ai-animate-in");
|
||||
void pane.offsetWidth;
|
||||
pane.classList.add("ai-animate-in");
|
||||
if (cacheAllowed) {
|
||||
window.giaWorkspaceCache[key] = {
|
||||
html: html,
|
||||
ts: Date.now(),
|
||||
};
|
||||
persistCache();
|
||||
setCachedIndicator(true, window.giaWorkspaceCache[key].ts);
|
||||
} else {
|
||||
setCachedIndicator(false, null);
|
||||
}
|
||||
if (window.htmx) {
|
||||
window.htmx.process(pane);
|
||||
}
|
||||
if (operation === "draft_reply" && typeof window.giaWorkspaceUseDraft === "function") {
|
||||
window.giaWorkspaceUseDraft(personId, operation, 0);
|
||||
}
|
||||
if (operation === "artifacts") {
|
||||
applyMitigationTabSelection();
|
||||
}
|
||||
if (window.giaWorkspaceState[personId]) {
|
||||
window.giaWorkspaceState[personId].pendingTabKey = "";
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
pane.innerHTML = '<div class="notification is-danger is-light ai-animate-in">Failed to load AI response.</div>';
|
||||
});
|
||||
pane.innerHTML = '<div class="notification is-danger is-light ai-animate-in">Failed to load AI response.</div>';
|
||||
});
|
||||
};
|
||||
|
||||
window.giaWorkspaceRefresh = function(pid) {
|
||||
@@ -663,8 +663,8 @@
|
||||
const state = window.giaWorkspaceState[personId] || {};
|
||||
const currentTab = state.currentTab || (
|
||||
state.current === "artifacts"
|
||||
? (state.currentMitigationTab || "plan_board")
|
||||
: (state.current || "plan_board")
|
||||
? (state.currentMitigationTab || "plan_board")
|
||||
: (state.current || "plan_board")
|
||||
);
|
||||
window.giaWorkspaceOpenTab(personId, currentTab, true);
|
||||
};
|
||||
@@ -754,15 +754,15 @@
|
||||
})
|
||||
.then(function(resp) { return resp.text(); })
|
||||
.then(function(html) {
|
||||
if (statusHost) {
|
||||
statusHost.innerHTML = html;
|
||||
}
|
||||
})
|
||||
if (statusHost) {
|
||||
statusHost.innerHTML = html;
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
if (statusHost) {
|
||||
statusHost.innerHTML = '<div class="notification is-danger is-light" style="padding: 0.45rem 0.6rem;">Failed to queue draft.</div>';
|
||||
}
|
||||
});
|
||||
if (statusHost) {
|
||||
statusHost.innerHTML = '<div class="notification is-danger is-light" style="padding: 0.45rem 0.6rem;">Failed to queue draft.</div>';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function getSelectedTargetId() {
|
||||
@@ -841,92 +841,92 @@
|
||||
};
|
||||
|
||||
defineGlobal("giaMitigationShowTab", function(pid, tabName) {
|
||||
const names = ["plan_board", "corrections", "engage", "fundamentals", "ask_ai"];
|
||||
names.forEach(function(name) {
|
||||
const pane = document.getElementById("mitigation-tab-" + pid + "-" + name);
|
||||
const tab = document.getElementById("mitigation-tab-btn-" + pid + "-" + name);
|
||||
if (!pane) {
|
||||
return;
|
||||
}
|
||||
const active = (name === tabName);
|
||||
pane.style.display = active ? "block" : "none";
|
||||
if (tab) {
|
||||
tab.classList.toggle("is-active", active);
|
||||
}
|
||||
});
|
||||
const shell = document.getElementById("mitigation-shell-" + pid);
|
||||
if (!shell) {
|
||||
const names = ["plan_board", "corrections", "engage", "fundamentals", "ask_ai"];
|
||||
names.forEach(function(name) {
|
||||
const pane = document.getElementById("mitigation-tab-" + pid + "-" + name);
|
||||
const tab = document.getElementById("mitigation-tab-btn-" + pid + "-" + name);
|
||||
if (!pane) {
|
||||
return;
|
||||
}
|
||||
shell.querySelectorAll('input[name="active_tab"]').forEach(function(input) {
|
||||
input.value = tabName;
|
||||
});
|
||||
const active = (name === tabName);
|
||||
pane.style.display = active ? "block" : "none";
|
||||
if (tab) {
|
||||
tab.classList.toggle("is-active", active);
|
||||
}
|
||||
});
|
||||
const shell = document.getElementById("mitigation-shell-" + pid);
|
||||
if (!shell) {
|
||||
return;
|
||||
}
|
||||
shell.querySelectorAll('input[name="active_tab"]').forEach(function(input) {
|
||||
input.value = tabName;
|
||||
});
|
||||
});
|
||||
|
||||
defineGlobal("giaMitigationToggleEdit", function(button) {
|
||||
const form = button ? button.closest("form") : null;
|
||||
if (!form) {
|
||||
return;
|
||||
const form = button ? button.closest("form") : null;
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
const card = form.closest(".mitigation-artifact-card");
|
||||
const editing = button.dataset.editState === "edit";
|
||||
const fields = form.querySelectorAll('[data-editable="1"]');
|
||||
const toggles = form.querySelectorAll('[data-editable-toggle="1"]');
|
||||
if (!editing) {
|
||||
fields.forEach(function(field) {
|
||||
field.removeAttribute("readonly");
|
||||
});
|
||||
toggles.forEach(function(field) {
|
||||
field.removeAttribute("disabled");
|
||||
});
|
||||
if (card) {
|
||||
card.classList.add("is-editing");
|
||||
}
|
||||
const card = form.closest(".mitigation-artifact-card");
|
||||
const editing = button.dataset.editState === "edit";
|
||||
const fields = form.querySelectorAll('[data-editable="1"]');
|
||||
const toggles = form.querySelectorAll('[data-editable-toggle="1"]');
|
||||
if (!editing) {
|
||||
fields.forEach(function(field) {
|
||||
field.removeAttribute("readonly");
|
||||
});
|
||||
toggles.forEach(function(field) {
|
||||
field.removeAttribute("disabled");
|
||||
});
|
||||
if (card) {
|
||||
card.classList.add("is-editing");
|
||||
}
|
||||
button.dataset.editState = "edit";
|
||||
button.classList.remove("is-light");
|
||||
button.title = "Save";
|
||||
button.innerHTML = '<span class="icon is-small"><i class="fa-solid fa-check"></i></span>';
|
||||
} else {
|
||||
form.requestSubmit();
|
||||
}
|
||||
});
|
||||
button.dataset.editState = "edit";
|
||||
button.classList.remove("is-light");
|
||||
button.title = "Save";
|
||||
button.innerHTML = '<span class="icon is-small"><i class="fa-solid fa-check"></i></span>';
|
||||
} else {
|
||||
form.requestSubmit();
|
||||
}
|
||||
});
|
||||
|
||||
defineGlobal("giaEngageSetAction", function(pid, action) {
|
||||
const actionInput = document.getElementById("engage-action-input-" + pid);
|
||||
if (actionInput) {
|
||||
actionInput.value = action;
|
||||
}
|
||||
});
|
||||
const actionInput = document.getElementById("engage-action-input-" + pid);
|
||||
if (actionInput) {
|
||||
actionInput.value = action;
|
||||
}
|
||||
});
|
||||
|
||||
defineGlobal("giaEngageAutoPreview", function(pid) {
|
||||
const form = document.getElementById("engage-form-" + pid);
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
window.giaEngageSetAction(pid, "preview");
|
||||
form.requestSubmit();
|
||||
});
|
||||
const form = document.getElementById("engage-form-" + pid);
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
window.giaEngageSetAction(pid, "preview");
|
||||
form.requestSubmit();
|
||||
});
|
||||
|
||||
defineGlobal("giaEngageSelect", function(pid, kind, value, node) {
|
||||
let inputId = "";
|
||||
if (kind === "share") {
|
||||
inputId = "engage-share-input-" + pid;
|
||||
} else if (kind === "framing") {
|
||||
inputId = "engage-framing-input-" + pid;
|
||||
}
|
||||
const input = inputId ? document.getElementById(inputId) : null;
|
||||
if (input) {
|
||||
input.value = value;
|
||||
}
|
||||
const li = node && node.closest ? node.closest("li") : null;
|
||||
if (li && li.parentElement) {
|
||||
Array.from(li.parentElement.children).forEach(function(child) {
|
||||
child.classList.remove("is-active");
|
||||
});
|
||||
li.classList.add("is-active");
|
||||
}
|
||||
window.giaEngageAutoPreview(pid);
|
||||
});
|
||||
let inputId = "";
|
||||
if (kind === "share") {
|
||||
inputId = "engage-share-input-" + pid;
|
||||
} else if (kind === "framing") {
|
||||
inputId = "engage-framing-input-" + pid;
|
||||
}
|
||||
const input = inputId ? document.getElementById(inputId) : null;
|
||||
if (input) {
|
||||
input.value = value;
|
||||
}
|
||||
const li = node && node.closest ? node.closest("li") : null;
|
||||
if (li && li.parentElement) {
|
||||
Array.from(li.parentElement.children).forEach(function(child) {
|
||||
child.classList.remove("is-active");
|
||||
});
|
||||
li.classList.add("is-active");
|
||||
}
|
||||
window.giaEngageAutoPreview(pid);
|
||||
});
|
||||
|
||||
window.giaWorkspaceOpenTab(personId, "plan_board", false);
|
||||
syncTargetInputs();
|
||||
|
||||
@@ -2323,8 +2323,8 @@
|
||||
glanceState && glanceState.gap ? glanceState.gap.lag_ms : 0
|
||||
);
|
||||
const baselineMs = baselineFromGapMs > 0
|
||||
? baselineFromGapMs
|
||||
: toInt(snapshot.counterpartBaselineMs);
|
||||
? baselineFromGapMs
|
||||
: toInt(snapshot.counterpartBaselineMs);
|
||||
if (!baselineMs) {
|
||||
replyTimingState = {
|
||||
sinceLabel: sinceLabel,
|
||||
@@ -2758,13 +2758,13 @@
|
||||
const safe = Array.isArray(items) ? items.slice(0, 3) : [];
|
||||
const ordered = safe
|
||||
.filter(function (item) {
|
||||
return /^delay$/i.test(String(item && item.label ? item.label : ""));
|
||||
})
|
||||
.concat(
|
||||
safe.filter(function (item) {
|
||||
return !/^delay$/i.test(String(item && item.label ? item.label : ""));
|
||||
return /^delay$/i.test(String(item && item.label ? item.label : ""));
|
||||
})
|
||||
);
|
||||
.concat(
|
||||
safe.filter(function (item) {
|
||||
return !/^delay$/i.test(String(item && item.label ? item.label : ""));
|
||||
})
|
||||
);
|
||||
glanceNode.innerHTML = "";
|
||||
ordered.forEach(function (item) {
|
||||
const url = String(item.url || "").trim();
|
||||
@@ -3326,11 +3326,11 @@
|
||||
bubble.appendChild(blockGap);
|
||||
}
|
||||
const imageCandidatesFromPayload = Array.isArray(msg.image_urls) && msg.image_urls.length
|
||||
? msg.image_urls
|
||||
: (msg.image_url ? [msg.image_url] : []);
|
||||
? msg.image_urls
|
||||
: (msg.image_url ? [msg.image_url] : []);
|
||||
const imageCandidates = imageCandidatesFromPayload.length
|
||||
? imageCandidatesFromPayload
|
||||
: extractUrlCandidates(msg.text || msg.display_text || "");
|
||||
? imageCandidatesFromPayload
|
||||
: extractUrlCandidates(msg.text || msg.display_text || "");
|
||||
appendImageCandidates(bubble, imageCandidates);
|
||||
|
||||
if (!msg.hide_text) {
|
||||
@@ -3376,8 +3376,8 @@
|
||||
const deletedFlag = document.createElement("span");
|
||||
deletedFlag.className = "compose-msg-flag is-deleted";
|
||||
deletedFlag.title = "Deleted"
|
||||
+ (msg.deleted_display ? (" at " + String(msg.deleted_display)) : "")
|
||||
+ (msg.deleted_actor ? (" by " + String(msg.deleted_actor)) : "");
|
||||
+ (msg.deleted_display ? (" at " + String(msg.deleted_display)) : "")
|
||||
+ (msg.deleted_actor ? (" by " + String(msg.deleted_actor)) : "");
|
||||
deletedFlag.textContent = "deleted";
|
||||
meta.appendChild(deletedFlag);
|
||||
}
|
||||
@@ -5184,7 +5184,7 @@
|
||||
} catch (err) {
|
||||
setCardLoading(card, false);
|
||||
card.querySelector(".compose-ai-content").textContent =
|
||||
"Failed to load quick insights.";
|
||||
"Failed to load quick insights.";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5203,8 +5203,8 @@
|
||||
const customText = card.querySelector(".engage-custom-text");
|
||||
const selectedSource = (
|
||||
preferredSource !== undefined
|
||||
? preferredSource
|
||||
: (sourceSelect ? sourceSelect.value : "")
|
||||
? preferredSource
|
||||
: (sourceSelect ? sourceSelect.value : "")
|
||||
);
|
||||
const customValue = customText ? String(customText.value || "").trim() : "";
|
||||
const showCustom = selectedSource === "custom";
|
||||
@@ -5382,8 +5382,8 @@
|
||||
const selectedPerson = selected.dataset.person || thread.dataset.person || "";
|
||||
const selectedPageUrl = (
|
||||
renderMode === "page"
|
||||
? selected.dataset.pageUrl
|
||||
: selected.dataset.widgetUrl
|
||||
? selected.dataset.pageUrl
|
||||
: selected.dataset.widgetUrl
|
||||
) || "";
|
||||
switchThreadContext(
|
||||
selectedService,
|
||||
@@ -5412,8 +5412,8 @@
|
||||
const selectedPerson = selected.dataset.person || "";
|
||||
let selectedPageUrl = (
|
||||
renderMode === "page"
|
||||
? selected.dataset[servicePageUrlKey]
|
||||
: selected.dataset[serviceWidgetUrlKey]
|
||||
? selected.dataset[servicePageUrlKey]
|
||||
: selected.dataset[serviceWidgetUrlKey]
|
||||
) || "";
|
||||
if (!selectedIdentifier) {
|
||||
selectedService = selected.dataset.service || selectedService;
|
||||
@@ -5422,8 +5422,8 @@
|
||||
if (!selectedPageUrl) {
|
||||
selectedPageUrl = (
|
||||
renderMode === "page"
|
||||
? selected.dataset.pageUrl
|
||||
: selected.dataset.widgetUrl
|
||||
? selected.dataset.pageUrl
|
||||
: selected.dataset.widgetUrl
|
||||
) || "";
|
||||
}
|
||||
switchThreadContext(
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<div class="column is-12-mobile is-12-tablet">
|
||||
<div
|
||||
style="
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.5rem 0.25rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
">
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.5rem 0.25rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
">
|
||||
<p class="is-size-7 has-text-weight-semibold">Manual Workspace</p>
|
||||
<h3 class="title is-6" style="margin-bottom: 0.5rem;">Choose A Contact</h3>
|
||||
<p class="is-size-7">
|
||||
@@ -17,10 +17,10 @@
|
||||
<form
|
||||
id="compose-workspace-window-form"
|
||||
style="
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.5rem 0.25rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
">
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.5rem 0.25rem;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||
">
|
||||
<label class="label is-small" for="compose-workspace-limit">Window</label>
|
||||
<div class="select is-fullwidth is-small">
|
||||
<select id="compose-workspace-limit" name="limit">
|
||||
@@ -43,12 +43,12 @@
|
||||
<button
|
||||
class="button is-fullwidth"
|
||||
style="
|
||||
border-radius: 8px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
"
|
||||
border-radius: 8px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
"
|
||||
hx-get="{{ row.compose_widget_url }}"
|
||||
hx-include="#compose-workspace-window-form"
|
||||
hx-target="#widgets-here"
|
||||
@@ -56,42 +56,42 @@
|
||||
<span
|
||||
class="tags has-addons"
|
||||
style="
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
">
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
">
|
||||
<span
|
||||
class="tag is-white"
|
||||
style="
|
||||
flex: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
padding-left: 0.7rem;
|
||||
padding-right: 0.7rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
min-width: 0;
|
||||
">
|
||||
flex: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
padding-left: 0.7rem;
|
||||
padding-right: 0.7rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
min-width: 0;
|
||||
">
|
||||
<span
|
||||
style="
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 0.45rem;
|
||||
min-width: 0;
|
||||
">
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 0.45rem;
|
||||
min-width: 0;
|
||||
">
|
||||
<strong>{{ row.person_name }}</strong>
|
||||
<small class="has-text-grey">{{ row.service|title }}</small>
|
||||
</span>
|
||||
<small
|
||||
class="has-text-grey"
|
||||
style="
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-all;
|
||||
text-align: right;
|
||||
">
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-all;
|
||||
text-align: right;
|
||||
">
|
||||
{{ row.identifier }}
|
||||
</small>
|
||||
</span>
|
||||
|
||||
@@ -153,14 +153,14 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content is-size-7" style="margin-top: 0.2rem;">
|
||||
<ul>
|
||||
<li><strong>Min/Max Sent.</strong>: sentiment bounds for people/contact results (-1 to 1).</li>
|
||||
<li><strong>Annotate snippets</strong>: shows contextual snippets around query hits.</li>
|
||||
<li><strong>Deduplicate</strong>: removes near-identical repeated rows.</li>
|
||||
<li><strong>Reverse output</strong>: reverses final result order after sorting.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content is-size-7" style="margin-top: 0.2rem;">
|
||||
<ul>
|
||||
<li><strong>Min/Max Sent.</strong>: sentiment bounds for people/contact results (-1 to 1).</li>
|
||||
<li><strong>Annotate snippets</strong>: shows contextual snippets around query hits.</li>
|
||||
<li><strong>Deduplicate</strong>: removes near-identical repeated rows.</li>
|
||||
<li><strong>Reverse output</strong>: reverses final result order after sorting.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</form>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<div class="is-flex is-justify-content-space-between is-align-items-center" style="margin-bottom: 0.75rem; gap: 0.5rem; flex-wrap: wrap;">
|
||||
<div>
|
||||
<h3 class="title is-6" style="margin-bottom: 0.15rem;">Outgoing Queue</h3>
|
||||
<h3 class="title is-6" style="margin-bottom: 0.15rem;">Approvals Queue</h3>
|
||||
<p class="is-size-7">Review queued drafts and approve or reject each message.</p>
|
||||
</div>
|
||||
<span class="tag is-dark is-medium">{{ object_list|length }} pending</span>
|
||||
@@ -57,7 +57,7 @@
|
||||
</div>
|
||||
|
||||
<div class="is-flex is-justify-content-space-between is-align-items-center" style="gap: 0.5rem; flex-wrap: wrap;">
|
||||
<small class="has-text-grey">Queue ID: {{ item.id }}</small>
|
||||
<small class="has-text-grey">Approval ID: {{ item.id }}</small>
|
||||
<div class="buttons are-small" style="margin: 0;">
|
||||
<button
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
@@ -92,7 +92,7 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<article class="box" style="padding: 0.8rem; border: 1px dashed rgba(0, 0, 0, 0.25); box-shadow: none;">
|
||||
<p class="is-size-7 has-text-grey">Queue is empty.</p>
|
||||
<p class="is-size-7 has-text-grey">Approvals Queue is empty.</p>
|
||||
</article>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<div class="tabs is-boxed is-small mb-4 security-page-tabs">
|
||||
<ul>
|
||||
{% for tab in settings_nav.tabs %}
|
||||
<li class="{% if tab.active %}is-active{% endif %}">
|
||||
<a href="{{ tab.href }}">{{ tab.label }}</a>
|
||||
</li>
|
||||
<li class="{% if tab.active %}is-active{% endif %}">
|
||||
<a href="{{ tab.href }}">{{ tab.label }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -1,154 +1,154 @@
|
||||
{% include 'mixins/partials/notify.html' %}
|
||||
<table
|
||||
class="table is-fullwidth is-hoverable"
|
||||
hx-target="#{{ context_object_name }}-table"
|
||||
id="{{ context_object_name }}-table"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="{{ context_object_name_singular }}Event from:body"
|
||||
hx-get="{{ list_url }}">
|
||||
<thead>
|
||||
<th>number</th>
|
||||
<th>uuid</th>
|
||||
<th>account</th>
|
||||
<th>name</th>
|
||||
<th>person</th>
|
||||
<th>availability</th>
|
||||
<th>actions</th>
|
||||
</thead>
|
||||
{% for item in object_list %}
|
||||
<tr>
|
||||
<td>{% if item.chat %}{{ item.chat.source_number }}{% endif %}</td>
|
||||
<td>
|
||||
{% if item.chat %}
|
||||
<a
|
||||
class="has-text-grey button nowrap-child"
|
||||
onclick="window.prompt('Copy to clipboard: Ctrl+C, Enter', '{{ item.chat.source_uuid }}');">
|
||||
<span class="icon" data-tooltip="Copy to clipboard">
|
||||
<i class="fa-solid fa-copy" aria-hidden="true"></i>
|
||||
</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if item.chat %}{{ item.chat.account }}{% endif %}</td>
|
||||
<td>
|
||||
{% if item.is_group %}
|
||||
<span class="tag is-info is-light is-small mr-1"><i class="fa-solid fa-users"></i></span>
|
||||
{% endif %}
|
||||
{% if item.chat %}{{ item.chat.source_name }}{% else %}{{ item.name }}{% endif %}
|
||||
</td>
|
||||
<td>{{ item.person_name|default:"-" }}</td>
|
||||
<td>
|
||||
{% if item.availability_label %}
|
||||
<span class="tag is-light">{{ item.availability_label }}</span>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
{% if not item.is_group %}
|
||||
<button
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-delete="{# url 'account_delete' type=type pk=item.id #}"
|
||||
hx-trigger="click"
|
||||
hx-target="#modals-here"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="Are you sure you wish to unlink {{ item.chat }}?"
|
||||
class="button">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</span>
|
||||
<table
|
||||
class="table is-fullwidth is-hoverable"
|
||||
hx-target="#{{ context_object_name }}-table"
|
||||
id="{{ context_object_name }}-table"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="{{ context_object_name_singular }}Event from:body"
|
||||
hx-get="{{ list_url }}">
|
||||
<thead>
|
||||
<th>number</th>
|
||||
<th>uuid</th>
|
||||
<th>account</th>
|
||||
<th>name</th>
|
||||
<th>person</th>
|
||||
<th>availability</th>
|
||||
<th>actions</th>
|
||||
</thead>
|
||||
{% for item in object_list %}
|
||||
<tr>
|
||||
<td>{% if item.chat %}{{ item.chat.source_number }}{% endif %}</td>
|
||||
<td>
|
||||
{% if item.chat %}
|
||||
<a
|
||||
class="has-text-grey button nowrap-child"
|
||||
onclick="window.prompt('Copy to clipboard: Ctrl+C, Enter', '{{ item.chat.source_uuid }}');">
|
||||
<span class="icon" data-tooltip="Copy to clipboard">
|
||||
<i class="fa-solid fa-copy" aria-hidden="true"></i>
|
||||
</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{% if item.chat %}{{ item.chat.account }}{% endif %}</td>
|
||||
<td>
|
||||
{% if item.is_group %}
|
||||
<span class="tag is-info is-light is-small mr-1"><i class="fa-solid fa-users"></i></span>
|
||||
{% endif %}
|
||||
{% if item.chat %}{{ item.chat.source_name }}{% else %}{{ item.name }}{% endif %}
|
||||
</td>
|
||||
<td>{{ item.person_name|default:"-" }}</td>
|
||||
<td>
|
||||
{% if item.availability_label %}
|
||||
<span class="tag is-light">{{ item.availability_label }}</span>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
{% if not item.is_group %}
|
||||
<button
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-delete="{# url 'account_delete' type=type pk=item.id #}"
|
||||
hx-trigger="click"
|
||||
hx-target="#modals-here"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="Are you sure you wish to unlink {{ item.chat }}?"
|
||||
class="button">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if type == 'page' %}
|
||||
{% if item.can_compose %}
|
||||
<a href="{{ item.compose_page_url }}"><button
|
||||
class="button"
|
||||
title="Manual text mode">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="{{ item.manual_icon_class }}"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="button" disabled title="No identifier available for manual send">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="{{ item.manual_icon_class }}"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if not item.is_group %}
|
||||
<a href="{{ item.match_url }}"><button
|
||||
class="button"
|
||||
title="Match identifier to person">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-link"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ item.ai_url }}"><button
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if type == 'page' %}
|
||||
{% if item.can_compose %}
|
||||
<a href="{{ item.compose_page_url }}"><button
|
||||
class="button"
|
||||
title="Open AI workspace">
|
||||
title="Manual text mode">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-brain-circuit"></i>
|
||||
<i class="{{ item.manual_icon_class }}"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</a>
|
||||
{% else %}
|
||||
{% if item.can_compose %}
|
||||
<button
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{{ item.compose_widget_url }}"
|
||||
hx-trigger="click"
|
||||
hx-target="#widgets-here"
|
||||
hx-swap="afterend"
|
||||
class="button">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="{{ item.manual_icon_class }}"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="button" disabled title="No identifier available for manual send">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="{{ item.manual_icon_class }}"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if not item.is_group %}
|
||||
<a href="{{ item.match_url }}"><button class="button" title="Match identifier to person">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-link"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button></a>
|
||||
{% endif %}
|
||||
<a href="{{ item.ai_url }}"><button class="button">
|
||||
<button class="button" disabled title="No identifier available for manual send">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-brain-circuit"></i>
|
||||
<i class="{{ item.manual_icon_class }}"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if not item.is_group %}
|
||||
<a href="{{ item.match_url }}"><button
|
||||
class="button"
|
||||
title="Match identifier to person">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-link"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ item.ai_url }}"><button
|
||||
class="button"
|
||||
title="Open AI workspace">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-brain-circuit"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</a>
|
||||
{% else %}
|
||||
{% if item.can_compose %}
|
||||
<button
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{{ item.compose_widget_url }}"
|
||||
hx-trigger="click"
|
||||
hx-target="#widgets-here"
|
||||
hx-swap="afterend"
|
||||
class="button">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="{{ item.manual_icon_class }}"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="button" disabled title="No identifier available for manual send">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="{{ item.manual_icon_class }}"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if not item.is_group %}
|
||||
<a href="{{ item.match_url }}"><button class="button" title="Match identifier to person">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-link"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<a href="{{ item.ai_url }}"><button class="button">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-brain-circuit"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
</table>
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
src="data:image/png;base64, {{ object.image_b64 }}"
|
||||
alt="WhatsApp QR code"
|
||||
style="
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
" />
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
" />
|
||||
{% if object.warning %}
|
||||
<p class="is-size-7" style="margin-top: 0.6rem;">{{ object.warning }}</p>
|
||||
{% endif %}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<div class="buttons">
|
||||
{% if cancel_url %}
|
||||
<a href="{{ cancel_url }}"
|
||||
class="button">{% trans "Cancel" %}</a>
|
||||
class="button">{% trans "Cancel" %}</a>
|
||||
{% endif %}
|
||||
{% if wizard.steps.prev %}
|
||||
<button name="wizard_goto_step" type="submit"
|
||||
value="{{ wizard.steps.prev }}"
|
||||
class="button">{% trans "Back" %}</button>
|
||||
value="{{ wizard.steps.prev }}"
|
||||
class="button">{% trans "Back" %}</button>
|
||||
{% else %}
|
||||
<button disabled name="" type="button" class="button">{% trans "Back" %}</button>
|
||||
{% endif %}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<form method="post">{% csrf_token %}{{ form }}
|
||||
<a href="{% url 'security_2fa' %}"
|
||||
class="float-right button">{% trans "Back to Account Security" %}</a>
|
||||
class="float-right button">{% trans "Back to Account Security" %}</a>
|
||||
<button class="button" type="submit">{% trans "Generate Tokens" %}</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<p class="subtitle">
|
||||
{% for other in other_devices %}
|
||||
<button name="challenge_device" value="{{ other.persistent_id }}"
|
||||
class="button" type="submit">
|
||||
class="button" type="submit">
|
||||
{{ other.generate_challenge_button_title }}
|
||||
</button>
|
||||
{% endfor %}</p>
|
||||
@@ -43,7 +43,7 @@
|
||||
<p class="subtitle">{% trans "As a last resort, you can use a backup token:" %}</p>
|
||||
<p class="subtitle">
|
||||
<button name="wizard_goto_step" type="submit" value="backup"
|
||||
class="button">{% trans "Use Backup Token" %}</button>
|
||||
class="button">{% trans "Use Backup Token" %}</button>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="buttons">
|
||||
|
||||
<a href="javascript:history.go(-1)"
|
||||
class="float-right button">{% trans "Go back" %}</a>
|
||||
class="float-right button">{% trans "Go back" %}</a>
|
||||
<a href="{% url 'two_factor:setup' %}" class="button">
|
||||
{% trans "Enable Two-Factor Authentication" %}</a>
|
||||
</div>
|
||||
|
||||
@@ -9,16 +9,16 @@
|
||||
|
||||
{% if not phone_methods %}
|
||||
<p class="subtitle"><a href="{% url 'security_2fa' %}"
|
||||
class="button">{% trans "Back to Account Security" %}</a></p>
|
||||
class="button">{% trans "Back to Account Security" %}</a></p>
|
||||
{% else %}
|
||||
<p class="subtitle">{% blocktrans trimmed %}However, it might happen that you don't have access to
|
||||
your primary token device. To enable account recovery, add a phone
|
||||
number.{% endblocktrans %}</p>
|
||||
|
||||
<a href="{% url 'security_2fa' %}"
|
||||
class="float-right button">{% trans "Back to Account Security" %}</a>
|
||||
class="float-right button">{% trans "Back to Account Security" %}</a>
|
||||
<p class="subtitle"><a href="{% url 'two_factor:phone_create' %}"
|
||||
class="button">{% trans "Add Phone Number" %}</a></p>
|
||||
class="button">{% trans "Add Phone Number" %}</a></p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
{% csrf_token %}
|
||||
<table>{{ form }}</table>
|
||||
<button class="button"
|
||||
type="submit">{% trans "Disable" %}</button>
|
||||
type="submit">{% trans "Disable" %}</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -22,16 +22,16 @@
|
||||
<li>
|
||||
{{ phone.generate_challenge_button_title }}
|
||||
<form method="post" action="{% url 'two_factor:phone_delete' phone.id %}"
|
||||
onsubmit="return confirm({% trans 'Are you sure?' %})">
|
||||
onsubmit="return confirm({% trans 'Are you sure?' %})">
|
||||
{% csrf_token %}
|
||||
<button class="button is-warning"
|
||||
type="submit">{% trans "Unregister" %}</button>
|
||||
type="submit">{% trans "Unregister" %}</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="subtitle"><a href="{% url 'two_factor:phone_create' %}"
|
||||
class="button">{% trans "Add Phone Number" %}</a></p>
|
||||
class="button">{% trans "Add Phone Number" %}</a></p>
|
||||
{% endif %}
|
||||
|
||||
<h2 class="title is-4">{% trans "Backup Tokens" %}</h2>
|
||||
@@ -45,7 +45,7 @@
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p class="subtitle"><a href="{% url 'two_factor:backup_tokens' %}"
|
||||
class="button">{% trans "Show Codes" %}</a></p>
|
||||
class="button">{% trans "Show Codes" %}</a></p>
|
||||
|
||||
<h3 class="title is-5">{% trans "Disable Two-Factor Authentication" %}</h3>
|
||||
<p class="subtitle">{% blocktrans trimmed %}However we strongly discourage you to do so, you can
|
||||
|
||||
Reference in New Issue
Block a user