adispatchd 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +195 -2
- package/dist/api/errors.d.ts +27 -0
- package/dist/api/errors.js +80 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.js +11 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/logs.d.ts +30 -0
- package/dist/api/logs.js +88 -0
- package/dist/api/logs.js.map +1 -0
- package/dist/api/serialize.d.ts +65 -0
- package/dist/api/serialize.js +135 -0
- package/dist/api/serialize.js.map +1 -0
- package/dist/api/server.d.ts +89 -0
- package/dist/api/server.js +751 -0
- package/dist/api/server.js.map +1 -0
- package/dist/api/sse.d.ts +90 -0
- package/dist/api/sse.js +225 -0
- package/dist/api/sse.js.map +1 -0
- package/dist/app.d.ts +173 -0
- package/dist/app.js +488 -0
- package/dist/app.js.map +1 -0
- package/dist/cli/config-validate.d.ts +6 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/config-validate.js.map +1 -0
- package/dist/cli/control.d.ts +31 -0
- package/dist/cli/control.js +186 -0
- package/dist/cli/control.js.map +1 -0
- package/dist/cli/http-client.d.ts +57 -0
- package/dist/cli/http-client.js +174 -0
- package/dist/cli/http-client.js.map +1 -0
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.js +14 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init-templates.d.ts +20 -0
- package/dist/cli/init-templates.js +275 -0
- package/dist/cli/init-templates.js.map +1 -0
- package/dist/cli/init.d.ts +16 -0
- package/dist/cli/init.js +107 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +25 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/program.d.ts +102 -0
- package/dist/cli/program.js +133 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/cli/service.d.ts +27 -0
- package/dist/cli/service.js +252 -0
- package/dist/cli/service.js.map +1 -0
- package/dist/cli/start.d.ts +22 -0
- package/dist/cli/start.js +133 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/cli/workflow-dry-run.d.ts +13 -0
- package/dist/cli/workflow-dry-run.js +144 -0
- package/dist/cli/workflow-dry-run.js.map +1 -0
- package/dist/config/field-catalog.d.ts +33 -0
- package/dist/config/field-catalog.js +172 -0
- package/dist/config/field-catalog.js.map +1 -0
- package/dist/config/filter-schema.d.ts +15 -0
- package/dist/config/filter-schema.js +110 -0
- package/dist/config/filter-schema.js.map +1 -0
- package/dist/config/index.d.ts +15 -0
- package/dist/config/index.js +16 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/issues.d.ts +27 -0
- package/dist/config/issues.js +37 -0
- package/dist/config/issues.js.map +1 -0
- package/dist/config/loader.d.ts +32 -0
- package/dist/config/loader.js +250 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/manager.d.ts +153 -0
- package/dist/config/manager.js +276 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/model.d.ts +334 -0
- package/dist/config/model.js +2 -0
- package/dist/config/model.js.map +1 -0
- package/dist/config/normalize.d.ts +20 -0
- package/dist/config/normalize.js +200 -0
- package/dist/config/normalize.js.map +1 -0
- package/dist/config/parameters.d.ts +37 -0
- package/dist/config/parameters.js +71 -0
- package/dist/config/parameters.js.map +1 -0
- package/dist/config/public.d.ts +6 -0
- package/dist/config/public.js +18 -0
- package/dist/config/public.js.map +1 -0
- package/dist/config/schemas.d.ts +538 -0
- package/dist/config/schemas.js +321 -0
- package/dist/config/schemas.js.map +1 -0
- package/dist/config/snapshot.d.ts +38 -0
- package/dist/config/snapshot.js +57 -0
- package/dist/config/snapshot.js.map +1 -0
- package/dist/config/validate.d.ts +33 -0
- package/dist/config/validate.js +346 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/core/clock.d.ts +59 -0
- package/dist/core/clock.js +133 -0
- package/dist/core/clock.js.map +1 -0
- package/dist/core/duration.d.ts +19 -0
- package/dist/core/duration.js +93 -0
- package/dist/core/duration.js.map +1 -0
- package/dist/core/errors.d.ts +62 -0
- package/dist/core/errors.js +72 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/ids.d.ts +57 -0
- package/dist/core/ids.js +88 -0
- package/dist/core/ids.js.map +1 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +100 -0
- package/dist/core/logger.js +207 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/paths.d.ts +84 -0
- package/dist/core/paths.js +138 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/template.d.ts +136 -0
- package/dist/core/template.js +261 -0
- package/dist/core/template.js.map +1 -0
- package/dist/core/types.d.ts +544 -0
- package/dist/core/types.js +172 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/worker-protocol.d.ts +102 -0
- package/dist/core/worker-protocol.js +75 -0
- package/dist/core/worker-protocol.js.map +1 -0
- package/dist/executors/classify.d.ts +25 -0
- package/dist/executors/classify.js +54 -0
- package/dist/executors/classify.js.map +1 -0
- package/dist/executors/dispatcher.d.ts +35 -0
- package/dist/executors/dispatcher.js +68 -0
- package/dist/executors/dispatcher.js.map +1 -0
- package/dist/executors/env.d.ts +43 -0
- package/dist/executors/env.js +66 -0
- package/dist/executors/env.js.map +1 -0
- package/dist/executors/event-parser.d.ts +41 -0
- package/dist/executors/event-parser.js +106 -0
- package/dist/executors/event-parser.js.map +1 -0
- package/dist/executors/index.d.ts +12 -0
- package/dist/executors/index.js +13 -0
- package/dist/executors/index.js.map +1 -0
- package/dist/executors/log-capture.d.ts +57 -0
- package/dist/executors/log-capture.js +155 -0
- package/dist/executors/log-capture.js.map +1 -0
- package/dist/executors/process-identity.d.ts +124 -0
- package/dist/executors/process-identity.js +283 -0
- package/dist/executors/process-identity.js.map +1 -0
- package/dist/executors/shell-executor.d.ts +35 -0
- package/dist/executors/shell-executor.js +478 -0
- package/dist/executors/shell-executor.js.map +1 -0
- package/dist/executors/stream-redactor.d.ts +40 -0
- package/dist/executors/stream-redactor.js +89 -0
- package/dist/executors/stream-redactor.js.map +1 -0
- package/dist/executors/workspace.d.ts +10 -0
- package/dist/executors/workspace.js +47 -0
- package/dist/executors/workspace.js.map +1 -0
- package/dist/filters/evaluate.d.ts +77 -0
- package/dist/filters/evaluate.js +187 -0
- package/dist/filters/evaluate.js.map +1 -0
- package/dist/filters/fields.d.ts +35 -0
- package/dist/filters/fields.js +75 -0
- package/dist/filters/fields.js.map +1 -0
- package/dist/filters/index.d.ts +11 -0
- package/dist/filters/index.js +12 -0
- package/dist/filters/index.js.map +1 -0
- package/dist/filters/iso-date.d.ts +10 -0
- package/dist/filters/iso-date.js +42 -0
- package/dist/filters/iso-date.js.map +1 -0
- package/dist/filters/predicate.d.ts +24 -0
- package/dist/filters/predicate.js +178 -0
- package/dist/filters/predicate.js.map +1 -0
- package/dist/filters/priority.d.ts +51 -0
- package/dist/filters/priority.js +91 -0
- package/dist/filters/priority.js.map +1 -0
- package/dist/filters/pushdown.d.ts +12 -0
- package/dist/filters/pushdown.js +52 -0
- package/dist/filters/pushdown.js.map +1 -0
- package/dist/filters/shell-check.d.ts +110 -0
- package/dist/filters/shell-check.js +304 -0
- package/dist/filters/shell-check.js.map +1 -0
- package/dist/identities/github-app.d.ts +55 -0
- package/dist/identities/github-app.js +160 -0
- package/dist/identities/github-app.js.map +1 -0
- package/dist/identities/index.d.ts +6 -0
- package/dist/identities/index.js +7 -0
- package/dist/identities/index.js.map +1 -0
- package/dist/identities/jwt.d.ts +24 -0
- package/dist/identities/jwt.js +56 -0
- package/dist/identities/jwt.js.map +1 -0
- package/dist/lifecycle/context.d.ts +106 -0
- package/dist/lifecycle/context.js +170 -0
- package/dist/lifecycle/context.js.map +1 -0
- package/dist/lifecycle/engine.d.ts +185 -0
- package/dist/lifecycle/engine.js +551 -0
- package/dist/lifecycle/engine.js.map +1 -0
- package/dist/lifecycle/errors.d.ts +48 -0
- package/dist/lifecycle/errors.js +71 -0
- package/dist/lifecycle/errors.js.map +1 -0
- package/dist/lifecycle/index.d.ts +9 -0
- package/dist/lifecycle/index.js +9 -0
- package/dist/lifecycle/index.js.map +1 -0
- package/dist/lifecycle/performers.d.ts +66 -0
- package/dist/lifecycle/performers.js +179 -0
- package/dist/lifecycle/performers.js.map +1 -0
- package/dist/notifications/index.d.ts +6 -0
- package/dist/notifications/index.js +7 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/service.d.ts +115 -0
- package/dist/notifications/service.js +364 -0
- package/dist/notifications/service.js.map +1 -0
- package/dist/persistence/actions.d.ts +101 -0
- package/dist/persistence/actions.js +196 -0
- package/dist/persistence/actions.js.map +1 -0
- package/dist/persistence/blocks.d.ts +85 -0
- package/dist/persistence/blocks.js +140 -0
- package/dist/persistence/blocks.js.map +1 -0
- package/dist/persistence/cron-firings.d.ts +55 -0
- package/dist/persistence/cron-firings.js +70 -0
- package/dist/persistence/cron-firings.js.map +1 -0
- package/dist/persistence/database.d.ts +60 -0
- package/dist/persistence/database.js +124 -0
- package/dist/persistence/database.js.map +1 -0
- package/dist/persistence/errors.d.ts +39 -0
- package/dist/persistence/errors.js +67 -0
- package/dist/persistence/errors.js.map +1 -0
- package/dist/persistence/events.d.ts +72 -0
- package/dist/persistence/events.js +83 -0
- package/dist/persistence/events.js.map +1 -0
- package/dist/persistence/index.d.ts +18 -0
- package/dist/persistence/index.js +19 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/instance-lock.d.ts +26 -0
- package/dist/persistence/instance-lock.js +117 -0
- package/dist/persistence/instance-lock.js.map +1 -0
- package/dist/persistence/lock-keys.d.ts +16 -0
- package/dist/persistence/lock-keys.js +41 -0
- package/dist/persistence/lock-keys.js.map +1 -0
- package/dist/persistence/notifications.d.ts +113 -0
- package/dist/persistence/notifications.js +213 -0
- package/dist/persistence/notifications.js.map +1 -0
- package/dist/persistence/persistence.d.ts +46 -0
- package/dist/persistence/persistence.js +42 -0
- package/dist/persistence/persistence.js.map +1 -0
- package/dist/persistence/retention.d.ts +46 -0
- package/dist/persistence/retention.js +68 -0
- package/dist/persistence/retention.js.map +1 -0
- package/dist/persistence/runs.d.ts +215 -0
- package/dist/persistence/runs.js +417 -0
- package/dist/persistence/runs.js.map +1 -0
- package/dist/persistence/schema.d.ts +16 -0
- package/dist/persistence/schema.js +183 -0
- package/dist/persistence/schema.js.map +1 -0
- package/dist/persistence/sqlite.d.ts +49 -0
- package/dist/persistence/sqlite.js +131 -0
- package/dist/persistence/sqlite.js.map +1 -0
- package/dist/runs/controller.d.ts +307 -0
- package/dist/runs/controller.js +1078 -0
- package/dist/runs/controller.js.map +1 -0
- package/dist/runs/errors.d.ts +20 -0
- package/dist/runs/errors.js +26 -0
- package/dist/runs/errors.js.map +1 -0
- package/dist/runs/event-bus.d.ts +106 -0
- package/dist/runs/event-bus.js +105 -0
- package/dist/runs/event-bus.js.map +1 -0
- package/dist/runs/execution.d.ts +39 -0
- package/dist/runs/execution.js +146 -0
- package/dist/runs/execution.js.map +1 -0
- package/dist/runs/index.d.ts +12 -0
- package/dist/runs/index.js +13 -0
- package/dist/runs/index.js.map +1 -0
- package/dist/runs/retention.d.ts +64 -0
- package/dist/runs/retention.js +232 -0
- package/dist/runs/retention.js.map +1 -0
- package/dist/runs/workspace.d.ts +20 -0
- package/dist/runs/workspace.js +51 -0
- package/dist/runs/workspace.js.map +1 -0
- package/dist/scheduler/checks.d.ts +41 -0
- package/dist/scheduler/checks.js +111 -0
- package/dist/scheduler/checks.js.map +1 -0
- package/dist/scheduler/cron.d.ts +32 -0
- package/dist/scheduler/cron.js +83 -0
- package/dist/scheduler/cron.js.map +1 -0
- package/dist/scheduler/dry-run.d.ts +42 -0
- package/dist/scheduler/dry-run.js +167 -0
- package/dist/scheduler/dry-run.js.map +1 -0
- package/dist/scheduler/errors.d.ts +22 -0
- package/dist/scheduler/errors.js +22 -0
- package/dist/scheduler/errors.js.map +1 -0
- package/dist/scheduler/format.d.ts +3 -0
- package/dist/scheduler/format.js +73 -0
- package/dist/scheduler/format.js.map +1 -0
- package/dist/scheduler/index.d.ts +13 -0
- package/dist/scheduler/index.js +14 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/planner.d.ts +88 -0
- package/dist/scheduler/planner.js +255 -0
- package/dist/scheduler/planner.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +160 -0
- package/dist/scheduler/scheduler.js +680 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/state.d.ts +26 -0
- package/dist/scheduler/state.js +25 -0
- package/dist/scheduler/state.js.map +1 -0
- package/dist/scheduler/types.d.ts +197 -0
- package/dist/scheduler/types.js +3 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/service/index.d.ts +6 -0
- package/dist/service/index.js +7 -0
- package/dist/service/index.js.map +1 -0
- package/dist/service/install.d.ts +93 -0
- package/dist/service/install.js +250 -0
- package/dist/service/install.js.map +1 -0
- package/dist/service/unit.d.ts +50 -0
- package/dist/service/unit.js +171 -0
- package/dist/service/unit.js.map +1 -0
- package/dist/sources/github/adapter.d.ts +99 -0
- package/dist/sources/github/adapter.js +336 -0
- package/dist/sources/github/adapter.js.map +1 -0
- package/dist/sources/github/fields.d.ts +8 -0
- package/dist/sources/github/fields.js +72 -0
- package/dist/sources/github/fields.js.map +1 -0
- package/dist/sources/github/http.d.ts +100 -0
- package/dist/sources/github/http.js +288 -0
- package/dist/sources/github/http.js.map +1 -0
- package/dist/sources/github/index.d.ts +6 -0
- package/dist/sources/github/index.js +7 -0
- package/dist/sources/github/index.js.map +1 -0
- package/dist/sources/github/pushdown.d.ts +14 -0
- package/dist/sources/github/pushdown.js +47 -0
- package/dist/sources/github/pushdown.js.map +1 -0
- package/dist/sources/github/refs.d.ts +17 -0
- package/dist/sources/github/refs.js +61 -0
- package/dist/sources/github/refs.js.map +1 -0
- package/dist/sources/index.d.ts +6 -0
- package/dist/sources/index.js +7 -0
- package/dist/sources/index.js.map +1 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.js +28 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/public/app.css +239 -0
- package/dist/ui/public/favicon.svg +1 -0
- package/dist/ui/public/index.html +27 -0
- package/dist/ui/public/js/client/api.js +68 -0
- package/dist/ui/public/js/client/app.js +146 -0
- package/dist/ui/public/js/client/dom.js +34 -0
- package/dist/ui/public/js/client/page.js +9 -0
- package/dist/ui/public/js/client/pages/actions.js +35 -0
- package/dist/ui/public/js/client/pages/blocked.js +58 -0
- package/dist/ui/public/js/client/pages/config.js +126 -0
- package/dist/ui/public/js/client/pages/dashboard.js +175 -0
- package/dist/ui/public/js/client/pages/run.js +367 -0
- package/dist/ui/public/js/client/pages/runs.js +72 -0
- package/dist/ui/public/js/client/pages/views.js +80 -0
- package/dist/ui/public/js/client/pages/workflows.js +291 -0
- package/dist/ui/public/js/client/types.js +1 -0
- package/dist/ui/public/js/client/ui.js +152 -0
- package/dist/ui/public/js/shared/dom.js +77 -0
- package/dist/ui/public/js/shared/events.js +88 -0
- package/dist/ui/public/js/shared/format.js +213 -0
- package/dist/ui/public/js/shared/logs.js +136 -0
- package/dist/ui/public/js/shared/params.js +90 -0
- package/dist/ui/public/js/shared/route.js +36 -0
- package/dist/ui/public/js/shared/sort.js +70 -0
- package/dist/ui/public/js/shared/sse.js +193 -0
- package/dist/ui/public/js/shared/throttle.js +65 -0
- package/dist/views/index.d.ts +6 -0
- package/dist/views/index.js +7 -0
- package/dist/views/index.js.map +1 -0
- package/dist/views/service.d.ts +131 -0
- package/dist/views/service.js +264 -0
- package/dist/views/service.js.map +1 -0
- package/docs/api.md +239 -0
- package/docs/configuration-reference.md +1785 -0
- package/docs/cron-semantics.md +96 -0
- package/docs/manual-tests/github-fixture/config.yaml +30 -0
- package/docs/manual-tests/github-fixture/helpers.sh +93 -0
- package/docs/manual-tests/github-fixture/identities.yaml +18 -0
- package/docs/manual-tests/github-fixture/scripts/test-worker.sh +100 -0
- package/docs/manual-tests/github-fixture/sources.yaml +13 -0
- package/docs/manual-tests/github-fixture/views/dependencies.yaml +17 -0
- package/docs/manual-tests/github-fixture/views/needs-maintainer.yaml +17 -0
- package/docs/manual-tests/github-fixture/workflows/coder-test.yaml +76 -0
- package/docs/manual-tests/github-fixture/workflows/commenter-test.yaml +63 -0
- package/docs/manual-tests/github-fixture/workflows/reviewer-test.yaml +60 -0
- package/docs/manual-tests/github.md +1308 -0
- package/docs/manual-tests/process-supervision.md +64 -0
- package/docs/manual-tests/systemd.md +68 -0
- package/docs/operations.md +271 -0
- package/docs/worker-protocol.md +145 -0
- package/examples/config/config.yaml +35 -0
- package/examples/config/identities.yaml +18 -0
- package/examples/config/sources.yaml +13 -0
- package/examples/config/views/needs-maintainer.yaml +15 -0
- package/examples/config/workflows/coder-project-a.yaml +86 -0
- package/examples/config/workflows/deploy.yaml +15 -0
- package/examples/config/workflows/nightly-maintenance.yaml +13 -0
- package/package.json +75 -4
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/* Agent Dispatch Daemon (adispatchd) web UI – plain CSS, light/dark via prefers-color-scheme. */
|
|
2
|
+
:root {
|
|
3
|
+
--bg: #f6f7f9;
|
|
4
|
+
--surface: #ffffff;
|
|
5
|
+
--surface-2: #f0f2f5;
|
|
6
|
+
--text: #1c2330;
|
|
7
|
+
--muted: #5d6878;
|
|
8
|
+
--border: #d9dee6;
|
|
9
|
+
--accent: #2563eb;
|
|
10
|
+
--accent-text: #ffffff;
|
|
11
|
+
--ok: #157f3b;
|
|
12
|
+
--ok-bg: #e3f5e9;
|
|
13
|
+
--warn: #8a5a00;
|
|
14
|
+
--warn-bg: #fff3d6;
|
|
15
|
+
--error: #b42318;
|
|
16
|
+
--error-bg: #fde8e7;
|
|
17
|
+
--info: #1d4ed8;
|
|
18
|
+
--info-bg: #e5edff;
|
|
19
|
+
--muted-bg: #eceff3;
|
|
20
|
+
--code-bg: #f3f4f6;
|
|
21
|
+
--shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
|
|
22
|
+
--radius: 8px;
|
|
23
|
+
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;
|
|
24
|
+
color-scheme: light dark;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@media (prefers-color-scheme: dark) {
|
|
28
|
+
:root {
|
|
29
|
+
--bg: #0f1218;
|
|
30
|
+
--surface: #171b23;
|
|
31
|
+
--surface-2: #1f2430;
|
|
32
|
+
--text: #e5e9f0;
|
|
33
|
+
--muted: #9aa4b2;
|
|
34
|
+
--border: #2c3341;
|
|
35
|
+
--accent: #5b8cff;
|
|
36
|
+
--accent-text: #0b1020;
|
|
37
|
+
--ok: #5fd38a;
|
|
38
|
+
--ok-bg: #12301f;
|
|
39
|
+
--warn: #f5c062;
|
|
40
|
+
--warn-bg: #352812;
|
|
41
|
+
--error: #ff8a80;
|
|
42
|
+
--error-bg: #3a1716;
|
|
43
|
+
--info: #8fb0ff;
|
|
44
|
+
--info-bg: #19254a;
|
|
45
|
+
--muted-bg: #252b37;
|
|
46
|
+
--code-bg: #11151c;
|
|
47
|
+
--shadow: none;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
* { box-sizing: border-box; }
|
|
52
|
+
html, body { margin: 0; padding: 0; }
|
|
53
|
+
body {
|
|
54
|
+
background: var(--bg);
|
|
55
|
+
color: var(--text);
|
|
56
|
+
font: 14px/1.45 system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
57
|
+
}
|
|
58
|
+
a { color: var(--accent); text-decoration: none; }
|
|
59
|
+
a:hover { text-decoration: underline; }
|
|
60
|
+
code, pre, .mono { font-family: var(--mono); font-size: 12.5px; }
|
|
61
|
+
code { overflow-wrap: anywhere; }
|
|
62
|
+
small { font-size: 12px; }
|
|
63
|
+
h1 { font-size: 22px; margin: 4px 0 14px; overflow-wrap: anywhere; }
|
|
64
|
+
h1 small { font-weight: normal; }
|
|
65
|
+
h2 { font-size: 16px; margin: 0 0 10px; }
|
|
66
|
+
h3 { font-size: 14px; margin: 16px 0 6px; }
|
|
67
|
+
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
68
|
+
|
|
69
|
+
/* ---------- header / nav ---------- */
|
|
70
|
+
.top {
|
|
71
|
+
position: sticky; top: 0; z-index: 10;
|
|
72
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px;
|
|
73
|
+
padding: 8px 16px; background: var(--surface); border-bottom: 1px solid var(--border);
|
|
74
|
+
}
|
|
75
|
+
.brand { font-weight: 700; color: var(--text); white-space: nowrap; }
|
|
76
|
+
#nav { flex: 1 1 auto; min-width: 0; }
|
|
77
|
+
.menu { list-style: none; display: flex; flex-wrap: wrap; gap: 2px; margin: 0; padding: 0; }
|
|
78
|
+
.menu > li { position: relative; }
|
|
79
|
+
.menu a, .menu summary {
|
|
80
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
81
|
+
padding: 6px 10px; border-radius: 6px; color: var(--text); cursor: pointer; white-space: nowrap;
|
|
82
|
+
}
|
|
83
|
+
.menu summary { list-style: none; }
|
|
84
|
+
.menu summary::-webkit-details-marker { display: none; }
|
|
85
|
+
.menu summary::after { content: '▾'; font-size: 10px; color: var(--muted); }
|
|
86
|
+
.menu a:hover, .menu summary:hover { background: var(--surface-2); text-decoration: none; }
|
|
87
|
+
.menu a.active, .menu summary.active { background: var(--info-bg); color: var(--info); }
|
|
88
|
+
.submenu {
|
|
89
|
+
list-style: none; margin: 4px 0 0; padding: 6px; position: absolute; left: 0; top: 100%;
|
|
90
|
+
min-width: 200px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
|
|
91
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15); z-index: 20;
|
|
92
|
+
}
|
|
93
|
+
.submenu a { display: block; }
|
|
94
|
+
.count .badge { margin-left: 2px; }
|
|
95
|
+
.live { font-size: 12px; padding: 2px 8px; border-radius: 999px; white-space: nowrap; }
|
|
96
|
+
.live::before { content: '●'; margin-right: 4px; }
|
|
97
|
+
.live-open { color: var(--ok); background: var(--ok-bg); }
|
|
98
|
+
.live-connecting, .live-reconnecting { color: var(--warn); background: var(--warn-bg); }
|
|
99
|
+
.live-closed { color: var(--error); background: var(--error-bg); }
|
|
100
|
+
|
|
101
|
+
/* ---------- layout ---------- */
|
|
102
|
+
main { padding: 16px; max-width: 1400px; margin: 0 auto; outline: none; }
|
|
103
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 14px; align-items: start; }
|
|
104
|
+
.span-all { grid-column: 1 / -1; }
|
|
105
|
+
.card {
|
|
106
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
|
|
107
|
+
padding: 14px; box-shadow: var(--shadow); min-width: 0; margin-bottom: 14px;
|
|
108
|
+
}
|
|
109
|
+
.grid > .card { margin-bottom: 0; }
|
|
110
|
+
.crumbs { margin: 0 0 4px; }
|
|
111
|
+
.hint { color: var(--muted); font-size: 12.5px; margin: 8px 0; }
|
|
112
|
+
.muted { color: var(--muted); }
|
|
113
|
+
.empty { color: var(--muted); font-style: italic; }
|
|
114
|
+
.unknown { color: var(--muted); }
|
|
115
|
+
.error-text { color: var(--error); white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12.5px; }
|
|
116
|
+
.wrap { white-space: pre-wrap; word-break: break-all; }
|
|
117
|
+
|
|
118
|
+
/* ---------- key/value ---------- */
|
|
119
|
+
.kv { display: grid; grid-template-columns: minmax(110px, max-content) 1fr; gap: 6px 14px; margin: 0; }
|
|
120
|
+
.kv dt { color: var(--muted); }
|
|
121
|
+
.kv dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
|
|
122
|
+
|
|
123
|
+
/* ---------- tables ---------- */
|
|
124
|
+
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
|
125
|
+
table { width: 100%; border-collapse: collapse; }
|
|
126
|
+
th, td { text-align: left; padding: 7px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
127
|
+
th { font-weight: 600; color: var(--muted); font-size: 12.5px; white-space: nowrap; }
|
|
128
|
+
tbody tr:hover { background: var(--surface-2); }
|
|
129
|
+
td { overflow-wrap: anywhere; }
|
|
130
|
+
.event-message { white-space: pre-wrap; }
|
|
131
|
+
button.sort { background: none; border: 0; padding: 0; font: inherit; color: inherit; cursor: pointer; font-weight: 600; }
|
|
132
|
+
button.sort:hover { color: var(--text); }
|
|
133
|
+
ul.plain, ul.issues, ul.checks { list-style: none; padding: 0; margin: 0; }
|
|
134
|
+
ul.issues li, ul.checks li { margin: 4px 0; }
|
|
135
|
+
ul.trace { list-style: none; margin: 2px 0 2px 8px; padding-left: 10px; border-left: 2px solid var(--border); }
|
|
136
|
+
ul.trace li { margin: 3px 0; }
|
|
137
|
+
ol.attempts { margin: 4px 0; padding-left: 18px; }
|
|
138
|
+
|
|
139
|
+
/* ---------- badges ---------- */
|
|
140
|
+
.badges { display: inline-flex; flex-wrap: wrap; gap: 4px; }
|
|
141
|
+
.badge {
|
|
142
|
+
display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 12px; font-weight: 600;
|
|
143
|
+
white-space: nowrap; border: 1px solid transparent; line-height: 18px; margin-right: 2px;
|
|
144
|
+
}
|
|
145
|
+
.badge-ok { color: var(--ok); background: var(--ok-bg); }
|
|
146
|
+
.badge-warn { color: var(--warn); background: var(--warn-bg); }
|
|
147
|
+
.badge-error { color: var(--error); background: var(--error-bg); }
|
|
148
|
+
.badge-info { color: var(--info); background: var(--info-bg); }
|
|
149
|
+
.badge-muted { color: var(--muted); background: var(--muted-bg); }
|
|
150
|
+
|
|
151
|
+
/* ---------- alerts / toasts ---------- */
|
|
152
|
+
.alert { padding: 10px 12px; border-radius: var(--radius); margin: 8px 0; border: 1px solid transparent; overflow-wrap: anywhere; }
|
|
153
|
+
.alert-ok { background: var(--ok-bg); color: var(--ok); }
|
|
154
|
+
.alert-warn { background: var(--warn-bg); color: var(--warn); }
|
|
155
|
+
.alert-error { background: var(--error-bg); color: var(--error); }
|
|
156
|
+
.alert-info { background: var(--info-bg); color: var(--info); }
|
|
157
|
+
.alert pre { color: var(--text); }
|
|
158
|
+
.toasts { position: fixed; right: 16px; bottom: 16px; display: flex; flex-direction: column; gap: 8px; z-index: 100; max-width: min(420px, calc(100vw - 32px)); }
|
|
159
|
+
.toast { padding: 10px 14px; border-radius: var(--radius); background: var(--surface); border: 1px solid var(--border); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18); overflow-wrap: anywhere; }
|
|
160
|
+
.toast-ok { border-left: 4px solid var(--ok); }
|
|
161
|
+
.toast-error { border-left: 4px solid var(--error); }
|
|
162
|
+
.toast-info, .toast-warn, .toast-muted { border-left: 4px solid var(--info); }
|
|
163
|
+
|
|
164
|
+
/* ---------- buttons / forms ---------- */
|
|
165
|
+
.actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 10px 0; }
|
|
166
|
+
.btn {
|
|
167
|
+
font: inherit; padding: 6px 12px; border-radius: 6px; border: 1px solid var(--border);
|
|
168
|
+
background: var(--surface-2); color: var(--text); cursor: pointer; white-space: nowrap;
|
|
169
|
+
}
|
|
170
|
+
.btn:hover:not(:disabled) { filter: brightness(0.96); }
|
|
171
|
+
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
172
|
+
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
|
|
173
|
+
.btn-danger { background: var(--error-bg); border-color: var(--error); color: var(--error); }
|
|
174
|
+
.segmented { display: inline-flex; }
|
|
175
|
+
.segmented .btn { border-radius: 0; }
|
|
176
|
+
.segmented .btn:first-child { border-radius: 6px 0 0 6px; }
|
|
177
|
+
.segmented .btn:last-child { border-radius: 0 6px 6px 0; }
|
|
178
|
+
.input {
|
|
179
|
+
font: inherit; padding: 6px 8px; border-radius: 6px; border: 1px solid var(--border);
|
|
180
|
+
background: var(--surface); color: var(--text); min-width: 0; max-width: 100%;
|
|
181
|
+
}
|
|
182
|
+
.input-narrow { width: 7em; }
|
|
183
|
+
.form { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
|
|
184
|
+
.field { display: flex; flex-direction: column; gap: 4px; }
|
|
185
|
+
.field > span { font-weight: 600; font-size: 12.5px; }
|
|
186
|
+
.check { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
|
|
187
|
+
.inline-form { display: inline-flex; flex-wrap: wrap; gap: 6px; align-items: center; }
|
|
188
|
+
.filters { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0; }
|
|
189
|
+
.pager { display: flex; gap: 16px; align-items: center; margin: 10px 0; color: var(--muted); }
|
|
190
|
+
.tabs { display: flex; flex-wrap: wrap; gap: 4px; border-bottom: 1px solid var(--border); margin: 10px 0 14px; }
|
|
191
|
+
.tabs a { padding: 7px 12px; border-bottom: 2px solid transparent; color: var(--muted); }
|
|
192
|
+
.tabs a.active { color: var(--text); border-bottom-color: var(--accent); font-weight: 600; }
|
|
193
|
+
.tabs a:hover { text-decoration: none; color: var(--text); }
|
|
194
|
+
.meter-wrap { display: inline-flex; align-items: center; gap: 6px; }
|
|
195
|
+
meter { width: 120px; height: 12px; }
|
|
196
|
+
|
|
197
|
+
/* ---------- run ---------- */
|
|
198
|
+
.run-status { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
199
|
+
.status-message { color: var(--muted); font-style: italic; overflow-wrap: anywhere; }
|
|
200
|
+
pre.log, pre.json, pre.yaml {
|
|
201
|
+
background: var(--code-bg); border: 1px solid var(--border); border-radius: 6px;
|
|
202
|
+
padding: 10px; margin: 6px 0; overflow: auto; white-space: pre-wrap; word-break: break-all;
|
|
203
|
+
}
|
|
204
|
+
pre.log { height: 60vh; min-height: 240px; white-space: pre; word-break: normal; }
|
|
205
|
+
pre.json { max-height: 420px; }
|
|
206
|
+
pre.yaml { max-height: 70vh; white-space: pre; word-break: normal; }
|
|
207
|
+
.log-info { font-size: 12px; }
|
|
208
|
+
details > summary { cursor: pointer; }
|
|
209
|
+
details.file { border-bottom: 1px solid var(--border); padding: 6px 0; }
|
|
210
|
+
|
|
211
|
+
/* ---------- dialog ---------- */
|
|
212
|
+
.dialog {
|
|
213
|
+
border: 1px solid var(--border); border-radius: 10px; padding: 18px; width: min(520px, calc(100vw - 24px));
|
|
214
|
+
background: var(--surface); color: var(--text); box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35);
|
|
215
|
+
}
|
|
216
|
+
.dialog::backdrop { background: rgba(0, 0, 0, 0.45); }
|
|
217
|
+
.dialog h2 { margin-top: 0; }
|
|
218
|
+
.dialog-body { margin: 6px 0 12px; overflow-wrap: anywhere; }
|
|
219
|
+
.dialog .field { margin: 10px 0; }
|
|
220
|
+
.dialog-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
|
|
221
|
+
|
|
222
|
+
/* ---------- narrow screens ---------- */
|
|
223
|
+
@media (max-width: 640px) {
|
|
224
|
+
main { padding: 10px; }
|
|
225
|
+
.top { padding: 6px 10px; }
|
|
226
|
+
.grid { grid-template-columns: 1fr; }
|
|
227
|
+
.kv { grid-template-columns: 1fr; gap: 2px; }
|
|
228
|
+
.kv dt { margin-top: 6px; }
|
|
229
|
+
h1 { font-size: 19px; }
|
|
230
|
+
.menu a, .menu summary { padding: 5px 8px; }
|
|
231
|
+
.submenu { position: static; box-shadow: none; }
|
|
232
|
+
pre.log { height: 50vh; }
|
|
233
|
+
.table-wrap > table { min-width: 640px; }
|
|
234
|
+
.table-wrap td code { overflow-wrap: normal; white-space: nowrap; }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* events table: keep type and payload columns compact */
|
|
238
|
+
table.events td:nth-child(4) code, table.events td:nth-child(2) { white-space: nowrap; overflow-wrap: normal; }
|
|
239
|
+
table.events td:last-child { white-space: nowrap; }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="6" fill="#2563eb"/><circle cx="10" cy="16" r="4" fill="#fff"/><circle cx="22" cy="9" r="3" fill="#fff"/><circle cx="22" cy="23" r="3" fill="#fff"/><path d="M13 14l6-4M13 18l6 4" stroke="#fff" stroke-width="2"/></svg>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta
|
|
7
|
+
http-equiv="Content-Security-Policy"
|
|
8
|
+
content="default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self'; font-src 'self'; base-uri 'none'; form-action 'none'; object-src 'none'"
|
|
9
|
+
/>
|
|
10
|
+
<meta name="referrer" content="no-referrer" />
|
|
11
|
+
<meta name="color-scheme" content="light dark" />
|
|
12
|
+
<title>Agent Dispatch Daemon</title>
|
|
13
|
+
<link rel="icon" href="favicon.svg" type="image/svg+xml" />
|
|
14
|
+
<link rel="stylesheet" href="app.css" />
|
|
15
|
+
<script type="module" src="js/client/app.js"></script>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<header class="top">
|
|
19
|
+
<a class="brand" href="#/" title="Agent Dispatch Daemon">adispatchd</a>
|
|
20
|
+
<nav id="nav" aria-label="Main"></nav>
|
|
21
|
+
<span id="live" class="live live-connecting" role="status" aria-live="polite">connecting…</span>
|
|
22
|
+
</header>
|
|
23
|
+
<main id="main" tabindex="-1">
|
|
24
|
+
<noscript><p>This interface needs JavaScript.</p></noscript>
|
|
25
|
+
</main>
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for `/api/v1` (docs/api.md). Mutating requests always carry
|
|
3
|
+
* the CSRF header `X-Adispatch-Request: 1` and a JSON body.
|
|
4
|
+
*/
|
|
5
|
+
/** Error returned by the API (`{error:{code,message,details}}`) or the network. */
|
|
6
|
+
export class ApiError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
code;
|
|
9
|
+
details;
|
|
10
|
+
constructor(status, code, message, details = null) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.details = details;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async function request(method, path, body) {
|
|
18
|
+
const headers = { accept: 'application/json' };
|
|
19
|
+
const mutating = method !== 'GET' && method !== 'HEAD';
|
|
20
|
+
if (mutating) {
|
|
21
|
+
headers['x-adispatch-request'] = '1';
|
|
22
|
+
headers['content-type'] = 'application/json';
|
|
23
|
+
}
|
|
24
|
+
let res;
|
|
25
|
+
try {
|
|
26
|
+
res = await fetch(path, {
|
|
27
|
+
method,
|
|
28
|
+
headers,
|
|
29
|
+
credentials: 'same-origin',
|
|
30
|
+
cache: 'no-store',
|
|
31
|
+
...(mutating ? { body: JSON.stringify(body ?? {}) } : {}),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
throw new ApiError(0, 'network_error', `Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
36
|
+
}
|
|
37
|
+
let json = null;
|
|
38
|
+
const text = await res.text();
|
|
39
|
+
if (text) {
|
|
40
|
+
try {
|
|
41
|
+
json = JSON.parse(text);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
json = null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const e = json?.error;
|
|
49
|
+
throw new ApiError(res.status, typeof e?.code === 'string' ? e.code : `http_${res.status}`, typeof e?.message === 'string' ? e.message : `HTTP ${res.status}`, e?.details ?? null);
|
|
50
|
+
}
|
|
51
|
+
return json;
|
|
52
|
+
}
|
|
53
|
+
/** Query string from an object (empty values omitted). */
|
|
54
|
+
export function qs(params) {
|
|
55
|
+
const p = new URLSearchParams();
|
|
56
|
+
for (const [k, v] of Object.entries(params))
|
|
57
|
+
if (v !== null && v !== undefined && v !== '')
|
|
58
|
+
p.set(k, String(v));
|
|
59
|
+
const s = p.toString();
|
|
60
|
+
return s ? `?${s}` : '';
|
|
61
|
+
}
|
|
62
|
+
export const api = {
|
|
63
|
+
get: (path) => request('GET', path),
|
|
64
|
+
post: (path, body) => request('POST', path, body),
|
|
65
|
+
patch: (path, body) => request('PATCH', path, body),
|
|
66
|
+
};
|
|
67
|
+
/** Encode one path segment. */
|
|
68
|
+
export const seg = (s) => encodeURIComponent(String(s));
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry point of the web UI (WI-11, Spec §16): navigation, hash router, one
|
|
3
|
+
* shared SSE connection with reconnect + REST resync.
|
|
4
|
+
*/
|
|
5
|
+
import { api } from './api.js';
|
|
6
|
+
import { h, replaceChildren, $ } from './dom.js';
|
|
7
|
+
import { parseHash } from '../shared/route.js';
|
|
8
|
+
import { LiveConnection } from '../shared/sse.js';
|
|
9
|
+
import { createRefresher } from '../shared/throttle.js';
|
|
10
|
+
import { badge, errorBox } from './ui.js';
|
|
11
|
+
import { dashboardPage } from './pages/dashboard.js';
|
|
12
|
+
import { runsPage } from './pages/runs.js';
|
|
13
|
+
import { runPage } from './pages/run.js';
|
|
14
|
+
import { workflowPage, workflowsPage } from './pages/workflows.js';
|
|
15
|
+
import { blockedPage } from './pages/blocked.js';
|
|
16
|
+
import { failedActionsPage } from './pages/actions.js';
|
|
17
|
+
import { viewPage, viewsListPage } from './pages/views.js';
|
|
18
|
+
import { configPage } from './pages/config.js';
|
|
19
|
+
/** Views listed directly in the menu up to this count (more → only "All Views"). */
|
|
20
|
+
const MAX_DIRECT_VIEWS = 8;
|
|
21
|
+
const main = $('#main');
|
|
22
|
+
const nav = $('#nav');
|
|
23
|
+
const liveIndicator = $('#live');
|
|
24
|
+
let current = null;
|
|
25
|
+
// ------------------------------------------------------------------ nav
|
|
26
|
+
const counters = { blocks: h('span', { class: 'count' }), actions: h('span', { class: 'count' }) };
|
|
27
|
+
const viewsMenu = h('ul', { class: 'submenu' });
|
|
28
|
+
const navItem = (key, href, label, extra) => h('li', null, h('a', { href, 'data-nav': key }, label, extra ?? null));
|
|
29
|
+
replaceChildren(nav, h('ul', { class: 'menu' }, navItem('', '#/', 'Dashboard'), navItem('runs', '#/runs', 'Runs'), navItem('workflows', '#/workflows', 'Workflows'), navItem('blocked', '#/blocked', 'Blocked', counters.blocks), navItem('actions', '#/actions', 'Failed Actions', counters.actions), h('li', { class: 'has-sub' }, h('details', { class: 'views-menu' }, h('summary', { 'data-nav': 'views' }, 'Views'), viewsMenu)), navItem('config', '#/config', 'Configuration')));
|
|
30
|
+
const renderCount = (el, n, tone) => {
|
|
31
|
+
replaceChildren(el, n > 0 ? badge(String(n), tone) : null);
|
|
32
|
+
};
|
|
33
|
+
const loadNav = async () => {
|
|
34
|
+
try {
|
|
35
|
+
const [status, views] = await Promise.all([api.get('/api/v1/status'), api.get('/api/v1/views')]);
|
|
36
|
+
renderCount(counters.blocks, status.counts.activeBlocks, 'warn');
|
|
37
|
+
renderCount(counters.actions, status.counts.failedActions, 'error');
|
|
38
|
+
const direct = views.views.length <= MAX_DIRECT_VIEWS ? views.views : [];
|
|
39
|
+
replaceChildren(viewsMenu, direct.map((v) => h('li', null, h('a', { href: `#/views/${encodeURIComponent(v.id)}`, 'data-nav': `views/${v.id}` }, v.title))), h('li', null, h('a', { href: '#/views', 'data-nav': 'views' }, 'All Views')));
|
|
40
|
+
markActiveNav();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// shown by the live indicator / pages
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const navRefresher = createRefresher(loadNav, 2000);
|
|
47
|
+
const markActiveNav = () => {
|
|
48
|
+
const r = parseHash(location.hash);
|
|
49
|
+
const top = r.segments[0] ?? '';
|
|
50
|
+
const full = r.segments.slice(0, 2).join('/');
|
|
51
|
+
nav.querySelectorAll('[data-nav]').forEach((a) => {
|
|
52
|
+
const key = a.getAttribute('data-nav') ?? '';
|
|
53
|
+
const active = key === full || (key === top && !(top === 'views' && r.segments.length > 1 && a.tagName === 'A'));
|
|
54
|
+
a.classList.toggle('active', active);
|
|
55
|
+
if (a.tagName === 'A') {
|
|
56
|
+
if (active)
|
|
57
|
+
a.setAttribute('aria-current', 'page');
|
|
58
|
+
else
|
|
59
|
+
a.removeAttribute('aria-current');
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
// --------------------------------------------------------------- router
|
|
64
|
+
const ctxFor = (route) => ({
|
|
65
|
+
route,
|
|
66
|
+
navigate: (hash) => {
|
|
67
|
+
if (location.hash === hash)
|
|
68
|
+
render();
|
|
69
|
+
else
|
|
70
|
+
location.hash = hash;
|
|
71
|
+
},
|
|
72
|
+
refreshNav: () => navRefresher.trigger(),
|
|
73
|
+
setTitle: (title) => {
|
|
74
|
+
document.title = `${title} · adispatchd`;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
function pageFor(route, ctx) {
|
|
78
|
+
const [a, b] = route.segments;
|
|
79
|
+
switch (a) {
|
|
80
|
+
case undefined:
|
|
81
|
+
return dashboardPage(ctx);
|
|
82
|
+
case 'runs':
|
|
83
|
+
return b ? runPage(ctx, b) : runsPage(ctx);
|
|
84
|
+
case 'workflows':
|
|
85
|
+
return b ? workflowPage(ctx, b) : workflowsPage(ctx);
|
|
86
|
+
case 'blocked':
|
|
87
|
+
return blockedPage(ctx);
|
|
88
|
+
case 'actions':
|
|
89
|
+
return failedActionsPage(ctx);
|
|
90
|
+
case 'views':
|
|
91
|
+
return b ? viewPage(ctx, b) : viewsListPage(ctx);
|
|
92
|
+
case 'config':
|
|
93
|
+
return configPage(ctx);
|
|
94
|
+
default:
|
|
95
|
+
return { el: h('div', { class: 'page' }, h('h1', null, 'Not found'), h('p', null, 'Unknown page. ', h('a', { href: '#/' }, 'Go to the dashboard'))) };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function render() {
|
|
99
|
+
current?.dispose?.();
|
|
100
|
+
current = null;
|
|
101
|
+
const route = parseHash(location.hash);
|
|
102
|
+
try {
|
|
103
|
+
current = pageFor(route, ctxFor(route));
|
|
104
|
+
replaceChildren(main, current.el);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
replaceChildren(main, errorBox(err));
|
|
108
|
+
}
|
|
109
|
+
nav.querySelectorAll('details').forEach((d) => {
|
|
110
|
+
d.open = false;
|
|
111
|
+
});
|
|
112
|
+
markActiveNav();
|
|
113
|
+
main.focus({ preventScroll: true });
|
|
114
|
+
window.scrollTo(0, 0);
|
|
115
|
+
}
|
|
116
|
+
window.addEventListener('hashchange', render);
|
|
117
|
+
// ----------------------------------------------------------------- live
|
|
118
|
+
const STATE_LABEL = { connecting: 'connecting…', open: 'live', reconnecting: 'reconnecting…', closed: 'offline' };
|
|
119
|
+
const live = new LiveConnection({
|
|
120
|
+
url: '/api/v1/events',
|
|
121
|
+
// The DOM EventSource is structurally compatible (handler parameter types differ only nominally).
|
|
122
|
+
create: (url) => new EventSource(url),
|
|
123
|
+
onMessage: (msg) => {
|
|
124
|
+
if (msg.type === 'block.created' || msg.type === 'block.cleared' || msg.type === 'run.action' || msg.type === 'config.reloaded' || msg.type === 'system.status')
|
|
125
|
+
navRefresher.trigger();
|
|
126
|
+
try {
|
|
127
|
+
current?.onLive?.(msg);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
console.error('live handler failed', err);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
onResync: () => {
|
|
134
|
+
navRefresher.trigger();
|
|
135
|
+
current?.onResync?.();
|
|
136
|
+
},
|
|
137
|
+
onState: (state) => {
|
|
138
|
+
liveIndicator.textContent = STATE_LABEL[state];
|
|
139
|
+
liveIndicator.className = `live live-${state}`;
|
|
140
|
+
liveIndicator.title = state === 'open' ? 'Live updates via SSE' : 'Live updates interrupted; data is reloaded after reconnecting';
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
window.addEventListener('online', () => live.reconnectNow());
|
|
144
|
+
render();
|
|
145
|
+
void loadNav();
|
|
146
|
+
live.start();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser binding of the safe element builder (see shared/dom.ts). All
|
|
3
|
+
* rendering goes through `h()`; untrusted strings therefore always end up in
|
|
4
|
+
* text nodes. `innerHTML` is never used in the UI.
|
|
5
|
+
*/
|
|
6
|
+
import { createH } from '../shared/dom.js';
|
|
7
|
+
/** Element builder bound to `document`. */
|
|
8
|
+
export const h = createH(document);
|
|
9
|
+
/** Remove all children of `el` and append `children`. */
|
|
10
|
+
export function replaceChildren(el, ...children) {
|
|
11
|
+
el.replaceChildren();
|
|
12
|
+
const tmp = h('div', null, ...children);
|
|
13
|
+
while (tmp.firstChild)
|
|
14
|
+
el.appendChild(tmp.firstChild);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Like {@link replaceChildren}, but keeps the open state of `<details>`
|
|
18
|
+
* elements (matched by position) across live re-renders.
|
|
19
|
+
*/
|
|
20
|
+
export function rerender(el, ...children) {
|
|
21
|
+
const open = [...el.querySelectorAll('details')].map((d) => d.open);
|
|
22
|
+
replaceChildren(el, ...children);
|
|
23
|
+
el.querySelectorAll('details').forEach((d, i) => {
|
|
24
|
+
if (open[i])
|
|
25
|
+
d.open = true;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** Typed `querySelector` that throws if missing. */
|
|
29
|
+
export function $(selector, root = document) {
|
|
30
|
+
const el = root.querySelector(selector);
|
|
31
|
+
if (!el)
|
|
32
|
+
throw new Error(`element ${selector} not found`);
|
|
33
|
+
return el;
|
|
34
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** True for live messages that change run/system state (used for coalesced refreshes). */
|
|
2
|
+
export function isStateChange(msg) {
|
|
3
|
+
return msg.type !== 'run.output' && msg.type !== 'stream.open' && msg.type !== 'retention.finished';
|
|
4
|
+
}
|
|
5
|
+
/** Run ID a live message refers to, if any. */
|
|
6
|
+
export function messageRunId(msg) {
|
|
7
|
+
const p = msg.payload;
|
|
8
|
+
return typeof p.runId === 'string' ? p.runId : null;
|
|
9
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Failed Actions (Spec §10, §16): errors, attempts, manual retry / skip. */
|
|
2
|
+
import { api } from '../api.js';
|
|
3
|
+
import { h, replaceChildren, rerender } from '../dom.js';
|
|
4
|
+
import { createRefresher } from '../../shared/throttle.js';
|
|
5
|
+
import { errorBox, loading } from '../ui.js';
|
|
6
|
+
import { actionsTable } from './run.js';
|
|
7
|
+
export const failedActionsPage = (ctx) => {
|
|
8
|
+
ctx.setTitle('Failed Actions');
|
|
9
|
+
const body = h('div', null, loading());
|
|
10
|
+
const el = h('div', { class: 'page' }, h('h1', null, 'Failed Actions'), h('p', { class: 'hint' }, 'Lifecycle actions whose attempts are exhausted ("lifecycle failed"). Retry starts a fresh attempt budget; the hook chain continues afterwards in the background.'), body);
|
|
11
|
+
const after = () => {
|
|
12
|
+
ctx.refreshNav();
|
|
13
|
+
refresher.trigger();
|
|
14
|
+
};
|
|
15
|
+
const load = async () => {
|
|
16
|
+
try {
|
|
17
|
+
const res = await api.get('/api/v1/actions/failed');
|
|
18
|
+
rerender(body, res.actions.length ? actionsTable(res.actions, after, { showRun: true }) : h('p', { class: 'empty' }, 'No failed lifecycle actions.'));
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
replaceChildren(body, errorBox(err));
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const refresher = createRefresher(load, 1000);
|
|
25
|
+
void load();
|
|
26
|
+
return {
|
|
27
|
+
el,
|
|
28
|
+
onLive: (msg) => {
|
|
29
|
+
if (msg.type === 'run.action' || (msg.type === 'run.event' && String(msg.payload.type).startsWith('lifecycle.')))
|
|
30
|
+
refresher.trigger();
|
|
31
|
+
},
|
|
32
|
+
onResync: () => void refresher.now(),
|
|
33
|
+
dispose: () => refresher.dispose(),
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blocked (Spec §11, §16): internal retry blocks of the orchestrator after
|
|
3
|
+
* repeated technical failures – explicitly NOT GitHub "blocked by" or labels.
|
|
4
|
+
*/
|
|
5
|
+
import { api } from '../api.js';
|
|
6
|
+
import { h, replaceChildren, rerender } from '../dom.js';
|
|
7
|
+
import { createRefresher } from '../../shared/throttle.js';
|
|
8
|
+
import { act, button, confirmDialog, errorBox, externalLink, loading, routeLink, runLink, table, time } from '../ui.js';
|
|
9
|
+
export const blockedPage = (ctx) => {
|
|
10
|
+
ctx.setTitle('Blocked');
|
|
11
|
+
const body = h('div', null, loading());
|
|
12
|
+
const el = h('div', { class: 'page' }, h('h1', null, 'Blocked'), h('div', { class: 'alert alert-info' }, h('strong', null, 'Internal retry blocks only. '), 'A block is created by the orchestrator after too many consecutive technical failures (crashed, start error, timeout) of a workflow/item. ', 'This is unrelated to GitHub "blocked by" dependencies or GitHub labels. Removing a block starts a new counting period; history is kept.'), body);
|
|
13
|
+
const clear = async (b, btn) => {
|
|
14
|
+
const r = await confirmDialog({
|
|
15
|
+
title: 'Remove retry block?',
|
|
16
|
+
message: h('div', null, h('p', null, `Block #${b.id} for workflow ${b.workflowId}${b.itemKey ? `, item ${b.itemKey}` : ' (whole workflow)'} is removed.`), h('p', null, 'The scheduler may start the item again in the next planning step. The failure counter starts a new period.')),
|
|
17
|
+
confirmLabel: 'Remove block',
|
|
18
|
+
danger: true,
|
|
19
|
+
});
|
|
20
|
+
if (!r.ok)
|
|
21
|
+
return;
|
|
22
|
+
await act(btn, () => api.post(`/api/v1/blocks/${b.id}/clear`, { clearedBy: 'ui' }), 'Block removed');
|
|
23
|
+
ctx.refreshNav();
|
|
24
|
+
await refresher.now();
|
|
25
|
+
};
|
|
26
|
+
const load = async () => {
|
|
27
|
+
try {
|
|
28
|
+
const res = await api.get('/api/v1/blocks');
|
|
29
|
+
rerender(body, table(['Block key', 'Reason', 'Failures', 'Created', 'Runs', 'Source item', ''], res.blocks.map((b) => {
|
|
30
|
+
const btn = button('Remove Block', () => void clear(b, btn), { kind: 'danger' });
|
|
31
|
+
const runs = [...new Set([...(b.runId ? [b.runId] : []), ...b.affectedRunIds])];
|
|
32
|
+
return [
|
|
33
|
+
h('span', null, routeLink(['workflows', b.workflowId], b.workflowName ?? b.workflowId), h('br'), h('code', null, b.itemKey ?? '(workflow scope)')),
|
|
34
|
+
b.reason,
|
|
35
|
+
String(b.failureCount),
|
|
36
|
+
time(b.createdAt),
|
|
37
|
+
runs.length ? h('ul', { class: 'plain' }, runs.slice(0, 10).map((id) => h('li', null, runLink(id))), runs.length > 10 ? h('li', { class: 'muted' }, `+${runs.length - 10} more`) : null) : '—',
|
|
38
|
+
externalLink(b.sourceUrl),
|
|
39
|
+
btn,
|
|
40
|
+
];
|
|
41
|
+
}), { empty: 'No active retry blocks.' }));
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
replaceChildren(body, errorBox(err));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const refresher = createRefresher(load, 1000);
|
|
48
|
+
void load();
|
|
49
|
+
return {
|
|
50
|
+
el,
|
|
51
|
+
onLive: (msg) => {
|
|
52
|
+
if (msg.type === 'block.created' || msg.type === 'block.cleared')
|
|
53
|
+
refresher.trigger();
|
|
54
|
+
},
|
|
55
|
+
onResync: () => void refresher.now(),
|
|
56
|
+
dispose: () => refresher.dispose(),
|
|
57
|
+
};
|
|
58
|
+
};
|