@songsid/agend 2.2.0-beta.2 → 2.2.0-beta.20
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/README.md +40 -24
- package/README.zh-TW.md +40 -24
- package/dist/agent-cli-instructions.md +6 -2
- package/dist/agent-cli.js +15 -7
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +3 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.js +16 -4
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +3 -1
- package/dist/auth-api.js +17 -1
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.d.ts +33 -0
- package/dist/backend/antigravity.js +126 -18
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-carry.d.ts +22 -0
- package/dist/backend/claude-carry.js +170 -0
- package/dist/backend/claude-carry.js.map +1 -0
- package/dist/backend/claude-code.d.ts +65 -2
- package/dist/backend/claude-code.js +226 -33
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +33 -0
- package/dist/backend/codex-metadata.js +122 -18
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex.d.ts +3 -0
- package/dist/backend/codex.js +17 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +40 -3
- package/dist/backend/credential-profile.js +113 -5
- package/dist/backend/credential-profile.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +37 -1
- package/dist/backend/effort-metadata.js +86 -15
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/grok.d.ts +4 -0
- package/dist/backend/grok.js +14 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +27 -1
- package/dist/backend/kiro.js +63 -10
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.d.ts +61 -0
- package/dist/backend/launch-effort.js +170 -0
- package/dist/backend/launch-effort.js.map +1 -0
- package/dist/backend/muse.d.ts +84 -0
- package/dist/backend/muse.js +449 -53
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +48 -2
- package/dist/backend/types.js +12 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-command.d.ts +91 -0
- package/dist/backend-command.js +122 -0
- package/dist/backend-command.js.map +1 -0
- package/dist/background-work-ended.d.ts +18 -0
- package/dist/background-work-ended.js +30 -0
- package/dist/background-work-ended.js.map +1 -0
- package/dist/cache-service.d.ts +2 -0
- package/dist/cache-service.js +2 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +26 -1
- package/dist/channel/adapters/discord.js +103 -3
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +35 -3
- package/dist/channel/adapters/telegram.js +122 -23
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +5 -3
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-edit-errors.d.ts +5 -0
- package/dist/channel/message-edit-errors.js +24 -0
- package/dist/channel/message-edit-errors.js.map +1 -0
- package/dist/channel/telegram-markdown.d.ts +6 -0
- package/dist/channel/telegram-markdown.js +104 -0
- package/dist/channel/telegram-markdown.js.map +1 -0
- package/dist/channel/tool-router.d.ts +2 -0
- package/dist/channel/tool-router.js +6 -5
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +46 -4
- package/dist/cli.js +184 -34
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +2 -2
- package/dist/command-table.js +5 -2
- package/dist/command-table.js.map +1 -1
- package/dist/compact-watch.d.ts +86 -0
- package/dist/compact-watch.js +288 -0
- package/dist/compact-watch.js.map +1 -0
- package/dist/config-validator.js +55 -5
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-envelope.js +2 -0
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +129 -1
- package/dist/daemon.js +754 -94
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +64 -8
- package/dist/delivery-outbox.js +315 -24
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +18 -1
- package/dist/delivery-reconciliation.js +67 -2
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/doctor.js +7 -0
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-context.d.ts +12 -1
- package/dist/fleet-manager.d.ts +392 -19
- package/dist/fleet-manager.js +2030 -275
- package/dist/fleet-manager.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +4 -3
- package/dist/forged-envelope-scan.js +4 -3
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +84 -16
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +16 -1
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +4 -0
- package/dist/host-memory.d.ts +10 -0
- package/dist/host-memory.js +33 -1
- package/dist/host-memory.js.map +1 -1
- package/dist/human-delivery-receipts.d.ts +83 -0
- package/dist/human-delivery-receipts.js +194 -0
- package/dist/human-delivery-receipts.js.map +1 -0
- package/dist/install-lock.d.ts +18 -1
- package/dist/install-lock.js +30 -3
- package/dist/install-lock.js.map +1 -1
- package/dist/instance-avatar.d.ts +5 -4
- package/dist/instance-avatar.js +12 -8
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-config-impact.js +1 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +25 -6
- package/dist/instance-lifecycle.js +76 -27
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +3 -3
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-compact-pane.d.ts +38 -0
- package/dist/kiro-compact-pane.js +57 -0
- package/dist/kiro-compact-pane.js.map +1 -0
- package/dist/kiro-log-gate.d.ts +42 -0
- package/dist/kiro-log-gate.js +39 -0
- package/dist/kiro-log-gate.js.map +1 -0
- package/dist/launchd-handoff.d.ts +43 -0
- package/dist/launchd-handoff.js +56 -0
- package/dist/launchd-handoff.js.map +1 -0
- package/dist/locale.js +268 -42
- package/dist/locale.js.map +1 -1
- package/dist/log-service.d.ts +99 -0
- package/dist/log-service.js +243 -0
- package/dist/log-service.js.map +1 -0
- package/dist/log-steps.d.ts +169 -0
- package/dist/log-steps.js +858 -0
- package/dist/log-steps.js.map +1 -0
- package/dist/login-controller.d.ts +20 -3
- package/dist/login-controller.js +71 -19
- package/dist/login-controller.js.map +1 -1
- package/dist/login-flows.d.ts +5 -2
- package/dist/login-flows.js +5 -4
- package/dist/login-flows.js.map +1 -1
- package/dist/login-shell.d.ts +21 -0
- package/dist/login-shell.js +98 -0
- package/dist/login-shell.js.map +1 -0
- package/dist/memory-pressure.d.ts +13 -1
- package/dist/memory-pressure.js +50 -10
- package/dist/memory-pressure.js.map +1 -1
- package/dist/needs-you-hub.js +2 -0
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +6 -0
- package/dist/needs-you-live.js +23 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/outbound-handlers.d.ts +100 -0
- package/dist/outbound-handlers.js +277 -93
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +30 -15
- package/dist/outbound-schemas.js +23 -5
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/provider-probe.d.ts +29 -0
- package/dist/provider-probe.js +198 -2
- package/dist/provider-probe.js.map +1 -1
- package/dist/public-link-progress.d.ts +1 -1
- package/dist/public-link-progress.js +8 -2
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-preview.d.ts +101 -0
- package/dist/public-preview.js +236 -0
- package/dist/public-preview.js.map +1 -0
- package/dist/public-web-gateway.d.ts +2 -0
- package/dist/public-web-gateway.js +20 -6
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +24 -1
- package/dist/public-web-link.js +63 -9
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +1 -1
- package/dist/quickstart-api.js +8 -6
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +12 -0
- package/dist/quickstart.js +36 -6
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +89 -5
- package/dist/reply-buttons-controller.js +273 -8
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-buttons.d.ts +48 -3
- package/dist/reply-buttons.js +105 -10
- package/dist/reply-buttons.js.map +1 -1
- package/dist/reply-polls.d.ts +85 -0
- package/dist/reply-polls.js +154 -0
- package/dist/reply-polls.js.map +1 -0
- package/dist/restart-guard.d.ts +6 -3
- package/dist/restart-guard.js +6 -3
- package/dist/restart-guard.js.map +1 -1
- package/dist/restart-progress.d.ts +33 -2
- package/dist/restart-progress.js +30 -2
- package/dist/restart-progress.js.map +1 -1
- package/dist/sd-notify.d.ts +22 -1
- package/dist/sd-notify.js +63 -10
- package/dist/sd-notify.js.map +1 -1
- package/dist/service-activation.d.ts +15 -7
- package/dist/service-activation.js +42 -26
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.d.ts +55 -0
- package/dist/service-installer.js +90 -3
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +11 -0
- package/dist/service-plan.js +23 -0
- package/dist/service-plan.js.map +1 -1
- package/dist/settings-api.js +14 -4
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +4 -2
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +2 -0
- package/dist/settings-effect.js.map +1 -1
- package/dist/spawn-gate.js +5 -1
- package/dist/spawn-gate.js.map +1 -1
- package/dist/spawn-hold-reply.d.ts +12 -0
- package/dist/spawn-hold-reply.js +39 -0
- package/dist/spawn-hold-reply.js.map +1 -0
- package/dist/systemd-peer-bus.d.ts +95 -0
- package/dist/systemd-peer-bus.js +430 -0
- package/dist/systemd-peer-bus.js.map +1 -0
- package/dist/systemd-runtime.d.ts +27 -1
- package/dist/systemd-runtime.js +69 -7
- package/dist/systemd-runtime.js.map +1 -1
- package/dist/tips.js +1 -1
- package/dist/tips.js.map +1 -1
- package/dist/tool-permissions.js +3 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tool-progress.js +3 -2
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-commands.d.ts +71 -4
- package/dist/topic-commands.js +167 -34
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-delta-reader.d.ts +3 -3
- package/dist/transcript-delta-reader.js +8 -8
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/transcript-jsonl.d.ts +85 -3
- package/dist/transcript-jsonl.js +156 -13
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-monitor.d.ts +11 -0
- package/dist/transcript-monitor.js +61 -13
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +24 -0
- package/dist/transcript-sources.js +211 -56
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +7 -15
- package/dist/tunnel/cloudflared.js +36 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/purpose-lane.d.ts +5 -1
- package/dist/tunnel/purpose-lane.js +14 -7
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/tunnel/types.d.ts +5 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/turn-resume.d.ts +2 -0
- package/dist/turn-resume.js +1 -1
- package/dist/turn-resume.js.map +1 -1
- package/dist/types.d.ts +22 -0
- package/dist/ui/chat-commands.js +1 -0
- package/dist/ui/chat-mentions.js +63 -0
- package/dist/ui/chat-render.js +80 -10
- package/dist/ui/chat-store.js +70 -6
- package/dist/ui/chat-strings.js +62 -32
- package/dist/ui/chat-thread.js +199 -24
- package/dist/ui/chat-time.js +80 -0
- package/dist/ui/chat-tour.js +17 -14
- package/dist/ui/panel-chat.js +45 -21
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +213 -58
- package/dist/ui/panel-log.js +428 -0
- package/dist/ui/panel-needs.js +14 -10
- package/dist/ui/panel-settings.js +82 -17
- package/dist/ui/preview-panel.js +55 -15
- package/dist/ui/preview-status.js +28 -0
- package/dist/ui/preview.js +44 -3
- package/dist/ui/settings-dialogs.js +11 -6
- package/dist/ui/settings-model.js +3 -1
- package/dist/ui/settings-strings.js +32 -12
- package/dist/ui/settings-token.js +8 -1
- package/dist/ui/settings-wizard.js +5 -3
- package/dist/ui/shared/app-i18n.js +41 -15
- package/dist/ui/shared/app-prefs.js +250 -0
- package/dist/ui/shared/app-route.js +6 -1
- package/dist/ui/shared/app-shell.js +165 -8
- package/dist/ui/shared/app-store.js +5 -1
- package/dist/ui/shared/app-stream.js +26 -5
- package/dist/ui/shared/app.css +170 -30
- package/dist/ui/shared/app.js +11 -1
- package/dist/ui/shared/fonts/README.md +1 -1
- package/dist/ui/shared/header-tools.js +83 -13
- package/dist/ui/shared/instance-nav.js +178 -51
- package/dist/ui/shared/instance-switch.js +8 -5
- package/dist/ui/shared/panel-view.js +21 -4
- package/dist/ui/shared/signin.js +15 -9
- package/dist/ui/shared/theme.js +39 -7
- package/dist/ui/shared/tokens.css +1 -1
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/usage-dialog.js +23 -5
- package/dist/ui/shared/view-strings.js +40 -4
- package/dist/ui/signin.html +8 -3
- package/dist/unrecognised-prompt.d.ts +23 -0
- package/dist/unrecognised-prompt.js +74 -0
- package/dist/unrecognised-prompt.js.map +1 -0
- package/dist/update-check.d.ts +31 -0
- package/dist/update-check.js +47 -0
- package/dist/update-check.js.map +1 -1
- package/dist/update-install.d.ts +13 -1
- package/dist/update-install.js +16 -4
- package/dist/update-install.js.map +1 -1
- package/dist/update-launch.d.ts +48 -1
- package/dist/update-launch.js +159 -5
- package/dist/update-launch.js.map +1 -1
- package/dist/update-marker.d.ts +51 -0
- package/dist/update-marker.js +61 -0
- package/dist/update-marker.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +1 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/providers.d.ts +62 -13
- package/dist/usage/providers.js +256 -68
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/statusline-usage.d.ts +6 -2
- package/dist/usage/statusline-usage.js +10 -4
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/view-api.js +31 -9
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +82 -4
- package/dist/web-api.js +632 -88
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +5 -2
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +26 -1
- package/dist/web-chat-history.js +47 -8
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +41 -5
- package/dist/web-commands.js +25 -7
- package/dist/web-commands.js.map +1 -1
- package/dist/web-login.d.ts +14 -0
- package/dist/web-login.js +4 -2
- package/dist/web-login.js.map +1 -1
- package/dist/web-mentions.d.ts +44 -0
- package/dist/web-mentions.js +75 -0
- package/dist/web-mentions.js.map +1 -0
- package/dist/web-prefs.d.ts +81 -0
- package/dist/web-prefs.js +297 -0
- package/dist/web-prefs.js.map +1 -0
- package/dist/web-preview.d.ts +14 -1
- package/dist/web-preview.js +35 -13
- package/dist/web-preview.js.map +1 -1
- package/dist/web-profile.d.ts +32 -0
- package/dist/web-profile.js +132 -0
- package/dist/web-profile.js.map +1 -0
- package/dist/web-reply-files.d.ts +45 -0
- package/dist/web-reply-files.js +223 -0
- package/dist/web-reply-files.js.map +1 -0
- package/dist/web-schedules.d.ts +12 -0
- package/dist/web-schedules.js +34 -0
- package/dist/web-schedules.js.map +1 -0
- package/dist/web-server-drain.d.ts +10 -0
- package/dist/web-server-drain.js +50 -0
- package/dist/web-server-drain.js.map +1 -0
- package/dist/web-session.d.ts +14 -0
- package/dist/web-session.js +11 -1
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +3 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-shell-routes.d.ts +4 -0
- package/dist/web-shell-routes.js +9 -0
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-upload.d.ts +135 -7
- package/dist/web-upload.js +315 -51
- package/dist/web-upload.js.map +1 -1
- package/launcher/install-admission.cjs +61 -6
- package/package.json +3 -3
- package/templates/launchd.plist.ejs +3 -1
- package/templates/systemd.service.ejs +2 -1
package/dist/ui/panel-fleet.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// here re-reads anything). Every read is under the navigation's lease: a list that arrives after the person moved on
|
|
6
6
|
// is dropped (#1408 §4).
|
|
7
7
|
import { html, useEffect, useRef, useState } from "/assets/app-html.js";
|
|
8
|
-
import { t, register } from "/assets/app-i18n.js";
|
|
8
|
+
import { t, register, lang } from "/assets/app-i18n.js";
|
|
9
9
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
10
10
|
import { useLease } from "/assets/app-ctx.js";
|
|
11
11
|
import { PanelHeader, setTitle } from "/assets/app-shell.js";
|
|
@@ -19,11 +19,15 @@ import { toast } from "/assets/ui-toast.js";
|
|
|
19
19
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
20
20
|
import { confirmedWrite } from "./settings-confirm.js";
|
|
21
21
|
import { OrgChart } from "./fleet-org.js";
|
|
22
|
+
import { labelOf } from "/assets/instance-nav.js";
|
|
22
23
|
import { CacheReport } from "./fleet-cache.js";
|
|
23
24
|
|
|
24
25
|
register("fleet", {
|
|
25
26
|
en: {
|
|
27
|
+
hostMemory_elevated: "Host memory pressure: elevated — new agents start one at a time, at least 5 s apart",
|
|
28
|
+
hostMemory_critical: "Host memory pressure: critical — new agents wait for memory to recover",
|
|
26
29
|
title: "Fleet", tasks: "Tasks", schedules: "Schedules", teams: "Teams", org: "Org chart", cache: "Cache", config: "Config",
|
|
30
|
+
docs: "Docs", docsNewTab: "Docs (opens in a new tab)",
|
|
27
31
|
newTask: "New task", newSchedule: "New schedule", newTeam: "New team",
|
|
28
32
|
noTasks: "No tasks yet", noTasksHint: "Create one to coordinate fleet work.", noSchedules: "No schedules yet", noSchedulesHint: "Automate recurring work with cron.",
|
|
29
33
|
noTeams: "No teams yet", noTeamsHint: "Group instances for coordinated work.", countTasks: "{0} tasks", countSchedules: "{0} schedules", countTeams: "{0} teams", countTask: "1 task", countSchedule: "1 schedule", countTeam: "1 team",
|
|
@@ -31,9 +35,17 @@ register("fleet", {
|
|
|
31
35
|
taskTitle: "Title", taskDesc: "Description", priority: "Priority", assignee: "Assignee", unassigned: "Unassigned",
|
|
32
36
|
pNormal: "Normal", pLow: "Low", pHigh: "High", pUrgent: "Urgent",
|
|
33
37
|
cron: "Cron expression", message: "Message", target: "Target instance", label: "Label", teamName: "Team name", members: "Members",
|
|
38
|
+
// #1724: schedules in words; Edit; once or repeating
|
|
39
|
+
schedOnce: "Once · {0}", schedNext: "next {0}", schedRetrying: "retrying", schedDisabled: "disabled", schedSilent: "silent", schedTz: "Timezone: {0}",
|
|
40
|
+
editSchedule: "Edit schedule", edit: "Edit", scheduleSaved: "Schedule saved", schedRepeat: "Repeat", schedOnceMode: "Once",
|
|
41
|
+
schedWhen: "Date and time", schedTimezone: "Timezone", schedEnabled: "Enabled", schedWhenIn: "In {0}", schedWhenRequired: "Pick a date and time",
|
|
42
|
+
schedTzBad: "Unknown timezone: {0}",
|
|
43
|
+
cronEveryMinute: "Every minute", cronEveryMinutes: "Every {0} minutes", cronEveryHours: "Every {0} hours", cronDaily: "Daily {0}",
|
|
44
|
+
cronWeekdays: "Weekdays {0}", cronDays: "{0} {1}", cronMonthly: "Day {0} of each month {1}", dayNames: "Sun,Mon,Tue,Wed,Thu,Fri,Sat", dayJoin: ", ",
|
|
34
45
|
titleRequired: "Title required", scheduleFieldsRequired: "Cron, message, and target required", teamFieldsRequired: "Name and at least one member required",
|
|
35
46
|
deleteSchedule: "Delete this schedule?", deleteTeam: "Delete team “{0}”?", taskCreated: "Task created", scheduleCreated: "Schedule created", scheduleDeleted: "Schedule deleted",
|
|
36
|
-
teamCreated: "Team created", teamDeleted: "Team deleted",
|
|
47
|
+
teamCreated: "Team created", teamDeleted: "Team deleted", teamUpdated: "Team updated", edit: "Edit", editTeam: "Edit team",
|
|
48
|
+
teamNoRename: "Renaming isn't supported — create a new team and delete this one.", taskDone: "Task updated", configSaved: "Config saved", configRestart: " (restart the fleet to apply channel changes)",
|
|
37
49
|
channel: "Channel", type: "Type", tokenEnv: "Bot token env", groupId: "Group ID", show: "Show", hide: "Hide", access: "Access control", mode: "Mode", locked: "Locked", open: "Open",
|
|
38
50
|
allowedUsers: "Allowed users", allowedUsersNote: "Comma-separated user IDs", defaults: "Defaults", backend: "Backend", roots: "Project roots", addRoot: "Add root", removeRoot: "Remove",
|
|
39
51
|
saveNote: "Saving rewrites fleet.yaml (its comments are lost).",
|
|
@@ -43,7 +55,10 @@ register("fleet", {
|
|
|
43
55
|
fleetDefault: "Use the fleet default", notInstalled: "(not installed)", deprecated: "(deprecated)", instanceCreated: "Instance created",
|
|
44
56
|
},
|
|
45
57
|
"zh-TW": {
|
|
58
|
+
hostMemory_elevated: "主機記憶體壓力偏高:新的 agent 一次啟動一個,至少間隔 5 秒",
|
|
59
|
+
hostMemory_critical: "主機記憶體壓力嚴重:新的 agent 會等記憶體恢復再啟動",
|
|
46
60
|
title: "Fleet", tasks: "Tasks", schedules: "排程", teams: "Teams", org: "組織圖", cache: "快取", config: "設定",
|
|
61
|
+
docs: "文件", docsNewTab: "文件(在新分頁開啟)",
|
|
47
62
|
newTask: "新增 task", newSchedule: "新增排程", newTeam: "新增 team",
|
|
48
63
|
noTasks: "還沒有 task", noTasksHint: "建立一個來協調 fleet 的工作。", noSchedules: "還沒有排程", noSchedulesHint: "用 cron 自動執行例行工作。",
|
|
49
64
|
noTeams: "還沒有 team", noTeamsHint: "把 instance 分組一起工作。", countTasks: "{0} 個 task", countSchedules: "{0} 個排程", countTeams: "{0} 個 team", countTask: "1 個 task", countSchedule: "1 個排程", countTeam: "1 個 team",
|
|
@@ -51,9 +66,16 @@ register("fleet", {
|
|
|
51
66
|
taskTitle: "標題", taskDesc: "說明", priority: "優先順序", assignee: "負責人", unassigned: "未指派",
|
|
52
67
|
pNormal: "一般", pLow: "低", pHigh: "高", pUrgent: "緊急",
|
|
53
68
|
cron: "Cron 表示式", message: "訊息", target: "目標 instance", label: "標籤", teamName: "Team 名稱", members: "成員",
|
|
69
|
+
schedOnce: "一次 · {0}", schedNext: "下次 {0}", schedRetrying: "重試中", schedDisabled: "已停用", schedSilent: "靜默", schedTz: "時區:{0}",
|
|
70
|
+
editSchedule: "編輯排程", edit: "編輯", scheduleSaved: "已儲存排程", schedRepeat: "重複", schedOnceMode: "一次",
|
|
71
|
+
schedWhen: "日期與時間", schedTimezone: "時區", schedEnabled: "啟用", schedWhenIn: "以 {0} 時間", schedWhenRequired: "請選擇日期與時間",
|
|
72
|
+
schedTzBad: "不認得的時區:{0}",
|
|
73
|
+
cronEveryMinute: "每分鐘", cronEveryMinutes: "每 {0} 分鐘", cronEveryHours: "每 {0} 小時", cronDaily: "每天 {0}",
|
|
74
|
+
cronWeekdays: "每週一到五 {0}", cronDays: "每週{0} {1}", cronMonthly: "每月 {0} 日 {1}", dayNames: "日,一,二,三,四,五,六", dayJoin: "、",
|
|
54
75
|
titleRequired: "必須填寫標題", scheduleFieldsRequired: "必須填寫 Cron、訊息與目標 instance", teamFieldsRequired: "必須填寫名稱並選擇至少一名成員",
|
|
55
76
|
deleteSchedule: "確定刪除此排程嗎?", deleteTeam: "確定刪除 team「{0}」嗎?", taskCreated: "Task 已建立", scheduleCreated: "排程已建立", scheduleDeleted: "排程已刪除",
|
|
56
|
-
teamCreated: "Team 已建立", teamDeleted: "Team 已刪除",
|
|
77
|
+
teamCreated: "Team 已建立", teamDeleted: "Team 已刪除", teamUpdated: "Team 已更新", edit: "編輯", editTeam: "編輯 team",
|
|
78
|
+
teamNoRename: "不支援重新命名:請建立新的 team,再刪除這個。", taskDone: "Task 已更新", configSaved: "設定已儲存", configRestart: "(請重新啟動 fleet 以套用頻道變更)",
|
|
57
79
|
channel: "頻道", type: "類型", tokenEnv: "Bot token 環境變數", groupId: "Group ID", show: "顯示", hide: "隱藏", access: "存取控制", mode: "模式", locked: "鎖定", open: "開放",
|
|
58
80
|
allowedUsers: "允許的使用者", allowedUsersNote: "以逗號分隔的使用者 ID", defaults: "預設值", backend: "Backend", roots: "專案根目錄", addRoot: "新增根目錄", removeRoot: "移除",
|
|
59
81
|
saveNote: "儲存會改寫 fleet.yaml(其中的註解會遺失)。",
|
|
@@ -64,6 +86,11 @@ register("fleet", {
|
|
|
64
86
|
},
|
|
65
87
|
});
|
|
66
88
|
|
|
89
|
+
// The project's docs site (website/, GitHub Pages), in the dashboard's language — its own pages, not the app's.
|
|
90
|
+
const DOCS_SITE = "https://songsid.github.io/AgEnD";
|
|
91
|
+
export function docsUrl(language = lang()) {
|
|
92
|
+
return `${DOCS_SITE}${language === "zh-TW" ? "/zh-tw" : ""}/docs/web-guide/`;
|
|
93
|
+
}
|
|
67
94
|
const ICONS = { tasks: "tasks", schedules: "clock", teams: "team", org: "org", cache: "chart", config: "sliders" };
|
|
68
95
|
const BACKENDS = ["claude-code", "codex", "opencode", "kiro-cli", "antigravity", "grok", "muse"];
|
|
69
96
|
|
|
@@ -104,14 +131,23 @@ export function FleetPanel({ route, navKey }) {
|
|
|
104
131
|
const tab = route.tab;
|
|
105
132
|
useEffect(() => { setTitle(`${t("fleet.title")} · ${t(`fleet.${tab}`)}`); }, [tab, navKey]);
|
|
106
133
|
const Body = { tasks: Tasks, schedules: Schedules, teams: Teams, org: Org, cache: Cache, config: Config }[tab] || Tasks;
|
|
134
|
+
// #1685: host memory pressure slowing or holding new agents — shown here, where macOS's routine "elevated" is not a chat notice.
|
|
135
|
+
const { hostMemory } = useStore(appStore);
|
|
136
|
+
const sub = hostMemory ? html`<span class=${`status host-mem ${hostMemory}`} role="status"><span class=${`dot ${hostMemory === "critical" ? "bad" : "warn"}`} aria-hidden="true"></span>${t(`fleet.hostMemory_${hostMemory}`)}</span>` : null;
|
|
107
137
|
return html`<div class="panel p-fleet">
|
|
108
|
-
<${PanelHeader} title=${t("fleet.title")}><${HeaderTools} navKey=${navKey} /></${PanelHeader}>
|
|
138
|
+
<${PanelHeader} title=${t("fleet.title")} sub=${sub}><${DocsLink} /><${HeaderTools} navKey=${navKey} /></${PanelHeader}>
|
|
109
139
|
<nav class="seg" aria-label=${t("fleet.title")}>${FLEET_TABS.map(k => html`<a key=${k} href=${fleetPath(k)} class=${`seg-item${k === tab ? " active" : ""}`}
|
|
110
140
|
aria-current=${k === tab ? "page" : undefined}><${Icon} name=${ICONS[k]} size=${16} /><span>${t(`fleet.${k}`)}</span></a>`)}</nav>
|
|
111
141
|
<div class="panel-body"><div class=${`col${tab === "org" || tab === "cache" ? " col-wide" : ""}`}><${Body} lease=${lease} /></div></div>
|
|
112
142
|
</div>`;
|
|
113
143
|
}
|
|
114
144
|
|
|
145
|
+
/** #1844: the project's docs, from the overview — a link out (a new tab, no opener), not a Fleet tab; in front of the shared controls. */
|
|
146
|
+
function DocsLink() {
|
|
147
|
+
return html`<a class="btn btn-ghost btn-sm hd-docs" href=${docsUrl()} target="_blank" rel="noopener noreferrer" aria-label=${t("fleet.docsNewTab")} title=${t("fleet.docsNewTab")}>
|
|
148
|
+
<${Icon} name="book" size=${16} /><span class="hide-narrow" aria-hidden="true">${t("fleet.docs")}</span><${Icon} name="external" size=${12} cls="hd-ext" /></a>`;
|
|
149
|
+
}
|
|
150
|
+
|
|
115
151
|
function ListHead({ count, label, action }) {
|
|
116
152
|
return html`<div class="list-head"><span>${count}</span>${action ? html`<button type="button" class="btn btn-primary" onClick=${action.onClick}><${Icon} name="plus" size=${16} />${label}</button>` : null}</div>`;
|
|
117
153
|
}
|
|
@@ -143,10 +179,27 @@ function Tasks({ lease }) {
|
|
|
143
179
|
${creating ? html`<${CreateTask} onClose=${() => setCreating(false)} onDone=${() => setVersion(v => v + 1)} />` : null}`;
|
|
144
180
|
}
|
|
145
181
|
|
|
182
|
+
/** #1724: what a row says about when it runs — words for a cron, "Once · <time>" for a one-shot; never empty or "null". */
|
|
183
|
+
export function scheduleWhen(x) {
|
|
184
|
+
if (x && x.cron) return cronDesc(x.cron);
|
|
185
|
+
if (x && x.at) return t("fleet.schedOnce", x.at_text || x.at);
|
|
186
|
+
return "—";
|
|
187
|
+
}
|
|
188
|
+
/** #1724: its next run as the server worked it out (fleet zone), or "retrying"; nothing when there is none (disabled). */
|
|
189
|
+
export function scheduleNext(x) {
|
|
190
|
+
if (!x || !x.enabled) return "";
|
|
191
|
+
if (x.retrying) return t("fleet.schedRetrying");
|
|
192
|
+
return x.next_run_text ? t("fleet.schedNext", x.next_run_text) : "";
|
|
193
|
+
}
|
|
194
|
+
const clip = (s, n = 60) => { const a = Array.from(String(s || "").replace(/\s+/g, " ").trim()); return a.length > n ? `${a.slice(0, n).join("")}…` : a.join(""); };
|
|
195
|
+
|
|
146
196
|
function Schedules({ lease }) {
|
|
147
197
|
const [version, setVersion] = useState(0);
|
|
148
198
|
const [creating, setCreating] = useState(false);
|
|
199
|
+
const [editing, setEditing] = useState(null); // #1724: the schedule being edited
|
|
200
|
+
const { instances } = useStore(appStore);
|
|
149
201
|
const state = useLoad(lease, "/ui/schedules", version);
|
|
202
|
+
const targetName = (name) => { const it = (instances || []).find((i) => i && i.name === name); return it ? labelOf(it) : name; };
|
|
150
203
|
async function del(id) {
|
|
151
204
|
if (!(await confirmDialog({ message: t("fleet.deleteSchedule"), confirmLabel: t("fleet.delete"), danger: true }))) return;
|
|
152
205
|
if (!lease.current()) return; // answered after this tab went: the question no longer stands
|
|
@@ -155,21 +208,31 @@ function Schedules({ lease }) {
|
|
|
155
208
|
}
|
|
156
209
|
return html`<${Loaded} state=${state}>${(d) => {
|
|
157
210
|
const list = d.schedules || [];
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
211
|
+
// #1724: the public link lists schedules and changes none (the server refuses it too).
|
|
212
|
+
const writable = !publicLink();
|
|
213
|
+
return html`<${ListHead} count=${list.length === 1 ? t("fleet.countSchedule") : t("fleet.countSchedules", list.length)} label=${t("fleet.newSchedule")} action=${writable ? { onClick: () => setCreating(true) } : null} />
|
|
214
|
+
${list.length ? html`<ul class="rows">${list.map(x => {
|
|
215
|
+
const next = scheduleNext(x);
|
|
216
|
+
const tip = [x.cron || x.at, t("fleet.schedTz", x.timezone), x.silent ? t("fleet.schedSilent") : ""].filter(Boolean).join(" · ");
|
|
217
|
+
return html`<li key=${x.id} class=${`row-item sched${x.enabled ? "" : " is-off"}`} title=${tip}>
|
|
218
|
+
<span class=${`pill ${x.enabled ? "open" : "off"}`}>${x.enabled ? t("fleet.on") : t("fleet.schedDisabled")}</span>
|
|
219
|
+
<span class="grow sched-text"><span class="sched-title">${x.label || clip(x.message)}</span>
|
|
220
|
+
<span class="sched-sub muted"><span class="sched-when">${scheduleWhen(x)}</span>${next ? html`${" · "}<span class="sched-next">${next}</span>` : null}${" · "}<a class="link" href=${chatPath(x.target)} title=${x.target}>${targetName(x.target)}</a></span></span>
|
|
221
|
+
${writable ? html`<span class="sched-actions"><button type="button" class="btn btn-ghost btn-sm sched-edit" onClick=${() => setEditing({ ...x, default_timezone: d.default_timezone })}>${t("fleet.edit")}</button><button type="button" class="btn btn-ghost btn-sm danger" onClick=${() => del(x.id)}>${t("fleet.delete")}</button></span>` : null}</li>`;
|
|
222
|
+
})}</ul>`
|
|
164
223
|
: html`<${Empty} icon="clock" title=${t("fleet.noSchedules")} hint=${t("fleet.noSchedulesHint")} />`}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
224
|
+
${creating ? html`<${ScheduleForm} defaultTz=${d.default_timezone} onClose=${() => setCreating(false)} onDone=${() => setVersion(v => v + 1)} />` : null}
|
|
225
|
+
${editing ? html`<${ScheduleForm} schedule=${editing} defaultTz=${d.default_timezone} onClose=${() => setEditing(null)} onDone=${() => setVersion(v => v + 1)} />` : null}`;
|
|
226
|
+
}}</${Loaded}>`;
|
|
168
227
|
}
|
|
169
228
|
|
|
229
|
+
/** #1689: the public link changes no team (the server refuses it): it lists them only. */
|
|
230
|
+
const publicLink = () => typeof document !== "undefined" && !!document.body && document.body.dataset.publicLink === "1";
|
|
231
|
+
|
|
170
232
|
function Teams({ lease }) {
|
|
171
233
|
const [version, setVersion] = useState(0);
|
|
172
234
|
const [creating, setCreating] = useState(false);
|
|
235
|
+
const [editing, setEditing] = useState(null); // #1689: { name, members, description } of the team being edited
|
|
173
236
|
const state = useLoad(lease, "/ui/teams", version);
|
|
174
237
|
async function del(name) {
|
|
175
238
|
if (!(await confirmDialog({ message: t("fleet.deleteTeam", name), confirmLabel: t("fleet.delete"), danger: true }))) return;
|
|
@@ -179,14 +242,17 @@ function Teams({ lease }) {
|
|
|
179
242
|
}
|
|
180
243
|
return html`<${Loaded} state=${state}>${(d) => {
|
|
181
244
|
const entries = Object.entries(d.teams || {});
|
|
182
|
-
|
|
245
|
+
const writable = !publicLink();
|
|
246
|
+
return html`<${ListHead} count=${entries.length === 1 ? t("fleet.countTeam") : t("fleet.countTeams", entries.length)} label=${t("fleet.newTeam")} action=${writable ? { onClick: () => setCreating(true) } : null} />
|
|
183
247
|
${entries.length ? html`<ul class="rows">${entries.map(([name, team]) => html`<li key=${name} class="row-item">
|
|
184
248
|
<span class="grow strong">${name}</span><span class="muted">${(team.members || []).join(", ")}</span>
|
|
185
|
-
|
|
249
|
+
${writable ? html`<button type="button" class="btn btn-ghost btn-sm team-edit" onClick=${() => setEditing({ name, members: [...(team.members || [])], description: team.description || "" })}>${t("fleet.edit")}</button>
|
|
250
|
+
<button type="button" class="btn btn-ghost btn-sm danger" onClick=${() => del(name)}>${t("fleet.delete")}</button>` : null}</li>`)}</ul>`
|
|
186
251
|
: html`<${Empty} icon="team" title=${t("fleet.noTeams")} hint=${t("fleet.noTeamsHint")} />`}
|
|
187
252
|
`;
|
|
188
253
|
}}</${Loaded}>
|
|
189
|
-
${creating ? html`<${CreateTeam} onClose=${() => setCreating(false)} onDone=${() => setVersion(v => v + 1)} />` : null}
|
|
254
|
+
${creating ? html`<${CreateTeam} onClose=${() => setCreating(false)} onDone=${() => setVersion(v => v + 1)} />` : null}
|
|
255
|
+
${editing ? html`<${CreateTeam} team=${editing} onClose=${() => setEditing(null)} onDone=${() => setVersion(v => v + 1)} />` : null}`;
|
|
190
256
|
}
|
|
191
257
|
|
|
192
258
|
/** #1389: the org chart — its structure read once per opening, its live state from the app store (fleet-org.js). */
|
|
@@ -286,7 +352,7 @@ export function FormDialog({ title, onClose, submit, children, ready = true }) {
|
|
|
286
352
|
// the dialog goes at once and the shell follows the request. From then on the dialog is done: the decision is
|
|
287
353
|
// said in a toast, and nothing here closes, refreshes or acts again.
|
|
288
354
|
try {
|
|
289
|
-
const res = await confirmedWrite(submit.path, { method: "POST", body, label: title,
|
|
355
|
+
const res = await confirmedWrite(submit.path, { method: submit.method || "POST", body, label: title,
|
|
290
356
|
onPending: () => { handedOver = true; toast(t("app.pendingSent")); if (lease.current()) { setBusy(false); onClose(); } } });
|
|
291
357
|
r = res.ok ? res.body || {} : { error: (res.body && res.body.error) || `HTTP ${res.status}` };
|
|
292
358
|
} catch (err) { r = { error: err.message }; }
|
|
@@ -299,7 +365,7 @@ export function FormDialog({ title, onClose, submit, children, ready = true }) {
|
|
|
299
365
|
}
|
|
300
366
|
return html`<${Dialog} title=${title} onClose=${onClose} busy=${busy}
|
|
301
367
|
actions=${html`<button type="button" class="btn" onClick=${onClose} disabled=${busy}>${t("fleet.cancel")}</button>
|
|
302
|
-
<button type="button" class="btn btn-primary" onClick=${go} disabled=${busy || !ready}>${busy ? t("fleet.creating") : t("fleet.create")}</button>`}>
|
|
368
|
+
<button type="button" class="btn btn-primary" onClick=${go} disabled=${busy || !ready}>${busy ? t("fleet.creating") : submit.label || t("fleet.create")}</button>`}>
|
|
303
369
|
<form class="form" onSubmit=${(e) => { e.preventDefault(); go(); }}><fieldset class="form" disabled=${busy}>${children}</fieldset></form></${Dialog}>`;
|
|
304
370
|
}
|
|
305
371
|
const field = (label, input) => html`<label class="field"><span>${label}</span>${input}</label>`;
|
|
@@ -323,40 +389,120 @@ function CreateTask({ onClose, onDone }) {
|
|
|
323
389
|
</${FormDialog}>`;
|
|
324
390
|
}
|
|
325
391
|
|
|
326
|
-
|
|
392
|
+
/**
|
|
393
|
+
* #1724: a wall-clock date and time ("YYYY-MM-DDTHH:MM", a datetime-local value) read in `tz`, as the ISO time with
|
|
394
|
+
* offset the scheduler takes (`at`); null when either cannot be read. DST-safe: the offset is sampled at that instant.
|
|
395
|
+
*/
|
|
396
|
+
export function wallToIso(wall, tz) {
|
|
397
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/.exec(String(wall || ""));
|
|
398
|
+
if (!m) return null;
|
|
399
|
+
const asUtc = Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5]);
|
|
400
|
+
let offsetAt;
|
|
401
|
+
try {
|
|
402
|
+
const fmt = new Intl.DateTimeFormat("sv-SE", { timeZone: tz, hour12: false, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
403
|
+
offsetAt = (ms) => Date.parse(fmt.format(new Date(ms)).replace(" ", "T") + "Z") - ms;
|
|
404
|
+
} catch { return null; }
|
|
405
|
+
let off = offsetAt(asUtc);
|
|
406
|
+
off = offsetAt(asUtc - off);
|
|
407
|
+
if (!Number.isFinite(off)) return null;
|
|
408
|
+
const mins = Math.round(off / 60000), a = Math.abs(mins);
|
|
409
|
+
return `${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:00${mins < 0 ? "-" : "+"}${String(Math.floor(a / 60)).padStart(2, "0")}:${String(a % 60).padStart(2, "0")}`;
|
|
410
|
+
}
|
|
411
|
+
/** #1724: an ISO time as the wall clock in `tz` ("YYYY-MM-DDTHH:MM"), to fill the form; "" when it cannot be read. */
|
|
412
|
+
export function isoToWall(iso, tz) {
|
|
413
|
+
const ms = Date.parse(String(iso || ""));
|
|
414
|
+
if (!Number.isFinite(ms)) return "";
|
|
415
|
+
try {
|
|
416
|
+
return new Intl.DateTimeFormat("sv-SE", { timeZone: tz, hour12: false, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" }).format(new Date(ms)).replace(" ", "T");
|
|
417
|
+
} catch { return ""; }
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* New schedule, or (#1724) `schedule` given: edit it — the same form, filled in from it: Repeat (cron) or Once (a date
|
|
422
|
+
* and time in its timezone), message, target, label, timezone, enabled. An edit is PATCH /ui/schedules/<id>, which the
|
|
423
|
+
* host validates and applies as the update_schedule tool does (cron and at clear each other there).
|
|
424
|
+
*/
|
|
425
|
+
function ScheduleForm({ schedule = null, defaultTz, onClose, onDone }) {
|
|
327
426
|
const { instances } = useStore(appStore);
|
|
328
|
-
const
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
427
|
+
const tz0 = (schedule && schedule.timezone) || defaultTz || "";
|
|
428
|
+
const [f, setF] = useState(() => ({
|
|
429
|
+
mode: schedule && schedule.at ? "once" : "repeat",
|
|
430
|
+
cron: (schedule && schedule.cron) || "",
|
|
431
|
+
at: schedule && schedule.at ? isoToWall(schedule.at, tz0) : "",
|
|
432
|
+
msg: (schedule && schedule.message) || "",
|
|
433
|
+
target: schedule ? schedule.target : instances[0] ? instances[0].name : "",
|
|
434
|
+
label: (schedule && schedule.label) || "",
|
|
435
|
+
tz: tz0,
|
|
436
|
+
enabled: schedule ? !!schedule.enabled : true,
|
|
437
|
+
}));
|
|
438
|
+
const set = (k) => (e) => { const v = e.target.type === "checkbox" ? e.target.checked : e.target.value; setF((cur) => ({ ...cur, [k]: v })); };
|
|
439
|
+
const submit = {
|
|
440
|
+
path: schedule ? `/ui/schedules/${encodeURIComponent(schedule.id)}` : "/ui/schedules",
|
|
441
|
+
method: schedule ? "PATCH" : "POST",
|
|
442
|
+
label: schedule ? t("fleet.save") : undefined,
|
|
443
|
+
done: schedule ? t("fleet.scheduleSaved") : t("fleet.scheduleCreated"),
|
|
444
|
+
after: onDone,
|
|
445
|
+
collect: () => {
|
|
446
|
+
if (!f.msg.trim() || !f.target || (f.mode === "repeat" && !f.cron.trim())) { toast(t("fleet.scheduleFieldsRequired"), false); return null; }
|
|
447
|
+
const tz = f.tz.trim();
|
|
448
|
+
const body = { message: f.msg.trim(), target: f.target };
|
|
449
|
+
if (f.mode === "repeat") body.cron = f.cron.trim();
|
|
450
|
+
else {
|
|
451
|
+
if (!f.at) { toast(t("fleet.schedWhenRequired"), false); return null; }
|
|
452
|
+
const at = wallToIso(f.at, tz || defaultTz);
|
|
453
|
+
if (!at) { toast(t("fleet.schedTzBad", tz || defaultTz || ""), false); return null; }
|
|
454
|
+
body.at = at;
|
|
455
|
+
}
|
|
456
|
+
if (schedule) { body.label = f.label.trim(); body.enabled = !!f.enabled; if (tz) body.timezone = tz; }
|
|
457
|
+
else { if (f.label.trim()) body.label = f.label.trim(); if (tz) body.timezone = tz; }
|
|
458
|
+
return body;
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
const modeBtn = (mode, label) => html`<button type="button" class=${`btn btn-sm${f.mode === mode ? " on" : ""}`} aria-pressed=${f.mode === mode ? "true" : "false"}
|
|
462
|
+
onClick=${() => setF((cur) => ({ ...cur, mode }))}>${label}</button>`;
|
|
463
|
+
return html`<${FormDialog} title=${schedule ? t("fleet.editSchedule") : t("fleet.newSchedule")} onClose=${onClose} submit=${submit}>
|
|
464
|
+
<div class="seg-inline sched-mode" role="group" aria-label=${t("fleet.schedWhen")}>${modeBtn("repeat", t("fleet.schedRepeat"))}${modeBtn("once", t("fleet.schedOnceMode"))}</div>
|
|
465
|
+
${f.mode === "repeat"
|
|
466
|
+
? html`${field(`${t("fleet.cron")} *`, html`<input value=${f.cron} onInput=${set("cron")} placeholder="0 9 * * 1-5" class="mono" />`)}
|
|
467
|
+
${f.cron.trim() ? html`<p class="note sched-preview">${cronDesc(f.cron)}</p>` : null}`
|
|
468
|
+
: html`${field(`${t("fleet.schedWhen")} *`, html`<input type="datetime-local" value=${f.at} onInput=${set("at")} />`)}
|
|
469
|
+
<p class="note">${t("fleet.schedWhenIn", f.tz.trim() || defaultTz || "")}</p>`}
|
|
339
470
|
${field(`${t("fleet.message")} *`, html`<input value=${f.msg} onInput=${set("msg")} />`)}
|
|
340
|
-
${field(`${t("fleet.target")} *`, html`<select value=${f.target} onChange=${set("target")}>${instances.map(i => html`<option key=${i.name} value=${i.name}>${i
|
|
471
|
+
${field(`${t("fleet.target")} *`, html`<select value=${f.target} onChange=${set("target")}>${instances.map(i => html`<option key=${i.name} value=${i.name}>${labelOf(i)}</option>`)}</select>`)}
|
|
341
472
|
${field(t("fleet.label"), html`<input value=${f.label} onInput=${set("label")} />`)}
|
|
473
|
+
${field(t("fleet.schedTimezone"), html`<input value=${f.tz} onInput=${set("tz")} placeholder=${defaultTz || "Asia/Taipei"} />`)}
|
|
474
|
+
${schedule ? html`<label class="check"><input type="checkbox" checked=${f.enabled} onChange=${set("enabled")} /><span>${t("fleet.schedEnabled")}</span></label>` : null}
|
|
342
475
|
</${FormDialog}>`;
|
|
343
476
|
}
|
|
344
477
|
|
|
345
|
-
|
|
478
|
+
/**
|
|
479
|
+
* New team, or (#1689) `team` given: edit that one — the same form, filled in from it: its members ticked (one no longer
|
|
480
|
+
* in the fleet listed too, so it can be taken out), its description; the name is not editable (a team is not renamed).
|
|
481
|
+
* Saved with PATCH /ui/teams/<name> (the update_team path on the host).
|
|
482
|
+
*/
|
|
483
|
+
function CreateTeam({ onClose, onDone, team = null }) {
|
|
346
484
|
const { instances } = useStore(appStore);
|
|
347
|
-
const [f, setF] = useState({ name: "", desc: "", members: [] });
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
485
|
+
const [f, setF] = useState(team ? { name: team.name, desc: team.description || "", members: [...team.members] } : { name: "", desc: "", members: [] });
|
|
486
|
+
// From the state as it is, not this render's copy: two ticks before the next render must both count.
|
|
487
|
+
const toggle = (n) => setF((cur) => ({ ...cur, members: cur.members.includes(n) ? cur.members.filter(x => x !== n) : [...cur.members, n] }));
|
|
488
|
+
const names = [...new Set([...instances.map(i => i.name), ...(team ? team.members : [])])];
|
|
489
|
+
const submit = team
|
|
490
|
+
? { path: `/ui/teams/${encodeURIComponent(team.name)}`, method: "PATCH", label: t("fleet.save"), done: t("fleet.teamUpdated"), after: onDone, collect: () => {
|
|
491
|
+
if (!f.members.length) { toast(t("fleet.teamFieldsRequired"), false); return null; }
|
|
492
|
+
return { members: f.members, description: f.desc.trim() };
|
|
493
|
+
} }
|
|
494
|
+
: { path: "/ui/teams", done: t("fleet.teamCreated"), after: onDone, collect: () => {
|
|
495
|
+
if (!f.name.trim() || !f.members.length) { toast(t("fleet.teamFieldsRequired"), false); return null; }
|
|
496
|
+
const body = { name: f.name.trim(), members: f.members };
|
|
497
|
+
if (f.desc.trim()) body.description = f.desc.trim();
|
|
498
|
+
return body;
|
|
499
|
+
} };
|
|
500
|
+
return html`<${FormDialog} title=${team ? t("fleet.editTeam") : t("fleet.newTeam")} onClose=${onClose} submit=${submit}>
|
|
501
|
+
${field(`${t("fleet.teamName")} *`, html`<input value=${f.name} readOnly=${!!team} aria-readonly=${team ? "true" : undefined} onInput=${(e) => { const v = e.target.value; setF((cur) => ({ ...cur, name: v })); }} />`)}
|
|
502
|
+
${team ? html`<p class="note">${t("fleet.teamNoRename")}</p>` : null}
|
|
503
|
+
${field(t("fleet.taskDesc"), html`<input value=${f.desc} onInput=${(e) => { const v = e.target.value; setF((cur) => ({ ...cur, desc: v })); }} />`)}
|
|
504
|
+
<fieldset class="field"><legend>${t("fleet.members")} *</legend><div class="checks">${names.map(n => html`<label key=${n} class="check">
|
|
505
|
+
<input type="checkbox" checked=${f.members.includes(n)} onChange=${() => toggle(n)} />${n}</label>`)}</div></fieldset>
|
|
360
506
|
</${FormDialog}>`;
|
|
361
507
|
}
|
|
362
508
|
|
|
@@ -409,20 +555,29 @@ export function CreateInstanceDialog({ onClose }) {
|
|
|
409
555
|
</${FormDialog}>`;
|
|
410
556
|
}
|
|
411
557
|
|
|
412
|
-
// ── Cron in words ──
|
|
413
|
-
|
|
558
|
+
// ── Cron in words (#1724: in the page's language) ──
|
|
559
|
+
/**
|
|
560
|
+
* A cron expression as a person says it — "Weekdays 18:00", 「每週一到五 18:00」, "Every 15 minutes" — or the expression
|
|
561
|
+
* itself when it is not one of these shapes. Never "" for an expression; "" only for none.
|
|
562
|
+
*/
|
|
414
563
|
export function cronDesc(expr) {
|
|
415
|
-
if (!expr) return "";
|
|
564
|
+
if (!expr || typeof expr !== "string") return "";
|
|
416
565
|
const p = expr.trim().split(/\s+/);
|
|
417
|
-
if (p.length
|
|
566
|
+
if (p.length !== 5) return expr.trim();
|
|
418
567
|
const [min, hr, dom, mon, dow] = p;
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
if (
|
|
422
|
-
if (hr
|
|
423
|
-
if (
|
|
424
|
-
if (
|
|
425
|
-
if (
|
|
426
|
-
if (dom
|
|
427
|
-
return
|
|
568
|
+
const num = (s) => /^\d{1,2}$/.test(s);
|
|
569
|
+
const time = num(hr) && num(min) ? `${hr.padStart(2, "0")}:${min.padStart(2, "0")}` : null;
|
|
570
|
+
if (mon !== "*") return expr.trim();
|
|
571
|
+
if (min === "*" && hr === "*" && dom === "*" && dow === "*") return t("fleet.cronEveryMinute");
|
|
572
|
+
if (/^\*\/\d+$/.test(min) && hr === "*" && dom === "*" && dow === "*") return t("fleet.cronEveryMinutes", min.slice(2));
|
|
573
|
+
if (num(min) && /^\*\/\d+$/.test(hr) && dom === "*" && dow === "*") return t("fleet.cronEveryHours", hr.slice(2));
|
|
574
|
+
if (!time) return expr.trim();
|
|
575
|
+
if (dom === "*" && dow === "*") return t("fleet.cronDaily", time);
|
|
576
|
+
if (dom === "*" && dow === "1-5") return t("fleet.cronWeekdays", time);
|
|
577
|
+
if (dom === "*" && /^[0-7](,[0-7])*$/.test(dow)) {
|
|
578
|
+
const names = t("fleet.dayNames").split(",");
|
|
579
|
+
return t("fleet.cronDays", dow.split(",").map((d) => names[Number(d) % 7]).join(t("fleet.dayJoin")), time);
|
|
580
|
+
}
|
|
581
|
+
if (num(dom) && dow === "*") return t("fleet.cronMonthly", String(Number(dom)), time);
|
|
582
|
+
return expr.trim();
|
|
428
583
|
}
|