@songsid/agend 2.3.5-alpha.3 → 2.3.5-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-runtime-api.d.ts +4 -0
- package/dist/agent-runtime-api.js +59 -0
- package/dist/agent-runtime-api.js.map +1 -0
- package/dist/agent-runtime-change.d.ts +74 -0
- package/dist/agent-runtime-change.js +102 -0
- package/dist/agent-runtime-change.js.map +1 -0
- package/dist/auth-api.js +4 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/codex-footer-hints.d.ts +4 -0
- package/dist/backend/codex-footer-hints.js +27 -0
- package/dist/backend/codex-footer-hints.js.map +1 -0
- package/dist/backend/codex.js +8 -10
- package/dist/backend/codex.js.map +1 -1
- package/dist/fleet-manager.d.ts +3 -0
- package/dist/fleet-manager.js +157 -1
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-windows-runtime.d.ts +6 -1
- package/dist/fleet-windows-runtime.js +70 -1
- package/dist/fleet-windows-runtime.js.map +1 -1
- package/dist/office-share-page.js +1 -1
- package/dist/office-share-page.js.map +1 -1
- package/dist/public-web-link.d.ts +6 -0
- package/dist/public-web-link.js +9 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/runtime-api.js +84 -9
- package/dist/runtime-api.js.map +1 -1
- package/dist/runtime-build-info.d.ts +9 -0
- package/dist/runtime-build-info.js +57 -0
- package/dist/runtime-build-info.js.map +1 -0
- package/dist/runtime-bundle.d.ts +3 -1
- package/dist/runtime-bundle.js +14 -8
- package/dist/runtime-bundle.js.map +1 -1
- package/dist/runtime-cli.d.ts +6 -1
- package/dist/runtime-cli.js +46 -11
- package/dist/runtime-cli.js.map +1 -1
- package/dist/runtime-client-store.d.ts +20 -1
- package/dist/runtime-client-store.js +56 -4
- package/dist/runtime-client-store.js.map +1 -1
- package/dist/runtime-client.d.ts +12 -0
- package/dist/runtime-client.js +43 -7
- package/dist/runtime-client.js.map +1 -1
- package/dist/runtime-connection.d.ts +22 -0
- package/dist/runtime-connection.js +93 -0
- package/dist/runtime-connection.js.map +1 -0
- package/dist/runtime-coordinator.d.ts +9 -0
- package/dist/runtime-coordinator.js +81 -34
- package/dist/runtime-coordinator.js.map +1 -1
- package/dist/runtime-pane-boot.d.ts +7 -0
- package/dist/runtime-pane-boot.js +23 -0
- package/dist/runtime-pane-boot.js.map +1 -1
- package/dist/runtime-protocol.d.ts +13 -0
- package/dist/runtime-protocol.js +18 -0
- package/dist/runtime-protocol.js.map +1 -1
- package/dist/settings-effect.js +1 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +3 -0
- package/dist/settings-http-confirmation.js +16 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/ui/agent-runtime-settings.js +67 -0
- package/dist/ui/agent-runtime.js +20 -2
- package/dist/ui/backend-signin.js +36 -21
- package/dist/ui/native-instance-settings.js +5 -8
- package/dist/ui/panel-chat.js +3 -2
- package/dist/ui/panel-fleet.js +4 -2
- package/dist/ui/panel-runtimes.js +136 -33
- package/dist/ui/panel-settings.js +224 -139
- package/dist/ui/panel-windows-cli.js +77 -41
- package/dist/ui/settings-agent-filters.js +94 -0
- package/dist/ui/settings-appearance.js +122 -23
- package/dist/ui/settings-dialogs.js +5 -5
- package/dist/ui/settings-guests.js +25 -11
- package/dist/ui/settings-layout.js +245 -0
- package/dist/ui/settings-model.js +8 -0
- package/dist/ui/settings-registry.js +5 -3
- package/dist/ui/settings-shortcuts.js +65 -0
- package/dist/ui/settings-strings.js +93 -0
- package/dist/ui/settings-web-authority.js +14 -11
- package/dist/ui/shared/app-shell.js +17 -7
- package/dist/ui/shared/app.css +1022 -12
- package/dist/ui/shared/instance-nav.js +3 -2
- package/dist/ui/shared/machine-name.js +13 -0
- package/dist/ui/shared/settings-width.js +31 -0
- package/dist/ui/shared/tokens.css +1 -0
- package/dist/ui/shared/ui-dialog.js +2 -2
- package/dist/ui/shared/ui-menu.js +3 -3
- package/dist/ui/windows-cli-catalog.js +41 -12
- package/dist/web-api.d.ts +1 -0
- package/dist/web-api.js +3 -0
- package/dist/web-api.js.map +1 -1
- package/dist/windows-agents.d.ts +3 -1
- package/dist/windows-agents.js +13 -10
- package/dist/windows-agents.js.map +1 -1
- package/dist/windows-cli-api.js +2 -1
- package/dist/windows-cli-api.js.map +1 -1
- package/dist/windows-cli-worker.d.ts +1 -0
- package/dist/windows-cli-worker.js +2 -2
- package/dist/windows-cli-worker.js.map +1 -1
- package/dist/windows-cli.d.ts +1 -0
- package/dist/windows-cli.js +7 -2
- package/dist/windows-cli.js.map +1 -1
- package/package.json +1 -1
|
@@ -56,6 +56,12 @@ import { WebAuthorityCard } from "./settings-web-authority.js";
|
|
|
56
56
|
import { OutsideCard } from "./settings-outside.js";
|
|
57
57
|
import { NamedLinkTest, setTypedTunnelToken } from "./settings-named-test.js";
|
|
58
58
|
import { SignInList } from "./backend-signin.js";
|
|
59
|
+
import { ProviderMark } from "./provider-marks.js";
|
|
60
|
+
import { CliProductMark, cliLabel } from "./cli-picker.js";
|
|
61
|
+
import { SettingsPageHead, SettingsTabs, SettingsTabPanel, useSettingsTabs, revealSettingsTarget, layoutText } from "./settings-layout.js";
|
|
62
|
+
import { AgentFilters, agentFilterText, agentRuntime, agentRuntimeOffline, agentStatus, filterAgentGroups } from "./settings-agent-filters.js";
|
|
63
|
+
import { useRuntimeCatalog } from "./runtime-catalog.js";
|
|
64
|
+
import { ShortcutsHelpRow } from "./settings-shortcuts.js";
|
|
59
65
|
import { GeneralLocation } from "./general-location.js";
|
|
60
66
|
|
|
61
67
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
@@ -107,7 +113,7 @@ async function loadLive(lease) {
|
|
|
107
113
|
if (!lease.current()) return null;
|
|
108
114
|
const live = {}, classicLive = {};
|
|
109
115
|
if (f.ok && f.body && Array.isArray(f.body.instances)) for (const i of f.body.instances) {
|
|
110
|
-
live[i.name] = { status: i.status, state: i.state, backend: i.backend, model: i.model, model_display: modelText(i, i.model_display), effort: i.effort, effort_supported: i.effort_supported, runtime:i.runtime, capabilities:i.capabilities };
|
|
116
|
+
live[i.name] = { status: i.status, state: i.state, backend: i.backend, model: i.model, model_display: modelText(i, i.model_display), effort: i.effort, effort_supported: i.effort_supported, runtime:i.runtime, capabilities:i.capabilities, machine_id:i.machine_id, machine_name:i.machine_name, machine_status:i.machine_status };
|
|
111
117
|
if (i.classic) classicLive[i.name] = { status: i.status, state: i.state };
|
|
112
118
|
}
|
|
113
119
|
// /api/fleet carries live state for ClassicBot rooms; the profiles are the fallback for one not listed there yet.
|
|
@@ -381,20 +387,23 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
381
387
|
const formKey = data && !data.error ? `${data.loadedAt}:${draftEpoch}` : "";
|
|
382
388
|
// #2347 regrouping: each card where the person looks for it (the IA in #2347 §3); a form split across sections (the
|
|
383
389
|
// defaults, Web) is one form with one draft, shown a part at a time.
|
|
384
|
-
const sectionBody = section === "preferences" ? html`<${DeviceSection} key=${you.key}
|
|
390
|
+
const sectionBody = section === "preferences" ? html`<${DeviceSection} key=${you.key} />`
|
|
385
391
|
: section === "look" ? html`<${AppearanceSection} ...${you} part="look" />`
|
|
386
392
|
: section === "runtimes" ? html`<${RuntimesPanel} navKey=${navKey} />`
|
|
387
393
|
: !data ? html`<${Skeleton} lines=${6} />`
|
|
388
394
|
: data.error ? html`<${ErrorState} message=${tn("configLoadFailed")} onRetry=${reload} />`
|
|
389
|
-
: section === "agents" ? html`<${YamlLock} staged=${staged}><${Agents} ctx=${ctx} search=${search} openDialog=${toDialog} /></${YamlLock}
|
|
395
|
+
: section === "agents" ? html`<${YamlLock} staged=${staged}><${Agents} ctx=${ctx} search=${search} clearSearch=${() => setSearch("")} openDialog=${toDialog} /></${YamlLock}><details class="s-ui-disclosure s-ai-accounts"><summary><span>${layoutText("aiAccounts")}</span><small>${layoutText("aiAccountsSummary")}</small></summary><div class="s-disclosure-body s-auth-body">
|
|
396
|
+
<div class="s-auth-scope"><${Icon} name="view" size=${18} /><div><strong>${layoutText("aiHostScope")}</strong><p>${layoutText("aiAccountsHint")}</p><a href=${settingsPath("runtimes")}>${layoutText("aiWindowsLink")}<span aria-hidden="true">→</span></a></div></div>
|
|
397
|
+
<${SignInList} />${ctx.providerOk ? html`<${ProviderKeys} ctx=${ctx} />` : html`<section class="s-auth-section s-api-unavailable"><header class="s-auth-section-head"><div><h3>${layoutText("apiKeysTitle")}</h3><p>${layoutText("apiKeysHint")}</p></div><span class="s-auth-state">${layoutText("apiKeysUnavailableState")}</span></header><p class="note">${layoutText("apiKeysUnavailable")}</p></section>`}
|
|
398
|
+
</div></details>`
|
|
390
399
|
: section === "agent-defaults" ? html`<${GeneralLocation} /><${YamlLock} staged=${staged}><${General} key=${formKey} ctx=${ctx} part="fleet" /></${YamlLock}>`
|
|
391
400
|
: section === "chat" ? html`<${YamlLock} staged=${staged}><${Bots} ctx=${ctx} search=${search} openDialog=${toDialog} /></${YamlLock}>
|
|
392
401
|
<${WebSection} key=${formKey} ctx=${ctx} staged=${staged} part="chat" />
|
|
393
|
-
<div id="classicbot" class="s-anchor"><h2 class="s-ap-group">ClassicBot</h2><${Classic} ctx=${ctx} search=${search} openDialog=${toDialog}
|
|
394
|
-
|
|
402
|
+
<div id="classicbot" class="s-anchor s-classic-area"><section class="card s-chat-classic"><h2 class="s-ap-group">ClassicBot</h2><${Classic} ctx=${ctx} search=${search} openDialog=${toDialog} /></section>
|
|
403
|
+
<details class="s-ui-disclosure s-classic-defaults"><summary>${layoutText("classicDefaults")}</summary><div class="s-disclosure-body"><${General} key=${formKey} ctx=${ctx} part="classic" /></div></details></div>`
|
|
395
404
|
: section === "web-dashboard" ? html`<${WebSection} key=${formKey} ctx=${ctx} staged=${staged} part="dashboard" /><${AppearanceSection} ...${you} part="dashboard" />`
|
|
396
405
|
: section === "access" ? html`<${WebSection} key=${formKey} ctx=${ctx} staged=${staged} part="access" />`
|
|
397
|
-
: html`<${
|
|
406
|
+
: html`<${AdvancedSettings} ctx=${ctx} staged=${staged} formKey=${formKey} draftEpoch=${draftEpoch} />`;
|
|
398
407
|
// #2347 §6: the setting a search result opened — brought into view once its section is drawn (its data may still be
|
|
399
408
|
// on its way), focused, and flashed for 2 s.
|
|
400
409
|
const wanted = useRef(null);
|
|
@@ -413,6 +422,7 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
413
422
|
if (++tries < 40) timer = setTimeout(find, 50); else wanted.current = null;
|
|
414
423
|
return;
|
|
415
424
|
}
|
|
425
|
+
if (revealSettingsTarget(el)) { timer = setTimeout(find, 0); return; }
|
|
416
426
|
wanted.current = null;
|
|
417
427
|
// #2347 §10: a setting in a closed drawer (Advanced) is shown — every drawer around it opens first.
|
|
418
428
|
for (let d = el.closest && el.closest("details"); d; d = d.parentElement && d.parentElement.closest("details")) d.open = true;
|
|
@@ -453,7 +463,14 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
453
463
|
if (!id) return;
|
|
454
464
|
let el = null;
|
|
455
465
|
try { el = document.getElementById(decodeURIComponent(id)); } catch { el = null; }
|
|
456
|
-
if (el
|
|
466
|
+
if (!el) return;
|
|
467
|
+
const show = () => {
|
|
468
|
+
// Hash links, like settings-search targets, must reveal every enclosing disclosure before scrolling.
|
|
469
|
+
for (let d = el.closest && el.closest("details"); d; d = d.parentElement && d.parentElement.closest("details")) d.open = true;
|
|
470
|
+
if (typeof el.scrollIntoView === "function") el.scrollIntoView({ block: "start" });
|
|
471
|
+
};
|
|
472
|
+
if (revealSettingsTarget(el)) { const timer = setTimeout(show, 0); return () => clearTimeout(timer); }
|
|
473
|
+
show();
|
|
457
474
|
}, [section, navKey, !!data]);
|
|
458
475
|
// A dialog belongs to the navigation that opened it (its draft names one object, never another).
|
|
459
476
|
const open = dialog && dialog.key === navKey ? dialog : null;
|
|
@@ -472,14 +489,15 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
472
489
|
<span class="s-nav-head" id=${`s-grp-${g.id}`}>${tn(`grp_${g.id}`)}</span>
|
|
473
490
|
${g.sections.map((k) => html`<a key=${k} href=${sectionPath(k)} class=${`seg-item${k === section ? " active" : ""}`}
|
|
474
491
|
aria-current=${k === section ? "page" : undefined}><${Icon} name=${ICONS[k]} size=${16} /><span>${tn(`sec_${k}`)}</span></a>`)}</div>`)}</div></nav>
|
|
475
|
-
<div data-trust="panel-content" class="panel-body"><div class="col">
|
|
492
|
+
<div data-trust="panel-content" class="panel-body"><div class="col" data-settings-section=${section}>
|
|
476
493
|
<a class="s-back" href=${settingsPath()}><${Icon} name="back" size=${16} /><span>${tn("title")}</span><span class="s-back-here">${tn(`sec_${section}`)}</span></a>
|
|
494
|
+
${section !== "runtimes" ? html`<${SettingsPageHead} section=${section}>${section === "agents" && ctx ? html`<button type="button" id="newAgentBtn" class="btn btn-primary" disabled=${yamlStaged(staged)} onClick=${() => toDialog({ kind: "create" })}><${Icon} name="plus" size=${16} />${tn("newAgent")}</button>` : null}</${SettingsPageHead}>` : null}
|
|
477
495
|
${searchable ? html`<div class="s-toolbar"><label class="s-search"><${Icon} name="search" size=${14} /><span class="sr-only">${tn("searchAll")}</span>
|
|
478
496
|
<input type="search" value=${search} placeholder=${tn("searchAll")} onInput=${(e) => setSearch(e.target.value)} /></label></div>` : null}
|
|
479
497
|
${op ? html`<${OperationCard} op=${op} schema=${data && data.schema} />` : null}
|
|
480
498
|
<${PendingChanges} inline=${true} />
|
|
481
499
|
${ctx && (section === "agents" || section === "chat") ? html`<${FirstRunCard} onConnect=${openWizard} />` : null}
|
|
482
|
-
|
|
500
|
+
<div class="s-section-layout" data-section=${section}>${sectionBody}</div>
|
|
483
501
|
</div></div>
|
|
484
502
|
${staged.size ? html`<${PendingBar} staged=${staged} busy=${opBusy(op)} onApply=${apply} onDiscard=${discard} section=${section} />` : null}
|
|
485
503
|
${open && ctx ? renderDialog(open, ctx, close) : null}
|
|
@@ -487,6 +505,14 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
487
505
|
</div>`;
|
|
488
506
|
}
|
|
489
507
|
|
|
508
|
+
function AdvancedSettings({ ctx, staged, formKey, draftEpoch }) {
|
|
509
|
+
const [tab, setTab] = useSettingsTabs("advanced", "editor");
|
|
510
|
+
return html`<div class="s-advanced-layout"><${SettingsTabs} id="advanced" value=${tab} onChange=${setTab} items=${[{ id: "editor", label: layoutText("editor") }, { id: "effective", label: layoutText("effective") }]} />
|
|
511
|
+
<${SettingsTabPanel} group="advanced" id="editor" active=${tab === "editor"}><${Developer} key=${`dev:${draftEpoch}`} ctx=${ctx} /></${SettingsTabPanel}>
|
|
512
|
+
<${SettingsTabPanel} group="advanced" id="effective" active=${tab === "effective"}><${WebSection} key=${formKey} ctx=${ctx} staged=${staged} part="readonly" /></${SettingsTabPanel}>
|
|
513
|
+
</div>`;
|
|
514
|
+
}
|
|
515
|
+
|
|
490
516
|
/**
|
|
491
517
|
* #1604, #1665: Preferences. Synced across the person's devices (kept on the host, app-prefs.js): theme (theme.js),
|
|
492
518
|
* language (app-i18n.js) and HTML previews (preview.js, through the app's confirm). On the public link they are
|
|
@@ -608,17 +634,23 @@ export function DeviceSection() {
|
|
|
608
634
|
setTimeout(go, 0);
|
|
609
635
|
};
|
|
610
636
|
const publicLink = typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1";
|
|
611
|
-
return html
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
637
|
+
return html`<div class="s-pref-stack">
|
|
638
|
+
<section class="card s-device s-pref-personal"><h3>${tn("prefsProfileGroup")}</h3>
|
|
639
|
+
<${ProfileCard} embedded=${true} />
|
|
640
|
+
<div class="s-pref-name-format"><${DevicePrefs} labels=${true} fields=${["names"]} /></div>
|
|
641
|
+
</section>
|
|
642
|
+
<section class="card s-device s-synced s-pref-content"><div class="s-pref-group-head"><h3>${tn("prefsContentGroup")}</h3><span class="s-scope" title=${publicLink ? tn("syncedPublicLink") : tn("syncedLead")}>${tn("prefsAccountScope")}</span></div>
|
|
643
|
+
${publicLink ? html`<p class="sub">${tn("syncedPublicLink")}</p>` : null}
|
|
644
|
+
<${DevicePrefs} labels=${true} fields=${["language"]} />
|
|
645
|
+
${P() ? html`<label class="check s-pref-preview"><span>${t("chat.previews")}</span><input id="pref-previews" class="s-ui-toggle" type="checkbox" checked=${on} onChange=${(e) => { const want = e.target.checked; setPreviewOptIn(want).then(setOn); }} /><small>${layoutText("previewsPersonalHint")}</small></label>` : null}
|
|
646
|
+
</section>
|
|
647
|
+
<section class="card s-device s-local s-pref-help"><h3>${tn("prefsHelpGroup")}</h3>
|
|
648
|
+
<div class="s-pref-help-row"><div><strong>${t("chat.tour")}</strong><p class="sub">${tn("prefsTourHint")}</p></div>
|
|
649
|
+
<button type="button" id="tourBtn" class="btn" title=${t("chat.tourBtnTitle")} onClick=${replay}><${Icon} name="info" size=${16} />${tn("prefsReplayTour")}</button></div>
|
|
650
|
+
<${ShortcutsHelpRow} />
|
|
651
|
+
</section>
|
|
652
|
+
<a class="s-pref-look-link" href=${settingsPath("look")}>${tn("prefsLookHint")}<${Icon} name="chevron" size=${16} /></a>
|
|
653
|
+
</div>`;
|
|
622
654
|
}
|
|
623
655
|
|
|
624
656
|
/**
|
|
@@ -628,7 +660,7 @@ export function DeviceSection() {
|
|
|
628
660
|
* refuses one; this only shows what it says.
|
|
629
661
|
*/
|
|
630
662
|
const view = (b) => ({ phase: "ready", name: b.name || "", shown: b.shown || "", id: b.id || "", bound: b.bound === true, platform: b.platform_name || "", error: "" });
|
|
631
|
-
export function ProfileCard() {
|
|
663
|
+
export function ProfileCard({ embedded = false } = {}) {
|
|
632
664
|
const [state, setState] = useState({ phase: "loading", name: "", shown: "", id: "", bound: false, platform: "", error: "" });
|
|
633
665
|
const [draft, setDraft] = useState("");
|
|
634
666
|
const lease = useLease();
|
|
@@ -655,16 +687,22 @@ export function ProfileCard() {
|
|
|
655
687
|
};
|
|
656
688
|
const busy = state.phase !== "ready";
|
|
657
689
|
const changed = draft.trim() !== state.name;
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
690
|
+
const initial = [...(state.shown || state.name || "web-user")][0]?.toUpperCase() || "W";
|
|
691
|
+
const fields = html`<div class="s-pref-profile-layout">
|
|
692
|
+
${embedded ? html`<span class="s-pref-avatar" aria-hidden="true">${initial}</span>` : null}
|
|
693
|
+
<div class="s-pref-profile-main"><div class="field"><label for="profile-name">${tn("profileName")}</label>
|
|
694
|
+
<div class="s-pref-name-action"><input id="profile-name" type="text" maxlength="200" autocomplete="nickname" value=${draft} disabled=${busy}
|
|
662
695
|
placeholder=${state.platform || "web-user"} aria-invalid=${state.error ? "true" : "false"} aria-describedby="profile-help"
|
|
663
696
|
onInput=${(e) => setDraft(e.target.value)} onKeyDown=${(e) => { if (e.key === "Enter" && changed && draft.trim()) save(false); }} />
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
697
|
+
<button type="button" class="btn primary" disabled=${busy || !changed || !draft.trim()} onClick=${() => save(false)}>${embedded ? tn("prefsSaveName") : tn("profileSave")}</button></div>
|
|
698
|
+
<p class="sub" id="profile-help">${state.error ? html`<span class="err" role="alert">${state.error}</span>` : tn("profileHelp", state.shown || "web-user", state.id)}</p>
|
|
699
|
+
</div>
|
|
700
|
+
<p class="sub s-pref-immediate">${tn("prefsNameImmediate")}</p>
|
|
701
|
+
${state.name ? html`<div class="s-device-row"><button type="button" class="btn btn-ghost btn-sm" disabled=${busy} onClick=${() => save(true)}>${state.bound ? tn("profileUseAccount", state.platform || "web-user") : tn("profileClear")}</button></div>` : null}
|
|
702
|
+
<details class="s-pref-identity-details"><summary>${layoutText("profileIdentityDetails")}</summary><p class="sub">${state.bound ? tn("profileLeadBound") : tn("profileLeadUnbound")}</p></details>
|
|
703
|
+
</div></div>`;
|
|
704
|
+
return embedded ? html`<div class="s-profile s-pref-profile-fields">${fields}</div>`
|
|
705
|
+
: html`<section class="card s-device s-profile"><h3>${tn("profileTitle")}</h3>${fields}</section>`;
|
|
668
706
|
}
|
|
669
707
|
|
|
670
708
|
/** The operation in hand still owns Apply: its writes and job, or a restart of AgEnD from the press to its end. */
|
|
@@ -761,60 +799,67 @@ function agentChannelLabel(inst, chs) {
|
|
|
761
799
|
return idx >= 0 ? `${chs[idx].type} (${chLabel(idx)})` : inst.channel_id;
|
|
762
800
|
}
|
|
763
801
|
|
|
764
|
-
function Agents({ ctx, search, openDialog }) {
|
|
802
|
+
function Agents({ ctx, search, clearSearch, openDialog }) {
|
|
765
803
|
const insts = ctx.fleet.instances || {};
|
|
766
804
|
const [collapsed, setCollapsed] = useState(() => new Set());
|
|
767
805
|
const [busy, setBusy] = useState(() => new Map()); // name → the action under way
|
|
806
|
+
const [runtime, setRuntime] = useState("");
|
|
807
|
+
const [status, setStatus] = useState("");
|
|
808
|
+
// The computers' names come from the same list as the Runtime page (revoked ones included, so an Agent still bound to one can say so).
|
|
809
|
+
const catalog = useRuntimeCatalog(Object.values(insts).some((inst) => inst?.machine_id));
|
|
810
|
+
// A fleet with remote Runtimes off answers { enabled: false, machines: [] }: that is no list, not an empty one.
|
|
811
|
+
const machines = catalog.data && catalog.data.enabled !== false ? new Map(catalog.data.machines.map((machine) => [machine.id, { name: machine.name, status: machine.status }])) : null;
|
|
768
812
|
const chs = channelsOf(ctx.fleet);
|
|
769
813
|
// #2172: the sidebar's grouping — an agent in a team under its team, else under its first tag.
|
|
770
814
|
const teamsOf = (name) => Object.keys(ctx.fleet.teams || {}).filter((t) => Array.isArray(ctx.fleet.teams[t]?.members) && ctx.fleet.teams[t].members.includes(name));
|
|
771
|
-
const groups = groupAgents(insts, ctx.live, search, (name) => navGroupOf({ tags: insts[name]?.tags, teams: teamsOf(name) }), groupLabel);
|
|
815
|
+
const groups = filterAgentGroups(groupAgents(insts, ctx.live, search, (name) => navGroupOf({ tags: insts[name]?.tags, teams: teamsOf(name) }), groupLabel), insts, ctx.live, { runtime, status });
|
|
816
|
+
const active = !!(runtime || status || search.trim());
|
|
772
817
|
// One action per agent at a time: the whole row is off (Settings and ⋯ too), and the pressed button says it is working.
|
|
773
818
|
const run = async (name, action, work) => {
|
|
774
819
|
if (busy.has(name)) return;
|
|
775
820
|
setBusy((b) => new Map(b).set(name, action));
|
|
776
821
|
try { await work(); } finally { setBusy((b) => { const n = new Map(b); n.delete(name); return n; }); }
|
|
777
822
|
};
|
|
778
|
-
const head = html
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if (!
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
const folded = collapsed.has(group) && !
|
|
823
|
+
const head = html`<${AgentFilters} instances=${insts} live=${ctx.live} machines=${machines} runtime=${runtime} status=${status} active=${active}
|
|
824
|
+
matched=${groups.reduce((count, group) => count + group.names.length, 0)} total=${Object.keys(insts).length} onRuntime=${setRuntime} onStatus=${setStatus}
|
|
825
|
+
onClear=${() => { setRuntime(""); setStatus(""); clearSearch(); }} />`;
|
|
826
|
+
if (!Object.keys(insts).length) return html`${head}<${Empty} icon="bot" title=${tn("noAgents")} />`;
|
|
827
|
+
if (!groups.length) return html`${head}<${Empty} icon="search" title=${search.trim() ? tn("noAgentMatch", search.trim()) : agentFilterText("noMatch")} hint=${agentFilterText("noMatchHint")} />`;
|
|
828
|
+
return html`${head}<div class="s-list s-agents-list">
|
|
829
|
+
<div class="s-list-columns s-agent-columns" aria-hidden="true"><span>${tn("uiAgentColumnName")}</span><span>${agentFilterText("runtime")}</span><span>${tn("uiAgentColumnCli")}</span><span>${tn("uiListColumnState")}</span><span>${tn("uiListColumnActions")}</span></div>${groups.map(({ group, names }) => {
|
|
830
|
+
const folded = collapsed.has(group) && !active; // searching/filtering opens every matching group
|
|
786
831
|
return html`<section key=${group} class="s-group">
|
|
787
832
|
<button type="button" class="s-group-head" aria-expanded=${folded ? "false" : "true"}
|
|
788
833
|
onClick=${() => setCollapsed((c) => { const n = new Set(c); n.has(group) ? n.delete(group) : n.add(group); return n; })}>
|
|
789
834
|
<${Icon} name="chevron" size=${14} cls=${folded ? "caret folded" : "caret"} /><${GroupMark} group=${group} /><span class="grow">${groupLabel(group)}</span><span class="count">${names.length}</span></button>
|
|
790
835
|
${folded ? null : names.map((name) => {
|
|
791
836
|
const inst = insts[name];
|
|
792
|
-
const st = (ctx.live[name]
|
|
837
|
+
const st = agentStatus(ctx.live[name]);
|
|
793
838
|
const sum = effectiveSummary(ctx.live[name] || {}, inst, ctx.fleet.defaults || {});
|
|
794
839
|
const ch = agentChannelLabel(inst, chs);
|
|
795
840
|
const working = busy.has(name), doing = busy.get(name);
|
|
796
841
|
const label = (action, text) => (doing === action ? tn("working") : text);
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
<span class="s-name" title=${name}>${shortName(name, inst)}</span>
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
<span class="
|
|
842
|
+
const runtimeTarget = agentRuntime(inst, ctx.live[name], machines);
|
|
843
|
+
return html`<div key=${name} class="s-row s-agent-row">
|
|
844
|
+
<div class="s-agent-identity"><span class="s-name" title=${name}>${shortName(name, inst)}</span>
|
|
845
|
+
<div class="s-agent-context">${inst.runtime !== "windows" || ctx.live[name]?.capabilities?.effort === true ? html`<span class="tag">${tn("effortTag", sum.effort)}</span>` : null}
|
|
846
|
+
${ch ? html`<span class=${`tag${ch.includes("persona") ? " persona" : ""}`}>${ch}</span>` : null}</div>
|
|
847
|
+
</div>
|
|
848
|
+
<div class="s-agent-runtime"><span class=${`tag${runtimeTarget.gone ? " warn" : ""}`} title=${runtimeTarget.title || runtimeTarget.label}>${runtimeTarget.label}</span></div>
|
|
849
|
+
<div class="s-agent-cli"><${CliProductMark} id=${sum.backend} size=${20} /><span><strong>${cliLabel(sum.backend)}</strong><span class="s-meta" title=${sum.model}>${sum.model}</span></span></div>
|
|
850
|
+
<span class="s-agent-status"><span class=${dotClass(agentStatus(ctx.live[name]))} aria-hidden="true"></span><span>${agentFilterText(agentStatus(ctx.live[name]))}${agentRuntimeOffline(inst, ctx.live[name]) ? html`<small class="s-agent-runtime-state">${agentFilterText("offline")}</small>` : null}</span></span>
|
|
806
851
|
<span class="s-actions">
|
|
807
852
|
${inst.runtime === "windows" ? html`<a class="btn btn-sm" href=${chatPath(name)}>${t("app.chat")}</a>` : null}
|
|
808
853
|
<button type="button" class="btn btn-sm" disabled=${working} onClick=${() => openDialog({ kind: "agent", name, inst })}>${tn("settingsButton")}</button>
|
|
809
854
|
${st === "running" ? html`<button type="button" class="btn btn-sm" disabled=${working} onClick=${() => run(name, "pause", () => ctx.pauseWake(name, "pause"))}>${label("pause", tn("pause"))}</button>` : null}
|
|
810
855
|
${st === "paused" ? html`<button type="button" class="btn btn-sm" disabled=${working} onClick=${() => run(name, "wake", () => ctx.pauseWake(name, "wake"))}>${label("wake", tn("wake"))}</button>` : null}
|
|
811
|
-
${
|
|
812
|
-
<${Menu} label=${t("app.more")} items=${[{ key: "delete", label: tn("deleteAgentMenu"), icon: "trash", danger: true, disabled: working, onSelect: () => ctx.deleteAgent(name) }]} />
|
|
856
|
+
${["running", "stopped", "crashed"].includes(st) ? html`<button type="button" class="btn btn-sm" disabled=${working} onClick=${() => run(name, "startStop", () => ctx.startStop(name, st === "running"))}>${label("startStop", st === "running" ? tn("stop") : tn("start"))}</button>` : null}
|
|
857
|
+
<${Menu} cls="s-agent-menu" fit=${true} label=${t("app.more")} items=${[{ key: "delete", label: tn("deleteAgentMenu"), icon: "trash", danger: true, disabled: working, onSelect: () => ctx.deleteAgent(name) }]} />
|
|
813
858
|
</span>
|
|
814
859
|
</div>`;
|
|
815
860
|
})}
|
|
816
861
|
</section>`;
|
|
817
|
-
})}</div
|
|
862
|
+
})}</div>`;
|
|
818
863
|
}
|
|
819
864
|
|
|
820
865
|
function ConnectionRetry({ id, ctx }) {
|
|
@@ -844,10 +889,10 @@ function Bots({ ctx, search, openDialog }) {
|
|
|
844
889
|
const all = channelsOf(ctx.fleet);
|
|
845
890
|
const needle = search.trim().toLowerCase();
|
|
846
891
|
const rows = all.map((ch, i) => ({ ch, i })).filter(({ ch }) => !needle || [ch.id, ch.type, ch.group_id, ch.bot_token_env].some((v) => String(v ?? "").toLowerCase().includes(needle)));
|
|
847
|
-
const head = html`<div class="list-head"><span>${all.length === 1 ? tn("countBot") : tn("countBots", all.length)}</span>
|
|
892
|
+
const head = html`<div class="list-head s-list-toolbar"><span>${all.length === 1 ? tn("countBot") : tn("countBots", all.length)}</span>
|
|
848
893
|
<button type="button" id="newBotBtn" class="btn btn-primary" onClick=${() => openDialog({ kind: "newBot" })}><${Icon} name="plus" size=${16} />${tn("newBot")}</button></div>`;
|
|
849
|
-
return html
|
|
850
|
-
${!rows.length ? html
|
|
894
|
+
return html`<section class="card s-chat-connections"><div class="s-chat-section-head"><h3>${layoutText("chatConnections")}</h3>${head}</div>
|
|
895
|
+
${!rows.length ? html`<div class="s-chat-empty"><${Icon} name="plug" size=${22} /><p>${needle ? tn("noAgentMatch", search.trim()) : tn("noBots")}</p></div>` : html`<div class="s-list s-connections-list"><div class="s-list-columns s-connection-columns" aria-hidden="true"><span>${tn("uiConnectionColumnService")}</span><span>${tn("uiConnectionColumnInfo")}</span><span>${tn("uiListColumnActions")}</span></div>${rows.map(({ ch, i }) => {
|
|
851
896
|
const users = (ch.access && ch.access.allowed_users) || [];
|
|
852
897
|
const meta = ctx.connections.find((c, j) => channelId(c, j) === channelId(ch, i)) || {};
|
|
853
898
|
const token = meta.token_present;
|
|
@@ -872,7 +917,7 @@ function Bots({ ctx, search, openDialog }) {
|
|
|
872
917
|
${!overPublicLink && ctx.fleetUp && meta.status !== "connected" ? html`<${ConnectionRetry} id=${channelId(ch, i)} ctx=${ctx} />` : null}
|
|
873
918
|
<button type="button" class="btn btn-sm" onClick=${() => openDialog({ kind: "bot", id: channelId(ch, i) })}>${tn("settingsButton")}</button></span>
|
|
874
919
|
</div>`;
|
|
875
|
-
})}</div>`}
|
|
920
|
+
})}</div>`}</section>`;
|
|
876
921
|
}
|
|
877
922
|
|
|
878
923
|
/**
|
|
@@ -922,40 +967,40 @@ function ProviderKeys({ ctx }) {
|
|
|
922
967
|
} catch { say(spec.id, { error: tn("failed") }); }
|
|
923
968
|
finally { inFlight.current.delete(spec.id); if (lease.current()) setBusy(new Set(inFlight.current)); }
|
|
924
969
|
};
|
|
925
|
-
return html`<section class="card s-provider-keys"><h3>${
|
|
926
|
-
${!ctx.providerSecrets.length ? html`<p class="note">${tn("noProviderVerifiers")}</p>` : ctx.providerSecrets.map((spec) =>
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
970
|
+
return html`<section class="card s-provider-keys s-auth-section"><header class="s-auth-section-head"><div><h3>${layoutText("apiKeysTitle")}</h3><p>${layoutText("apiKeysHint")}</p></div></header>
|
|
971
|
+
${!ctx.providerSecrets.length ? html`<p class="note">${tn("noProviderVerifiers")}</p>` : html`<div class="s-api-key-list">${ctx.providerSecrets.map((spec) => {
|
|
972
|
+
const provider = spec.id.split(".")[0], mark = ["openai", "anthropic"].includes(provider) ? provider : null;
|
|
973
|
+
return html`<div key=${spec.id} class="s-key s-api-key-row">
|
|
974
|
+
<div class="s-api-key-heading"><span class="s-provider-symbol">${mark ? html`<${ProviderMark} provider=${mark} size=${22} />` : html`<${Icon} name="key" size=${22} />`}</span><strong>${spec.display_name}</strong><span class=${`s-auth-state${spec.token_present ? " is-usable" : ""}`}>${spec.token_present ? tn("keyConfigured") : tn("keyNotConfigured")}</span></div>
|
|
975
|
+
${spec.verifier === "available" ? html`<div class="s-api-key-form"><label><span>${tn("providerKeyInput", spec.display_name)}</span><input type="password" autocomplete="new-password" aria-label=${tn("providerKeyInput", spec.display_name)}
|
|
976
|
+
placeholder=${layoutText(spec.token_present ? "apiReplacePlaceholder" : "apiNewPlaceholder")} value=${keys[spec.id] || ""} onInput=${(e) => setKeys((k) => ({ ...k, [spec.id]: e.target.value }))} /></label>
|
|
977
|
+
<button type="button" class="btn btn-sm btn-primary" disabled=${busy.has(spec.id)} onClick=${() => go(spec)}>${tn("verifyApply")}</button></div>`
|
|
978
|
+
: html`<div class="s-api-key-unsupported"><span class="s-auth-state">${tn("unsupportedVerifier")}</span><p class="note">${tn("unsupportedVerifierHint")}</p></div>`}
|
|
979
|
+
${notes[spec.id] ? html`<p class=${`feedback${notes[spec.id].error ? " error" : ""}`} role="status">${notes[spec.id].error || notes[spec.id].text}</p>` : null}
|
|
980
|
+
</div>`;
|
|
981
|
+
})}</div>`}
|
|
982
|
+
<p class="note s-api-action-note">${tn("uiProviderKeyActionHint")}</p>
|
|
935
983
|
</section>`;
|
|
936
984
|
}
|
|
937
985
|
|
|
938
986
|
function Classic({ ctx, search, openDialog }) {
|
|
939
987
|
const rooms = Object.entries((ctx.classic && ctx.classic.channels) || {}).map(([key, ch]) => ({ key, ...ch }));
|
|
940
|
-
if (!rooms.length) return html
|
|
988
|
+
if (!rooms.length) return html`<div class="s-chat-empty"><${Icon} name="room" size=${22} /><p>${tn("noClassic")}</p></div>`;
|
|
941
989
|
const needle = search.trim().toLowerCase();
|
|
942
990
|
const list = rooms.filter((c) => !needle || String(c.name || "").toLowerCase().includes(needle) || String(c.instanceName || "").toLowerCase().includes(needle))
|
|
943
991
|
.sort((a, b) => ((STATUS_ORDER[ctx.classicRuntime(a.instanceName).status] ?? 9) - (STATUS_ORDER[ctx.classicRuntime(b.instanceName).status] ?? 9))
|
|
944
992
|
|| String(a.name || "").localeCompare(String(b.name || "")));
|
|
945
|
-
if (!list.length) return html
|
|
946
|
-
return html`<div class="list-head"><span>${rooms.length === 1 ? tn("countRoom") : tn("countRooms", rooms.length)}</span></div><div class="s-list">${list.map((c) => {
|
|
993
|
+
if (!list.length) return html`<div class="s-chat-empty"><${Icon} name="search" size=${22} /><p>${tn("noAgentMatch", search.trim())}</p></div>`;
|
|
994
|
+
return html`<div class="list-head s-list-toolbar"><span>${rooms.length === 1 ? tn("countRoom") : tn("countRooms", rooms.length)}</span></div><div class="s-list s-classic-list"><div class="s-list-columns s-classic-columns" aria-hidden="true"><span>${tn("uiClassicColumnRoom")}</span><span>${tn("uiAgentColumnCli")}</span><span>${tn("uiListColumnState")}</span><span>${tn("uiListColumnActions")}</span></div>${list.map((c) => {
|
|
947
995
|
const rt = ctx.classicRuntime(c.instanceName);
|
|
948
996
|
const sum = effectiveSummary(ctx.live[c.instanceName] || {}, c, ctx.fleet.defaults || {}, (ctx.classic && ctx.classic.defaults) || {});
|
|
949
997
|
const adapter = c.adapterId || "discord";
|
|
950
|
-
return html`<div key=${c.key} class="s-row">
|
|
951
|
-
<span class=${
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
<
|
|
955
|
-
<span class="
|
|
956
|
-
<span class=${`tag${adapter !== "discord" ? " persona" : ""}`}>${adapter}</span>
|
|
957
|
-
${c.collab ? html`<span class="tag">collab</span>` : null}
|
|
958
|
-
<span class="tag">${tn(`exec_${rt.execution}`)}</span>
|
|
998
|
+
return html`<div key=${c.key} class="s-row s-classic-row">
|
|
999
|
+
<div class="s-classic-identity"><span class="s-name" title=${c.instanceName || ""}>${shortName(c.name || c.instanceName || "?", { display_name: c.display_name })}</span>
|
|
1000
|
+
<div class="s-agent-context"><span class="tag">${tn("effortTag", sum.effort)}</span><span class=${`tag${adapter !== "discord" ? " persona" : ""}`}>${adapter}</span>${c.collab ? html`<span class="tag">collab</span>` : null}</div>
|
|
1001
|
+
</div>
|
|
1002
|
+
<div class="s-classic-cli"><${CliProductMark} id=${sum.backend} size=${20} /><span><strong>${cliLabel(sum.backend)}</strong><span class="s-meta" title=${sum.model}>${sum.model}</span></span></div>
|
|
1003
|
+
<span class="s-classic-status"><span class=${dotClass(rt.execution)} aria-hidden="true"></span><span>${tn(`exec_${rt.execution}`)}</span></span>
|
|
959
1004
|
<span class="s-actions">
|
|
960
1005
|
<button type="button" class="btn btn-sm" onClick=${() => openDialog({ kind: "classic", room: c })}>${tn("settingsButton")}</button>
|
|
961
1006
|
${rt.status === "running" ? html`<button type="button" class="btn btn-sm" onClick=${() => ctx.pauseWake(c.instanceName, "pause")}>${tn("pause")}</button>` : null}
|
|
@@ -1091,30 +1136,43 @@ function General({ ctx, part = "all" }) {
|
|
|
1091
1136
|
const classicReply = replyGuardSupported(f.classicBackend, f.agent_mode, d.kiro_ui) ? tn("replyGuardSupported") : tn("replyGuardUnsupported", f.classicBackend, f.agent_mode);
|
|
1092
1137
|
// #2347: Agent defaults show the fleet's part; Chat & connections ClassicBot's. One form, one draft either way.
|
|
1093
1138
|
const fleetPart = part !== "classic", classicPart = part !== "fleet";
|
|
1094
|
-
return html`<div class=${`form s-general s-part-${part}`}>
|
|
1095
|
-
${fleetPart ? html`<section class="card"><h3>${tn("
|
|
1139
|
+
return html`<div class=${fleetPart ? "s-defaults-layout" : "s-classic-layout"}><div class=${`form s-general s-part-${part}`}>
|
|
1140
|
+
${fleetPart ? html`<section class="card s-defaults-model"><h3>${tn("uiDefaultsModelsTitle")}</h3>
|
|
1096
1141
|
<div class="grid2">
|
|
1097
|
-
<div class=${fc("field", "locale")}>${L(tn("language"), "defaults.locale", "g-locale", "locale")}<${Select} id="g-locale" value=${f.locale} onChange=${set("locale")}
|
|
1098
|
-
options=${localeOptions(f.locale).map((o) => (o.auto ? { value: "", label: tn("localeAuto") } : o))} /></div>
|
|
1099
1142
|
<div class=${fc("field", "backend")}>${L(tn("defaultBackend"), "defaults.backend", "g-be", "backend")}<${BackendSelect} id="g-be" value=${f.backend} onChange=${set("backend")} /></div>
|
|
1100
1143
|
<div class=${fc("field", "model")}>${L(tn("defaultModel"), "defaults.model", "g-model", "model")}<${ModelSelect} id="g-model" value=${f.model} onChange=${set("model")}
|
|
1101
1144
|
backend=${f.backend || "claude-code"} inherit=${tn("modelCliDefault")} /></div>
|
|
1145
|
+
<div class=${fc("field", "locale")}>${L(tn("language"), "defaults.locale", "g-locale", "locale")}<${Select} id="g-locale" value=${f.locale} onChange=${set("locale")}
|
|
1146
|
+
options=${localeOptions(f.locale).map((o) => (o.auto ? { value: "", label: tn("localeAuto") } : o))} /></div>
|
|
1147
|
+
</div>
|
|
1148
|
+
</section>
|
|
1149
|
+
<section class="card s-defaults-work"><h3>${tn("uiDefaultsWorkTitle")}</h3>
|
|
1150
|
+
<div class="grid2">
|
|
1102
1151
|
<div class=${fc("field", "auto")}>${L(`${tn("autoPause")} (${tn("minutes")})`, "defaults.auto_pause_after", "g-auto", "auto")}<input id="g-auto" type="number" min="0" step="1" class=${errors.auto ? "invalid" : ""} value=${f.auto} onInput=${(e) => set("auto")(e.target.value)} /><${Feedback} error=${errors.auto} /></div>
|
|
1103
1152
|
<div class=${fc("field", "tool_progress")}>${L(tn("toolProgress"), "defaults.tool_progress", "g-tp", "tool_progress")}<${Select} id="g-tp" value=${f.tool_progress} onChange=${set("tool_progress")} options=${TOOL_PROGRESS} /></div>
|
|
1153
|
+
<div class=${fc("field", "tips")}>${L(tn("dailyTips"), "defaults.tips", "g-tips", "tips")}<${Select} id="g-tips" value=${f.tips} onChange=${set("tips")} options=${noticeChoices()} /><${InEffect} text=${noticeLabel(noticeMode(f.tips))} /><p class="note">${tn("dailyTipsHint")}</p></div>
|
|
1154
|
+
</div>
|
|
1155
|
+
</section>
|
|
1156
|
+
<section class="card s-defaults-collaboration"><h3>${tn("uiDefaultsCollaborationTitle")}</h3>
|
|
1157
|
+
<div class="grid2">
|
|
1104
1158
|
<div class=${fc("field", "visibility")}>${L(tn("crossInstanceVisibility"), "defaults.cross_instance_visibility", "g-vis", "visibility")}<${Select} id="g-vis" value=${f.visibility} onChange=${set("visibility")} options=${VISIBILITY_MODES} />
|
|
1105
1159
|
<p class="note">${tn("crossInstanceVisibilityHint")}</p></div>
|
|
1106
|
-
<div class=${fc("field", "
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1160
|
+
<div class=${fc("field", "replyTriggers")}><label for="g-reply-triggers" class="s-defaults-toggle-label"><span>${tn("replyTriggers")}</span>${dm("replyTriggers")}</label>
|
|
1161
|
+
<input id="g-reply-triggers" class="s-ui-toggle" type="checkbox" checked=${f.replyTriggers} onChange=${(e) => set("replyTriggers")(e.target.checked)} />
|
|
1162
|
+
<div class="s-defaults-toggle-help"><details class="s-defaults-explanation"><summary>${layoutText("replyRulesHelp")}</summary><p class="note">${tn("replyTriggersHint")}</p></details><${Impact} schema=${schema} field="defaults.reply_triggers" /></div></div>
|
|
1163
|
+
<div class=${fc("field", "reply")}><label for="g-reply" class="s-defaults-toggle-label"><span>${tn("replyCompletionGuard")}</span>${dm("reply")}</label>
|
|
1164
|
+
<input id="g-reply" class="s-ui-toggle" type="checkbox" checked=${f.reply} onChange=${(e) => set("reply")(e.target.checked)} />
|
|
1165
|
+
<div class="s-defaults-toggle-help"><details class="s-defaults-explanation"><summary>${layoutText("replySupportHelp")}</summary><p class="note">${fleetReply}</p></details><${Impact} schema=${schema} field="defaults.reply_completion_guard" /></div></div>
|
|
1110
1166
|
</div>
|
|
1167
|
+
</section>
|
|
1168
|
+
<details class="s-ui-disclosure s-defaults-permissions"><summary>${tn("uiDefaultsCapabilitiesTitle")}</summary><div class="s-ui-disclosure-body">
|
|
1111
1169
|
<fieldset id="g-dash" class=${fc("field dash-perms", ...DASHBOARD_FEATURES.map((k) => `dash_${k}`))}><legend>${L(tn("dashboardAccess"), "defaults.dashboard")}</legend>
|
|
1112
1170
|
<p class="note">${tn("dashboardDefaultsHint")}</p>
|
|
1113
1171
|
${DASHBOARD_FEATURES.map((k) => html`<label key=${k} class="check"><input type="checkbox" checked=${f[`dash_${k}`]}
|
|
1114
1172
|
onChange=${(e) => set(`dash_${k}`)(e.target.checked)} /><span>${tn(`dashboard_${k}`)}</span></label>`)}
|
|
1115
1173
|
</fieldset>
|
|
1116
|
-
</
|
|
1117
|
-
${classicPart ? html`<section class="card"><h3>${tn("classicDefaultsTitle")}</h3>
|
|
1174
|
+
</div></details>` : null}
|
|
1175
|
+
${classicPart ? html`<section class="card s-classic-defaults-card"><h3>${tn("classicDefaultsTitle")}</h3>
|
|
1118
1176
|
<div class="grid2">
|
|
1119
1177
|
<div class=${fc("field", "classicBackend")}>${L(tn("classicBackend"), "classic_defaults.backend", "g-cbe")}<${BackendSelect} id="g-cbe" value=${f.classicBackend} onChange=${set("classicBackend")} />
|
|
1120
1178
|
${!c.backend && f.classicBackend === base.classicBackend ? html`<${Inherited} text=${valueText(f.classicBackend, "backend")} builtin=${!d.backend} from=${inAgentDefaults("#g-be")} />` : null}</div>
|
|
@@ -1135,12 +1193,12 @@ function General({ ctx, part = "all" }) {
|
|
|
1135
1193
|
<div id="g-guilds" class=${fc("field", "guilds")}>${L(tn("allowedGuilds"), "classic.allowed_guilds")}<${ChipList} items=${f.guilds} onChange=${set("guilds")} placeholder=${tn("guildIdPlaceholder")} label=${tn("allowedGuilds")} /></div>
|
|
1136
1194
|
</div>
|
|
1137
1195
|
</section>` : null}
|
|
1138
|
-
${fleetPart ? html`<
|
|
1196
|
+
${fleetPart ? html`<details class="s-ui-disclosure s-defaults-workspace" id="project-roots"><summary>${tn("uiDefaultsWorkspaceTitle")}</summary><div class="s-ui-disclosure-body"><section class="card"><h3>${tn("projectRootsTitle")}</h3>
|
|
1139
1197
|
<p class="note">${tn("projectRootsHint")}</p>
|
|
1140
1198
|
<div id="g-roots" class=${fc("field", "roots")}>${L(tn("projectRootsTitle"), "fleet.project_roots")}<${ChipList} items=${f.roots} onChange=${set("roots")} placeholder="/path/to/projects" label=${tn("projectRootsTitle")} />
|
|
1141
1199
|
${f.roots.length ? null : html`<${InEffect} text=${tn("projectRootsAnywhere")} />`}</div>
|
|
1142
|
-
</section>` : null}
|
|
1143
|
-
${fleetPart ? html`<${Drawer} title=${tn("
|
|
1200
|
+
</section></div></details>` : null}
|
|
1201
|
+
${fleetPart ? html`<${Drawer} title=${tn("uiDefaultsAdvancedTitle")}>
|
|
1144
1202
|
<section class="card"><h3>${tn("runtimeResources")}</h3><div class="grid2">
|
|
1145
1203
|
<div class=${fc("field", "tool_set")}>${L(tn("toolSet"), "defaults.tool_set", "g-ts", "tool_set")}<${Select} id="g-ts" value=${f.tool_set} onChange=${set("tool_set")}
|
|
1146
1204
|
options=${[{ value: "", label: tn("toolSetUnset", toolSetDefault({}, {}), toolSetDefault({}, { general_topic: true })) }, ...TOOL_SETS]} /></div>
|
|
@@ -1159,7 +1217,7 @@ function General({ ctx, part = "all" }) {
|
|
|
1159
1217
|
<section class="card"><h3>${tn("accessTitle")}</h3>
|
|
1160
1218
|
<p class="note">${primary ? tn("accessMovedHint", primary.id || chLabel(0)) : tn("noBots")}</p></section>
|
|
1161
1219
|
</${Drawer}>` : null}
|
|
1162
|
-
</div>`;
|
|
1220
|
+
</div></div>`;
|
|
1163
1221
|
}
|
|
1164
1222
|
|
|
1165
1223
|
/**
|
|
@@ -1199,6 +1257,7 @@ export function uploadMbInEffect(web) {
|
|
|
1199
1257
|
}
|
|
1200
1258
|
function WebSection({ ctx, staged, part = "all" }) {
|
|
1201
1259
|
const { schema } = ctx;
|
|
1260
|
+
const [accessTab, setAccessTab] = useSettingsTabs("access", "login");
|
|
1202
1261
|
const web = ctx.fleet.web || {}, local = (web.session && web.session.local) || {}, term = ctx.fleet.web_terminal || {};
|
|
1203
1262
|
// Over the public link the fields read only; the Guests and Web authority cards are not offered (the server never
|
|
1204
1263
|
// serves their reads there).
|
|
@@ -1329,17 +1388,31 @@ function WebSection({ ctx, staged, part = "all" }) {
|
|
|
1329
1388
|
if (v === undefined) return html`${roText(webReadOnlyDefault(path, ctx.fleet))} <${DefaultMark} />${capped ? html` <span class="tag">${tn("cappedByTotal")}</span>` : null}`;
|
|
1330
1389
|
return applies === v ? roText(v) : html`${roText(v)} <span class="tag">${tn("inEffectShort", roText(applies))}</span>`;
|
|
1331
1390
|
};
|
|
1332
|
-
//
|
|
1333
|
-
//
|
|
1334
|
-
// usage over the public link), Advanced (what only fleet.yaml sets).
|
|
1391
|
+
// One Web draft remains mounted across the Access tabs. A tab only changes what is visible;
|
|
1392
|
+
// validation, confirmation and staged writes continue to belong to the existing form.
|
|
1335
1393
|
const has = (p) => part === "all" || part === p;
|
|
1336
1394
|
const lock = (body) => html`<fieldset class="s-web-fields" disabled=${overPublicLink}>${body}</fieldset>`;
|
|
1395
|
+
const tabs = [
|
|
1396
|
+
{ id: "login", label: layoutText("accessLogin") },
|
|
1397
|
+
{ id: "guests", label: layoutText("accessGuests") },
|
|
1398
|
+
{ id: "sharing", label: layoutText("accessSharing") },
|
|
1399
|
+
];
|
|
1400
|
+
const toggleRow = (key, id, label, hint, impact) => html`<div class=${fc("s-setting-row", key)}>
|
|
1401
|
+
<div class="s-setting-copy"><label for=${id}>${part === "dashboard" ? layoutText(`revDashboard_${key}`) : tn(label)}<${Impact} schema=${schema} field=${impact} /></label>
|
|
1402
|
+
<p class="note" id=${`${id}-hint`}>${part === "dashboard" ? layoutText(`revDashboardHint_${key}`) : tn(hint)}</p>
|
|
1403
|
+
${part === "dashboard" ? html`<details class="s-setting-details"><summary>${layoutText("revDetails")}</summary><p class="note"><strong>${tn(label)}</strong> ${tn(hint)}</p></details>` : null}
|
|
1404
|
+
</div>
|
|
1405
|
+
<input class="s-ui-toggle" id=${id} type="checkbox" checked=${f[key]} aria-describedby=${`${id}-hint`} onChange=${(e) => set(key)(e.target.checked)} />
|
|
1406
|
+
</div>`;
|
|
1407
|
+
const configured = (path) => { const [top, key] = path.split("."); return (top === "web" ? web : term)[key] !== undefined; };
|
|
1337
1408
|
return html`<div class=${`form s-web s-part-${part}`}>
|
|
1338
|
-
${
|
|
1339
|
-
${
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1409
|
+
${overPublicLink && part !== "readonly" ? html`<p class="note s-access-notice" role="status" id="webPublicRefused">${tn("webPublicRefused")}</p>` : null}
|
|
1410
|
+
${has("access") ? html`<${SettingsTabs} id="access" value=${accessTab} onChange=${setAccessTab} items=${tabs} />
|
|
1411
|
+
<div class="s-access-banner"><${Icon} name="info" size=${18} /><p>${layoutText("revAccessPolicy")}</p><details class="s-access-policy"><summary>${layoutText("revDetails")}</summary><p class="note">${tn("webLead")}</p></details></div>
|
|
1412
|
+
<${SettingsTabPanel} group="access" id="login" active=${accessTab === "login"}>
|
|
1413
|
+
<div class="s-access-login-layout"><div class="s-access-stack">
|
|
1414
|
+
${lock(html`<section class="card s-access-session"><h3>${layoutText("revLoginSessions")}</h3>
|
|
1415
|
+
<div class=${fc("field", "notify")}><label for="webNotifyLogin">${tn("webNotifyLogin")}<${Impact} schema=${schema} field="web.notify_login" /></label><${Select} id="webNotifyLogin" value=${f.notify} onChange=${set("notify")} options=${noticeChoices()} /><${InEffect} text=${noticeLabel(noticeMode(f.notify))} /></div>
|
|
1343
1416
|
<div class="grid2">
|
|
1344
1417
|
<div class=${fc("field", "abs")}><label for="webSessionAbsolute">${tn("sessionAbsolute")}</label><input id="webSessionAbsolute" type="number" min="1" max="720" step="any"
|
|
1345
1418
|
class=${errors.abs ? "invalid" : ""} value=${f.abs} onInput=${(e) => set("abs")(e.target.value)} /><${Feedback} error=${errors.abs} />
|
|
@@ -1348,8 +1421,13 @@ function WebSection({ ctx, staged, part = "all" }) {
|
|
|
1348
1421
|
class=${errors.idle ? "invalid" : ""} value=${f.idle} onInput=${(e) => set("idle")(e.target.value)} /><${Feedback} error=${errors.idle} />
|
|
1349
1422
|
${value(f.idle) === null && !errors.abs ? html`<${InEffect} text=${tn("hoursValue", idle)} why=${idle < 48 ? tn("cappedBySession") : null} />` : null}</div>
|
|
1350
1423
|
</div>
|
|
1351
|
-
</
|
|
1352
|
-
|
|
1424
|
+
<p class="note s-session-ceiling">${layoutText("revPublicSessionLimit")}</p>
|
|
1425
|
+
<details class="s-setting-details"><summary>${layoutText("revSessionDetails")}</summary><p class="note">${tn("webSessionHint")}</p><p class="note">${tn("webNotifyLoginHint")}</p></details>
|
|
1426
|
+
</section>`)}
|
|
1427
|
+
${!overPublicLink ? html`<${WebAuthorityCard} ctx=${ctx} staged=${staged} />` : null}
|
|
1428
|
+
${/* #3008: a web-only fleet's connections from outside — changed on this computer only, shown (read-only) elsewhere. */ ""}
|
|
1429
|
+
<${OutsideCard} />
|
|
1430
|
+
<details class="drawer s-terminal-disclosure"><summary>${tn("webTerminalTitle")}</summary><div class="drawer-body">${lock(html`<section class="card s-access-terminal"><h3>${tn("webTerminalTitle")}</h3>
|
|
1353
1431
|
<p class="note">${tn("webTerminalHint")}</p>
|
|
1354
1432
|
<label class=${fc("check", "termOn")}><input id="webTerminalEnabled" type="checkbox" checked=${f.termOn} onChange=${(e) => set("termOn")(e.target.checked)} /><span>${tn("webTerminalEnabled")}</span><${Impact} schema=${schema} field="web_terminal.enabled" /></label>
|
|
1355
1433
|
<div class="grid2">
|
|
@@ -1357,8 +1435,18 @@ function WebSection({ ctx, staged, part = "all" }) {
|
|
|
1357
1435
|
class=${errors.termTtl ? "invalid" : ""} value=${f.termTtl} onInput=${(e) => set("termTtl")(e.target.value)} /><${Feedback} error=${errors.termTtl} />
|
|
1358
1436
|
${value(f.termTtl) === null ? html`<${InEffect} text=${tn("minutesValue", 10)} />` : null}</div>
|
|
1359
1437
|
</div>
|
|
1360
|
-
</section>
|
|
1361
|
-
|
|
1438
|
+
</section>`)}</div></details>
|
|
1439
|
+
</div><aside class="card s-access-related"><h3>${layoutText("revRelatedAccess")}</h3>
|
|
1440
|
+
<button type="button" onClick=${() => setAccessTab("guests")}><${Icon} name="user" size=${18} /><span><strong>${layoutText("accessGuests")}</strong><small>${layoutText("revRelatedGuests")}</small></span><${Icon} name="chevron" size=${16} /></button>
|
|
1441
|
+
<button type="button" onClick=${() => setAccessTab("sharing")}><${Icon} name="globe" size=${18} /><span><strong>${layoutText("accessSharing")}</strong><small>${layoutText("revRelatedSharing")}</small></span><${Icon} name="chevron" size=${16} /></button>
|
|
1442
|
+
</aside></div>
|
|
1443
|
+
</${SettingsTabPanel}>
|
|
1444
|
+
<${SettingsTabPanel} group="access" id="guests" active=${accessTab === "guests"}>
|
|
1445
|
+
${!overPublicLink ? html`<${GuestsCard} ctx=${ctx} staged=${staged} />` : html`<p class="note">${tn("webPublicRefused")}</p>`}
|
|
1446
|
+
</${SettingsTabPanel}>
|
|
1447
|
+
<${SettingsTabPanel} group="access" id="sharing" active=${accessTab === "sharing"}>
|
|
1448
|
+
<div class="s-access-stack">
|
|
1449
|
+
${lock(html`<section class="card s-public-entry"><h3>${layoutText("revPublicEntry")}</h3>
|
|
1362
1450
|
<label class=${fc("check", "pub")}><input id="publicWebLink" type="checkbox" checked=${f.pub} onChange=${(e) => set("pub")(e.target.checked)} /><span>${tn("publicLink")}</span><${Impact} schema=${schema} field="web.public_link.allow_public" /></label>
|
|
1363
1451
|
<p class="note">${tn("publicLinkHint")}</p>
|
|
1364
1452
|
<div class="grid2">
|
|
@@ -1374,35 +1462,31 @@ function WebSection({ ctx, staged, part = "all" }) {
|
|
|
1374
1462
|
${named ? html`<p class="note" id="publicTtlNamedHint">${tn("publicTtlNamedHint")}</p>` : null}
|
|
1375
1463
|
${named || f.autoOpen ? html`<label class=${fc("check", "autoOpen")}><input id="publicAutoOpen" type="checkbox" checked=${f.autoOpen} onChange=${(e) => set("autoOpen")(e.target.checked)} /><span>${tn("publicAutoOpen")}</span><${Impact} schema=${schema} field="web.public_link.auto_open" /></label>
|
|
1376
1464
|
<p class="note">${tn("publicAutoOpenHint")}</p><${Feedback} error=${errors.autoOpen} />` : null}
|
|
1465
|
+
|
|
1466
|
+
${!overPublicLink ? html`<div class="s-public-credentials"><${PublicTunnelToken} /></div>` : null}
|
|
1467
|
+
</section>`)}
|
|
1468
|
+
<div class="s-sharing-list"><h3>${layoutText("revReadOnlySharing")}</h3><p class="note">${layoutText("revSharingOrigin")}</p><${OfficeSharesCard} /><${PageSharesCard} /></div>
|
|
1469
|
+
</div>
|
|
1470
|
+
</${SettingsTabPanel}>` : null}
|
|
1471
|
+
${has("chat") ? lock(html`<section class="card s-web-chat"><h3>${layoutText("messageBehavior")}</h3>
|
|
1472
|
+
${toggleRow("echo", "webEchoToChannel", "webEcho", "webEchoHint", "web.echo_to_channel")}
|
|
1377
1473
|
</section>`) : null}
|
|
1378
|
-
${has("
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
${has("access") ? html`<${OutsideCard} />` : null}
|
|
1384
|
-
${has("chat") ? lock(html`<section class="card"><h3>${tn("webChat")}</h3>
|
|
1385
|
-
<label class=${fc("check", "echo")}><input id="webEchoToChannel" type="checkbox" checked=${f.echo} onChange=${(e) => set("echo")(e.target.checked)} /><span>${tn("webEcho")}</span><${Impact} schema=${schema} field="web.echo_to_channel" /></label>
|
|
1386
|
-
<p class="note">${tn("webEchoHint")}</p>
|
|
1387
|
-
</section>`) : null}
|
|
1388
|
-
${has("dashboard") ? lock(html`<section class="card"><h3>${tn("webDashboardTitle")}</h3>
|
|
1389
|
-
<label class=${fc("check", "log")}><input id="webLogTab" type="checkbox" checked=${f.log} onChange=${(e) => set("log")(e.target.checked)} /><span>${tn("webLogTab")}</span><${Impact} schema=${schema} field="web.log_tab" /></label>
|
|
1390
|
-
<p class="note">${tn("webLogTabHint")}</p>
|
|
1391
|
-
<label class=${fc("check", "usagePub")}><input id="webUsagePublic" type="checkbox" checked=${f.usagePub} onChange=${(e) => set("usagePub")(e.target.checked)} /><span>${tn("webUsagePublic")}</span><${Impact} schema=${schema} field="web.usage_report_on_public_link" /></label>
|
|
1392
|
-
<p class="note">${tn("webUsagePublicHint")}</p>
|
|
1393
|
-
<label class=${fc("check", "levels")}><input id="webOfficeLevels" type="checkbox" checked=${f.levels} onChange=${(e) => set("levels")(e.target.checked)} /><span>${tn("webOfficeLevels")}</span><${Impact} schema=${schema} field="web.office_levels" /></label>
|
|
1394
|
-
<p class="note">${tn("webOfficeLevelsHint")}</p>
|
|
1395
|
-
<label class=${fc("check", "cdn")}><input id="webPreviewCdn" type="checkbox" checked=${f.cdn} onChange=${(e) => set("cdn")(e.target.checked)} /><span>${tn("webPreviewCdn")}</span><${Impact} schema=${schema} field="web.preview_cdn" /></label>
|
|
1396
|
-
<p class="note">${tn("webPreviewCdnHint")}</p>
|
|
1474
|
+
${has("dashboard") ? lock(html`<section class="card s-dashboard-features"><h3>${layoutText("dashboardFeatures")}</h3>
|
|
1475
|
+
${toggleRow("log", "webLogTab", "webLogTab", "webLogTabHint", "web.log_tab")}
|
|
1476
|
+
${toggleRow("usagePub", "webUsagePublic", "webUsagePublic", "webUsagePublicHint", "web.usage_report_on_public_link")}
|
|
1477
|
+
${toggleRow("levels", "webOfficeLevels", "webOfficeLevels", "webOfficeLevelsHint", "web.office_levels")}
|
|
1478
|
+
${toggleRow("cdn", "webPreviewCdn", "webPreviewCdn", "webPreviewCdnHint", "web.preview_cdn")}
|
|
1397
1479
|
</section>`) : null}
|
|
1398
|
-
${has("readonly") ? html`<
|
|
1399
|
-
<
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1480
|
+
${has("readonly") ? html`<div class="s-effective-view"><p class="s-effective-notice"><${Icon} name="info" size=${18} />${layoutText("revEffectiveHint")}</p>
|
|
1481
|
+
<section class="card s-web-readonly"><h3 class="sr-only">${tn("webReadOnlyTitle")}</h3>
|
|
1482
|
+
<div class="s-ro-columns" aria-hidden="true"><span>${layoutText("effectiveKey")}</span><span>${layoutText("effectiveValue")}</span><span>${layoutText("effectiveSource")}</span></div>
|
|
1483
|
+
${["web", "web_terminal"].map(group => html`<h4 class="s-ro-group">${group === "web" ? tn("sec_web-dashboard") : tn("webTerminalTitle")}</h4>
|
|
1484
|
+
<dl class="s-web-ro">${WEB_READ_ONLY.filter(k => k.startsWith(`${group}.`)).map((k) => html`<div key=${k}><dt><code>${k}</code></dt><dd class="s-ro-value">${shown(k)}</dd><dd class="s-ro-source" aria-label=${layoutText("effectiveSource")}><span class="tag">${layoutText(configured(k) ? "effectiveSourceConfig" : "effectiveSourceDefault")}</span></dd></div>`)}</dl>`)}
|
|
1485
|
+
</section><div class="s-effective-footnotes"><p class="note">${tn("webReadOnlyLead")}</p><p class="note">${tn("webSearchHint")}</p></div>
|
|
1486
|
+
<div class="s-effective-actions"><button type="button" class="btn" onClick=${() => revealSettingsTarget(document.getElementById("advanced-panel-editor"))}><${Icon} name="edit" size=${16} />${layoutText("revGoEditor")}</button></div>
|
|
1487
|
+
</div>` : null}
|
|
1403
1488
|
</div>`;
|
|
1404
1489
|
}
|
|
1405
|
-
|
|
1406
1490
|
/**
|
|
1407
1491
|
* Developer: the fleet model as YAML or JSON, to read, copy, download — or edit. #2347: an edit's Apply stages its
|
|
1408
1492
|
* writes in the Save bar like every form's change (Save writes them, in order, then one apply; Discard drops them),
|
|
@@ -1456,14 +1540,15 @@ function Developer({ ctx }) {
|
|
|
1456
1540
|
return html`<section class="card s-dev">
|
|
1457
1541
|
<div class="s-dev-bar">
|
|
1458
1542
|
<div class="seg-inline" role="group" aria-label=${tn("format")}>
|
|
1459
|
-
${["yaml", "json"].map((k) => html`<button key=${k} type="button" class=${`btn btn-sm${fmt === k ? " on" : ""}`} aria-pressed=${fmt === k ? "true" : "false"} onClick=${() => setFmt(k)}>${k.toUpperCase()}</button>`)}</div>
|
|
1460
|
-
<button type="button" class="btn btn-sm" disabled=${blocked && !editing} title=${blocked && !editing ? tn("devFormsUnsaved") : undefined} onClick=${() => setEditing(!editing)}>${editing ? tn("viewMode") : tn("editMode")}</button>
|
|
1543
|
+
${["yaml", "json"].map((k) => html`<button key=${k} type="button" class=${`btn btn-sm${fmt === k ? " on" : ""}`} aria-pressed=${fmt === k ? "true" : "false"} onClick=${() => setFmt(k)}>${k.toUpperCase()}</button>`)}</div><p class="s-dev-context"><${Icon} name="info" size=${15} />${layoutText("revEditorContext")}</p>
|
|
1544
|
+
<div class="s-dev-actions"><button type="button" class="btn btn-sm" disabled=${blocked && !editing} title=${blocked && !editing ? tn("devFormsUnsaved") : undefined} onClick=${() => setEditing(!editing)}>${editing ? tn("viewMode") : tn("editMode")}</button>
|
|
1461
1545
|
<button type="button" class="btn btn-sm" title=${tn("copy")} onClick=${copy}><${Icon} name="copy" size=${14} /><span class="lbl">${tn("copy")}</span></button>
|
|
1462
|
-
<button type="button" class="btn btn-sm" title=${tn("devDownload")} onClick=${download}><${Icon} name="download" size=${14} /><span class="lbl">${tn("devDownload")}</span></button>
|
|
1546
|
+
<button type="button" class="btn btn-sm" title=${tn("devDownload")} onClick=${download}><${Icon} name="download" size=${14} /><span class="lbl">${tn("devDownload")}</span></button></div>
|
|
1463
1547
|
</div>
|
|
1464
1548
|
${editing ? html`<textarea class="s-yaml" aria-label=${tn("devEditor")} spellcheck=${false} wrap="off" value=${draft} onInput=${(e) => setDraft(e.target.value)}></textarea>
|
|
1465
1549
|
<div class="save-row"><button type="button" class="btn btn-primary" disabled=${blocked} onClick=${save}>${tn("devApply")}</button><span class="note">${tn("devApplyHint")}</span></div>`
|
|
1466
|
-
: html`<pre class="s-yaml">${text}</pre>`}
|
|
1550
|
+
: html`<pre class="s-yaml" tabindex="0">${text}</pre>`}
|
|
1551
|
+
<div class="s-dev-state"><span class="s-dev-file">fleet.yaml</span><${Icon} name=${editing ? "edit" : "view"} size=${14} /><span>${layoutText(editing ? "developerEditing" : "developerReadOnly")}</span></div>
|
|
1467
1552
|
${blocked ? html`<p class="note s-dev-blocked" role="status">${tn("devFormsUnsaved")}</p>`
|
|
1468
1553
|
: held && !msg ? html`<p class="note s-dev-held" role="status">${tn("devStaged", heldCount)}</p>` : null}
|
|
1469
1554
|
${msg ? html`<p class=${`feedback${msg.error ? " error" : ""}`} role="status">${msg.error || msg.text}</p>` : null}
|
|
@@ -1480,12 +1565,13 @@ function PendingBar({ staged, busy, onApply, onDiscard, section }) {
|
|
|
1480
1565
|
const invalid = changes.filter((c) => c.invalid).length;
|
|
1481
1566
|
// #2382 review: a field to fix in another section is named, and the message goes there (its field focused).
|
|
1482
1567
|
const elsewhere = changes.find((c) => c.invalid && c.section && c.section !== section);
|
|
1568
|
+
const problem = elsewhere || changes.find((c) => c.invalid && c.target);
|
|
1483
1569
|
const count = changes.reduce((n, c) => n + (Number.isInteger(c.count) && c.count > 0 ? c.count : 1), 0);
|
|
1484
1570
|
return html`<div data-trust="panel-controls" class="s-pending" role="region" aria-label=${tn("pendingRegion")}>
|
|
1485
1571
|
<strong>${tn("changesPending", count)}</strong>
|
|
1486
1572
|
<span class="note">${kinds.map(impactText).join(" · ")}</span>
|
|
1487
1573
|
<span class="s-pending-actions">
|
|
1488
|
-
${!invalid ? null :
|
|
1574
|
+
${!invalid ? null : problem ? html`<button type="button" class="link-btn feedback error s-fix" role="status" onClick=${() => goToTarget(problem.section || section, problem.target)}>${elsewhere ? tn("fixToSaveIn", invalid, tn(`sec_${elsewhere.section}`)) : tn("fixToSave", invalid)}</button>`
|
|
1489
1575
|
: html`<span class="feedback error s-fix" role="status">${tn("fixToSave", invalid)}</span>`}
|
|
1490
1576
|
<button type="button" class="btn" onClick=${onDiscard} disabled=${busy}>${tn("discard")}</button>
|
|
1491
1577
|
<button type="button" class="btn btn-primary" onClick=${onApply} disabled=${busy || invalid > 0} title=${busy ? tn("applyBusyLocal") : undefined}>${tn("applyChanges")}</button>
|
|
@@ -1592,12 +1678,11 @@ function PublicTunnelToken() {
|
|
|
1592
1678
|
} catch { if (lease.current()) setMessage(tn("applyFailed")); }
|
|
1593
1679
|
finally { inFlight.current = false; if (lease.current()) setBusy(false); }
|
|
1594
1680
|
};
|
|
1595
|
-
return html`<section class="card"><h3>${tn("namedToken")}</h3>
|
|
1681
|
+
return html`<section class="card s-tunnel-token"><div class="s-token-head"><h3>${tn("namedToken")}</h3><span class="tag">${present ? tn("keyConfigured") : tn("keyNotConfigured")}</span></div>
|
|
1596
1682
|
<p class="note">${tn("namedTokenHint")}</p>
|
|
1597
|
-
<
|
|
1598
|
-
<
|
|
1599
|
-
<button type="button" class="btn btn-sm" disabled=${busy || !
|
|
1600
|
-
<button type="button" class="btn btn-sm" disabled=${busy || !present} onClick=${() => go(true)}>${tn("namedTokenClear")}</button>
|
|
1683
|
+
<details class="s-token-editor"><summary>${layoutText(present ? "revUpdateCredential" : "revSetCredential")}</summary><div class="s-token-editor-body"><input class="s-token-input" id="publicTunnelToken" type="password" autocomplete="new-password" aria-label=${tn("namedToken")} value=${secret} onInput=${e => { setSecret(e.target.value); setTypedTunnelToken(e.target.value); }} />
|
|
1684
|
+
<div class="s-token-actions"><button type="button" class="btn btn-sm" disabled=${busy || !secret} onClick=${() => go(false)}>${tn("namedTokenSet")}</button>
|
|
1685
|
+
<button type="button" class="btn btn-sm" disabled=${busy || !present} onClick=${() => go(true)}>${tn("namedTokenClear")}</button></div></div></details>
|
|
1601
1686
|
${message ? html`<p class="note" role="status">${message}</p>` : null}
|
|
1602
1687
|
</section>`;
|
|
1603
1688
|
}
|