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,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration / System (Spec §16, §19): YAML read-only, validation, manual
|
|
3
|
+
* reload, runtime overrides note, notification delivery failures.
|
|
4
|
+
*/
|
|
5
|
+
import { ApiError, api, qs } from '../api.js';
|
|
6
|
+
import { h, replaceChildren, rerender } from '../dom.js';
|
|
7
|
+
import { createRefresher } from '../../shared/throttle.js';
|
|
8
|
+
import { buildHash } from '../../shared/route.js';
|
|
9
|
+
import { stateTone } from '../../shared/format.js';
|
|
10
|
+
import { act, badge, button, confirmDialog, errorBox, jsonBlock, kv, loading, section, table, text, time, toast } from '../ui.js';
|
|
11
|
+
function issuesList(issues) {
|
|
12
|
+
return h('ul', { class: 'issues' }, issues.map((i) => h('li', null, badge(i.severity, i.severity === 'error' ? 'error' : 'warn'), ' ', h('code', null, `${i.file}${i.line ? `:${i.line}` : ''}${i.path ? ` ${i.path}` : ''}`), ' ', i.message)));
|
|
13
|
+
}
|
|
14
|
+
export const configPage = (ctx) => {
|
|
15
|
+
ctx.setTitle('Configuration');
|
|
16
|
+
const tab = ctx.route.query.tab === 'notifications' ? 'notifications' : 'config';
|
|
17
|
+
const tabs = h('nav', { class: 'tabs' }, h('a', { href: buildHash(['config']), class: tab === 'config' ? 'active' : null }, 'Configuration'), h('a', { href: buildHash(['config'], { tab: 'notifications' }), class: tab === 'notifications' ? 'active' : null }, 'Notifications'));
|
|
18
|
+
const body = h('div', null, loading());
|
|
19
|
+
const el = h('div', { class: 'page' }, h('h1', null, 'Configuration / System'), tabs, body);
|
|
20
|
+
// ------------------------------------------------------------ config tab
|
|
21
|
+
const result = h('div');
|
|
22
|
+
const validateBtn = button('Validate', () => {
|
|
23
|
+
void (async () => {
|
|
24
|
+
const res = await act(validateBtn, () => api.post('/api/v1/config/validate'));
|
|
25
|
+
if (!res)
|
|
26
|
+
return;
|
|
27
|
+
replaceChildren(result, h('div', { class: `alert ${res.ok ? 'alert-ok' : 'alert-error'}` }, res.ok ? `Configuration on disk is valid${res.hash ? ` (hash ${res.hash.slice(0, 12)})` : ''}. Nothing was activated.` : `Configuration on disk is invalid (${res.errors.length} error(s)).`, res.errors.length ? issuesList(res.errors) : null, res.warnings.length ? issuesList(res.warnings) : null));
|
|
28
|
+
})();
|
|
29
|
+
});
|
|
30
|
+
const reloadBtn = button('Reload', () => {
|
|
31
|
+
void (async () => {
|
|
32
|
+
const r = await confirmDialog({
|
|
33
|
+
title: 'Reload configuration?',
|
|
34
|
+
message: h('div', null, h('p', null, 'All YAML files are read and validated. Only if everything is valid, the new configuration is activated atomically; otherwise the active configuration is kept.'), h('p', null, 'Runtime overrides of affected settings are reset to the YAML values. Running runs keep their configuration snapshot.')),
|
|
35
|
+
confirmLabel: 'Reload',
|
|
36
|
+
});
|
|
37
|
+
if (!r.ok)
|
|
38
|
+
return;
|
|
39
|
+
reloadBtn.disabled = true;
|
|
40
|
+
try {
|
|
41
|
+
const res = await api.post('/api/v1/config/reload');
|
|
42
|
+
toast(res.changed ? `Configuration v${res.version} activated` : 'Configuration unchanged', 'ok');
|
|
43
|
+
replaceChildren(result, h('div', { class: 'alert alert-ok' }, res.changed ? `Reloaded: version ${res.version}, hash ${res.hash.slice(0, 12)}.` : 'No changes.', res.warnings.length ? issuesList(res.warnings) : null, h('details', null, h('summary', null, 'Diff / reset overrides'), jsonBlock({ diff: res.diff, resetOverrides: res.resetOverrides }))));
|
|
44
|
+
void loadConfig();
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const issues = err instanceof ApiError ? (err.details?.errors ?? []) : [];
|
|
48
|
+
replaceChildren(result, h('div', { class: 'alert alert-error' }, err instanceof ApiError && err.code === 'config_invalid' ? 'Reload rejected – the active configuration was kept.' : errorBox(err), issues.length ? issuesList(issues) : null));
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
reloadBtn.disabled = false;
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
}, { kind: 'primary' });
|
|
55
|
+
const systemBox = h('div');
|
|
56
|
+
const filesBox = h('div', null, loading());
|
|
57
|
+
const loadConfig = async () => {
|
|
58
|
+
try {
|
|
59
|
+
const [status, files, cfg] = await Promise.all([
|
|
60
|
+
api.get('/api/v1/status'),
|
|
61
|
+
api.get('/api/v1/config/files'),
|
|
62
|
+
api.get('/api/v1/config'),
|
|
63
|
+
]);
|
|
64
|
+
const overrideCount = Object.keys(cfg.overrides.global ?? {}).length + Object.values(cfg.overrides.workflows ?? {}).reduce((n, o) => n + Object.keys(o ?? {}).length, 0);
|
|
65
|
+
rerender(systemBox, h('div', { class: 'grid' }, section('Active configuration', kv([
|
|
66
|
+
['Directory', h('code', null, status.config.configDir)],
|
|
67
|
+
['Version', String(status.config.version)],
|
|
68
|
+
['Hash', h('code', null, status.config.hash)],
|
|
69
|
+
['Loaded', time(status.config.loadedAt)],
|
|
70
|
+
['Runtime overrides', overrideCount > 0 ? h('span', null, badge(String(overrideCount), 'info'), ' active – not persistent, lost on restart, reset by reload') : 'none'],
|
|
71
|
+
]), h('div', { class: 'actions' }, validateBtn, reloadBtn), result, overrideCount > 0 ? h('details', null, h('summary', null, 'Overrides'), jsonBlock(cfg.overrides)) : null), section('System', kv([
|
|
72
|
+
['Version', status.version],
|
|
73
|
+
['Started', time(status.startedAt)],
|
|
74
|
+
['Bind', h('span', null, `${status.server.host}:${status.server.port ?? '?'} `, status.server.loopback ? badge('loopback', 'ok') : badge('not loopback – protect with proxy/VPN', 'warn'))],
|
|
75
|
+
['Readiness', h('span', null, Object.entries(status.readiness.checks).map(([k, v]) => badge(k, (k === 'shuttingDown' ? !v : v) ? 'ok' : 'warn')))],
|
|
76
|
+
['SSE clients', String(status.sse.clients)],
|
|
77
|
+
['Notifications', Object.entries(status.counts.notifications).map(([k, v]) => `${k} ${v}`).join(', ') || '—'],
|
|
78
|
+
['Retention', status.retention ? h('span', null, 'last run ', time(status.retention.lastRunAt), ` · deleted ${JSON.stringify(status.retention.deletedRuns)} · errors ${status.retention.errors}`) : text(null)],
|
|
79
|
+
]))));
|
|
80
|
+
rerender(filesBox, h('h2', null, 'YAML files (read-only, secrets redacted)'), files.files.length === 0 ? h('p', { class: 'empty' }, 'No files.') : null, files.files.map((f) => h('details', { class: 'file' }, h('summary', null, h('code', null, f.file), f.error ? badge('unavailable', 'warn', f.error) : null), f.content !== null ? h('pre', { class: 'yaml' }, f.content) : h('p', { class: 'error-text' }, f.error ?? 'not available'))));
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
replaceChildren(systemBox, errorBox(err));
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
// ----------------------------------------------------- notifications tab
|
|
87
|
+
let notifState = ctx.route.query.state === 'all' ? '' : 'failed';
|
|
88
|
+
const loadNotifications = async () => {
|
|
89
|
+
try {
|
|
90
|
+
const res = await api.get(`/api/v1/notifications${qs({ state: notifState, limit: 200 })}`);
|
|
91
|
+
const stateSel = h('select', { class: 'input', 'aria-label': 'Delivery state', on: { change: () => { notifState = stateSel.value; void loadNotifications(); } } }, h('option', { value: 'failed', selected: notifState === 'failed' }, `Failed deliveries (${res.counts.failed ?? 0})`), h('option', { value: '', selected: notifState === '' }, 'All'));
|
|
92
|
+
rerender(body, section('Orchestrator notifications', h('p', { class: 'hint' }, 'Central notifications (retry block created, lifecycle action failed, recovery failed) and their delivery attempts per channel.'), h('div', { class: 'filters' }, stateSel), table(['#', 'Event', 'Created', 'State', 'Deliveries', ''], res.notifications.map((n) => {
|
|
93
|
+
const retry = n.deliveryState === 'failed'
|
|
94
|
+
? button('Retry', () => void act(retry, () => api.post(`/api/v1/notifications/${n.id}/retry`), 'Delivery re-queued').then(() => loadNotifications()), { kind: 'primary' })
|
|
95
|
+
: null;
|
|
96
|
+
return [
|
|
97
|
+
String(n.id),
|
|
98
|
+
h('span', null, h('code', null, n.eventType), h('details', null, h('summary', null, 'payload'), jsonBlock(n.payload, 20_000))),
|
|
99
|
+
time(n.createdAt),
|
|
100
|
+
badge(n.deliveryState, stateTone(n.deliveryState)),
|
|
101
|
+
n.deliveries.length
|
|
102
|
+
? h('ul', { class: 'plain' }, n.deliveries.map((d) => h('li', null, h('code', null, d.channelId), ' ', badge(d.state, stateTone(d.state)), ` ${d.attempts} attempt(s)`, d.lastStatus !== null ? ` · HTTP ${d.lastStatus}` : '', d.lastError ? h('div', { class: 'error-text' }, d.lastError) : null, d.nextAttemptAt ? h('div', { class: 'muted' }, 'next attempt ', time(d.nextAttemptAt)) : null)))
|
|
103
|
+
: text(n.lastError ?? 'no channels'),
|
|
104
|
+
retry,
|
|
105
|
+
];
|
|
106
|
+
}), { empty: notifState === 'failed' ? 'No failed deliveries.' : 'No notifications.' })));
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
replaceChildren(body, errorBox(err));
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
const load = tab === 'config' ? loadConfig : loadNotifications;
|
|
113
|
+
if (tab === 'config')
|
|
114
|
+
replaceChildren(body, systemBox, h('section', { class: 'card' }, filesBox));
|
|
115
|
+
const refresher = createRefresher(load, 2000);
|
|
116
|
+
void load();
|
|
117
|
+
return {
|
|
118
|
+
el,
|
|
119
|
+
onLive: (msg) => {
|
|
120
|
+
if (msg.type === 'config.reloaded' || (tab === 'notifications' && msg.type === 'notification'))
|
|
121
|
+
refresher.trigger();
|
|
122
|
+
},
|
|
123
|
+
onResync: () => void refresher.now(),
|
|
124
|
+
dispose: () => refresher.dispose(),
|
|
125
|
+
};
|
|
126
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/** Dashboard (Spec §16): scheduler state, limit/usage, pause/resume, drain, cancel all, active runs. */
|
|
2
|
+
import { api, qs, seg } from '../api.js';
|
|
3
|
+
import { h, replaceChildren, rerender } from '../dom.js';
|
|
4
|
+
import { createRefresher } from '../../shared/throttle.js';
|
|
5
|
+
import { formatDuration, formatRelative } from '../../shared/format.js';
|
|
6
|
+
import { isStateChange } from '../page.js';
|
|
7
|
+
import { act, badge, button, confirmDialog, errorBox, isEditing, kv, loading, routeLink, runLink, runOutcomeBadges, section, table, text, time, externalLink } from '../ui.js';
|
|
8
|
+
export function schedulerStateBadge(s) {
|
|
9
|
+
const tone = s.state === 'running' ? 'ok' : s.state === 'paused' ? 'warn' : s.state === 'recovering' ? 'info' : 'error';
|
|
10
|
+
return badge(s.state, tone, 'Scheduler state');
|
|
11
|
+
}
|
|
12
|
+
function meter(active, max) {
|
|
13
|
+
const pct = max > 0 ? Math.min(100, Math.round((active / max) * 100)) : 0;
|
|
14
|
+
return h('span', { class: 'meter-wrap' }, h('meter', { min: 0, max: Math.max(1, max), value: active, high: Math.max(1, max) - 0.5, title: `${active}/${max} (${pct}%)` }), ` ${active} / ${max}`);
|
|
15
|
+
}
|
|
16
|
+
export function cancelRunButton(run, after) {
|
|
17
|
+
if (!run.active || run.status === 'cancelling' || run.status === 'finalizing' || run.status === 'recovery_pending')
|
|
18
|
+
return null;
|
|
19
|
+
const btn = button('Cancel', () => {
|
|
20
|
+
void (async () => {
|
|
21
|
+
const r = await confirmDialog({
|
|
22
|
+
title: 'Cancel run?',
|
|
23
|
+
message: h('p', null, 'The run ', h('code', null, run.id), ' will be cancelled (SIGTERM, then SIGKILL after the grace period). Its on_cancel hook runs afterwards.'),
|
|
24
|
+
confirmLabel: 'Cancel run',
|
|
25
|
+
danger: true,
|
|
26
|
+
input: { label: 'Reason (optional)', placeholder: 'cancelled via UI' },
|
|
27
|
+
});
|
|
28
|
+
if (!r.ok)
|
|
29
|
+
return;
|
|
30
|
+
await act(btn, () => api.post(`/api/v1/runs/${seg(run.id)}/cancel`, r.value ? { reason: r.value } : {}), 'Cancel requested');
|
|
31
|
+
after();
|
|
32
|
+
})();
|
|
33
|
+
}, { kind: 'danger' });
|
|
34
|
+
return btn;
|
|
35
|
+
}
|
|
36
|
+
export const dashboardPage = (ctx) => {
|
|
37
|
+
ctx.setTitle('Dashboard');
|
|
38
|
+
const el = h('div', { class: 'page' }, h('h1', null, 'Dashboard'));
|
|
39
|
+
const body = h('div', { class: 'grid' }, loading());
|
|
40
|
+
el.appendChild(body);
|
|
41
|
+
const load = async () => {
|
|
42
|
+
if (isEditing(body)) {
|
|
43
|
+
setTimeout(() => refresher.trigger(), 2000);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const [status, active, recent] = await Promise.all([
|
|
48
|
+
api.get('/api/v1/status'),
|
|
49
|
+
api.get(`/api/v1/runs${qs({ active: 'true', limit: 100 })}`),
|
|
50
|
+
api.get(`/api/v1/runs${qs({ active: 'false', limit: 10 })}`),
|
|
51
|
+
]);
|
|
52
|
+
render(status, active.runs, active.total, recent.runs);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
replaceChildren(body, errorBox(err));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const refresher = createRefresher(load, 1000);
|
|
59
|
+
const render = (s, active, activeTotal, recent) => {
|
|
60
|
+
const sch = s.scheduler;
|
|
61
|
+
const controls = h('div', { class: 'actions' });
|
|
62
|
+
if (sch.paused) {
|
|
63
|
+
const b = button('Resume', () => void act(b, () => api.post('/api/v1/scheduler/resume'), 'Scheduler resumed').then(() => refresher.now()), { kind: 'primary' });
|
|
64
|
+
controls.appendChild(b);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const b = button('Pause', () => void act(b, () => api.post('/api/v1/scheduler/pause'), 'Automatic starts paused').then(() => refresher.now()), {
|
|
68
|
+
disabled: sch.stopping || sch.draining,
|
|
69
|
+
title: 'Pause automatic starts (manual starts still possible)',
|
|
70
|
+
});
|
|
71
|
+
controls.appendChild(b);
|
|
72
|
+
}
|
|
73
|
+
const drain = button('Drain', () => {
|
|
74
|
+
void (async () => {
|
|
75
|
+
const r = await confirmDialog({
|
|
76
|
+
title: 'Drain and stop the orchestrator?',
|
|
77
|
+
message: h('div', null, h('p', null, 'No new runs are started. Active runs (', String(s.usage.activeRuns), ') finish normally, then the orchestrator process exits.'), h('p', null, 'The UI is unavailable afterwards until the service is started again.')),
|
|
78
|
+
confirmLabel: 'Start drain',
|
|
79
|
+
danger: true,
|
|
80
|
+
});
|
|
81
|
+
if (r.ok)
|
|
82
|
+
await act(drain, () => api.post('/api/v1/shutdown/drain'), 'Drain started');
|
|
83
|
+
await refresher.now();
|
|
84
|
+
})();
|
|
85
|
+
}, { kind: 'danger', disabled: sch.draining || sch.stopping });
|
|
86
|
+
const cancelAll = button('Cancel All', () => {
|
|
87
|
+
void (async () => {
|
|
88
|
+
const r = await confirmDialog({
|
|
89
|
+
title: 'Cancel all runs and stop?',
|
|
90
|
+
message: h('div', null, h('p', null, 'All ', String(s.usage.activeRuns), ' active run(s) are cancelled (on_cancel hooks run), then the orchestrator process exits.'), h('p', null, 'This cannot be undone.')),
|
|
91
|
+
confirmLabel: 'Cancel all and stop',
|
|
92
|
+
danger: true,
|
|
93
|
+
input: { label: 'Reason (optional)', placeholder: 'cancel all via UI' },
|
|
94
|
+
});
|
|
95
|
+
if (r.ok)
|
|
96
|
+
await act(cancelAll, () => api.post('/api/v1/shutdown/cancel-all', r.value ? { reason: r.value } : {}), 'Cancel all started');
|
|
97
|
+
await refresher.now();
|
|
98
|
+
})();
|
|
99
|
+
}, { kind: 'danger', disabled: sch.stopping });
|
|
100
|
+
controls.append(drain, cancelAll);
|
|
101
|
+
const notices = [];
|
|
102
|
+
if (sch.draining)
|
|
103
|
+
notices.push(h('div', { class: 'alert alert-warn' }, 'Drain in progress: no new runs; the process exits when active runs have finished.'));
|
|
104
|
+
if (sch.stopping)
|
|
105
|
+
notices.push(h('div', { class: 'alert alert-error' }, 'Shutdown in progress.'));
|
|
106
|
+
if (!s.readiness.ready)
|
|
107
|
+
notices.push(h('div', { class: 'alert alert-info' }, 'Not ready (recovery or startup in progress).'));
|
|
108
|
+
if (!s.server.loopback) {
|
|
109
|
+
notices.push(h('div', { class: 'alert alert-warn' }, `The API is bound to ${s.server.host} without authentication – only expose it behind an authenticating proxy/VPN.`));
|
|
110
|
+
}
|
|
111
|
+
// Global limit override
|
|
112
|
+
const limitInput = h('input', { type: 'number', min: 1, step: 1, class: 'input input-narrow', value: s.limits.override ?? '', placeholder: String(s.limits.configuredMaxConcurrent), 'aria-label': 'Global max concurrent override' });
|
|
113
|
+
const setLimit = button('Set', () => {
|
|
114
|
+
const v = limitInput.value.trim();
|
|
115
|
+
const n = Number(v);
|
|
116
|
+
if (v === '' || !Number.isInteger(n) || n < 1) {
|
|
117
|
+
limitInput.setCustomValidity('Integer ≥ 1');
|
|
118
|
+
limitInput.reportValidity();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
limitInput.setCustomValidity('');
|
|
122
|
+
void act(setLimit, () => api.patch('/api/v1/scheduler/runtime', { maxConcurrent: n }), 'Limit override set').then(() => refresher.now());
|
|
123
|
+
});
|
|
124
|
+
const resetLimit = button('Reset to YAML', () => void act(resetLimit, () => api.patch('/api/v1/scheduler/runtime', { maxConcurrent: null }), 'Limit reset to YAML value').then(() => refresher.now()), { disabled: s.limits.override === null });
|
|
125
|
+
const byWf = Object.entries(s.usage.byWorkflow).map(([id, u]) => [routeLink(['workflows', id], id), meter(u.active, u.max)]);
|
|
126
|
+
const counts = s.counts;
|
|
127
|
+
const failedNotifications = counts.notifications.failed ?? 0;
|
|
128
|
+
rerender(body, notices.length ? h('div', { class: 'span-all' }, notices) : null, section('Scheduler', kv([
|
|
129
|
+
['State', schedulerStateBadge(sch)],
|
|
130
|
+
['Ready', s.readiness.ready ? badge('ready', 'ok') : badge('not ready', 'warn')],
|
|
131
|
+
['Polling interval', formatDuration(sch.pollingIntervalMs)],
|
|
132
|
+
[
|
|
133
|
+
'Last planning step',
|
|
134
|
+
sch.lastPlanningStep
|
|
135
|
+
? h('span', null, time(sch.lastPlanningStep.finishedAt), sch.lastPlanningStep.skipped ? ` (skipped: ${sch.lastPlanningStep.skipped})` : ` – ${sch.lastPlanningStep.startedRunIds.length} run(s) started`)
|
|
136
|
+
: text(null),
|
|
137
|
+
],
|
|
138
|
+
['Version', `${s.version}, up since ${formatRelative(s.startedAt, Date.now()).replace(' ago', '')}`],
|
|
139
|
+
]), controls), section('Capacity', kv([
|
|
140
|
+
['Global', meter(s.usage.activeRuns, s.limits.maxConcurrent)],
|
|
141
|
+
['Limit', h('span', null, String(s.limits.maxConcurrent), s.limits.override !== null ? badge('override', 'info', `YAML value: ${s.limits.configuredMaxConcurrent}`) : ' (YAML)')],
|
|
142
|
+
['Override', h('span', { class: 'inline-form' }, limitInput, setLimit, resetLimit)],
|
|
143
|
+
['Recovery pending', s.usage.recoveryPending > 0 ? badge(String(s.usage.recoveryPending), 'warn') : '0'],
|
|
144
|
+
]), h('p', { class: 'hint' }, 'Runtime overrides are not persistent: they are lost on restart and reset by a configuration reload.'), byWf.length ? table(['Workflow', 'Active / max'], byWf) : null), section('Attention', kv([
|
|
145
|
+
['Retry blocks', counts.activeBlocks > 0 ? routeLink(['blocked'], badge(String(counts.activeBlocks), 'warn')) : '0'],
|
|
146
|
+
['Failed actions', counts.failedActions > 0 ? routeLink(['actions'], badge(String(counts.failedActions), 'error')) : '0'],
|
|
147
|
+
['Failed notifications', failedNotifications > 0 ? routeLink(['config'], badge(String(failedNotifications), 'error'), { tab: 'notifications' }) : '0'],
|
|
148
|
+
])), h('section', { class: 'card span-all' }, h('h2', null, `Active runs (${activeTotal})`), table(['Run', 'Workflow', 'Item', 'State', 'Started', 'Last status message', ''], active.map((r) => [
|
|
149
|
+
runLink(r.id),
|
|
150
|
+
r.workflowName ?? r.workflowId,
|
|
151
|
+
r.sourceUrl ? externalLink(r.sourceUrl, r.sourceItemKey ?? r.sourceUrl) : text(r.sourceItemKey ?? r.triggerType),
|
|
152
|
+
runOutcomeBadges(r),
|
|
153
|
+
r.startedAt ? h('span', { title: r.startedAt }, formatRelative(r.startedAt, Date.now())) : text(null),
|
|
154
|
+
text(r.statusMessage),
|
|
155
|
+
cancelRunButton(r, () => refresher.trigger()),
|
|
156
|
+
]), { empty: 'No active runs.' }), activeTotal > active.length ? h('p', null, routeLink(['runs'], `Show all ${activeTotal} active runs`, { scope: 'active' })) : null), h('section', { class: 'card span-all' }, h('h2', null, 'Recently finished'), table(['Run', 'Workflow', 'Outcome', 'Finished', 'Duration', 'Message'], recent.map((r) => [
|
|
157
|
+
runLink(r.id),
|
|
158
|
+
r.workflowName ?? r.workflowId,
|
|
159
|
+
runOutcomeBadges(r),
|
|
160
|
+
time(r.finishedAt),
|
|
161
|
+
formatDuration(r.durationMs),
|
|
162
|
+
text(r.resultMessage ?? r.finalReason),
|
|
163
|
+
]), { empty: 'No finished runs yet.' }), h('p', null, routeLink(['runs'], 'All runs →', { scope: 'history' }))));
|
|
164
|
+
};
|
|
165
|
+
void load();
|
|
166
|
+
return {
|
|
167
|
+
el,
|
|
168
|
+
onLive: (msg) => {
|
|
169
|
+
if (isStateChange(msg))
|
|
170
|
+
refresher.trigger();
|
|
171
|
+
},
|
|
172
|
+
onResync: () => void refresher.now(),
|
|
173
|
+
dispose: () => refresher.dispose(),
|
|
174
|
+
};
|
|
175
|
+
};
|