@songsid/agend 2.3.2 → 2.3.3-beta.10
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-cli-instructions.md +2 -2
- package/dist/agent-cli.js +13 -112
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +0 -1
- package/dist/agent-endpoint.js +1 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +23 -5
- package/dist/agent-whats-new.js +58 -40
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +0 -7
- package/dist/auth-api.js +1 -15
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.js +1 -1
- package/dist/authority/authority-state.js.map +1 -1
- package/dist/authority/web-authority.d.ts +9 -4
- package/dist/authority/web-authority.js +10 -5
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/antigravity.js +6 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +20 -1
- package/dist/backend/claude-code.js +54 -29
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/claude-transcript-errors.d.ts +18 -0
- package/dist/backend/claude-transcript-errors.js +19 -0
- package/dist/backend/claude-transcript-errors.js.map +1 -0
- package/dist/backend/codex-session-lookup.d.ts +16 -3
- package/dist/backend/codex-session-lookup.js +45 -11
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.d.ts +16 -0
- package/dist/backend/codex.js +184 -35
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +58 -12
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-reconcile-store.js +8 -1
- package/dist/backend/kiro-reconcile-store.js.map +1 -1
- package/dist/backend/kiro.d.ts +0 -5
- package/dist/backend/kiro.js +2 -10
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.js +2 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +53 -4
- package/dist/backend/types.js.map +1 -1
- package/dist/bundled-skills.d.ts +4 -6
- package/dist/bundled-skills.js +10 -14
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/adapters/discord.js +60 -17
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-server.js +5 -5
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +1 -3
- package/dist/channel/mcp-tools.js +4 -58
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +4 -1
- package/dist/classic-channel-manager.js +21 -7
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +23 -6
- package/dist/cli.js.map +1 -1
- package/dist/config-history.d.ts +50 -0
- package/dist/config-history.js +263 -0
- package/dist/config-history.js.map +1 -0
- package/dist/config-validator.js +10 -8
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +11 -4
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +10 -5
- package/dist/cross-instance-envelope.js +20 -17
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +130 -64
- package/dist/daemon.js +603 -428
- package/dist/daemon.js.map +1 -1
- package/dist/daily-summary.d.ts +8 -1
- package/dist/daily-summary.js +8 -5
- package/dist/daily-summary.js.map +1 -1
- package/dist/dashboard-live.d.ts +1 -1
- package/dist/dashboard-live.js +3 -3
- package/dist/dashboard-live.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +1 -1
- package/dist/dashboard-permissions.js +1 -3
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/delivery-outbox.d.ts +31 -22
- package/dist/delivery-outbox.js +119 -161
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-outbox.test.js +10 -25
- package/dist/delivery-outbox.test.js.map +1 -1
- package/dist/doctor.d.ts +4 -0
- package/dist/doctor.js +16 -1
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-level-config.js +1 -1
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +74 -66
- package/dist/fleet-manager.js +285 -414
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +5 -0
- package/dist/fleet-process-handlers.js +21 -0
- package/dist/fleet-process-handlers.js.map +1 -1
- package/dist/fleet-service-owner.d.ts +11 -0
- package/dist/fleet-service-owner.js +3 -0
- package/dist/fleet-service-owner.js.map +1 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +11 -1
- package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +1 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -1
- package/dist/general-knowledge/skills/session-management/SKILL.md +7 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +13 -204
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +30 -68
- package/dist/instance-lifecycle.d.ts +19 -0
- package/dist/instance-lifecycle.js +73 -15
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +6 -7
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-reconcile.js +3 -3
- package/dist/kiro-reconcile.js.map +1 -1
- package/dist/locale.js +96 -58
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/needs-you-hub.d.ts +1 -0
- package/dist/needs-you-hub.js +6 -3
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +3 -1
- package/dist/needs-you-live.js +24 -2
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +13 -2
- package/dist/needs-you.js +11 -1
- package/dist/needs-you.js.map +1 -1
- package/dist/notify-socket.d.ts +10 -0
- package/dist/notify-socket.js +45 -0
- package/dist/notify-socket.js.map +1 -0
- package/dist/outbound-handlers.js +20 -8
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +0 -62
- package/dist/outbound-schemas.js +4 -43
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/parked-notices.d.ts +87 -0
- package/dist/parked-notices.js +194 -0
- package/dist/parked-notices.js.map +1 -0
- package/dist/public-link-progress.d.ts +5 -2
- package/dist/public-link-progress.js +4 -3
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-web-gateway.js +0 -9
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +12 -0
- package/dist/public-web-link.js +29 -5
- package/dist/public-web-link.js.map +1 -1
- package/dist/quota-notice.d.ts +23 -0
- package/dist/quota-notice.js +22 -0
- package/dist/quota-notice.js.map +1 -0
- package/dist/scheduler/task-board.d.ts +17 -25
- package/dist/scheduler/task-board.js +29 -39
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/sd-notify.d.ts +23 -15
- package/dist/sd-notify.js +88 -29
- package/dist/sd-notify.js.map +1 -1
- package/dist/search/search-docs.d.ts +1 -1
- package/dist/search/search-docs.js +2 -2
- package/dist/search/search-docs.js.map +1 -1
- package/dist/settings-api.d.ts +16 -1
- package/dist/settings-api.js +56 -16
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +3 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +17 -3
- package/dist/settings-confirmation.js +8 -5
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-effect.js +0 -2
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +31 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/setup-wizard.d.ts +3 -0
- package/dist/setup-wizard.js +4 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/task-board-waker.d.ts +43 -1
- package/dist/task-board-waker.js +121 -9
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-control.d.ts +1 -1
- package/dist/tmux-control.js +3 -2
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.js +18 -16
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.d.ts +2 -0
- package/dist/tmux-read.js +7 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/tool-permissions.js +3 -10
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +29 -7
- package/dist/topic-commands.js +97 -37
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.d.ts +7 -0
- package/dist/transcript-monitor.js +48 -3
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +5 -0
- package/dist/tunnel/cloudflared.js +32 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +2 -1
- package/dist/tunnel/named-diagnostics.js +4 -1
- package/dist/tunnel/named-diagnostics.js.map +1 -1
- package/dist/tunnel/named-link-test.d.ts +90 -0
- package/dist/tunnel/named-link-test.js +270 -0
- package/dist/tunnel/named-link-test.js.map +1 -0
- package/dist/tunnel/purpose-lane.d.ts +3 -1
- package/dist/tunnel/purpose-lane.js +2 -0
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/types.d.ts +9 -11
- package/dist/ui/chat-ambient.js +4 -4
- package/dist/ui/chat-commands.js +3 -2
- package/dist/ui/chat-store.js +4 -1
- package/dist/ui/chat-strings.js +12 -46
- package/dist/ui/chat-thread.js +51 -59
- package/dist/ui/fleet-office-skin.js +37 -14
- package/dist/ui/fleet-office.js +31 -7
- package/dist/ui/html-attachment.js +22 -8
- package/dist/ui/panel-chat.js +21 -50
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-needs.js +11 -9
- package/dist/ui/panel-settings.js +5 -3
- package/dist/ui/preview-panel.js +12 -21
- package/dist/ui/preview.js +10 -3
- package/dist/ui/search-dialog.js +30 -14
- package/dist/ui/settings-appearance.js +3 -37
- package/dist/ui/settings-confirm.js +18 -0
- package/dist/ui/settings-guests.js +13 -4
- package/dist/ui/settings-learn.js +1 -1
- package/dist/ui/settings-mods.js +5 -12
- package/dist/ui/settings-named-test.js +144 -0
- package/dist/ui/settings-registry.js +0 -2
- package/dist/ui/settings-strings.js +16 -16
- package/dist/ui/settings-web-authority.js +4 -3
- package/dist/ui/shared/app-i18n.js +15 -4
- package/dist/ui/shared/app-needs.js +5 -1
- package/dist/ui/shared/app-prefs.js +17 -17
- package/dist/ui/shared/app-shell.js +47 -9
- package/dist/ui/shared/app-store.js +6 -2
- package/dist/ui/shared/app.css +33 -39
- package/dist/ui/shared/app.js +5 -4
- package/dist/ui/shared/instance-nav.js +9 -9
- package/dist/ui/shared/instance-switch.js +8 -2
- package/dist/ui/shared/panel-view.js +22 -8
- package/dist/ui/shared/view-strings.js +6 -0
- package/dist/ui/shared/web-authorize.js +35 -13
- package/dist/update-check.d.ts +13 -4
- package/dist/update-check.js +17 -4
- package/dist/update-check.js.map +1 -1
- package/dist/usage/usage-api.d.ts +3 -0
- package/dist/usage/usage-api.js +5 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +2 -0
- package/dist/view-api.js +64 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +9 -17
- package/dist/web-api.js +257 -371
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +0 -2
- package/dist/web-appearance-tools.js +2 -5
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +0 -6
- package/dist/web-appearance-view.js +0 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +14 -7
- package/dist/web-appearance.js +5 -6
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +1 -17
- package/dist/web-asset-parts.js +1 -15
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +1 -7
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +3 -7
- package/dist/web-chat-history.js +10 -17
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +9 -1
- package/dist/web-commands.js +13 -0
- package/dist/web-commands.js.map +1 -1
- package/dist/web-experimental.d.ts +3 -8
- package/dist/web-experimental.js +9 -33
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-guest-access.d.ts +40 -2
- package/dist/web-guest-access.js +50 -14
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +2 -45
- package/dist/web-mod-bundle.js +15 -63
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-store.d.ts +3 -48
- package/dist/web-mod-store.js +30 -97
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-prefs.d.ts +13 -0
- package/dist/web-prefs.js +38 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +6 -13
- package/dist/web-preview.js +12 -23
- package/dist/web-preview.js.map +1 -1
- package/dist/web-session.d.ts +11 -3
- package/dist/web-session.js +22 -11
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +1 -1
- package/dist/web-settings-patch.js +9 -7
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +0 -13
- package/dist/web-settings-tools.js +12 -84
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/workflow-templates/default.md +2 -2
- package/dist/working-recovery.d.ts +0 -2
- package/dist/working-recovery.js +2 -23
- package/dist/working-recovery.js.map +1 -1
- package/launcher/agend-install.cjs +7 -1
- package/launcher/fleet-service-owner.cjs +150 -0
- package/package.json +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +0 -376
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +0 -673
- package/dist/task-board-summary.d.ts +0 -101
- package/dist/task-board-summary.js +0 -177
- package/dist/task-board-summary.js.map +0 -1
- package/dist/ui/app-effects.js +0 -251
- package/dist/ui/chat-effect-posters.js +0 -42
- package/dist/ui/chat-effects.js +0 -167
- package/dist/ui/effect-trial-controller.js +0 -124
- package/dist/ui/effect-trial-offer.js +0 -2
- package/dist/ui/effect-trial-panel.js +0 -40
- package/dist/ui/effects-waker.js +0 -2
- package/dist/ui/settings-effects.js +0 -202
- package/dist/ui/settings-owner-effects.js +0 -82
- package/dist/ui/shared/effect-app-runtime.js +0 -299
- package/dist/ui/shared/effect-baseline.js +0 -64
- package/dist/ui/shared/effect-chat-runtime.js +0 -162
- package/dist/ui/shared/effect-cursor.js +0 -126
- package/dist/ui/shared/effect-display-metadata.js +0 -39
- package/dist/ui/shared/effect-frame.js +0 -146
- package/dist/ui/shared/effect-geometry.js +0 -137
- package/dist/ui/shared/effect-host-style.js +0 -10
- package/dist/ui/shared/effect-owner-regions.js +0 -74
- package/dist/ui/shared/effect-owner-source.js +0 -122
- package/dist/ui/shared/effect-policy.js +0 -151
- package/dist/ui/shared/effect-poster-reader.js +0 -98
- package/dist/ui/shared/effect-posters.js +0 -117
- package/dist/ui/shared/effect-profiler.js +0 -184
- package/dist/ui/shared/effect-profiles.js +0 -65
- package/dist/ui/shared/effect-registry.js +0 -545
- package/dist/ui/shared/effect-runtime-loader.js +0 -236
- package/dist/ui/shared/effect-safety.js +0 -153
- package/dist/ui/shared/effect-stock-snapshot.js +0 -43
- package/dist/ui/shared/effect-stock-trust.js +0 -217
- package/dist/ui/shared/effect-trial-shadow.js +0 -62
- package/dist/ui/shared/effect-trial-source.js +0 -103
- package/dist/ui/shared/effect-trial-stage.js +0 -170
- package/dist/ui/shared/effect-trial.css +0 -40
- package/dist/ui/shared/feature-flags.js +0 -127
- package/dist/ui/shared/owner-effect-prefs.js +0 -117
- package/dist/upgrade-notices.d.ts +0 -17
- package/dist/upgrade-notices.js +0 -93
- package/dist/upgrade-notices.js.map +0 -1
- package/dist/web-effect-bootstrap.d.ts +0 -12
- package/dist/web-effect-bootstrap.js +0 -49
- package/dist/web-effect-bootstrap.js.map +0 -1
- package/dist/web-effect-bundle.d.ts +0 -28
- package/dist/web-effect-bundle.js +0 -171
- package/dist/web-effect-bundle.js.map +0 -1
- package/dist/web-effect-canonical.d.ts +0 -8
- package/dist/web-effect-canonical.js +0 -27
- package/dist/web-effect-canonical.js.map +0 -1
- package/dist/web-effect-library.d.ts +0 -114
- package/dist/web-effect-library.js +0 -530
- package/dist/web-effect-library.js.map +0 -1
- package/dist/web-effect-playback.d.ts +0 -30
- package/dist/web-effect-playback.js +0 -71
- package/dist/web-effect-playback.js.map +0 -1
- package/dist/web-effect-poster.d.ts +0 -8
- package/dist/web-effect-poster.js +0 -52
- package/dist/web-effect-poster.js.map +0 -1
- package/dist/web-effect-profiles.d.ts +0 -118
- package/dist/web-effect-profiles.js +0 -181
- package/dist/web-effect-profiles.js.map +0 -1
- package/dist/web-effect-service.d.ts +0 -93
- package/dist/web-effect-service.js +0 -335
- package/dist/web-effect-service.js.map +0 -1
- package/dist/web-effect-tools.d.ts +0 -48
- package/dist/web-effect-tools.js +0 -129
- package/dist/web-effect-tools.js.map +0 -1
- package/dist/web-effect-trials.d.ts +0 -86
- package/dist/web-effect-trials.js +0 -204
- package/dist/web-effect-trials.js.map +0 -1
- package/dist/web-effect-upload.d.ts +0 -27
- package/dist/web-effect-upload.js +0 -185
- package/dist/web-effect-upload.js.map +0 -1
- package/dist/web-effect-worker.d.ts +0 -8
- package/dist/web-effect-worker.js +0 -170
- package/dist/web-effect-worker.js.map +0 -1
- package/dist/web-effect.d.ts +0 -103
- package/dist/web-effect.js +0 -153
- package/dist/web-effect.js.map +0 -1
- package/dist/web-experimental-http.d.ts +0 -2
- package/dist/web-experimental-http.js +0 -12
- package/dist/web-experimental-http.js.map +0 -1
- package/dist/web-mod-agent.d.ts +0 -50
- package/dist/web-mod-agent.js +0 -207
- package/dist/web-mod-agent.js.map +0 -1
package/dist/ui/search-dialog.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
// ↑↓ and Enter, Esc or a click outside to close; filters folded under one toggle. A chat row still in the agent's
|
|
4
4
|
// history opens there, at the message (#m=); anything else — a chat row that has left the history, a message between
|
|
5
5
|
// agents, a task — opens here as the index's copy (GET /ui/search/context). Text only: no innerHTML, no style attribute.
|
|
6
|
+
// #2855: a trusted guest's — the chats it can see only (the server's rule): no settings, teams, sources or "only mine".
|
|
6
7
|
import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
7
8
|
import { t, register } from "/assets/app-i18n.js";
|
|
8
9
|
import { recordEdit } from "/assets/text-undo.js";
|
|
9
|
-
import { appStore, useStore } from "/assets/app-store.js";
|
|
10
|
+
import { appStore, guestPage, useStore } from "/assets/app-store.js";
|
|
10
11
|
import { navigate } from "/assets/app-nav.js";
|
|
11
12
|
import { chatPath, fleetPath } from "/assets/app-route.js";
|
|
12
13
|
import { namesOf, namesStore } from "/assets/instance-nav.js";
|
|
@@ -28,6 +29,8 @@ register("search", {
|
|
|
28
29
|
building: "Still indexing — some older results may be missing.",
|
|
29
30
|
limited: "Words of 1–2 characters alone search the newest 5,000 entries only — add a longer word to search everything.",
|
|
30
31
|
unavailable: "Search is off or not available (web.search in fleet.yaml).", busy: "Search is busy — try again in a moment.",
|
|
32
|
+
guestPlaceholder: "Search your chats…", guestHint: "Find something said in the chats you can see: a word or two. Short words (1–2 characters) work too.",
|
|
33
|
+
tooMany: "Too many searches. Try again in {0} seconds.",
|
|
31
34
|
signedOut: "Signed out — sign in again to search.", failed: "Search failed — try again.",
|
|
32
35
|
keys: "↑↓ move · Enter open · Esc close", results: "Results", back: "Results",
|
|
33
36
|
gone: "This message is no longer in the chat's history — this is the copy kept by search.",
|
|
@@ -47,6 +50,8 @@ register("search", {
|
|
|
47
50
|
building: "索引仍在建立中,較舊的結果可能還沒出現。",
|
|
48
51
|
limited: "只有 1–2 個字的短詞時,只會搜尋最新的 5,000 筆;加上較長的詞即可搜尋全部。",
|
|
49
52
|
unavailable: "搜尋已關閉或無法使用(fleet.yaml 的 web.search)。", busy: "搜尋忙碌中,請稍後再試。",
|
|
53
|
+
guestPlaceholder: "搜尋你的聊天…", guestHint: "找你看得到的聊天裡說過的事:輸入一兩個詞。1–2 個字的短詞也可以。",
|
|
54
|
+
tooMany: "搜尋太多次了,請 {0} 秒後再試。",
|
|
50
55
|
signedOut: "已登出,請重新登入後再搜尋。", failed: "搜尋失敗,請再試一次。",
|
|
51
56
|
keys: "↑↓ 移動 · Enter 開啟 · Esc 關閉", results: "結果", back: "結果",
|
|
52
57
|
gone: "這則訊息已不在聊天紀錄中,以下是搜尋保留的副本。",
|
|
@@ -144,7 +149,10 @@ export function jumpToMessage(instance, key, w = window) {
|
|
|
144
149
|
w.dispatchEvent(new Event("hashchange"));
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
const errorText = (status) => (status === 404 ? t("search.unavailable") : status === 503 ? t("search.busy") : status === 401 ? t("search.signedOut")
|
|
152
|
+
const errorText = (status, retryS) => (status === 404 ? t("search.unavailable") : status === 503 ? t("search.busy") : status === 401 ? t("search.signedOut")
|
|
153
|
+
: status === 429 ? t("search.tooMany", retryS || 60) : t("search.failed"));
|
|
154
|
+
/** #2855: a refused guest search's "try again in" (seconds), from its answer. */
|
|
155
|
+
const retryOf = async (r) => { try { const b = await r.json(); return { error: b && b.error, s: Number(b && b.retry_after_s) || 0 }; } catch { return { error: null, s: 0 }; } };
|
|
148
156
|
|
|
149
157
|
/** #2347 §6: how many settings the dialog lists above the messages (the Settings page's own search lists them all). */
|
|
150
158
|
const SETTINGS_SHOWN = 5;
|
|
@@ -154,7 +162,8 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
154
162
|
const app = useStore(appStore);
|
|
155
163
|
const names = useStore(namesStore);
|
|
156
164
|
const [q, setQ] = useState(preset.q || "");
|
|
157
|
-
const
|
|
165
|
+
const guest = guestPage();
|
|
166
|
+
const [f, setF] = useState({ instance: preset.instance || "", team: "", since: "any", sources: guest ? ["chat"] : [...SOURCES], mine: false });
|
|
158
167
|
const [showFilters, setShowFilters] = useState(!!preset.instance);
|
|
159
168
|
const [teams, setTeams] = useState(null);
|
|
160
169
|
const [res, setRes] = useState({ status: "idle" });
|
|
@@ -193,8 +202,15 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
193
202
|
const ac = typeof AbortController === "function" ? new AbortController() : null;
|
|
194
203
|
const timer = setTimeout(async () => {
|
|
195
204
|
try {
|
|
196
|
-
const
|
|
205
|
+
const ask = () => fetch(url, { headers: { Accept: "application/json" }, ...(ac ? { signal: ac.signal } : {}) });
|
|
206
|
+
let r = await ask();
|
|
197
207
|
if (mine !== seq.current) return;
|
|
208
|
+
if (r.status === 429) {
|
|
209
|
+
// #2855: a guest's searches run one at a time — the one before this may still be finishing: once more, shortly.
|
|
210
|
+
const why = await retryOf(r);
|
|
211
|
+
if (why.error === "guest_search_running") { await new Promise((ok) => setTimeout(ok, 400)); if (mine !== seq.current) return; r = await ask(); if (mine !== seq.current) return; }
|
|
212
|
+
if (r.status === 429) { const again = why.error === "guest_search_running" ? await retryOf(r) : why; setRes({ status: "error", error: errorText(429, again.s) }); return; }
|
|
213
|
+
}
|
|
198
214
|
if (!r.ok) { setRes({ status: "error", error: errorText(r.status) }); return; }
|
|
199
215
|
const body = await r.json();
|
|
200
216
|
if (mine !== seq.current) return;
|
|
@@ -209,7 +225,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
209
225
|
|
|
210
226
|
// Teams, once the filters are first shown.
|
|
211
227
|
useEffect(() => {
|
|
212
|
-
if (!showFilters || teams) return;
|
|
228
|
+
if (guest || !showFilters || teams) return;
|
|
213
229
|
fetch("/ui/teams", { headers: { Accept: "application/json" } }).then(r => (r.ok ? r.json() : { teams: {} })).then(
|
|
214
230
|
(b) => setTeams(Object.keys((b && b.teams) || {}).sort()), () => setTeams([]));
|
|
215
231
|
}, [showFilters, teams]);
|
|
@@ -219,9 +235,9 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
219
235
|
// While a new query runs, the last answer stays (dimmed) rather than flashing empty.
|
|
220
236
|
const groups = useMemo(() => groupHits(res.hits || []), [res]);
|
|
221
237
|
// #2347 §6: the settings matching the query, from the page's own registry (no request), above the messages.
|
|
222
|
-
const settings = useMemo(() => (q.trim() ? searchSettings(q).slice(0, SETTINGS_SHOWN) : []), [q]);
|
|
238
|
+
const settings = useMemo(() => (q.trim() && !guest ? searchSettings(q).slice(0, SETTINGS_SHOWN) : []), [q]);
|
|
223
239
|
const flat = useMemo(() => [...settings.map((entry) => ({ setting: entry })), ...flatHits(groups)], [settings, groups]);
|
|
224
|
-
const activeFilters = (f.instance ? 1 : 0) + (f.team ? 1 : 0) + (f.since !== "any" ? 1 : 0) + (f.sources.length < SOURCES.length ? 1 : 0) + (f.mine ? 1 : 0);
|
|
240
|
+
const activeFilters = (f.instance ? 1 : 0) + (f.team ? 1 : 0) + (f.since !== "any" ? 1 : 0) + (!guest && f.sources.length < SOURCES.length ? 1 : 0) + (f.mine ? 1 : 0);
|
|
225
241
|
|
|
226
242
|
// The active row stays in view.
|
|
227
243
|
useEffect(() => {
|
|
@@ -239,7 +255,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
239
255
|
// Any failure — the request, or a body that is not JSON — ends as an error, never "Loading…" for good.
|
|
240
256
|
fetch(`/ui/search/context?key=${encodeURIComponent(hit.key)}`, { headers: { Accept: "application/json" } })
|
|
241
257
|
.then(async (r) => {
|
|
242
|
-
if (!r.ok) { setDetail(d => (d && d.hit === hit ? { hit, status: "error", error: r.status === 404 ? t("search.notFound") : errorText(r.status) } : d)); return; }
|
|
258
|
+
if (!r.ok) { const s = r.status === 429 ? (await retryOf(r)).s : 0; setDetail(d => (d && d.hit === hit ? { hit, status: "error", error: r.status === 404 ? t("search.notFound") : errorText(r.status, s) } : d)); return; }
|
|
243
259
|
const body = await r.json();
|
|
244
260
|
setDetail(d => (d && d.hit === hit ? { hit, status: "ok", ctx: body } : d));
|
|
245
261
|
})
|
|
@@ -266,7 +282,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
266
282
|
<div class="search-box">
|
|
267
283
|
<div class="search-head">
|
|
268
284
|
<${Icon} name="search" />
|
|
269
|
-
<input ref=${input} class="search-input" type="search" value=${q} placeholder=${t("search.placeholder")} aria-label=${t("search.label")}
|
|
285
|
+
<input ref=${input} class="search-input" type="search" value=${q} placeholder=${t(guest ? "search.guestPlaceholder" : "search.placeholder")} aria-label=${t("search.label")}
|
|
270
286
|
role="combobox" aria-expanded=${flat.length && !detail ? "true" : "false"} aria-controls="search-results" aria-autocomplete="list"
|
|
271
287
|
aria-activedescendant=${activeId} autocomplete="off" spellcheck=${false} enterkeyhint="search"
|
|
272
288
|
onInput=${(e) => { setQ(e.target.value); setDetail(null); }} />
|
|
@@ -288,24 +304,24 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
288
304
|
${["any", "day", "week", "month"].map(k => html`<button type="button" class=${`chip-btn${f.since === k ? " on" : ""}`} aria-pressed=${f.since === k ? "true" : "false"}
|
|
289
305
|
onClick=${() => setFilter({ since: k })}>${k === "any" ? t("search.anyTime") : t(`search.${k}`)}</button>`)}
|
|
290
306
|
</div>
|
|
291
|
-
|
|
307
|
+
${guest ? null : html`<div class="search-chips" role="group" aria-label=${t("search.sources")}>
|
|
292
308
|
${SOURCES.map(s => html`<button type="button" class=${`chip-btn${f.sources.includes(s) ? " on" : ""}`} aria-pressed=${f.sources.includes(s) ? "true" : "false"}
|
|
293
309
|
onClick=${() => toggleSource(s)}>${t(`search.src_${s}`)}</button>`)}
|
|
294
310
|
<label class="search-mine"><input type="checkbox" checked=${f.mine} onChange=${(e) => setFilter({ mine: e.target.checked })} />${t("search.mine")}</label>
|
|
295
|
-
</div
|
|
311
|
+
</div>`}
|
|
296
312
|
</div>` : null}
|
|
297
313
|
<div class="search-body" ref=${list}>
|
|
298
314
|
${detail ? html`<${Detail} detail=${detail} q=${q} label=${label} onBack=${() => { setDetail(null); input.current && input.current.focus(); }} onClose=${onClose} />`
|
|
299
|
-
: html`<${Results} q=${q} res=${res} groups=${groups} settings=${settings} active=${active} setActive=${setActive} open=${open} label=${label} filtered=${activeFilters > 0}
|
|
315
|
+
: html`<${Results} guest=${guest} q=${q} res=${res} groups=${groups} settings=${settings} active=${active} setActive=${setActive} open=${open} label=${label} filtered=${activeFilters > 0}
|
|
300
316
|
recent=${recent} pick=${(s) => { recordEdit(input.current); setQ(s); input.current && input.current.focus(); }} clearRecent=${() => { forgetRecent(); setRecent([]); }} />`}
|
|
301
317
|
</div>
|
|
302
318
|
<div class="search-foot" aria-hidden="true">${t("search.keys")}</div>
|
|
303
319
|
</div></dialog>`;
|
|
304
320
|
}
|
|
305
321
|
|
|
306
|
-
function Results({ q, res, groups, settings = [], active, setActive, open, label, filtered, recent, pick, clearRecent }) {
|
|
322
|
+
function Results({ guest = false, q, res, groups, settings = [], active, setActive, open, label, filtered, recent, pick, clearRecent }) {
|
|
307
323
|
if (!q.trim()) {
|
|
308
|
-
return html`<div class="search-empty"><p class="search-hint">${t("search.hint")}</p>
|
|
324
|
+
return html`<div class="search-empty"><p class="search-hint">${t(guest ? "search.guestHint" : "search.hint")}</p>
|
|
309
325
|
${recent.length ? html`<div class="search-recent"><div class="search-recent-head"><span>${t("search.recent")}</span>
|
|
310
326
|
<button type="button" class="chip-btn" onClick=${clearRecent}>${t("search.clearRecent")}</button></div>
|
|
311
327
|
<ul>${recent.map(s => html`<li><button type="button" class="search-recent-item" onClick=${() => pick(s)}><${Icon} name="clock" size=${14} /><span>${s}</span></button></li>`)}</ul></div>` : null}</div>`;
|
|
@@ -6,8 +6,6 @@ import { register, t } from "/assets/app-i18n.js";
|
|
|
6
6
|
import { recordEdit } from "/assets/text-undo.js";
|
|
7
7
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
8
8
|
import { ModsCard } from "./settings-mods.js";
|
|
9
|
-
import { experimentalComponent, useCanvasWorker } from "/assets/feature-flags.js";
|
|
10
|
-
const EffectsCard = experimentalComponent(() => import("./settings-effects.js"), m => m.EffectsCard);
|
|
11
9
|
import { Icon } from "/assets/ui-icons.js";
|
|
12
10
|
import { toast } from "/assets/ui-toast.js";
|
|
13
11
|
import { ownerCssOffHere, ownerCssTrial, safeMode, setOwnerCssActive, setOwnerCssOffHere, startOwnerCssTrial } from "/assets/owner-css.js";
|
|
@@ -17,7 +15,7 @@ import { CARD_SIZES, cardSizeDefault, cardSizeStore, setCardSizeDefault } from "
|
|
|
17
15
|
import { AgentWidget, checkWidget, fleetBinds, setSidebarWidget, sidebarWidgetRaw, sidebarWidgetStore, widgetProblem } from "/assets/agent-widget.js";
|
|
18
16
|
import { confirmedWrite } from "./settings-confirm.js";
|
|
19
17
|
import { savePref, withoutHold } from "/assets/app-prefs.js";
|
|
20
|
-
import {
|
|
18
|
+
import { ownChoiceImpact } from "./settings-model.js";
|
|
21
19
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
22
20
|
import { colorsOff, defaultPlaces, identityClasses, identityOf, PLACEMENTS, paintIdentity, setColorsOff, setDefaultPlaces, useIdentityColors } from "/assets/identity-color.js";
|
|
23
21
|
import { CardTitle } from "./settings-learn.js";
|
|
@@ -66,12 +64,12 @@ export function AppearanceSection(props = {}) {
|
|
|
66
64
|
// dashboard) are drawn where each belongs. None of it is offered over the public link (Look says so).
|
|
67
65
|
if (publicLink) return part === "look" ? html`<section class="card s-appearance"><h3>${tn("cssTitle")}</h3><p class="sub">${tn("cssPublicLink")}</p></section>` : null;
|
|
68
66
|
if (part === "device") return html`<${MotionCard} ...${props} /><${DeviceCard} ...${props} />`;
|
|
69
|
-
if (part === "dashboard") return html`<${AppIconCard} /><${CardLimitsCard}
|
|
67
|
+
if (part === "dashboard") return html`<${AppIconCard} /><${CardLimitsCard} />`;
|
|
70
68
|
const group = (key) => html`<h2 class="s-ap-group">${tn(key)}</h2>`;
|
|
71
69
|
return html`${group("groupLook")}<${ThemesCard} ...${props} /><${IdentityColorCard} /><${CardSizeCard} />
|
|
72
70
|
${group("groupLayout")}<${SidebarWidgetCard} />
|
|
73
71
|
${group("groupCss")}<${OwnerCssCard} />
|
|
74
|
-
${group("groupMods")}<${ModsCard}
|
|
72
|
+
${group("groupMods")}<${ModsCard} />
|
|
75
73
|
<${RestoreDefaultsCard} ...${props} />`;
|
|
76
74
|
}
|
|
77
75
|
|
|
@@ -92,38 +90,6 @@ function deviceChoice(props, key, label, read, write) {
|
|
|
92
90
|
};
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
register("experimentalEffects", {
|
|
96
|
-
en: { title: "Experimental: Canvas Worker effects", help: "Enable uploaded Canvas Worker effects. Ambient effects are always available. Saved effects stay intact when this is off.", failed: "Could not change this setting. Try again." },
|
|
97
|
-
"zh-TW": { title: "實驗功能:Canvas Worker 特效", help: "啟用上傳的 Canvas Worker 特效。氛圍特效不受影響;關閉時保留已儲存的特效。", failed: "無法變更此設定,請重試。" },
|
|
98
|
-
});
|
|
99
|
-
const CANVAS_WORKER = "fleet:experimental.canvas_worker";
|
|
100
|
-
function ExperimentalEffectsCard(props = {}) {
|
|
101
|
-
const enabled = useCanvasWorker(), [writable, setWritable] = useState(false), [busy, setBusy] = useState(false), [failed, setFailed] = useState(false);
|
|
102
|
-
useEffect(() => { let live = true; Promise.resolve(globalThis.AgendAuth?.trialSession?.()).then(value => { if (live) setWritable(value?.localAdmin === true); }, () => {}); return () => { live = false; }; }, []);
|
|
103
|
-
if (!writable) return null;
|
|
104
|
-
// #2347: in Settings it is a fleet change like any other — staged here, written by the bar's Save (this page's switch
|
|
105
|
-
// follows once it applied).
|
|
106
|
-
const pending = props.staged && props.staged.get(CANVAS_WORKER);
|
|
107
|
-
const shown = pending ? pending.draft : enabled;
|
|
108
|
-
const stageIt = (value) => {
|
|
109
|
-
setFailed(false);
|
|
110
|
-
if (value === enabled) { props.unstage(CANVAS_WORKER); return; }
|
|
111
|
-
props.stage(CANVAS_WORKER, { label: t("experimentalEffects.title"), impact: impactOf(props.schema || DEFAULT_SCHEMA, "web.experimental.canvas_worker"), draft: value,
|
|
112
|
-
request: { method: "PUT", url: "/api/settings/fleet/web", body: { experimental: { canvas_worker: value } } },
|
|
113
|
-
onResult: (outcome) => { if (outcome && outcome.status === "applied") appStore.set({ canvasWorker: value }); else if (outcome && outcome.status === "failed") setFailed(true); } }, true);
|
|
114
|
-
};
|
|
115
|
-
const change = async value => {
|
|
116
|
-
if (props.stage) { stageIt(value); return; }
|
|
117
|
-
setBusy(true); setFailed(false);
|
|
118
|
-
try {
|
|
119
|
-
const result = await confirmedWrite("/api/settings/fleet/web", { method: "PUT", body: { experimental: { canvas_worker: value } }, label: t("experimentalEffects.title") });
|
|
120
|
-
if (result.ok) appStore.set({ canvasWorker: value }); else setFailed(true);
|
|
121
|
-
} catch { setFailed(true); }
|
|
122
|
-
finally { setBusy(false); }
|
|
123
|
-
};
|
|
124
|
-
return html`<section class="card s-appearance s-experimental-effects"><label class="check"><input type="checkbox" checked=${shown} disabled=${busy} onChange=${event => change(event.target.checked)} /><span>${t("experimentalEffects.title")}</span></label><p class="sub">${t("experimentalEffects.help")}</p>${failed ? html`<p role="alert">${t("experimentalEffects.failed")}</p>` : null}</section>`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
93
|
/** #2056 B: a sample sidebar row in the host's default colour places, so the choice is seen rather than described. */
|
|
128
94
|
const SAMPLE = { color: { person: "preset:teal" } };
|
|
129
95
|
function ColorSample({ places, off }) {
|
|
@@ -65,6 +65,8 @@ function publish() {
|
|
|
65
65
|
// Monotonic: when the server's countdown reaches zero on this page's clock.
|
|
66
66
|
deadline: at + Math.max(0, Number(view.remaining_ms) || 0),
|
|
67
67
|
withdraw: view.can_withdraw && view.state === "pending" ? () => withdraw(view.id) : null,
|
|
68
|
+
// #2868: this browser may confirm it itself now (the server's read says so; it checks again on the click).
|
|
69
|
+
applyNow: view.can_self_confirm && view.state === "pending" ? () => applyNow(view.id) : null,
|
|
68
70
|
dismiss: TERMINAL.has(view.state) ? () => forget(view.id) : null,
|
|
69
71
|
})),
|
|
70
72
|
});
|
|
@@ -150,6 +152,22 @@ export async function withdraw(id) {
|
|
|
150
152
|
return false;
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
/**
|
|
156
|
+
* #2868: "Apply now" — confirm this session's own pending request from this browser (#2170's rule, asked again by the
|
|
157
|
+
* server). Its answer is followed like any other; a refusal (no longer allowed here) leaves it waiting and is { ok: false }.
|
|
158
|
+
*/
|
|
159
|
+
export async function applyNow(id) {
|
|
160
|
+
const e = tracked.get(id);
|
|
161
|
+
if (!e) return { ok: false };
|
|
162
|
+
const token = ticket(e);
|
|
163
|
+
let res;
|
|
164
|
+
try { res = await api(`/api/settings/pending/${encodeURIComponent(id)}/confirm`, { method: "POST", body: "{}" }); } catch { return { ok: false }; }
|
|
165
|
+
if (isPendingResponse(res) && res.body.pending_change.id === id) { update(res.body.pending_change, token); schedule(); return { ok: true }; }
|
|
166
|
+
// Refused (403: not allowed here any more): read it again, so "Apply now" goes until the server offers it again.
|
|
167
|
+
if (e.view.can_self_confirm) update({ ...e.view, can_self_confirm: false }, token);
|
|
168
|
+
return { ok: false, status: res.status };
|
|
169
|
+
}
|
|
170
|
+
|
|
153
171
|
/** This session's requests still open on the server (after a reload): followed like new ones. */
|
|
154
172
|
export async function attach() {
|
|
155
173
|
let res;
|
|
@@ -22,13 +22,16 @@ const PLATFORMS = { discord: "Discord", telegram: "Telegram" };
|
|
|
22
22
|
const own = (o, k) => !!o && Object.prototype.hasOwnProperty.call(o, k);
|
|
23
23
|
const sameList = (a, b) => (a || []).length === (b || []).length && (a || []).every((n) => (b || []).includes(n));
|
|
24
24
|
const accountKey = (g) => `${g.adapterId}:${g.userId}`;
|
|
25
|
-
|
|
25
|
+
// #2855: a grant's level — "trusted", or chat (the default, so never written).
|
|
26
|
+
const grantOf = (v) => ({ ...(v.name ? { name: v.name } : {}), instances: [...v.instances], ...(v.level === "trusted" ? { level: "trusted" } : {}) });
|
|
27
|
+
const copyGrants = (m) => { const out = {}; for (const [k, v] of Object.entries(m || {})) if (v && Array.isArray(v.instances)) out[k] = grantOf(v); return out; };
|
|
26
28
|
/** The saved state, from the fleet's config: `grants` null while the fleet still uses #1920's derived access. */
|
|
27
29
|
export function savedGuests(fleet) {
|
|
28
30
|
const g = (fleet && fleet.web && fleet.web.guests) || {};
|
|
29
31
|
return { enabled: g.enabled === true, chat: g.chat !== false, grants: g.grants ? copyGrants(g.grants) : null };
|
|
30
32
|
}
|
|
31
|
-
const
|
|
33
|
+
const levelOf = (g) => (g && g.level === "trusted" ? "trusted" : "chat");
|
|
34
|
+
const sameGrant = (a, b) => !!a && !!b && (a.name || "") === (b.name || "") && sameList(a.instances, b.instances) && levelOf(a) === levelOf(b);
|
|
32
35
|
|
|
33
36
|
/**
|
|
34
37
|
* What changed against the saved state, as the PUT /api/settings/fleet/web body's `guests` (null: nothing), and how
|
|
@@ -44,7 +47,7 @@ export function guestsBody(original, f) {
|
|
|
44
47
|
for (const k of new Set([...Object.keys(was), ...Object.keys(f.grants)])) {
|
|
45
48
|
const a = own(was, k) ? was[k] : undefined, b = own(f.grants, k) ? f.grants[k] : undefined;
|
|
46
49
|
if (a && b && sameGrant(a, b)) continue;
|
|
47
|
-
diff[k] = b ?
|
|
50
|
+
diff[k] = b ? grantOf(b) : null;
|
|
48
51
|
count++;
|
|
49
52
|
}
|
|
50
53
|
// #2365 review: turning grants on is a change by itself, even with no one in them yet (default deny): `grants: {}`.
|
|
@@ -235,6 +238,12 @@ export function GuestsSection({ ctx, staged }) {
|
|
|
235
238
|
${removed ? html`<span class="g-state">${tn("grantsWillBeRemoved")}</span>` : added ? html`<span class="g-state">${tn("grantsNew")}</span>` : pill(person)}</div>
|
|
236
239
|
<div class="g-chips">${((draft || saved).instances || []).map((n) => html`<span key=${n} class="g-chip" title=${shownName(rooms, n) !== n ? n : undefined}>${shownName(rooms, n)}</span>`)}</div>
|
|
237
240
|
${draft && !draft.instances.length ? html`<p class="note g-error" role="alert">${tn("grantsNoAgents")}</p>` : null}
|
|
241
|
+
${draft ? html`<div class=${`g-level${levelOf(draft) !== levelOf(saved) && saved ? " s-dirty" : ""}`}>
|
|
242
|
+
<span class="note" id=${`level-${k}`}>${tn("grantsLevel", person.name)}</span>
|
|
243
|
+
<div class="seg-inline" role="group" aria-labelledby=${`level-${k}`}>${["chat", "trusted"].map((l) => html`<button key=${l} type="button"
|
|
244
|
+
class=${`btn btn-sm${levelOf(draft) === l ? " btn-primary" : ""}`} aria-pressed=${levelOf(draft) === l ? "true" : "false"} data-level=${l}
|
|
245
|
+
onClick=${() => setGrant(k, { ...draft, level: l === "trusted" ? "trusted" : undefined })}>${tn(l === "trusted" ? "grantsLevelTrusted" : "grantsLevelChat")}</button>`)}</div>
|
|
246
|
+
${levelOf(draft) === "trusted" ? html`<p class="note">${tn("grantsLevelTrustedHint")}</p>` : null}</div>` : null}
|
|
238
247
|
${editing === k && draft ? html`<div class="g-edit"><p class="note">${tn("grantsMaySee", person.name)}</p>
|
|
239
248
|
<${Names} id=${`grant-${k}`} all=${instances} rooms=${rooms} value=${draft.instances} wrote=${person.wrote} onChange=${(list) => setGrant(k, { ...draft, instances: list })} /></div>` : null}
|
|
240
249
|
${readOnly ? null : html`<div class="guest-actions">
|
|
@@ -260,7 +269,7 @@ export function GuestsSection({ ctx, staged }) {
|
|
|
260
269
|
${overview && overview.audit ? html`<h4>${tn("grantsRecent")}</h4>${!overview.audit.length ? html`<p class="note" id="grantsRecentNone">${tn("grantsRecentNone")}</p>`
|
|
261
270
|
: html`<ul class="g-audit" id="grantsRecentList">${overview.audit.map((e, i) => html`<li key=${`${e.at}-${i}`} class="g-audit-row" data-kind=${e.kind}>
|
|
262
271
|
<span class="g-when">${new Date(e.at).toLocaleString()}</span> <strong>${e.name}</strong>
|
|
263
|
-
${e.kind === "grant" ? html`${e.added.map((n) => html` <span key=${`+${n}`} class="g-wrote g-add">+${n}</span>`)}${e.removed.map((n) => html` <span key=${`-${n}`} class="g-wrote g-del">−${n}</span>`)}
|
|
272
|
+
${e.kind === "grant" ? html`${e.added.map((n) => html` <span key=${`+${n}`} class="g-wrote g-add">+${n}</span>`)}${e.removed.map((n) => html` <span key=${`-${n}`} class="g-wrote g-del">−${n}</span>`)}${e.level ? html` <span class="g-wrote g-level-to">${tn("grantsAuditLevel", tn(e.level === "trusted" ? "grantsLevelTrusted" : "grantsLevelChat"))}</span>` : null}
|
|
264
273
|
<span class="note"> · <span class="g-wrote">${tn(e.via === "confirmed" ? "grantsAuditConfirmed" : "grantsAuditDirect")}</span>${e.endedSessions ? html` · <span class="g-wrote">${tn("grantsAuditEnded", e.endedSessions)}</span>` : null}</span>`
|
|
265
274
|
: html` <span class="note">${e.outcome === "sent" ? tn("grantsAuditCodeSent") : tn("grantsAuditCodeNot", auditOutcome(e.outcome))}</span>`}</li>`)}</ul>`}` : null}
|
|
266
275
|
${status && status.kind === "waiting" ? html`<p class="note" role="status">${tn("grantsWaiting")}</p>` : null}
|
|
@@ -12,7 +12,7 @@ const DOCS = { en: "https://github.com/songsid/AgEnD/blob/main/docs/web-dashboar
|
|
|
12
12
|
export const LEARN_ANCHORS = {
|
|
13
13
|
colors: ["identity-colours", "識別色"], css: ["custom-css-settings--look", "自訂-css設定--外觀"], themes: ["agents-themes", "agent-的主題"],
|
|
14
14
|
cards: ["identity-cards", "身分卡"], limits: ["card-upload-limits", "身分卡上傳限制"], widgets: ["widgets", "小工具"], mods: ["mods", "mod"],
|
|
15
|
-
|
|
15
|
+
restore: ["restore-defaults", "還原預設值"],
|
|
16
16
|
"app-icon": ["app-icon", "app-圖示"],
|
|
17
17
|
};
|
|
18
18
|
export function learnHref(topic, language = lang()) {
|
package/dist/ui/settings-mods.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
// parts the person ticks are applied, all or nothing. Custom CSS from a file is never ticked by default. Installed
|
|
5
5
|
// mods can be downloaded again or uninstalled (what they applied stays: Reset and the CSS controls work on it).
|
|
6
6
|
// Export builds a file from what is in force now.
|
|
7
|
-
import { canvasWorkerEnabled, useCanvasWorker } from "/assets/feature-flags.js";
|
|
8
7
|
import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
|
|
9
8
|
import { t } from "/assets/app-i18n.js";
|
|
10
9
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
@@ -48,7 +47,6 @@ export function defaultChoice(plan) {
|
|
|
48
47
|
for (const it of plan.items) {
|
|
49
48
|
if (it.part === "theme" || it.part === "identity" || it.part === "ambient") c[it.part] = true;
|
|
50
49
|
else if (it.part === "widget") c.widgets[it.slot] = true;
|
|
51
|
-
else if (it.part === "effect" && canvasWorkerEnabled()) c.effects = true;
|
|
52
50
|
// ownerCss stays false: "read it before turning it on" (r4 §4).
|
|
53
51
|
}
|
|
54
52
|
return c;
|
|
@@ -62,7 +60,7 @@ function Swatch({ color }) {
|
|
|
62
60
|
return html`<span class="s-mod-sw"><span class="s-mod-sw-chip" ref=${ref} aria-hidden="true"></span><code>${color}</code></span>`;
|
|
63
61
|
}
|
|
64
62
|
/** Every field of the checked theme, as it will be applied — nothing left unseen (#1897 review). */
|
|
65
|
-
export function ThemeDetail({ theme, pictures
|
|
63
|
+
export function ThemeDetail({ theme, pictures }) {
|
|
66
64
|
const th = theme || {};
|
|
67
65
|
const row = (k, v) => html`<div class="s-mod-kv"><span class="k">${k}</span><span class="v">${v}</span></div>`;
|
|
68
66
|
const pal = (mode) => {
|
|
@@ -83,7 +81,6 @@ export function ThemeDetail({ theme, pictures, effects }) {
|
|
|
83
81
|
${th.background ? pic(th.background.image, tn("modBackground")) : null}
|
|
84
82
|
${th.background ? row(tn("modBackgroundFit"), `${th.background.fit} · ${Math.round(th.background.opacity * 100)}%`) : null}
|
|
85
83
|
${th.avatar ? pic(th.avatar, tn("modAvatar")) : null}
|
|
86
|
-
${(canvasWorkerEnabled() ? effects || [] : []).map((e, i) => html`<div key=${i}>${row(tn("modPlacesEffect"), html`<span><code>${e.effect}</code>${e.id ? html` <span class="muted">${e.id.slice(0, 8)}</span>` : null}${Object.keys(e.params || {}).length ? html` · <code>${JSON.stringify(e.params)}</code>` : null}</span>`)}</div>`)}
|
|
87
84
|
<details class="s-mod-raw"><summary>${tn("modAllFields")}</summary><pre class="s-mod-css-text">${JSON.stringify(th, null, 2)}</pre></details>
|
|
88
85
|
</div>`;
|
|
89
86
|
}
|
|
@@ -92,7 +89,7 @@ function PlanItem({ it, choice, setChoice, binds, fleet }) {
|
|
|
92
89
|
const tick = (label, on, set) => html`<label class="check s-mod-tick"><input type="checkbox" checked=${on} onChange=${(e) => set(e.target.checked)} /><span>${label}</span></label>`;
|
|
93
90
|
if (it.part === "theme") {
|
|
94
91
|
return html`<li class="s-mod-item">${tick(tn("modPartTheme"), choice.theme, (v) => setChoice({ ...choice, theme: v }))}
|
|
95
|
-
<div class="s-mod-detail"><${ThemeDetail} theme=${it.theme} pictures=${it.pictures}
|
|
92
|
+
<div class="s-mod-detail"><${ThemeDetail} theme=${it.theme} pictures=${it.pictures} /></div></li>`;
|
|
96
93
|
}
|
|
97
94
|
if (it.part === "identity") {
|
|
98
95
|
return html`<li class="s-mod-item">${tick(tn("modPartIdentity"), choice.identity, (v) => setChoice({ ...choice, identity: v }))}
|
|
@@ -116,11 +113,8 @@ function PlanItem({ it, choice, setChoice, binds, fleet }) {
|
|
|
116
113
|
<div class="s-mod-detail">${one("loop")}${one("click")}
|
|
117
114
|
<details class="s-mod-raw"><summary>${tn("modAllFields")}</summary><pre class="s-mod-css-text">${JSON.stringify({ loop: it.loop, click: it.click }, null, 2)}</pre></details></div></li>`;
|
|
118
115
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return html`<li class="s-mod-item">${tick(tn("modPartEffect", it.title), choice.effects, (v) => setChoice({ ...choice, effects: v }))}
|
|
122
|
-
<div class="s-mod-detail"><span>${kib(it.bytes)} · ${tn("modRegions", it.regions.join(", "))}</span>${it.wants.length ? html`<span>${tn("modWants", it.wants.join(", "))}</span>` : null}</div></li>`;
|
|
123
|
-
}
|
|
116
|
+
// #2799: a Canvas Worker effect entry (the server refuses the file that carries one) is not offered.
|
|
117
|
+
if (it.part === "effect") return null;
|
|
124
118
|
if (it.part === "ownerCss") {
|
|
125
119
|
return html`<li class="s-mod-item s-mod-css">${tick(tn("modPartCss"), choice.ownerCss, (v) => setChoice({ ...choice, ownerCss: v }))}
|
|
126
120
|
<p class="s-mod-warn">${tn("modCssWarn")}</p>
|
|
@@ -141,7 +135,7 @@ function Plan({ staged, onDone }) {
|
|
|
141
135
|
const names = (app.instances || []).map((i) => i.name);
|
|
142
136
|
const install = async () => {
|
|
143
137
|
setBusy(true); setError("");
|
|
144
|
-
const r = await postJson("/ui/mods/commit", { stage: staged.stage, plan_hash: staged.plan_hash, include: { ...choice, effects:
|
|
138
|
+
const r = await postJson("/ui/mods/commit", { stage: staged.stage, plan_hash: staged.plan_hash, include: { ...choice, effects: false }, ...(toInstance ? { target } : {}) });
|
|
145
139
|
setBusy(false);
|
|
146
140
|
// The stage this plan shows is gone or no longer this plan (replaced on another device, expired): read again —
|
|
147
141
|
// the page then shows whatever is staged now, with its own, fresh choices (keyed by stage).
|
|
@@ -209,7 +203,6 @@ function ExportForm() {
|
|
|
209
203
|
}
|
|
210
204
|
|
|
211
205
|
export function ModsCard() {
|
|
212
|
-
useCanvasWorker();
|
|
213
206
|
const [state, setState] = useState(null);
|
|
214
207
|
const [busy, setBusy] = useState(false);
|
|
215
208
|
const [error, setError] = useState("");
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// #2906 (the user, 2026-11-01): Settings → Access → the fixed address's 「測試連線」 / "Test connection". It tests the
|
|
2
|
+
// values as filled in (unsaved too: the typed connector token, else the saved one) and shows each step ✓ or ✗; every ✗
|
|
3
|
+
// says what the person can do next. The server answers closed step codes only (settings-api.ts, named-link-test.ts):
|
|
4
|
+
// the token never comes back. Words never name the person's workplace — "this machine's network", "network administrator".
|
|
5
|
+
import { html, useRef, useState } from "/assets/app-html.js";
|
|
6
|
+
import { register, t } from "/assets/app-i18n.js";
|
|
7
|
+
import { useLease } from "/assets/app-ctx.js";
|
|
8
|
+
import { api } from "./settings-confirm.js";
|
|
9
|
+
|
|
10
|
+
register("settings", {
|
|
11
|
+
en: {
|
|
12
|
+
ntButton: "Test connection",
|
|
13
|
+
ntRunning: "Testing… (up to about a minute)",
|
|
14
|
+
ntHint: "Tests the values above as filled in, saved or not — with the token typed below, or the saved one.",
|
|
15
|
+
ntBusy: "A test is already running. Try again when it ends.",
|
|
16
|
+
ntFailed: "The test could not run. Try again.",
|
|
17
|
+
ntNotRun: "Not tested (an earlier step failed).",
|
|
18
|
+
ntTimeout: "The test took too long and was stopped. Test again; if it repeats, check this machine's network.",
|
|
19
|
+
ntCancelled: "Your sign-in ended, so nothing was started. Sign in again and test again.",
|
|
20
|
+
ntCleanup: "The test's connector could not be confirmed stopped. Run agend restart before testing or opening the link again.",
|
|
21
|
+
ntStep_values: "The domain, port and token",
|
|
22
|
+
ntStep_origin: "This machine answers on port {1}",
|
|
23
|
+
ntStep_tunnel: "The tunnel connects to Cloudflare",
|
|
24
|
+
ntStep_dns: "{0} can be looked up",
|
|
25
|
+
ntStep_reach: "{0} reaches this AgEnD",
|
|
26
|
+
nt_values_ok: "Filled in correctly.",
|
|
27
|
+
"nt_values_bad-hostname": "Enter a domain such as agend.example.com (no https://, path or port).",
|
|
28
|
+
"nt_values_bad-port": "Enter a port from 1024 to 65535.",
|
|
29
|
+
"nt_values_no-token": "No connector token: paste it below (Cloudflare → your tunnel → the long string that starts with eyJ).",
|
|
30
|
+
"nt_values_bad-token": "This is not a connector token: copy only the long string that starts with eyJ.",
|
|
31
|
+
nt_origin_ok: "Answered for this test.",
|
|
32
|
+
nt_origin_live: "Your link is open on these values: it is tested as it is.",
|
|
33
|
+
"nt_origin_dashboard-port": "{1} is the dashboard's own port. Choose another (19282 by default), and make the Cloudflare route's service http://127.0.0.1:<that port>.",
|
|
34
|
+
"nt_origin_port-in-use": "Port {1} is used by another program on this machine. Choose another port here, and make the Cloudflare route's service http://127.0.0.1:<that port>.",
|
|
35
|
+
"nt_origin_origin-failed": "AgEnD could not listen on port {1}. Try another port.",
|
|
36
|
+
"nt_origin_link-open-other": "Your link is open with other settings. Close it (/web → Close public link), then test again.",
|
|
37
|
+
"nt_origin_tunnel-busy": "Another tunnel is starting or running (a public link or a sign-in). Try again in a minute.",
|
|
38
|
+
nt_tunnel_ok: "Connected to Cloudflare.",
|
|
39
|
+
nt_tunnel_live: "Your open link is connected.",
|
|
40
|
+
nt_tunnel_auth: "Cloudflare refused the token: it is wrong or was rotated. Copy the current token from the tunnel's page in Cloudflare and paste it again.",
|
|
41
|
+
nt_tunnel_network: "This machine could not reach Cloudflare within 20 seconds. Set the protocol to http2 here, or ask the network administrator to allow outbound connections to Cloudflare (port 7844).",
|
|
42
|
+
nt_tunnel_stopped: "cloudflared stopped before it connected. Test again; if it repeats, check the token and the protocol.",
|
|
43
|
+
"nt_tunnel_no-binary": "cloudflared could not be prepared on this machine (its download or install failed). Check this machine's internet access and test again.",
|
|
44
|
+
nt_dns_ok: "Found.",
|
|
45
|
+
"nt_dns_dns-missing": "{0} does not exist yet. In Cloudflare, add a Published application route for {0} on this tunnel.",
|
|
46
|
+
"nt_dns_dns-failed": "This machine could not look up {0}: this machine's network may block DNS. Ask the network administrator.",
|
|
47
|
+
nt_reach_ok: "It works: {0} reached this AgEnD.",
|
|
48
|
+
"nt_reach_wrong-service": "Cloudflare answered but could not reach the service: make the route's service http://127.0.0.1:{1} (HTTP, not HTTPS).",
|
|
49
|
+
"nt_reach_other-page": "{0} answered with another page: its route points to another service or tunnel. Point it to this tunnel, with the service http://127.0.0.1:{1}.",
|
|
50
|
+
"nt_reach_route-missing": "{0} is not routed to this tunnel. In Cloudflare, add a Published application route for {0}.",
|
|
51
|
+
"nt_reach_self-unreachable": "The tunnel is connected, but this machine's network can't open {0} (some networks filter new domains). From another network, such as a phone on mobile data, it may still work. Ask the network administrator to allow {0}.",
|
|
52
|
+
"nt_reach_reach-failed": "{0} did not answer as expected. Test again in a minute; if it repeats, check the route in Cloudflare.",
|
|
53
|
+
},
|
|
54
|
+
"zh-TW": {
|
|
55
|
+
ntButton: "測試連線",
|
|
56
|
+
ntRunning: "測試中…(最多約 1 分鐘)",
|
|
57
|
+
ntHint: "用上面填的設定測試(不論是否已儲存),token 用下方輸入的,沒有就用已儲存的。",
|
|
58
|
+
ntBusy: "已經有一個測試在進行,請等它結束再試。",
|
|
59
|
+
ntFailed: "測試沒有執行成功,請再試一次。",
|
|
60
|
+
ntNotRun: "未測試(前面的步驟失敗)。",
|
|
61
|
+
ntTimeout: "測試太久,已經停止。請再測一次;如果一直這樣,請檢查這台主機所在的網路。",
|
|
62
|
+
ntCancelled: "你的登入已結束,所以沒有啟動任何東西。請重新登入後再測。",
|
|
63
|
+
ntCleanup: "無法確認測試用的連接器已經停止。請先執行 agend restart,再測試或開啟連結。",
|
|
64
|
+
ntStep_values: "網域、port 與 token",
|
|
65
|
+
ntStep_origin: "這台主機在 port {1} 回應",
|
|
66
|
+
ntStep_tunnel: "tunnel 連上 Cloudflare",
|
|
67
|
+
ntStep_dns: "查得到 {0}",
|
|
68
|
+
ntStep_reach: "{0} 連得到這台 AgEnD",
|
|
69
|
+
nt_values_ok: "填寫正確。",
|
|
70
|
+
"nt_values_bad-hostname": "請填網域,例如 agend.example.com(不含 https://、路徑或 port)。",
|
|
71
|
+
"nt_values_bad-port": "請填 1024–65535 之間的 port。",
|
|
72
|
+
"nt_values_no-token": "沒有 connector token:請貼在下方(Cloudflare → 你的 tunnel → 以 eyJ 開頭的長字串)。",
|
|
73
|
+
"nt_values_bad-token": "這不是 connector token:只複製以 eyJ 開頭的那串長字串。",
|
|
74
|
+
nt_origin_ok: "這次測試有回應。",
|
|
75
|
+
nt_origin_live: "你的連結正以這些設定開著:直接測試它。",
|
|
76
|
+
"nt_origin_dashboard-port": "{1} 是儀表板自己的 port。請換一個(預設 19282),並把 Cloudflare 路由的 service 設成 http://127.0.0.1:<那個 port>。",
|
|
77
|
+
"nt_origin_port-in-use": "這台主機上有其他程式在用 port {1}。請在這裡換一個 port,並把 Cloudflare 路由的 service 改成 http://127.0.0.1:<那個 port>。",
|
|
78
|
+
"nt_origin_origin-failed": "AgEnD 無法在 port {1} 監聽,請換一個 port。",
|
|
79
|
+
"nt_origin_link-open-other": "你的連結正以其他設定開著。請先關閉(/web → 關閉公開連結),再測試。",
|
|
80
|
+
"nt_origin_tunnel-busy": "另一條 tunnel 正在啟動或執行(公開連結或登入用)。請過一分鐘再試。",
|
|
81
|
+
nt_tunnel_ok: "已連上 Cloudflare。",
|
|
82
|
+
nt_tunnel_live: "開著的連結已連線。",
|
|
83
|
+
nt_tunnel_auth: "Cloudflare 拒絕這個 token:它是錯的或已被更換。請到 Cloudflare 的 tunnel 頁面複製目前的 token 再貼上。",
|
|
84
|
+
nt_tunnel_network: "這台主機在 20 秒內連不上 Cloudflare。請在這裡把通訊協定改成 http2,或請網路管理員允許對 Cloudflare 的對外連線(port 7844)。",
|
|
85
|
+
nt_tunnel_stopped: "cloudflared 在連上之前就停止了。請再測一次;如果一直這樣,請檢查 token 與通訊協定。",
|
|
86
|
+
"nt_tunnel_no-binary": "這台主機無法準備 cloudflared(下載或安裝失敗)。請確認這台主機能連上網路後再測。",
|
|
87
|
+
nt_dns_ok: "查得到。",
|
|
88
|
+
"nt_dns_dns-missing": "{0} 還不存在。請在 Cloudflare 為這條 tunnel 新增 {0} 的 Published application 路由。",
|
|
89
|
+
"nt_dns_dns-failed": "這台主機查不到 {0}:這台主機所在的網路可能擋了 DNS。請洽網路管理員。",
|
|
90
|
+
nt_reach_ok: "正常:{0} 連到了這台 AgEnD。",
|
|
91
|
+
"nt_reach_wrong-service": "Cloudflare 有回應,但連不到服務:請把路由的 service 設成 http://127.0.0.1:{1}(HTTP,不是 HTTPS)。",
|
|
92
|
+
"nt_reach_other-page": "{0} 回應了別的頁面:它的路由指向別的服務或 tunnel。請把它指向這條 tunnel,service 設成 http://127.0.0.1:{1}。",
|
|
93
|
+
"nt_reach_route-missing": "{0} 沒有路由到這條 tunnel。請在 Cloudflare 新增 {0} 的 Published application 路由。",
|
|
94
|
+
"nt_reach_self-unreachable": "tunnel 已連上,但這台主機所在的網路打不開 {0}(有些網路會擋新的網域)。從其他網路(例如手機行動網路)可能可以正常使用。請網路管理員允許 {0}。",
|
|
95
|
+
"nt_reach_reach-failed": "{0} 沒有如預期回應。請過一分鐘再測;如果一直這樣,請檢查 Cloudflare 上的路由。",
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
99
|
+
const STEPS = ["values", "origin", "tunnel", "dns", "reach"];
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The connector token typed in the token card below (not yet saved): the test uses it when there is one. The card
|
|
103
|
+
* owns it: it sets it as typed and clears it when saved, cleared or unmounted (#2907 review: a hidden token from a
|
|
104
|
+
* card no longer on screen is never used).
|
|
105
|
+
*/
|
|
106
|
+
let typedToken = "";
|
|
107
|
+
export function setTypedTunnelToken(value) { typedToken = typeof value === "string" ? value : ""; }
|
|
108
|
+
|
|
109
|
+
/** The button and its result, for the fixed-address values the form holds now. */
|
|
110
|
+
export function NamedLinkTest({ hostname, port, protocol }) {
|
|
111
|
+
const lease = useLease("named-link-test");
|
|
112
|
+
const [busy, setBusy] = useState(false);
|
|
113
|
+
const [result, setResult] = useState(null);
|
|
114
|
+
const [note, setNote] = useState("");
|
|
115
|
+
const running = useRef(false);
|
|
116
|
+
const run = async () => {
|
|
117
|
+
if (running.current) return;
|
|
118
|
+
running.current = true; setBusy(true); setNote(""); setResult(null);
|
|
119
|
+
try {
|
|
120
|
+
const r = await api("/api/settings/public-link/test", { method: "POST", body: JSON.stringify({
|
|
121
|
+
hostname: String(hostname ?? "").trim(), origin_port: String(port ?? "").trim() || "19282", protocol, ...(typedToken.trim() ? { token: typedToken } : {}),
|
|
122
|
+
}) });
|
|
123
|
+
if (!lease.current()) return;
|
|
124
|
+
if (r.status === 409) setNote(tn("ntBusy"));
|
|
125
|
+
else if (!r.ok || !Array.isArray(r.body?.steps)) setNote(tn("ntFailed"));
|
|
126
|
+
else setResult(r.body);
|
|
127
|
+
} catch { if (lease.current()) setNote(tn("ntFailed")); }
|
|
128
|
+
finally { running.current = false; if (lease.current()) setBusy(false); }
|
|
129
|
+
};
|
|
130
|
+
const host = result?.hostname || String(hostname ?? "").trim() || "—", shownPort = result?.port ?? (String(port ?? "").trim() || "19282");
|
|
131
|
+
return html`<div class="named-test">
|
|
132
|
+
<div class="save-row"><button type="button" class="btn btn-sm" id="namedLinkTest" disabled=${busy} onClick=${run}>${busy ? tn("ntRunning") : tn("ntButton")}</button></div>
|
|
133
|
+
<p class="note">${tn("ntHint")}</p>
|
|
134
|
+
${note ? html`<p class="note" role="status">${note}</p>` : null}
|
|
135
|
+
${result ? html`<ol class="named-test-steps" role="status" aria-live="polite">${STEPS.map((step) => {
|
|
136
|
+
const r = result.steps.find((s) => s.step === step);
|
|
137
|
+
const mark = !r ? "–" : r.ok ? "✓" : "✗";
|
|
138
|
+
const said = !r ? tn("ntNotRun") : r.code === "timeout" ? tn("ntTimeout") : r.code === "cancelled" ? tn("ntCancelled") : tn(`nt_${step}_${r.code}`, host, shownPort);
|
|
139
|
+
return html`<li key=${step} class=${!r ? "nt-skip" : r.ok ? "nt-ok" : "nt-fail"} data-step=${step} data-code=${r ? r.code : ""}>
|
|
140
|
+
<span class="nt-mark" aria-hidden="true">${mark}</span> <strong>${tn(`ntStep_${step}`, host, shownPort)}</strong> — ${said}</li>`;
|
|
141
|
+
})}</ol>` : null}
|
|
142
|
+
${result?.cleanup ? html`<p class="feedback error" role="alert">${tn("ntCleanup")}</p>` : null}
|
|
143
|
+
</div>`;
|
|
144
|
+
}
|
|
@@ -28,7 +28,6 @@ const ROWS = [
|
|
|
28
28
|
["look.widget", "look", ".s-widget", "settings.widgetTitle", "widget.sidebar", ["sidebar widget", "widget", "json"], ["側邊欄小工具", "小工具"]],
|
|
29
29
|
["look.css", "look", ".s-owner-css", "settings.cssTitle", null, ["custom css", "stylesheet", "style", "css"], ["自訂 CSS", "樣式表", "樣式"]],
|
|
30
30
|
["look.mods", "look", ".s-mods", "settings.modsTitle", null, ["mod", "mods", "import", "add-on", "agendmod"], ["mod", "匯入", "擴充"]],
|
|
31
|
-
["look.effects", "look", ".s-effects", null, null, ["effects", "canvas", "ambient", "petals", "click stars"], ["特效", "效果", "花瓣", "氛圍"], L("Effects", "效果")],
|
|
32
31
|
["look.restore", "look", ".s-restore", "settings.restoreTitle", null, ["restore defaults", "reset", "default"], ["還原預設值", "重設", "預設"]],
|
|
33
32
|
// Fleet · Agents
|
|
34
33
|
["agents.new", "agents", "#newAgentBtn", "settings.newAgent", null, ["new agent", "create", "add agent", "instance"], ["新增 agent", "建立", "新 agent"]],
|
|
@@ -69,7 +68,6 @@ const ROWS = [
|
|
|
69
68
|
["dash.office-levels", "web-dashboard", "#webOfficeLevels", "settings.webOfficeLevels", "web.office_levels", ["office", "level", "badge", "profile card"], ["辦公室", "等級", "徽章", "名片"]],
|
|
70
69
|
["dash.app-icon", "web-dashboard", ".s-app-icon", "settings.appIconTitle", null, ["app icon", "favicon", "home screen", "icon"], ["App 圖示", "圖示", "主畫面"]],
|
|
71
70
|
["dash.card-limits", "web-dashboard", ".s-card-limits", "settings.cardLimitsTitle", "web.appearance", ["upload limits", "portrait", "banner", "card limits"], ["上傳限制", "身分卡上傳限制"]],
|
|
72
|
-
["dash.canvas-worker", "web-dashboard", ".s-experimental-effects", "experimentalEffects.title", "web.experimental.canvas_worker", ["canvas worker", "experimental", "effects"], ["Canvas Worker", "實驗功能", "特效"]],
|
|
73
71
|
// Fleet · Access & security
|
|
74
72
|
["access.session-absolute", "access", "#webSessionAbsolute", "settings.sessionAbsolute", "web.session.local.absolute_hours", ["session", "sign in", "hours", "expire", "logout"], ["session", "登入", "時數", "到期"]],
|
|
75
73
|
["access.session-idle", "access", "#webSessionIdle", "settings.sessionIdle", "web.session.local.idle_hours", ["session", "idle", "unused", "hours"], ["session", "閒置", "時數"]],
|