@songsid/agend 2.3.5-alpha.2 → 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/agend-home.d.ts +13 -0
- package/dist/agend-home.js +42 -0
- package/dist/agend-home.js.map +1 -1
- 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.d.ts +21 -0
- package/dist/auth-api.js +95 -4
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/web-authority.d.ts +12 -2
- package/dist/authority/web-authority.js +25 -4
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/claude-code.js +16 -1
- package/dist/backend/claude-code.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 +19 -11
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/kiro-identity.d.ts +2 -0
- package/dist/backend/kiro-identity.js +13 -6
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro.js +18 -3
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend-availability.d.ts +6 -0
- package/dist/backend-availability.js +13 -5
- package/dist/backend-availability.js.map +1 -1
- package/dist/channel/mcp-tools.js +1 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +6 -9
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +12 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +5 -0
- package/dist/daemon.js +107 -30
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +146 -8
- package/dist/fleet-manager.js +1030 -166
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-windows-runtime.d.ts +6 -1
- package/dist/fleet-windows-runtime.js +77 -1
- package/dist/fleet-windows-runtime.js.map +1 -1
- package/dist/fleet-yaml-patch.d.ts +3 -0
- package/dist/fleet-yaml-patch.js +103 -0
- package/dist/fleet-yaml-patch.js.map +1 -0
- package/dist/general-binding.d.ts +85 -0
- package/dist/general-binding.js +298 -0
- package/dist/general-binding.js.map +1 -0
- package/dist/general-host-owner.d.ts +8 -0
- package/dist/general-host-owner.js +26 -0
- package/dist/general-host-owner.js.map +1 -0
- package/dist/general-instance.d.ts +7 -0
- package/dist/general-instance.js +20 -3
- package/dist/general-instance.js.map +1 -1
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +2 -0
- package/dist/general-role.d.ts +5 -0
- package/dist/general-role.js +26 -0
- package/dist/general-role.js.map +1 -0
- package/dist/instance-lifecycle.d.ts +1 -0
- package/dist/instance-lifecycle.js +26 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +2 -0
- package/dist/instructions.js +6 -0
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-resume-summary.d.ts +37 -0
- package/dist/kiro-resume-summary.js +85 -0
- package/dist/kiro-resume-summary.js.map +1 -0
- package/dist/locale.js +59 -0
- package/dist/locale.js.map +1 -1
- package/dist/login-controller.js +9 -1
- package/dist/login-controller.js.map +1 -1
- package/dist/native-pane-lifecycle.js +4 -1
- package/dist/native-pane-lifecycle.js.map +1 -1
- package/dist/native-pane-manager.d.ts +1 -0
- package/dist/native-pane-manager.js +23 -2
- package/dist/native-pane-manager.js.map +1 -1
- package/dist/needs-you-hub.js +1 -1
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +8 -1
- package/dist/needs-you.js +5 -2
- package/dist/needs-you.js.map +1 -1
- package/dist/office-share-page.js +1 -1
- package/dist/office-share-page.js.map +1 -1
- package/dist/package-preimage.js +27 -2
- package/dist/package-preimage.js.map +1 -1
- package/dist/pane-port.d.ts +11 -0
- package/dist/public-web-gateway.js +9 -0
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +11 -0
- package/dist/public-web-link.js +13 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.js +9 -5
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +1 -0
- package/dist/quickstart.js +4 -1
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.js +10 -0
- package/dist/reply-buttons-controller.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 +17 -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-general-protocol.d.ts +22 -0
- package/dist/runtime-general-protocol.js +57 -0
- package/dist/runtime-general-protocol.js.map +1 -0
- package/dist/runtime-general-workspace.d.ts +17 -0
- package/dist/runtime-general-workspace.js +213 -0
- package/dist/runtime-general-workspace.js.map +1 -0
- package/dist/runtime-pane-assets.d.ts +1 -1
- package/dist/runtime-pane-assets.js +1 -1
- package/dist/runtime-pane-assets.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-pane-host.d.ts +1 -0
- package/dist/runtime-pane-host.js +22 -1
- package/dist/runtime-pane-host.js.map +1 -1
- package/dist/runtime-pane-mcp.d.ts +8 -0
- package/dist/runtime-pane-mcp.js +15 -2
- package/dist/runtime-pane-mcp.js.map +1 -1
- package/dist/runtime-pane-port.d.ts +6 -1
- package/dist/runtime-pane-port.js +21 -0
- package/dist/runtime-pane-port.js.map +1 -1
- package/dist/runtime-pane-protocol.d.ts +4 -1
- package/dist/runtime-pane-protocol.js +7 -1
- package/dist/runtime-pane-protocol.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-api.d.ts +5 -0
- package/dist/settings-api.js +37 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-baseline.d.ts +1 -0
- package/dist/settings-baseline.js +1 -1
- package/dist/settings-baseline.js.map +1 -1
- package/dist/settings-confirmation.d.ts +1 -0
- package/dist/settings-confirmation.js +1 -0
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.d.ts +4 -0
- package/dist/settings-effect.js +36 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +4 -0
- package/dist/settings-http-confirmation.js +20 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-request-capability.d.ts +2 -0
- package/dist/settings-request-capability.js +4 -0
- package/dist/settings-request-capability.js.map +1 -1
- package/dist/settings-route-lifetime.d.ts +14 -0
- package/dist/settings-route-lifetime.js +48 -0
- package/dist/settings-route-lifetime.js.map +1 -0
- package/dist/settings-transaction.d.ts +4 -0
- package/dist/settings-transaction.js +9 -0
- package/dist/settings-transaction.js.map +1 -1
- package/dist/setup-form.js +6 -3
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-i18n.js +3 -0
- package/dist/setup-i18n.js.map +1 -1
- package/dist/stall-reporter.d.ts +21 -0
- package/dist/stall-reporter.js +82 -0
- package/dist/stall-reporter.js.map +1 -0
- package/dist/tmux-control.d.ts +2 -2
- package/dist/tmux-control.js +4 -2
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.d.ts +6 -3
- package/dist/tmux-exec-helper.js +115 -53
- package/dist/tmux-exec-helper.js.map +1 -1
- package/dist/tool-permissions.js +2 -1
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tunnel/cloudflared-install.d.ts +4 -0
- package/dist/tunnel/cloudflared-install.js +20 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +8 -1
- package/dist/tunnel/cloudflared.js +34 -6
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/lease.d.ts +3 -3
- package/dist/tunnel/lease.js +23 -19
- package/dist/tunnel/lease.js.map +1 -1
- package/dist/tunnel/manager.js +6 -7
- package/dist/tunnel/manager.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/ui/agent-proposals.js +2 -2
- 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/general-location.js +71 -0
- package/dist/ui/native-instance-settings.js +5 -8
- package/dist/ui/native-pane-recovery.js +1 -1
- package/dist/ui/panel-chat.js +5 -2
- package/dist/ui/panel-fleet.js +4 -2
- package/dist/ui/panel-needs.js +2 -0
- package/dist/ui/panel-runtimes.js +136 -33
- package/dist/ui/panel-settings.js +227 -138
- 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 +13 -7
- package/dist/ui/settings-guests.js +25 -11
- package/dist/ui/settings-layout.js +245 -0
- package/dist/ui/settings-model.js +10 -1
- package/dist/ui/settings-outside.js +176 -0
- package/dist/ui/settings-registry.js +6 -3
- package/dist/ui/settings-shortcuts.js +65 -0
- package/dist/ui/settings-strings.js +95 -0
- package/dist/ui/settings-web-authority.js +14 -11
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-shell.js +17 -7
- package/dist/ui/shared/app.css +1038 -13
- package/dist/ui/shared/instance-nav.js +16 -5
- package/dist/ui/shared/machine-name.js +13 -0
- package/dist/ui/shared/panel-view.js +266 -17
- package/dist/ui/shared/qrcode.module.js +2237 -0
- package/dist/ui/shared/settings-width.js +31 -0
- package/dist/ui/shared/signin.js +39 -5
- 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/shared/vendor/qrcode-generator.LICENSE +27 -0
- package/dist/ui/shared/vendor/vendor.json +19 -2
- package/dist/ui/shared/view-strings.js +28 -0
- package/dist/ui/windows-cli-catalog.js +41 -12
- package/dist/unattended-install.d.ts +1 -1
- package/dist/unattended-install.js +19 -5
- package/dist/unattended-install.js.map +1 -1
- package/dist/update-check.d.ts +2 -8
- package/dist/update-check.js +2 -5
- package/dist/update-check.js.map +1 -1
- package/dist/view-api.d.ts +15 -0
- package/dist/view-api.js +94 -33
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +13 -0
- package/dist/web-api.js +80 -0
- package/dist/web-api.js.map +1 -1
- package/dist/web-login.d.ts +4 -3
- package/dist/web-login.js +23 -14
- package/dist/web-login.js.map +1 -1
- package/dist/web-only-fleet.d.ts +20 -0
- package/dist/web-only-fleet.js +21 -0
- package/dist/web-only-fleet.js.map +1 -1
- package/dist/web-remote-access.d.ts +152 -0
- package/dist/web-remote-access.js +368 -0
- package/dist/web-remote-access.js.map +1 -0
- package/dist/web-session.d.ts +12 -0
- package/dist/web-session.js +22 -8
- package/dist/web-session.js.map +1 -1
- package/dist/windows-agents.d.ts +15 -1
- package/dist/windows-agents.js +37 -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/launcher/agend-install.cjs +16 -2
- package/package.json +1 -1
|
@@ -53,8 +53,16 @@ import { OfficeSharesCard } from "./office-share-dialog.js";
|
|
|
53
53
|
import { PageSharesCard } from "./page-share-dialog.js";
|
|
54
54
|
import { syncStage, useDraft } from "./settings-draft.js";
|
|
55
55
|
import { WebAuthorityCard } from "./settings-web-authority.js";
|
|
56
|
+
import { OutsideCard } from "./settings-outside.js";
|
|
56
57
|
import { NamedLinkTest, setTypedTunnelToken } from "./settings-named-test.js";
|
|
57
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";
|
|
65
|
+
import { GeneralLocation } from "./general-location.js";
|
|
58
66
|
|
|
59
67
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
60
68
|
/** What a refused change says to a person: the server's code in words (the code itself only for one not listed). */
|
|
@@ -105,7 +113,7 @@ async function loadLive(lease) {
|
|
|
105
113
|
if (!lease.current()) return null;
|
|
106
114
|
const live = {}, classicLive = {};
|
|
107
115
|
if (f.ok && f.body && Array.isArray(f.body.instances)) for (const i of f.body.instances) {
|
|
108
|
-
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 };
|
|
109
117
|
if (i.classic) classicLive[i.name] = { status: i.status, state: i.state };
|
|
110
118
|
}
|
|
111
119
|
// /api/fleet carries live state for ClassicBot rooms; the profiles are the fallback for one not listed there yet.
|
|
@@ -379,20 +387,23 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
379
387
|
const formKey = data && !data.error ? `${data.loadedAt}:${draftEpoch}` : "";
|
|
380
388
|
// #2347 regrouping: each card where the person looks for it (the IA in #2347 §3); a form split across sections (the
|
|
381
389
|
// defaults, Web) is one form with one draft, shown a part at a time.
|
|
382
|
-
const sectionBody = section === "preferences" ? html`<${DeviceSection} key=${you.key}
|
|
390
|
+
const sectionBody = section === "preferences" ? html`<${DeviceSection} key=${you.key} />`
|
|
383
391
|
: section === "look" ? html`<${AppearanceSection} ...${you} part="look" />`
|
|
384
392
|
: section === "runtimes" ? html`<${RuntimesPanel} navKey=${navKey} />`
|
|
385
393
|
: !data ? html`<${Skeleton} lines=${6} />`
|
|
386
394
|
: data.error ? html`<${ErrorState} message=${tn("configLoadFailed")} onRetry=${reload} />`
|
|
387
|
-
: section === "agents" ? html`<${YamlLock} staged=${staged}><${Agents} ctx=${ctx} search=${search} openDialog=${toDialog} /></${YamlLock}
|
|
388
|
-
|
|
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>`
|
|
399
|
+
: section === "agent-defaults" ? html`<${GeneralLocation} /><${YamlLock} staged=${staged}><${General} key=${formKey} ctx=${ctx} part="fleet" /></${YamlLock}>`
|
|
389
400
|
: section === "chat" ? html`<${YamlLock} staged=${staged}><${Bots} ctx=${ctx} search=${search} openDialog=${toDialog} /></${YamlLock}>
|
|
390
401
|
<${WebSection} key=${formKey} ctx=${ctx} staged=${staged} part="chat" />
|
|
391
|
-
<div id="classicbot" class="s-anchor"><h2 class="s-ap-group">ClassicBot</h2><${Classic} ctx=${ctx} search=${search} openDialog=${toDialog}
|
|
392
|
-
|
|
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>`
|
|
393
404
|
: section === "web-dashboard" ? html`<${WebSection} key=${formKey} ctx=${ctx} staged=${staged} part="dashboard" /><${AppearanceSection} ...${you} part="dashboard" />`
|
|
394
405
|
: section === "access" ? html`<${WebSection} key=${formKey} ctx=${ctx} staged=${staged} part="access" />`
|
|
395
|
-
: html`<${
|
|
406
|
+
: html`<${AdvancedSettings} ctx=${ctx} staged=${staged} formKey=${formKey} draftEpoch=${draftEpoch} />`;
|
|
396
407
|
// #2347 §6: the setting a search result opened — brought into view once its section is drawn (its data may still be
|
|
397
408
|
// on its way), focused, and flashed for 2 s.
|
|
398
409
|
const wanted = useRef(null);
|
|
@@ -411,6 +422,7 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
411
422
|
if (++tries < 40) timer = setTimeout(find, 50); else wanted.current = null;
|
|
412
423
|
return;
|
|
413
424
|
}
|
|
425
|
+
if (revealSettingsTarget(el)) { timer = setTimeout(find, 0); return; }
|
|
414
426
|
wanted.current = null;
|
|
415
427
|
// #2347 §10: a setting in a closed drawer (Advanced) is shown — every drawer around it opens first.
|
|
416
428
|
for (let d = el.closest && el.closest("details"); d; d = d.parentElement && d.parentElement.closest("details")) d.open = true;
|
|
@@ -451,7 +463,14 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
451
463
|
if (!id) return;
|
|
452
464
|
let el = null;
|
|
453
465
|
try { el = document.getElementById(decodeURIComponent(id)); } catch { el = null; }
|
|
454
|
-
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();
|
|
455
474
|
}, [section, navKey, !!data]);
|
|
456
475
|
// A dialog belongs to the navigation that opened it (its draft names one object, never another).
|
|
457
476
|
const open = dialog && dialog.key === navKey ? dialog : null;
|
|
@@ -470,14 +489,15 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
470
489
|
<span class="s-nav-head" id=${`s-grp-${g.id}`}>${tn(`grp_${g.id}`)}</span>
|
|
471
490
|
${g.sections.map((k) => html`<a key=${k} href=${sectionPath(k)} class=${`seg-item${k === section ? " active" : ""}`}
|
|
472
491
|
aria-current=${k === section ? "page" : undefined}><${Icon} name=${ICONS[k]} size=${16} /><span>${tn(`sec_${k}`)}</span></a>`)}</div>`)}</div></nav>
|
|
473
|
-
<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}>
|
|
474
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}
|
|
475
495
|
${searchable ? html`<div class="s-toolbar"><label class="s-search"><${Icon} name="search" size=${14} /><span class="sr-only">${tn("searchAll")}</span>
|
|
476
496
|
<input type="search" value=${search} placeholder=${tn("searchAll")} onInput=${(e) => setSearch(e.target.value)} /></label></div>` : null}
|
|
477
497
|
${op ? html`<${OperationCard} op=${op} schema=${data && data.schema} />` : null}
|
|
478
498
|
<${PendingChanges} inline=${true} />
|
|
479
499
|
${ctx && (section === "agents" || section === "chat") ? html`<${FirstRunCard} onConnect=${openWizard} />` : null}
|
|
480
|
-
|
|
500
|
+
<div class="s-section-layout" data-section=${section}>${sectionBody}</div>
|
|
481
501
|
</div></div>
|
|
482
502
|
${staged.size ? html`<${PendingBar} staged=${staged} busy=${opBusy(op)} onApply=${apply} onDiscard=${discard} section=${section} />` : null}
|
|
483
503
|
${open && ctx ? renderDialog(open, ctx, close) : null}
|
|
@@ -485,6 +505,14 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
485
505
|
</div>`;
|
|
486
506
|
}
|
|
487
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
|
+
|
|
488
516
|
/**
|
|
489
517
|
* #1604, #1665: Preferences. Synced across the person's devices (kept on the host, app-prefs.js): theme (theme.js),
|
|
490
518
|
* language (app-i18n.js) and HTML previews (preview.js, through the app's confirm). On the public link they are
|
|
@@ -606,17 +634,23 @@ export function DeviceSection() {
|
|
|
606
634
|
setTimeout(go, 0);
|
|
607
635
|
};
|
|
608
636
|
const publicLink = typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1";
|
|
609
|
-
return html
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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>`;
|
|
620
654
|
}
|
|
621
655
|
|
|
622
656
|
/**
|
|
@@ -626,7 +660,7 @@ export function DeviceSection() {
|
|
|
626
660
|
* refuses one; this only shows what it says.
|
|
627
661
|
*/
|
|
628
662
|
const view = (b) => ({ phase: "ready", name: b.name || "", shown: b.shown || "", id: b.id || "", bound: b.bound === true, platform: b.platform_name || "", error: "" });
|
|
629
|
-
export function ProfileCard() {
|
|
663
|
+
export function ProfileCard({ embedded = false } = {}) {
|
|
630
664
|
const [state, setState] = useState({ phase: "loading", name: "", shown: "", id: "", bound: false, platform: "", error: "" });
|
|
631
665
|
const [draft, setDraft] = useState("");
|
|
632
666
|
const lease = useLease();
|
|
@@ -653,16 +687,22 @@ export function ProfileCard() {
|
|
|
653
687
|
};
|
|
654
688
|
const busy = state.phase !== "ready";
|
|
655
689
|
const changed = draft.trim() !== state.name;
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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}
|
|
660
695
|
placeholder=${state.platform || "web-user"} aria-invalid=${state.error ? "true" : "false"} aria-describedby="profile-help"
|
|
661
696
|
onInput=${(e) => setDraft(e.target.value)} onKeyDown=${(e) => { if (e.key === "Enter" && changed && draft.trim()) save(false); }} />
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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>`;
|
|
666
706
|
}
|
|
667
707
|
|
|
668
708
|
/** The operation in hand still owns Apply: its writes and job, or a restart of AgEnD from the press to its end. */
|
|
@@ -759,60 +799,67 @@ function agentChannelLabel(inst, chs) {
|
|
|
759
799
|
return idx >= 0 ? `${chs[idx].type} (${chLabel(idx)})` : inst.channel_id;
|
|
760
800
|
}
|
|
761
801
|
|
|
762
|
-
function Agents({ ctx, search, openDialog }) {
|
|
802
|
+
function Agents({ ctx, search, clearSearch, openDialog }) {
|
|
763
803
|
const insts = ctx.fleet.instances || {};
|
|
764
804
|
const [collapsed, setCollapsed] = useState(() => new Set());
|
|
765
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;
|
|
766
812
|
const chs = channelsOf(ctx.fleet);
|
|
767
813
|
// #2172: the sidebar's grouping — an agent in a team under its team, else under its first tag.
|
|
768
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));
|
|
769
|
-
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());
|
|
770
817
|
// One action per agent at a time: the whole row is off (Settings and ⋯ too), and the pressed button says it is working.
|
|
771
818
|
const run = async (name, action, work) => {
|
|
772
819
|
if (busy.has(name)) return;
|
|
773
820
|
setBusy((b) => new Map(b).set(name, action));
|
|
774
821
|
try { await work(); } finally { setBusy((b) => { const n = new Map(b); n.delete(name); return n; }); }
|
|
775
822
|
};
|
|
776
|
-
const head = html
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if (!
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
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
|
|
784
831
|
return html`<section key=${group} class="s-group">
|
|
785
832
|
<button type="button" class="s-group-head" aria-expanded=${folded ? "false" : "true"}
|
|
786
833
|
onClick=${() => setCollapsed((c) => { const n = new Set(c); n.has(group) ? n.delete(group) : n.add(group); return n; })}>
|
|
787
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>
|
|
788
835
|
${folded ? null : names.map((name) => {
|
|
789
836
|
const inst = insts[name];
|
|
790
|
-
const st = (ctx.live[name]
|
|
837
|
+
const st = agentStatus(ctx.live[name]);
|
|
791
838
|
const sum = effectiveSummary(ctx.live[name] || {}, inst, ctx.fleet.defaults || {});
|
|
792
839
|
const ch = agentChannelLabel(inst, chs);
|
|
793
840
|
const working = busy.has(name), doing = busy.get(name);
|
|
794
841
|
const label = (action, text) => (doing === action ? tn("working") : text);
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
<span class="s-name" title=${name}>${shortName(name, inst)}</span>
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
<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>
|
|
804
851
|
<span class="s-actions">
|
|
805
852
|
${inst.runtime === "windows" ? html`<a class="btn btn-sm" href=${chatPath(name)}>${t("app.chat")}</a>` : null}
|
|
806
853
|
<button type="button" class="btn btn-sm" disabled=${working} onClick=${() => openDialog({ kind: "agent", name, inst })}>${tn("settingsButton")}</button>
|
|
807
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}
|
|
808
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}
|
|
809
|
-
${
|
|
810
|
-
<${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) }]} />
|
|
811
858
|
</span>
|
|
812
859
|
</div>`;
|
|
813
860
|
})}
|
|
814
861
|
</section>`;
|
|
815
|
-
})}</div
|
|
862
|
+
})}</div>`;
|
|
816
863
|
}
|
|
817
864
|
|
|
818
865
|
function ConnectionRetry({ id, ctx }) {
|
|
@@ -842,10 +889,10 @@ function Bots({ ctx, search, openDialog }) {
|
|
|
842
889
|
const all = channelsOf(ctx.fleet);
|
|
843
890
|
const needle = search.trim().toLowerCase();
|
|
844
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)));
|
|
845
|
-
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>
|
|
846
893
|
<button type="button" id="newBotBtn" class="btn btn-primary" onClick=${() => openDialog({ kind: "newBot" })}><${Icon} name="plus" size=${16} />${tn("newBot")}</button></div>`;
|
|
847
|
-
return html
|
|
848
|
-
${!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 }) => {
|
|
849
896
|
const users = (ch.access && ch.access.allowed_users) || [];
|
|
850
897
|
const meta = ctx.connections.find((c, j) => channelId(c, j) === channelId(ch, i)) || {};
|
|
851
898
|
const token = meta.token_present;
|
|
@@ -870,7 +917,7 @@ function Bots({ ctx, search, openDialog }) {
|
|
|
870
917
|
${!overPublicLink && ctx.fleetUp && meta.status !== "connected" ? html`<${ConnectionRetry} id=${channelId(ch, i)} ctx=${ctx} />` : null}
|
|
871
918
|
<button type="button" class="btn btn-sm" onClick=${() => openDialog({ kind: "bot", id: channelId(ch, i) })}>${tn("settingsButton")}</button></span>
|
|
872
919
|
</div>`;
|
|
873
|
-
})}</div>`}
|
|
920
|
+
})}</div>`}</section>`;
|
|
874
921
|
}
|
|
875
922
|
|
|
876
923
|
/**
|
|
@@ -920,40 +967,40 @@ function ProviderKeys({ ctx }) {
|
|
|
920
967
|
} catch { say(spec.id, { error: tn("failed") }); }
|
|
921
968
|
finally { inFlight.current.delete(spec.id); if (lease.current()) setBusy(new Set(inFlight.current)); }
|
|
922
969
|
};
|
|
923
|
-
return html`<section class="card s-provider-keys"><h3>${
|
|
924
|
-
${!ctx.providerSecrets.length ? html`<p class="note">${tn("noProviderVerifiers")}</p>` : ctx.providerSecrets.map((spec) =>
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
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>
|
|
933
983
|
</section>`;
|
|
934
984
|
}
|
|
935
985
|
|
|
936
986
|
function Classic({ ctx, search, openDialog }) {
|
|
937
987
|
const rooms = Object.entries((ctx.classic && ctx.classic.channels) || {}).map(([key, ch]) => ({ key, ...ch }));
|
|
938
|
-
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>`;
|
|
939
989
|
const needle = search.trim().toLowerCase();
|
|
940
990
|
const list = rooms.filter((c) => !needle || String(c.name || "").toLowerCase().includes(needle) || String(c.instanceName || "").toLowerCase().includes(needle))
|
|
941
991
|
.sort((a, b) => ((STATUS_ORDER[ctx.classicRuntime(a.instanceName).status] ?? 9) - (STATUS_ORDER[ctx.classicRuntime(b.instanceName).status] ?? 9))
|
|
942
992
|
|| String(a.name || "").localeCompare(String(b.name || "")));
|
|
943
|
-
if (!list.length) return html
|
|
944
|
-
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) => {
|
|
945
995
|
const rt = ctx.classicRuntime(c.instanceName);
|
|
946
996
|
const sum = effectiveSummary(ctx.live[c.instanceName] || {}, c, ctx.fleet.defaults || {}, (ctx.classic && ctx.classic.defaults) || {});
|
|
947
997
|
const adapter = c.adapterId || "discord";
|
|
948
|
-
return html`<div key=${c.key} class="s-row">
|
|
949
|
-
<span class=${
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
<
|
|
953
|
-
<span class="
|
|
954
|
-
<span class=${`tag${adapter !== "discord" ? " persona" : ""}`}>${adapter}</span>
|
|
955
|
-
${c.collab ? html`<span class="tag">collab</span>` : null}
|
|
956
|
-
<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>
|
|
957
1004
|
<span class="s-actions">
|
|
958
1005
|
<button type="button" class="btn btn-sm" onClick=${() => openDialog({ kind: "classic", room: c })}>${tn("settingsButton")}</button>
|
|
959
1006
|
${rt.status === "running" ? html`<button type="button" class="btn btn-sm" onClick=${() => ctx.pauseWake(c.instanceName, "pause")}>${tn("pause")}</button>` : null}
|
|
@@ -1089,30 +1136,43 @@ function General({ ctx, part = "all" }) {
|
|
|
1089
1136
|
const classicReply = replyGuardSupported(f.classicBackend, f.agent_mode, d.kiro_ui) ? tn("replyGuardSupported") : tn("replyGuardUnsupported", f.classicBackend, f.agent_mode);
|
|
1090
1137
|
// #2347: Agent defaults show the fleet's part; Chat & connections ClassicBot's. One form, one draft either way.
|
|
1091
1138
|
const fleetPart = part !== "classic", classicPart = part !== "fleet";
|
|
1092
|
-
return html`<div class=${`form s-general s-part-${part}`}>
|
|
1093
|
-
${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>
|
|
1094
1141
|
<div class="grid2">
|
|
1095
|
-
<div class=${fc("field", "locale")}>${L(tn("language"), "defaults.locale", "g-locale", "locale")}<${Select} id="g-locale" value=${f.locale} onChange=${set("locale")}
|
|
1096
|
-
options=${localeOptions(f.locale).map((o) => (o.auto ? { value: "", label: tn("localeAuto") } : o))} /></div>
|
|
1097
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>
|
|
1098
1143
|
<div class=${fc("field", "model")}>${L(tn("defaultModel"), "defaults.model", "g-model", "model")}<${ModelSelect} id="g-model" value=${f.model} onChange=${set("model")}
|
|
1099
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">
|
|
1100
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>
|
|
1101
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">
|
|
1102
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} />
|
|
1103
1159
|
<p class="note">${tn("crossInstanceVisibilityHint")}</p></div>
|
|
1104
|
-
<div class=${fc("field", "
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
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>
|
|
1108
1166
|
</div>
|
|
1167
|
+
</section>
|
|
1168
|
+
<details class="s-ui-disclosure s-defaults-permissions"><summary>${tn("uiDefaultsCapabilitiesTitle")}</summary><div class="s-ui-disclosure-body">
|
|
1109
1169
|
<fieldset id="g-dash" class=${fc("field dash-perms", ...DASHBOARD_FEATURES.map((k) => `dash_${k}`))}><legend>${L(tn("dashboardAccess"), "defaults.dashboard")}</legend>
|
|
1110
1170
|
<p class="note">${tn("dashboardDefaultsHint")}</p>
|
|
1111
1171
|
${DASHBOARD_FEATURES.map((k) => html`<label key=${k} class="check"><input type="checkbox" checked=${f[`dash_${k}`]}
|
|
1112
1172
|
onChange=${(e) => set(`dash_${k}`)(e.target.checked)} /><span>${tn(`dashboard_${k}`)}</span></label>`)}
|
|
1113
1173
|
</fieldset>
|
|
1114
|
-
</
|
|
1115
|
-
${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>
|
|
1116
1176
|
<div class="grid2">
|
|
1117
1177
|
<div class=${fc("field", "classicBackend")}>${L(tn("classicBackend"), "classic_defaults.backend", "g-cbe")}<${BackendSelect} id="g-cbe" value=${f.classicBackend} onChange=${set("classicBackend")} />
|
|
1118
1178
|
${!c.backend && f.classicBackend === base.classicBackend ? html`<${Inherited} text=${valueText(f.classicBackend, "backend")} builtin=${!d.backend} from=${inAgentDefaults("#g-be")} />` : null}</div>
|
|
@@ -1133,12 +1193,12 @@ function General({ ctx, part = "all" }) {
|
|
|
1133
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>
|
|
1134
1194
|
</div>
|
|
1135
1195
|
</section>` : null}
|
|
1136
|
-
${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>
|
|
1137
1197
|
<p class="note">${tn("projectRootsHint")}</p>
|
|
1138
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")} />
|
|
1139
1199
|
${f.roots.length ? null : html`<${InEffect} text=${tn("projectRootsAnywhere")} />`}</div>
|
|
1140
|
-
</section>` : null}
|
|
1141
|
-
${fleetPart ? html`<${Drawer} title=${tn("
|
|
1200
|
+
</section></div></details>` : null}
|
|
1201
|
+
${fleetPart ? html`<${Drawer} title=${tn("uiDefaultsAdvancedTitle")}>
|
|
1142
1202
|
<section class="card"><h3>${tn("runtimeResources")}</h3><div class="grid2">
|
|
1143
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")}
|
|
1144
1204
|
options=${[{ value: "", label: tn("toolSetUnset", toolSetDefault({}, {}), toolSetDefault({}, { general_topic: true })) }, ...TOOL_SETS]} /></div>
|
|
@@ -1157,7 +1217,7 @@ function General({ ctx, part = "all" }) {
|
|
|
1157
1217
|
<section class="card"><h3>${tn("accessTitle")}</h3>
|
|
1158
1218
|
<p class="note">${primary ? tn("accessMovedHint", primary.id || chLabel(0)) : tn("noBots")}</p></section>
|
|
1159
1219
|
</${Drawer}>` : null}
|
|
1160
|
-
</div>`;
|
|
1220
|
+
</div></div>`;
|
|
1161
1221
|
}
|
|
1162
1222
|
|
|
1163
1223
|
/**
|
|
@@ -1197,6 +1257,7 @@ export function uploadMbInEffect(web) {
|
|
|
1197
1257
|
}
|
|
1198
1258
|
function WebSection({ ctx, staged, part = "all" }) {
|
|
1199
1259
|
const { schema } = ctx;
|
|
1260
|
+
const [accessTab, setAccessTab] = useSettingsTabs("access", "login");
|
|
1200
1261
|
const web = ctx.fleet.web || {}, local = (web.session && web.session.local) || {}, term = ctx.fleet.web_terminal || {};
|
|
1201
1262
|
// Over the public link the fields read only; the Guests and Web authority cards are not offered (the server never
|
|
1202
1263
|
// serves their reads there).
|
|
@@ -1327,17 +1388,31 @@ function WebSection({ ctx, staged, part = "all" }) {
|
|
|
1327
1388
|
if (v === undefined) return html`${roText(webReadOnlyDefault(path, ctx.fleet))} <${DefaultMark} />${capped ? html` <span class="tag">${tn("cappedByTotal")}</span>` : null}`;
|
|
1328
1389
|
return applies === v ? roText(v) : html`${roText(v)} <span class="tag">${tn("inEffectShort", roText(applies))}</span>`;
|
|
1329
1390
|
};
|
|
1330
|
-
//
|
|
1331
|
-
//
|
|
1332
|
-
// 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.
|
|
1333
1393
|
const has = (p) => part === "all" || part === p;
|
|
1334
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; };
|
|
1335
1408
|
return html`<div class=${`form s-web s-part-${part}`}>
|
|
1336
|
-
${
|
|
1337
|
-
${
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
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>
|
|
1341
1416
|
<div class="grid2">
|
|
1342
1417
|
<div class=${fc("field", "abs")}><label for="webSessionAbsolute">${tn("sessionAbsolute")}</label><input id="webSessionAbsolute" type="number" min="1" max="720" step="any"
|
|
1343
1418
|
class=${errors.abs ? "invalid" : ""} value=${f.abs} onInput=${(e) => set("abs")(e.target.value)} /><${Feedback} error=${errors.abs} />
|
|
@@ -1346,8 +1421,13 @@ function WebSection({ ctx, staged, part = "all" }) {
|
|
|
1346
1421
|
class=${errors.idle ? "invalid" : ""} value=${f.idle} onInput=${(e) => set("idle")(e.target.value)} /><${Feedback} error=${errors.idle} />
|
|
1347
1422
|
${value(f.idle) === null && !errors.abs ? html`<${InEffect} text=${tn("hoursValue", idle)} why=${idle < 48 ? tn("cappedBySession") : null} />` : null}</div>
|
|
1348
1423
|
</div>
|
|
1349
|
-
</
|
|
1350
|
-
|
|
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>
|
|
1351
1431
|
<p class="note">${tn("webTerminalHint")}</p>
|
|
1352
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>
|
|
1353
1433
|
<div class="grid2">
|
|
@@ -1355,8 +1435,18 @@ function WebSection({ ctx, staged, part = "all" }) {
|
|
|
1355
1435
|
class=${errors.termTtl ? "invalid" : ""} value=${f.termTtl} onInput=${(e) => set("termTtl")(e.target.value)} /><${Feedback} error=${errors.termTtl} />
|
|
1356
1436
|
${value(f.termTtl) === null ? html`<${InEffect} text=${tn("minutesValue", 10)} />` : null}</div>
|
|
1357
1437
|
</div>
|
|
1358
|
-
</section>
|
|
1359
|
-
|
|
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>
|
|
1360
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>
|
|
1361
1451
|
<p class="note">${tn("publicLinkHint")}</p>
|
|
1362
1452
|
<div class="grid2">
|
|
@@ -1372,33 +1462,31 @@ function WebSection({ ctx, staged, part = "all" }) {
|
|
|
1372
1462
|
${named ? html`<p class="note" id="publicTtlNamedHint">${tn("publicTtlNamedHint")}</p>` : null}
|
|
1373
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>
|
|
1374
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")}
|
|
1375
1473
|
</section>`) : null}
|
|
1376
|
-
${has("
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
<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>
|
|
1382
|
-
<p class="note">${tn("webEchoHint")}</p>
|
|
1383
|
-
</section>`) : null}
|
|
1384
|
-
${has("dashboard") ? lock(html`<section class="card"><h3>${tn("webDashboardTitle")}</h3>
|
|
1385
|
-
<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>
|
|
1386
|
-
<p class="note">${tn("webLogTabHint")}</p>
|
|
1387
|
-
<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>
|
|
1388
|
-
<p class="note">${tn("webUsagePublicHint")}</p>
|
|
1389
|
-
<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>
|
|
1390
|
-
<p class="note">${tn("webOfficeLevelsHint")}</p>
|
|
1391
|
-
<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>
|
|
1392
|
-
<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")}
|
|
1393
1479
|
</section>`) : null}
|
|
1394
|
-
${has("readonly") ? html`<
|
|
1395
|
-
<
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
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}
|
|
1399
1488
|
</div>`;
|
|
1400
1489
|
}
|
|
1401
|
-
|
|
1402
1490
|
/**
|
|
1403
1491
|
* Developer: the fleet model as YAML or JSON, to read, copy, download — or edit. #2347: an edit's Apply stages its
|
|
1404
1492
|
* writes in the Save bar like every form's change (Save writes them, in order, then one apply; Discard drops them),
|
|
@@ -1452,14 +1540,15 @@ function Developer({ ctx }) {
|
|
|
1452
1540
|
return html`<section class="card s-dev">
|
|
1453
1541
|
<div class="s-dev-bar">
|
|
1454
1542
|
<div class="seg-inline" role="group" aria-label=${tn("format")}>
|
|
1455
|
-
${["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>
|
|
1456
|
-
<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>
|
|
1457
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>
|
|
1458
|
-
<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>
|
|
1459
1547
|
</div>
|
|
1460
1548
|
${editing ? html`<textarea class="s-yaml" aria-label=${tn("devEditor")} spellcheck=${false} wrap="off" value=${draft} onInput=${(e) => setDraft(e.target.value)}></textarea>
|
|
1461
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>`
|
|
1462
|
-
: 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>
|
|
1463
1552
|
${blocked ? html`<p class="note s-dev-blocked" role="status">${tn("devFormsUnsaved")}</p>`
|
|
1464
1553
|
: held && !msg ? html`<p class="note s-dev-held" role="status">${tn("devStaged", heldCount)}</p>` : null}
|
|
1465
1554
|
${msg ? html`<p class=${`feedback${msg.error ? " error" : ""}`} role="status">${msg.error || msg.text}</p>` : null}
|
|
@@ -1476,12 +1565,13 @@ function PendingBar({ staged, busy, onApply, onDiscard, section }) {
|
|
|
1476
1565
|
const invalid = changes.filter((c) => c.invalid).length;
|
|
1477
1566
|
// #2382 review: a field to fix in another section is named, and the message goes there (its field focused).
|
|
1478
1567
|
const elsewhere = changes.find((c) => c.invalid && c.section && c.section !== section);
|
|
1568
|
+
const problem = elsewhere || changes.find((c) => c.invalid && c.target);
|
|
1479
1569
|
const count = changes.reduce((n, c) => n + (Number.isInteger(c.count) && c.count > 0 ? c.count : 1), 0);
|
|
1480
1570
|
return html`<div data-trust="panel-controls" class="s-pending" role="region" aria-label=${tn("pendingRegion")}>
|
|
1481
1571
|
<strong>${tn("changesPending", count)}</strong>
|
|
1482
1572
|
<span class="note">${kinds.map(impactText).join(" · ")}</span>
|
|
1483
1573
|
<span class="s-pending-actions">
|
|
1484
|
-
${!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>`
|
|
1485
1575
|
: html`<span class="feedback error s-fix" role="status">${tn("fixToSave", invalid)}</span>`}
|
|
1486
1576
|
<button type="button" class="btn" onClick=${onDiscard} disabled=${busy}>${tn("discard")}</button>
|
|
1487
1577
|
<button type="button" class="btn btn-primary" onClick=${onApply} disabled=${busy || invalid > 0} title=${busy ? tn("applyBusyLocal") : undefined}>${tn("applyChanges")}</button>
|
|
@@ -1588,12 +1678,11 @@ function PublicTunnelToken() {
|
|
|
1588
1678
|
} catch { if (lease.current()) setMessage(tn("applyFailed")); }
|
|
1589
1679
|
finally { inFlight.current = false; if (lease.current()) setBusy(false); }
|
|
1590
1680
|
};
|
|
1591
|
-
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>
|
|
1592
1682
|
<p class="note">${tn("namedTokenHint")}</p>
|
|
1593
|
-
<
|
|
1594
|
-
<
|
|
1595
|
-
<button type="button" class="btn btn-sm" disabled=${busy || !
|
|
1596
|
-
<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>
|
|
1597
1686
|
${message ? html`<p class="note" role="status">${message}</p>` : null}
|
|
1598
1687
|
</section>`;
|
|
1599
1688
|
}
|