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

@@ -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);
};

View File

@@ -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' }}");

View File

@@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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