Increase security and reformat

This commit is contained in:
2026-03-07 20:52:13 +00:00
parent 10588a18b9
commit bca4d6898f
144 changed files with 6735 additions and 3960 deletions

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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>

View 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 %}

View File

@@ -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 %}

View File

@@ -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

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 persons 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 persons 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 %}

View File

@@ -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