mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-15 00:12:26 -06:00
feat(console): policies pilot + the dialog tier — confirm/token-created go native
Tool policies join the shelf: one pane-scoped editor for create+edit with a
priority-neighbor read-out computed from the loaded policy list ('evaluates
after deny-rm (900) · before default-ask (0)' — policies run highest-first),
so where a priority lands answers itself while typing. Live tool-pattern
match chips are deferred until a cluster tool-registry endpoint exists.
The reusable confirm and the show-once token dialog move onto the
document-modal hatch tier (native showModal): the confirm keeps its
showConfirmModal(title, message, actionLabel, callback) contract for all
14 call sites, gains the danger chrome (red LED/hairline/title + err-filled
action), and deliberately moves autofocus from the action button to Cancel
— Enter on a fresh destructive confirm no longer fires the action. The
token dialog gets the success chrome + show-once callout, with copy wired
to the primary. Nested confirm-over-shelf now stacks via the top layer;
the z-index 650 special case and four more overlay-ID/dispatch entries die.
Also fixes a real war the livepass caught: display rules on form chrome
(label.toggle-switch's inline-flex) defeat the hidden attribute — hatch
containers now enforce [hidden] with display:none !important.
This commit is contained in:
@@ -7,12 +7,9 @@ let _adminChannelUserId = "";
|
||||
let _lastCreatedToken = "";
|
||||
let _cuTrapHandler = null;
|
||||
let _ctTrapHandler = null;
|
||||
let _tcTrapHandler = null;
|
||||
let _ccTrapHandler = null;
|
||||
let _cfTrapHandler = null;
|
||||
let _adminWatches = [];
|
||||
let _confirmCallbackFn = null;
|
||||
let _confirmTriggerEl = null;
|
||||
|
||||
// Settings whose choices are populated dynamically from the live model
|
||||
// alias list, and whose empty-string option renders as "(server default)".
|
||||
@@ -2554,16 +2551,19 @@ function submitCreateToken() {
|
||||
|
||||
function showTokenCreatedModal(token) {
|
||||
document.getElementById("token-created-value").textContent = token;
|
||||
document.getElementById("token-created-overlay").style.display = "flex";
|
||||
_tcTrapHandler = _installTrap("token-created-overlay", "token-created-box");
|
||||
window.TurnstoneHatch.openDialog(
|
||||
document.getElementById("token-created-dialog"),
|
||||
{
|
||||
onClose: function () {
|
||||
_lastCreatedToken = "";
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function hideTokenCreatedModal() {
|
||||
document.getElementById("token-created-overlay").style.display = "none";
|
||||
_tcTrapHandler = _removeTrap(_tcTrapHandler);
|
||||
_lastCreatedToken = "";
|
||||
const trigger = document.querySelector("#admin-tokens .admin-action-btn");
|
||||
if (trigger) trigger.focus();
|
||||
const d = document.getElementById("token-created-dialog");
|
||||
if (d.open) d.close();
|
||||
}
|
||||
|
||||
function copyCreatedToken() {
|
||||
@@ -2625,16 +2625,12 @@ function _installTrap(overlayId, boxId, trapRef) {
|
||||
if (e.target === overlay) {
|
||||
if (overlayId === "create-user-overlay") hideCreateUserModal();
|
||||
else if (overlayId === "create-token-overlay") hideCreateTokenModal();
|
||||
else if (overlayId === "token-created-overlay") hideTokenCreatedModal();
|
||||
else if (overlayId === "create-channel-overlay")
|
||||
hideCreateChannelModal();
|
||||
else if (overlayId === "schedule-runs-overlay") hideScheduleRunsModal();
|
||||
else if (overlayId === "confirm-overlay") hideConfirmModal();
|
||||
else if (overlayId === "create-role-overlay") hideCreateRoleModal();
|
||||
else if (overlayId === "edit-role-overlay") hideEditRoleModal();
|
||||
else if (overlayId === "user-roles-overlay") hideUserRolesModal();
|
||||
else if (overlayId === "create-policy-overlay") hideCreatePolicyModal();
|
||||
else if (overlayId === "edit-policy-overlay") hideEditPolicyModal();
|
||||
else if (overlayId === "create-template-overlay")
|
||||
hideCreateTemplateModal();
|
||||
else if (overlayId === "edit-template-overlay") hideEditTemplateModal();
|
||||
@@ -2689,12 +2685,6 @@ document.addEventListener("keydown", function (e) {
|
||||
hideCreateTokenModal();
|
||||
return;
|
||||
}
|
||||
const tc = document.getElementById("token-created-overlay");
|
||||
if (tc && tc.style.display !== "none") {
|
||||
e.preventDefault();
|
||||
hideTokenCreatedModal();
|
||||
return;
|
||||
}
|
||||
const cc = document.getElementById("create-channel-overlay");
|
||||
if (cc && cc.style.display !== "none") {
|
||||
e.preventDefault();
|
||||
@@ -2707,19 +2697,10 @@ document.addEventListener("keydown", function (e) {
|
||||
hideScheduleRunsModal();
|
||||
return;
|
||||
}
|
||||
const cf = document.getElementById("confirm-overlay");
|
||||
if (cf && cf.style.display !== "none") {
|
||||
e.preventDefault();
|
||||
hideConfirmModal();
|
||||
return;
|
||||
}
|
||||
// Governance modals
|
||||
const govOverlays = [
|
||||
["create-role-overlay", hideCreateRoleModal],
|
||||
["edit-role-overlay", hideEditRoleModal],
|
||||
["user-roles-overlay", hideUserRolesModal],
|
||||
["create-policy-overlay", hideCreatePolicyModal],
|
||||
["edit-policy-overlay", hideEditPolicyModal],
|
||||
["create-template-overlay", hideCreateTemplateModal],
|
||||
["edit-template-overlay", hideEditTemplateModal],
|
||||
["memory-detail-overlay", hideMemoryDetailModal],
|
||||
@@ -2751,41 +2732,31 @@ document.addEventListener("keydown", function (e) {
|
||||
|
||||
function showConfirmModal(title, message, actionLabel, callback) {
|
||||
_confirmCallbackFn = callback;
|
||||
_confirmTriggerEl = document.activeElement;
|
||||
document.getElementById("confirm-title").textContent = title;
|
||||
document.getElementById("confirm-message").textContent = message;
|
||||
const btn = document.getElementById("confirm-submit");
|
||||
btn.textContent = actionLabel;
|
||||
btn.disabled = false;
|
||||
const overlay = document.getElementById("confirm-overlay");
|
||||
overlay.style.display = "flex";
|
||||
_cfTrapHandler = _installTrap("confirm-overlay", "confirm-box");
|
||||
setTimeout(function () {
|
||||
btn.focus();
|
||||
}, 50);
|
||||
// Cancel carries the autofocus: Enter on a freshly-opened destructive
|
||||
// confirm must not fire the action (deliberate change from the legacy
|
||||
// action-focused behavior).
|
||||
window.TurnstoneHatch.openDialog(document.getElementById("confirm-dialog"), {
|
||||
onClose: function () {
|
||||
_confirmCallbackFn = null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function hideConfirmModal() {
|
||||
document.getElementById("confirm-overlay").style.display = "none";
|
||||
_cfTrapHandler = _removeTrap(_cfTrapHandler);
|
||||
if (
|
||||
_confirmTriggerEl &&
|
||||
_confirmTriggerEl.focus &&
|
||||
_confirmTriggerEl.isConnected
|
||||
) {
|
||||
_confirmTriggerEl.focus();
|
||||
}
|
||||
_confirmCallbackFn = null;
|
||||
_confirmTriggerEl = null;
|
||||
const d = document.getElementById("confirm-dialog");
|
||||
if (d.open) d.close();
|
||||
}
|
||||
|
||||
function _confirmCallback() {
|
||||
const fn = _confirmCallbackFn;
|
||||
_confirmCallbackFn = null;
|
||||
const btn = document.getElementById("confirm-submit");
|
||||
if (btn) btn.disabled = true;
|
||||
if (fn) fn();
|
||||
hideConfirmModal();
|
||||
if (fn) fn();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -6735,6 +6706,10 @@ function _refreshModelSuggestions() {
|
||||
|
||||
/* Register listeners once at page load */
|
||||
(function () {
|
||||
const confirmBtn = document.getElementById("confirm-submit");
|
||||
if (confirmBtn) confirmBtn.addEventListener("click", _confirmCallback);
|
||||
const tcCopy = document.getElementById("tc-copy");
|
||||
if (tcCopy) tcCopy.addEventListener("click", copyCreatedToken);
|
||||
const nameEl = document.getElementById("model-name");
|
||||
const provEl = document.getElementById("model-provider");
|
||||
const tmEl = document.getElementById("model-thinking-mode");
|
||||
|
||||
@@ -31,8 +31,6 @@ let _etmTrapHandler = null; // edit template
|
||||
let _crTriggerEl = null;
|
||||
let _erTriggerEl = null;
|
||||
let _urTriggerEl = null;
|
||||
let _cpTriggerEl = null;
|
||||
let _epTriggerEl = null;
|
||||
let _ctmTriggerEl = null;
|
||||
let _etmTriggerEl = null;
|
||||
|
||||
@@ -904,75 +902,107 @@ function _renderGovPolicies(items) {
|
||||
});
|
||||
}
|
||||
|
||||
// --- Tool-policy shelf (create + edit) ---
|
||||
// One pane-scoped shelf; the priority field is annotated with its evaluation
|
||||
// neighbors from the already-loaded _govPolicies (highest priority first), so
|
||||
// "where does 200 land?" answers itself while typing. No backend involved.
|
||||
|
||||
let _polWired = false;
|
||||
let _polShelfHandle = null;
|
||||
|
||||
function _polChip(text) {
|
||||
const span = document.createElement("span");
|
||||
span.className = "match-chip";
|
||||
span.textContent = text;
|
||||
return span;
|
||||
}
|
||||
|
||||
function _polRenderNeighbors() {
|
||||
const strip = document.getElementById("pol-neighbors");
|
||||
while (strip.firstChild) strip.removeChild(strip.firstChild);
|
||||
const selfId = document.getElementById("pol-id").value;
|
||||
const pr = parseInt(document.getElementById("pol-priority").value, 10) || 0;
|
||||
const others = _govPolicies.filter(function (q) {
|
||||
return q.policy_id !== selfId;
|
||||
});
|
||||
if (!others.length) return;
|
||||
// Highest priority evaluates first: the policy just BEFORE us is the
|
||||
// smallest priority above ours; just AFTER us, the largest below.
|
||||
let before = null;
|
||||
let after = null;
|
||||
others.forEach(function (q) {
|
||||
if (q.priority > pr && (!before || q.priority < before.priority))
|
||||
before = q;
|
||||
if (q.priority <= pr && (!after || q.priority > after.priority)) after = q;
|
||||
});
|
||||
const count = document.createElement("span");
|
||||
count.className = "match-count";
|
||||
count.textContent = "evaluates";
|
||||
strip.appendChild(count);
|
||||
if (before) {
|
||||
const lbl = document.createElement("span");
|
||||
lbl.className = "match-count";
|
||||
lbl.textContent = "after";
|
||||
strip.appendChild(lbl);
|
||||
strip.appendChild(_polChip(before.name + " (" + before.priority + ")"));
|
||||
} else {
|
||||
const first = document.createElement("span");
|
||||
first.className = "match-count";
|
||||
first.textContent = "first";
|
||||
strip.appendChild(first);
|
||||
}
|
||||
if (after) {
|
||||
const lbl2 = document.createElement("span");
|
||||
lbl2.className = "match-count";
|
||||
lbl2.textContent = "\u00b7 before";
|
||||
strip.appendChild(lbl2);
|
||||
strip.appendChild(_polChip(after.name + " (" + after.priority + ")"));
|
||||
} else if (before) {
|
||||
const last = document.createElement("span");
|
||||
last.className = "match-count";
|
||||
last.textContent = "\u00b7 last";
|
||||
strip.appendChild(last);
|
||||
}
|
||||
}
|
||||
|
||||
function _polWire() {
|
||||
if (_polWired) return;
|
||||
_polWired = true;
|
||||
document
|
||||
.getElementById("pol-priority")
|
||||
.addEventListener("input", _polRenderNeighbors);
|
||||
document
|
||||
.getElementById("pol-submit")
|
||||
.addEventListener("click", _submitPolicyShelf);
|
||||
}
|
||||
|
||||
function showCreatePolicyModal() {
|
||||
_cpTriggerEl = document.activeElement;
|
||||
const ov = document.getElementById("create-policy-overlay");
|
||||
ov.style.display = "flex";
|
||||
document.getElementById("cp-name").value = "";
|
||||
document.getElementById("cp-pattern").value = "";
|
||||
document.getElementById("cp-action").value = "ask";
|
||||
document.getElementById("cp-priority").value = "0";
|
||||
document.getElementById("create-policy-error").classList.remove("is-visible");
|
||||
document.getElementById("cp-name").focus();
|
||||
_cpTrapHandler = _installTrap("create-policy-overlay", "create-policy-box");
|
||||
_polWire();
|
||||
document.getElementById("policy-shelf-error").classList.remove("is-visible");
|
||||
document.getElementById("pol-id").value = "";
|
||||
document.getElementById("pol-name").value = "";
|
||||
document.getElementById("pol-pattern").value = "";
|
||||
document.getElementById("pol-action").value = "ask";
|
||||
document.getElementById("pol-priority").value = "0";
|
||||
document.getElementById("pol-enabled").checked = true;
|
||||
document.getElementById("pol-enabled-row").hidden = true;
|
||||
document.getElementById("policy-shelf-title").textContent = "New tool policy";
|
||||
document.getElementById("policy-shelf-tag").textContent = "POL-NEW";
|
||||
const shelf = document.getElementById("policy-shelf");
|
||||
shelf.setAttribute("data-kind", "create");
|
||||
document.getElementById("pol-submit").textContent = "Create";
|
||||
_polRenderNeighbors();
|
||||
_polShelfHandle = window.TurnstoneHatch.openShelf(shelf);
|
||||
document.getElementById("pol-name").focus();
|
||||
}
|
||||
|
||||
function hideCreatePolicyModal() {
|
||||
document.getElementById("create-policy-overlay").style.display = "none";
|
||||
_cpTrapHandler = _removeTrap(_cpTrapHandler);
|
||||
if (_cpTriggerEl && _cpTriggerEl.focus) {
|
||||
_cpTriggerEl.focus();
|
||||
}
|
||||
_cpTriggerEl = null;
|
||||
}
|
||||
|
||||
function submitCreatePolicy() {
|
||||
const name = document.getElementById("cp-name").value.trim();
|
||||
const pattern = document.getElementById("cp-pattern").value.trim();
|
||||
const action = document.getElementById("cp-action").value;
|
||||
const priority =
|
||||
parseInt(document.getElementById("cp-priority").value, 10) || 0;
|
||||
if (!name || !pattern) {
|
||||
const e = document.getElementById("create-policy-error");
|
||||
e.textContent = "Name and pattern are required";
|
||||
e.classList.add("is-visible");
|
||||
return;
|
||||
}
|
||||
document.getElementById("cp-submit").disabled = true;
|
||||
authFetch("/v1/api/admin/policies", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
tool_pattern: pattern,
|
||||
action: action,
|
||||
priority: priority,
|
||||
}),
|
||||
})
|
||||
.then(function (r) {
|
||||
if (!r.ok)
|
||||
return r.json().then(function (d) {
|
||||
throw new Error(d.error || "Failed");
|
||||
});
|
||||
return r.json();
|
||||
})
|
||||
.then(function () {
|
||||
hideCreatePolicyModal();
|
||||
showToast("Policy created");
|
||||
loadGovPolicies();
|
||||
})
|
||||
.catch(function (e) {
|
||||
const el = document.getElementById("create-policy-error");
|
||||
el.textContent = e.message;
|
||||
el.classList.add("is-visible");
|
||||
})
|
||||
.finally(function () {
|
||||
document.getElementById("cp-submit").disabled = false;
|
||||
});
|
||||
window.TurnstoneHatch.closeShelf(document.getElementById("policy-shelf"));
|
||||
_polShelfHandle = null;
|
||||
}
|
||||
|
||||
function showEditPolicyModal(policyId) {
|
||||
_epTriggerEl = document.activeElement;
|
||||
_polWire();
|
||||
let policy = null;
|
||||
for (let i = 0; i < _govPolicies.length; i++) {
|
||||
if (_govPolicies[i].policy_id === policyId) {
|
||||
@@ -981,41 +1011,57 @@ function showEditPolicyModal(policyId) {
|
||||
}
|
||||
}
|
||||
if (!policy) return;
|
||||
const ov = document.getElementById("edit-policy-overlay");
|
||||
ov.style.display = "flex";
|
||||
document.getElementById("ep-id").value = policyId;
|
||||
document.getElementById("ep-name").value = policy.name;
|
||||
document.getElementById("ep-pattern").value = policy.tool_pattern;
|
||||
document.getElementById("ep-action").value = policy.action;
|
||||
document.getElementById("ep-priority").value = policy.priority;
|
||||
document.getElementById("ep-enabled").checked = policy.enabled;
|
||||
document.getElementById("edit-policy-error").classList.remove("is-visible");
|
||||
_epTrapHandler = _installTrap("edit-policy-overlay", "edit-policy-box");
|
||||
document.getElementById("policy-shelf-error").classList.remove("is-visible");
|
||||
document.getElementById("pol-id").value = policyId;
|
||||
document.getElementById("pol-name").value = policy.name;
|
||||
document.getElementById("pol-pattern").value = policy.tool_pattern;
|
||||
document.getElementById("pol-action").value = policy.action;
|
||||
document.getElementById("pol-priority").value = policy.priority;
|
||||
document.getElementById("pol-enabled").checked = policy.enabled;
|
||||
document.getElementById("pol-enabled-row").hidden = false;
|
||||
document.getElementById("policy-shelf-title").textContent =
|
||||
"Edit tool policy \u2014 " + policy.name;
|
||||
document.getElementById("policy-shelf-tag").textContent = "POL-EDIT";
|
||||
const shelf = document.getElementById("policy-shelf");
|
||||
shelf.setAttribute("data-kind", "edit");
|
||||
document.getElementById("pol-submit").textContent = "Save";
|
||||
_polRenderNeighbors();
|
||||
_polShelfHandle = window.TurnstoneHatch.openShelf(shelf);
|
||||
document.getElementById("pol-name").focus();
|
||||
}
|
||||
|
||||
function hideEditPolicyModal() {
|
||||
document.getElementById("edit-policy-overlay").style.display = "none";
|
||||
_epTrapHandler = _removeTrap(_epTrapHandler);
|
||||
if (_epTriggerEl && _epTriggerEl.focus) {
|
||||
_epTriggerEl.focus();
|
||||
}
|
||||
_epTriggerEl = null;
|
||||
hideCreatePolicyModal();
|
||||
}
|
||||
|
||||
function submitEditPolicy() {
|
||||
const id = document.getElementById("ep-id").value;
|
||||
document.getElementById("ep-submit").disabled = true;
|
||||
authFetch("/v1/api/admin/policies/" + id, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
name: document.getElementById("ep-name").value.trim(),
|
||||
tool_pattern: document.getElementById("ep-pattern").value.trim(),
|
||||
action: document.getElementById("ep-action").value,
|
||||
priority: parseInt(document.getElementById("ep-priority").value, 10) || 0,
|
||||
enabled: document.getElementById("ep-enabled").checked,
|
||||
}),
|
||||
})
|
||||
function _submitPolicyShelf() {
|
||||
const shelf = document.getElementById("policy-shelf");
|
||||
const errEl = document.getElementById("policy-shelf-error");
|
||||
const policyId = document.getElementById("pol-id").value;
|
||||
const name = document.getElementById("pol-name").value.trim();
|
||||
const pattern = document.getElementById("pol-pattern").value.trim();
|
||||
if (!name || !pattern) {
|
||||
errEl.textContent = "Name and pattern are required";
|
||||
errEl.classList.add("is-visible");
|
||||
return;
|
||||
}
|
||||
const body = {
|
||||
name: name,
|
||||
tool_pattern: pattern,
|
||||
action: document.getElementById("pol-action").value,
|
||||
priority: parseInt(document.getElementById("pol-priority").value, 10) || 0,
|
||||
};
|
||||
if (policyId) body.enabled = document.getElementById("pol-enabled").checked;
|
||||
errEl.classList.remove("is-visible");
|
||||
window.TurnstoneHatch.setBusy(shelf, true);
|
||||
authFetch(
|
||||
policyId ? "/v1/api/admin/policies/" + policyId : "/v1/api/admin/policies",
|
||||
{
|
||||
method: policyId ? "PUT" : "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
)
|
||||
.then(function (r) {
|
||||
if (!r.ok)
|
||||
return r.json().then(function (d) {
|
||||
@@ -1024,17 +1070,15 @@ function submitEditPolicy() {
|
||||
return r.json();
|
||||
})
|
||||
.then(function () {
|
||||
hideEditPolicyModal();
|
||||
showToast("Policy updated");
|
||||
window.TurnstoneHatch.setBusy(shelf, false);
|
||||
hideCreatePolicyModal();
|
||||
showToast(policyId ? "Policy updated" : "Policy created");
|
||||
loadGovPolicies();
|
||||
})
|
||||
.catch(function (e) {
|
||||
const el = document.getElementById("edit-policy-error");
|
||||
el.textContent = e.message;
|
||||
el.classList.add("is-visible");
|
||||
})
|
||||
.finally(function () {
|
||||
document.getElementById("ep-submit").disabled = false;
|
||||
window.TurnstoneHatch.setBusy(shelf, false);
|
||||
errEl.textContent = e.message;
|
||||
errEl.classList.add("is-visible");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+134
-159
@@ -2427,6 +2427,99 @@
|
||||
</button>
|
||||
</footer>
|
||||
</dialog>
|
||||
|
||||
<!-- Tool-policy shelf (create + edit) — pane-scoped. The priority
|
||||
field is annotated with its evaluation neighbors (computed from
|
||||
the already-loaded policy list; policies run highest-first). -->
|
||||
<dialog
|
||||
class="hatch hatch--shelf"
|
||||
id="policy-shelf"
|
||||
data-kind="create"
|
||||
aria-labelledby="policy-shelf-title"
|
||||
>
|
||||
<header class="sh-head">
|
||||
<span class="sh-led" aria-hidden="true"></span>
|
||||
<h2 class="sh-title" id="policy-shelf-title">New tool policy</h2>
|
||||
<span class="sh-tag" aria-hidden="true" id="policy-shelf-tag"
|
||||
>POL-NEW</span
|
||||
>
|
||||
<button class="sh-x" data-close aria-label="Close">✕</button>
|
||||
</header>
|
||||
<div class="sh-body">
|
||||
<div
|
||||
id="policy-shelf-error"
|
||||
class="sh-alert"
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
aria-atomic="true"
|
||||
></div>
|
||||
<input id="pol-id" type="hidden" />
|
||||
<label for="pol-name">Name</label>
|
||||
<input
|
||||
id="pol-name"
|
||||
type="text"
|
||||
placeholder="e.g. Block shell access"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label for="pol-pattern"
|
||||
>Tool pattern
|
||||
<span class="label-hint"
|
||||
>glob: bash*, file_write, *</span
|
||||
></label
|
||||
>
|
||||
<input
|
||||
id="pol-pattern"
|
||||
type="text"
|
||||
class="sh-mono"
|
||||
placeholder="bash*"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<div class="field-pair">
|
||||
<div>
|
||||
<label for="pol-action">Action</label>
|
||||
<select id="pol-action">
|
||||
<option value="ask">Ask (require approval)</option>
|
||||
<option value="allow">Allow (auto-approve)</option>
|
||||
<option value="deny">Deny (block)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="pol-priority"
|
||||
>Priority
|
||||
<span class="label-hint"
|
||||
>higher = evaluated first</span
|
||||
></label
|
||||
>
|
||||
<input
|
||||
id="pol-priority"
|
||||
type="number"
|
||||
value="0"
|
||||
min="0"
|
||||
max="9999"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="match-strip"
|
||||
id="pol-neighbors"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
></div>
|
||||
<label class="toggle-switch" id="pol-enabled-row" hidden>
|
||||
<input id="pol-enabled" type="checkbox" checked />
|
||||
<span class="toggle-track" aria-hidden="true"></span>
|
||||
<span class="toggle-label">Enabled</span>
|
||||
</label>
|
||||
</div>
|
||||
<footer class="sh-foot">
|
||||
<div class="sh-foot-meta"></div>
|
||||
<button class="sh-btn" data-close>Cancel</button>
|
||||
<button id="pol-submit" class="sh-btn sh-btn--primary">
|
||||
Create
|
||||
</button>
|
||||
</footer>
|
||||
</dialog>
|
||||
</div>
|
||||
<!-- /admin-layout -->
|
||||
</div>
|
||||
@@ -2669,60 +2762,58 @@
|
||||
</div>
|
||||
|
||||
<!-- Token Created (show-once) Modal -->
|
||||
<div
|
||||
id="token-created-overlay"
|
||||
style="display: none"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
<dialog
|
||||
class="hatch hatch--dialog"
|
||||
id="token-created-dialog"
|
||||
data-kind="success"
|
||||
aria-labelledby="token-created-title"
|
||||
>
|
||||
<div id="token-created-box" class="admin-modal">
|
||||
<h2 id="token-created-title">Token Created</h2>
|
||||
<p class="token-created-warning">
|
||||
Copy this token now. It will not be shown again.
|
||||
<header class="sh-head">
|
||||
<span class="sh-led" aria-hidden="true"></span>
|
||||
<h2 class="sh-title" id="token-created-title">Token created</h2>
|
||||
<span class="sh-tag" aria-hidden="true">TOK-ISSUE</span>
|
||||
<button class="sh-x" data-close aria-label="Close">✕</button>
|
||||
</header>
|
||||
<div class="sh-body">
|
||||
<div id="token-created-value" class="sh-artifact"></div>
|
||||
<p class="sh-callout">
|
||||
Shown once — store it now. Turnstone keeps only the hash.
|
||||
</p>
|
||||
<div id="token-created-value" class="token-display"></div>
|
||||
<div class="modal-buttons">
|
||||
<button class="modal-submit" onclick="copyCreatedToken()">
|
||||
Copy to clipboard
|
||||
</button>
|
||||
<button class="modal-cancel" onclick="hideTokenCreatedModal()">
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="sh-foot">
|
||||
<div class="sh-foot-meta"></div>
|
||||
<button class="sh-btn" data-close>Done</button>
|
||||
<button id="tc-copy" class="sh-btn sh-btn--primary" autofocus>
|
||||
Copy token
|
||||
</button>
|
||||
</footer>
|
||||
</dialog>
|
||||
|
||||
<!-- Confirm Action Modal (reusable) -->
|
||||
<div
|
||||
id="confirm-overlay"
|
||||
style="display: none"
|
||||
<dialog
|
||||
class="hatch hatch--dialog"
|
||||
id="confirm-dialog"
|
||||
data-kind="danger"
|
||||
role="alertdialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="confirm-title"
|
||||
aria-describedby="confirm-message"
|
||||
>
|
||||
<div id="confirm-box" class="admin-modal">
|
||||
<h2 id="confirm-title">Confirm</h2>
|
||||
<p
|
||||
id="confirm-message"
|
||||
style="color: var(--fg-dim); margin: 12px 0 20px; line-height: 1.5"
|
||||
></p>
|
||||
<div class="modal-buttons">
|
||||
<button class="modal-cancel" onclick="hideConfirmModal()">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
id="confirm-submit"
|
||||
class="modal-submit"
|
||||
style="background: var(--red); border-color: var(--red)"
|
||||
onclick="_confirmCallback()"
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
<header class="sh-head">
|
||||
<span class="sh-led" aria-hidden="true"></span>
|
||||
<h2 class="sh-title" id="confirm-title">Confirm</h2>
|
||||
<button class="sh-x" data-close aria-label="Close">✕</button>
|
||||
</header>
|
||||
<div class="sh-body">
|
||||
<p class="sh-prose" id="confirm-message"></p>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="sh-foot">
|
||||
<div class="sh-foot-meta"></div>
|
||||
<button class="sh-btn" data-close autofocus>Cancel</button>
|
||||
<button id="confirm-submit" class="sh-btn sh-btn--danger">
|
||||
Confirm
|
||||
</button>
|
||||
</footer>
|
||||
</dialog>
|
||||
|
||||
<!-- Create Channel Link Modal -->
|
||||
<div
|
||||
@@ -2907,122 +2998,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Policy Modal -->
|
||||
<div
|
||||
id="create-policy-overlay"
|
||||
style="display: none"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="create-policy-title"
|
||||
>
|
||||
<div id="create-policy-box" class="admin-modal">
|
||||
<h2 id="create-policy-title">Create Tool Policy</h2>
|
||||
<div
|
||||
id="create-policy-error"
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
aria-atomic="true"
|
||||
></div>
|
||||
<label for="cp-name">Name</label>
|
||||
<input
|
||||
id="cp-name"
|
||||
type="text"
|
||||
placeholder="e.g. Block shell access"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label for="cp-pattern"
|
||||
>Tool pattern
|
||||
<span class="label-hint"
|
||||
>glob syntax: bash*, file_write, *</span
|
||||
></label
|
||||
>
|
||||
<input
|
||||
id="cp-pattern"
|
||||
type="text"
|
||||
placeholder="bash*"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<label for="cp-action">Action</label>
|
||||
<select id="cp-action">
|
||||
<option value="ask">Ask (require approval)</option>
|
||||
<option value="allow">Allow (auto-approve)</option>
|
||||
<option value="deny">Deny (block)</option>
|
||||
</select>
|
||||
<label for="cp-priority"
|
||||
>Priority
|
||||
<span class="label-hint">higher = evaluated first</span></label
|
||||
>
|
||||
<input id="cp-priority" type="number" value="0" min="0" max="9999" />
|
||||
<div class="modal-buttons">
|
||||
<button class="modal-cancel" onclick="hideCreatePolicyModal()">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
id="cp-submit"
|
||||
class="modal-submit"
|
||||
onclick="submitCreatePolicy()"
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Policy Modal -->
|
||||
<div
|
||||
id="edit-policy-overlay"
|
||||
style="display: none"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="edit-policy-title"
|
||||
>
|
||||
<div id="edit-policy-box" class="admin-modal">
|
||||
<h2 id="edit-policy-title">Edit Tool Policy</h2>
|
||||
<div
|
||||
id="edit-policy-error"
|
||||
role="alert"
|
||||
aria-live="assertive"
|
||||
aria-atomic="true"
|
||||
></div>
|
||||
<input id="ep-id" type="hidden" />
|
||||
<label for="ep-name">Name</label>
|
||||
<input id="ep-name" type="text" autocomplete="off" />
|
||||
<label for="ep-pattern">Tool pattern</label>
|
||||
<input
|
||||
id="ep-pattern"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
/>
|
||||
<label for="ep-action">Action</label>
|
||||
<select id="ep-action">
|
||||
<option value="ask">Ask (require approval)</option>
|
||||
<option value="allow">Allow (auto-approve)</option>
|
||||
<option value="deny">Deny (block)</option>
|
||||
</select>
|
||||
<label for="ep-priority">Priority</label>
|
||||
<input id="ep-priority" type="number" value="0" min="0" max="9999" />
|
||||
<label class="toggle-switch">
|
||||
<input id="ep-enabled" type="checkbox" checked />
|
||||
<span class="toggle-track" aria-hidden="true"></span>
|
||||
<span class="toggle-label">Enabled</span>
|
||||
</label>
|
||||
<div class="modal-buttons">
|
||||
<button class="modal-cancel" onclick="hideEditPolicyModal()">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
id="ep-submit"
|
||||
class="modal-submit"
|
||||
onclick="submitEditPolicy()"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Prompt Policy Modal -->
|
||||
<div
|
||||
id="create-ppolicy-overlay"
|
||||
|
||||
@@ -1774,15 +1774,11 @@ textarea.skill-content-area {
|
||||
|
||||
#create-user-overlay,
|
||||
#create-token-overlay,
|
||||
#token-created-overlay,
|
||||
#create-channel-overlay,
|
||||
#confirm-overlay,
|
||||
#schedule-runs-overlay,
|
||||
#create-role-overlay,
|
||||
#edit-role-overlay,
|
||||
#user-roles-overlay,
|
||||
#create-policy-overlay,
|
||||
#edit-policy-overlay,
|
||||
#create-ppolicy-overlay,
|
||||
#edit-ppolicy-overlay,
|
||||
#create-template-overlay,
|
||||
@@ -1807,14 +1803,6 @@ textarea.skill-content-area {
|
||||
justify-content: center;
|
||||
z-index: 600;
|
||||
}
|
||||
/* Confirm dialogs are launched FROM other overlays (e.g. unlock-skill from
|
||||
the edit-template modal). They share z-index 600, so DOM order picks the
|
||||
winner — and confirm-overlay is earlier in the DOM, so it would render
|
||||
underneath. Bump it above the per-feature overlays but keep it below
|
||||
toasts (z-index 700). */
|
||||
#confirm-overlay {
|
||||
z-index: 650;
|
||||
}
|
||||
|
||||
/* Token display (show-once) */
|
||||
.token-created-warning {
|
||||
|
||||
@@ -81,6 +81,11 @@
|
||||
.hatch:not([open]) {
|
||||
display: none;
|
||||
}
|
||||
/* hidden must always win inside a hatch — display rules on form chrome
|
||||
(e.g. label.toggle-switch's inline-flex) would otherwise defeat it. */
|
||||
.hatch [hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.hatch--shelf {
|
||||
position: absolute;
|
||||
|
||||
Reference in New Issue
Block a user