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.
Files changed (396) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -2
  3. package/dist/api/errors.d.ts +27 -0
  4. package/dist/api/errors.js +80 -0
  5. package/dist/api/errors.js.map +1 -0
  6. package/dist/api/index.d.ts +10 -0
  7. package/dist/api/index.js +11 -0
  8. package/dist/api/index.js.map +1 -0
  9. package/dist/api/logs.d.ts +30 -0
  10. package/dist/api/logs.js +88 -0
  11. package/dist/api/logs.js.map +1 -0
  12. package/dist/api/serialize.d.ts +65 -0
  13. package/dist/api/serialize.js +135 -0
  14. package/dist/api/serialize.js.map +1 -0
  15. package/dist/api/server.d.ts +89 -0
  16. package/dist/api/server.js +751 -0
  17. package/dist/api/server.js.map +1 -0
  18. package/dist/api/sse.d.ts +90 -0
  19. package/dist/api/sse.js +225 -0
  20. package/dist/api/sse.js.map +1 -0
  21. package/dist/app.d.ts +173 -0
  22. package/dist/app.js +488 -0
  23. package/dist/app.js.map +1 -0
  24. package/dist/cli/config-validate.d.ts +6 -0
  25. package/dist/cli/config-validate.js +39 -0
  26. package/dist/cli/config-validate.js.map +1 -0
  27. package/dist/cli/control.d.ts +31 -0
  28. package/dist/cli/control.js +186 -0
  29. package/dist/cli/control.js.map +1 -0
  30. package/dist/cli/http-client.d.ts +57 -0
  31. package/dist/cli/http-client.js +174 -0
  32. package/dist/cli/http-client.js.map +1 -0
  33. package/dist/cli/index.d.ts +14 -0
  34. package/dist/cli/index.js +14 -0
  35. package/dist/cli/index.js.map +1 -0
  36. package/dist/cli/init-templates.d.ts +20 -0
  37. package/dist/cli/init-templates.js +275 -0
  38. package/dist/cli/init-templates.js.map +1 -0
  39. package/dist/cli/init.d.ts +16 -0
  40. package/dist/cli/init.js +107 -0
  41. package/dist/cli/init.js.map +1 -0
  42. package/dist/cli/main.d.ts +2 -0
  43. package/dist/cli/main.js +25 -0
  44. package/dist/cli/main.js.map +1 -0
  45. package/dist/cli/program.d.ts +102 -0
  46. package/dist/cli/program.js +133 -0
  47. package/dist/cli/program.js.map +1 -0
  48. package/dist/cli/service.d.ts +27 -0
  49. package/dist/cli/service.js +252 -0
  50. package/dist/cli/service.js.map +1 -0
  51. package/dist/cli/start.d.ts +22 -0
  52. package/dist/cli/start.js +133 -0
  53. package/dist/cli/start.js.map +1 -0
  54. package/dist/cli/workflow-dry-run.d.ts +13 -0
  55. package/dist/cli/workflow-dry-run.js +144 -0
  56. package/dist/cli/workflow-dry-run.js.map +1 -0
  57. package/dist/config/field-catalog.d.ts +33 -0
  58. package/dist/config/field-catalog.js +172 -0
  59. package/dist/config/field-catalog.js.map +1 -0
  60. package/dist/config/filter-schema.d.ts +15 -0
  61. package/dist/config/filter-schema.js +110 -0
  62. package/dist/config/filter-schema.js.map +1 -0
  63. package/dist/config/index.d.ts +15 -0
  64. package/dist/config/index.js +16 -0
  65. package/dist/config/index.js.map +1 -0
  66. package/dist/config/issues.d.ts +27 -0
  67. package/dist/config/issues.js +37 -0
  68. package/dist/config/issues.js.map +1 -0
  69. package/dist/config/loader.d.ts +32 -0
  70. package/dist/config/loader.js +250 -0
  71. package/dist/config/loader.js.map +1 -0
  72. package/dist/config/manager.d.ts +153 -0
  73. package/dist/config/manager.js +276 -0
  74. package/dist/config/manager.js.map +1 -0
  75. package/dist/config/model.d.ts +334 -0
  76. package/dist/config/model.js +2 -0
  77. package/dist/config/model.js.map +1 -0
  78. package/dist/config/normalize.d.ts +20 -0
  79. package/dist/config/normalize.js +200 -0
  80. package/dist/config/normalize.js.map +1 -0
  81. package/dist/config/parameters.d.ts +37 -0
  82. package/dist/config/parameters.js +71 -0
  83. package/dist/config/parameters.js.map +1 -0
  84. package/dist/config/public.d.ts +6 -0
  85. package/dist/config/public.js +18 -0
  86. package/dist/config/public.js.map +1 -0
  87. package/dist/config/schemas.d.ts +538 -0
  88. package/dist/config/schemas.js +321 -0
  89. package/dist/config/schemas.js.map +1 -0
  90. package/dist/config/snapshot.d.ts +38 -0
  91. package/dist/config/snapshot.js +57 -0
  92. package/dist/config/snapshot.js.map +1 -0
  93. package/dist/config/validate.d.ts +33 -0
  94. package/dist/config/validate.js +346 -0
  95. package/dist/config/validate.js.map +1 -0
  96. package/dist/core/clock.d.ts +59 -0
  97. package/dist/core/clock.js +133 -0
  98. package/dist/core/clock.js.map +1 -0
  99. package/dist/core/duration.d.ts +19 -0
  100. package/dist/core/duration.js +93 -0
  101. package/dist/core/duration.js.map +1 -0
  102. package/dist/core/errors.d.ts +62 -0
  103. package/dist/core/errors.js +72 -0
  104. package/dist/core/errors.js.map +1 -0
  105. package/dist/core/ids.d.ts +57 -0
  106. package/dist/core/ids.js +88 -0
  107. package/dist/core/ids.js.map +1 -0
  108. package/dist/core/index.d.ts +13 -0
  109. package/dist/core/index.js +14 -0
  110. package/dist/core/index.js.map +1 -0
  111. package/dist/core/logger.d.ts +100 -0
  112. package/dist/core/logger.js +207 -0
  113. package/dist/core/logger.js.map +1 -0
  114. package/dist/core/paths.d.ts +84 -0
  115. package/dist/core/paths.js +138 -0
  116. package/dist/core/paths.js.map +1 -0
  117. package/dist/core/template.d.ts +136 -0
  118. package/dist/core/template.js +261 -0
  119. package/dist/core/template.js.map +1 -0
  120. package/dist/core/types.d.ts +544 -0
  121. package/dist/core/types.js +172 -0
  122. package/dist/core/types.js.map +1 -0
  123. package/dist/core/worker-protocol.d.ts +102 -0
  124. package/dist/core/worker-protocol.js +75 -0
  125. package/dist/core/worker-protocol.js.map +1 -0
  126. package/dist/executors/classify.d.ts +25 -0
  127. package/dist/executors/classify.js +54 -0
  128. package/dist/executors/classify.js.map +1 -0
  129. package/dist/executors/dispatcher.d.ts +35 -0
  130. package/dist/executors/dispatcher.js +68 -0
  131. package/dist/executors/dispatcher.js.map +1 -0
  132. package/dist/executors/env.d.ts +43 -0
  133. package/dist/executors/env.js +66 -0
  134. package/dist/executors/env.js.map +1 -0
  135. package/dist/executors/event-parser.d.ts +41 -0
  136. package/dist/executors/event-parser.js +106 -0
  137. package/dist/executors/event-parser.js.map +1 -0
  138. package/dist/executors/index.d.ts +12 -0
  139. package/dist/executors/index.js +13 -0
  140. package/dist/executors/index.js.map +1 -0
  141. package/dist/executors/log-capture.d.ts +57 -0
  142. package/dist/executors/log-capture.js +155 -0
  143. package/dist/executors/log-capture.js.map +1 -0
  144. package/dist/executors/process-identity.d.ts +124 -0
  145. package/dist/executors/process-identity.js +283 -0
  146. package/dist/executors/process-identity.js.map +1 -0
  147. package/dist/executors/shell-executor.d.ts +35 -0
  148. package/dist/executors/shell-executor.js +478 -0
  149. package/dist/executors/shell-executor.js.map +1 -0
  150. package/dist/executors/stream-redactor.d.ts +40 -0
  151. package/dist/executors/stream-redactor.js +89 -0
  152. package/dist/executors/stream-redactor.js.map +1 -0
  153. package/dist/executors/workspace.d.ts +10 -0
  154. package/dist/executors/workspace.js +47 -0
  155. package/dist/executors/workspace.js.map +1 -0
  156. package/dist/filters/evaluate.d.ts +77 -0
  157. package/dist/filters/evaluate.js +187 -0
  158. package/dist/filters/evaluate.js.map +1 -0
  159. package/dist/filters/fields.d.ts +35 -0
  160. package/dist/filters/fields.js +75 -0
  161. package/dist/filters/fields.js.map +1 -0
  162. package/dist/filters/index.d.ts +11 -0
  163. package/dist/filters/index.js +12 -0
  164. package/dist/filters/index.js.map +1 -0
  165. package/dist/filters/iso-date.d.ts +10 -0
  166. package/dist/filters/iso-date.js +42 -0
  167. package/dist/filters/iso-date.js.map +1 -0
  168. package/dist/filters/predicate.d.ts +24 -0
  169. package/dist/filters/predicate.js +178 -0
  170. package/dist/filters/predicate.js.map +1 -0
  171. package/dist/filters/priority.d.ts +51 -0
  172. package/dist/filters/priority.js +91 -0
  173. package/dist/filters/priority.js.map +1 -0
  174. package/dist/filters/pushdown.d.ts +12 -0
  175. package/dist/filters/pushdown.js +52 -0
  176. package/dist/filters/pushdown.js.map +1 -0
  177. package/dist/filters/shell-check.d.ts +110 -0
  178. package/dist/filters/shell-check.js +304 -0
  179. package/dist/filters/shell-check.js.map +1 -0
  180. package/dist/identities/github-app.d.ts +55 -0
  181. package/dist/identities/github-app.js +160 -0
  182. package/dist/identities/github-app.js.map +1 -0
  183. package/dist/identities/index.d.ts +6 -0
  184. package/dist/identities/index.js +7 -0
  185. package/dist/identities/index.js.map +1 -0
  186. package/dist/identities/jwt.d.ts +24 -0
  187. package/dist/identities/jwt.js +56 -0
  188. package/dist/identities/jwt.js.map +1 -0
  189. package/dist/lifecycle/context.d.ts +106 -0
  190. package/dist/lifecycle/context.js +170 -0
  191. package/dist/lifecycle/context.js.map +1 -0
  192. package/dist/lifecycle/engine.d.ts +185 -0
  193. package/dist/lifecycle/engine.js +551 -0
  194. package/dist/lifecycle/engine.js.map +1 -0
  195. package/dist/lifecycle/errors.d.ts +48 -0
  196. package/dist/lifecycle/errors.js +71 -0
  197. package/dist/lifecycle/errors.js.map +1 -0
  198. package/dist/lifecycle/index.d.ts +9 -0
  199. package/dist/lifecycle/index.js +9 -0
  200. package/dist/lifecycle/index.js.map +1 -0
  201. package/dist/lifecycle/performers.d.ts +66 -0
  202. package/dist/lifecycle/performers.js +179 -0
  203. package/dist/lifecycle/performers.js.map +1 -0
  204. package/dist/notifications/index.d.ts +6 -0
  205. package/dist/notifications/index.js +7 -0
  206. package/dist/notifications/index.js.map +1 -0
  207. package/dist/notifications/service.d.ts +115 -0
  208. package/dist/notifications/service.js +364 -0
  209. package/dist/notifications/service.js.map +1 -0
  210. package/dist/persistence/actions.d.ts +101 -0
  211. package/dist/persistence/actions.js +196 -0
  212. package/dist/persistence/actions.js.map +1 -0
  213. package/dist/persistence/blocks.d.ts +85 -0
  214. package/dist/persistence/blocks.js +140 -0
  215. package/dist/persistence/blocks.js.map +1 -0
  216. package/dist/persistence/cron-firings.d.ts +55 -0
  217. package/dist/persistence/cron-firings.js +70 -0
  218. package/dist/persistence/cron-firings.js.map +1 -0
  219. package/dist/persistence/database.d.ts +60 -0
  220. package/dist/persistence/database.js +124 -0
  221. package/dist/persistence/database.js.map +1 -0
  222. package/dist/persistence/errors.d.ts +39 -0
  223. package/dist/persistence/errors.js +67 -0
  224. package/dist/persistence/errors.js.map +1 -0
  225. package/dist/persistence/events.d.ts +72 -0
  226. package/dist/persistence/events.js +83 -0
  227. package/dist/persistence/events.js.map +1 -0
  228. package/dist/persistence/index.d.ts +18 -0
  229. package/dist/persistence/index.js +19 -0
  230. package/dist/persistence/index.js.map +1 -0
  231. package/dist/persistence/instance-lock.d.ts +26 -0
  232. package/dist/persistence/instance-lock.js +117 -0
  233. package/dist/persistence/instance-lock.js.map +1 -0
  234. package/dist/persistence/lock-keys.d.ts +16 -0
  235. package/dist/persistence/lock-keys.js +41 -0
  236. package/dist/persistence/lock-keys.js.map +1 -0
  237. package/dist/persistence/notifications.d.ts +113 -0
  238. package/dist/persistence/notifications.js +213 -0
  239. package/dist/persistence/notifications.js.map +1 -0
  240. package/dist/persistence/persistence.d.ts +46 -0
  241. package/dist/persistence/persistence.js +42 -0
  242. package/dist/persistence/persistence.js.map +1 -0
  243. package/dist/persistence/retention.d.ts +46 -0
  244. package/dist/persistence/retention.js +68 -0
  245. package/dist/persistence/retention.js.map +1 -0
  246. package/dist/persistence/runs.d.ts +215 -0
  247. package/dist/persistence/runs.js +417 -0
  248. package/dist/persistence/runs.js.map +1 -0
  249. package/dist/persistence/schema.d.ts +16 -0
  250. package/dist/persistence/schema.js +183 -0
  251. package/dist/persistence/schema.js.map +1 -0
  252. package/dist/persistence/sqlite.d.ts +49 -0
  253. package/dist/persistence/sqlite.js +131 -0
  254. package/dist/persistence/sqlite.js.map +1 -0
  255. package/dist/runs/controller.d.ts +307 -0
  256. package/dist/runs/controller.js +1078 -0
  257. package/dist/runs/controller.js.map +1 -0
  258. package/dist/runs/errors.d.ts +20 -0
  259. package/dist/runs/errors.js +26 -0
  260. package/dist/runs/errors.js.map +1 -0
  261. package/dist/runs/event-bus.d.ts +106 -0
  262. package/dist/runs/event-bus.js +105 -0
  263. package/dist/runs/event-bus.js.map +1 -0
  264. package/dist/runs/execution.d.ts +39 -0
  265. package/dist/runs/execution.js +146 -0
  266. package/dist/runs/execution.js.map +1 -0
  267. package/dist/runs/index.d.ts +12 -0
  268. package/dist/runs/index.js +13 -0
  269. package/dist/runs/index.js.map +1 -0
  270. package/dist/runs/retention.d.ts +64 -0
  271. package/dist/runs/retention.js +232 -0
  272. package/dist/runs/retention.js.map +1 -0
  273. package/dist/runs/workspace.d.ts +20 -0
  274. package/dist/runs/workspace.js +51 -0
  275. package/dist/runs/workspace.js.map +1 -0
  276. package/dist/scheduler/checks.d.ts +41 -0
  277. package/dist/scheduler/checks.js +111 -0
  278. package/dist/scheduler/checks.js.map +1 -0
  279. package/dist/scheduler/cron.d.ts +32 -0
  280. package/dist/scheduler/cron.js +83 -0
  281. package/dist/scheduler/cron.js.map +1 -0
  282. package/dist/scheduler/dry-run.d.ts +42 -0
  283. package/dist/scheduler/dry-run.js +167 -0
  284. package/dist/scheduler/dry-run.js.map +1 -0
  285. package/dist/scheduler/errors.d.ts +22 -0
  286. package/dist/scheduler/errors.js +22 -0
  287. package/dist/scheduler/errors.js.map +1 -0
  288. package/dist/scheduler/format.d.ts +3 -0
  289. package/dist/scheduler/format.js +73 -0
  290. package/dist/scheduler/format.js.map +1 -0
  291. package/dist/scheduler/index.d.ts +13 -0
  292. package/dist/scheduler/index.js +14 -0
  293. package/dist/scheduler/index.js.map +1 -0
  294. package/dist/scheduler/planner.d.ts +88 -0
  295. package/dist/scheduler/planner.js +255 -0
  296. package/dist/scheduler/planner.js.map +1 -0
  297. package/dist/scheduler/scheduler.d.ts +160 -0
  298. package/dist/scheduler/scheduler.js +680 -0
  299. package/dist/scheduler/scheduler.js.map +1 -0
  300. package/dist/scheduler/state.d.ts +26 -0
  301. package/dist/scheduler/state.js +25 -0
  302. package/dist/scheduler/state.js.map +1 -0
  303. package/dist/scheduler/types.d.ts +197 -0
  304. package/dist/scheduler/types.js +3 -0
  305. package/dist/scheduler/types.js.map +1 -0
  306. package/dist/service/index.d.ts +6 -0
  307. package/dist/service/index.js +7 -0
  308. package/dist/service/index.js.map +1 -0
  309. package/dist/service/install.d.ts +93 -0
  310. package/dist/service/install.js +250 -0
  311. package/dist/service/install.js.map +1 -0
  312. package/dist/service/unit.d.ts +50 -0
  313. package/dist/service/unit.js +171 -0
  314. package/dist/service/unit.js.map +1 -0
  315. package/dist/sources/github/adapter.d.ts +99 -0
  316. package/dist/sources/github/adapter.js +336 -0
  317. package/dist/sources/github/adapter.js.map +1 -0
  318. package/dist/sources/github/fields.d.ts +8 -0
  319. package/dist/sources/github/fields.js +72 -0
  320. package/dist/sources/github/fields.js.map +1 -0
  321. package/dist/sources/github/http.d.ts +100 -0
  322. package/dist/sources/github/http.js +288 -0
  323. package/dist/sources/github/http.js.map +1 -0
  324. package/dist/sources/github/index.d.ts +6 -0
  325. package/dist/sources/github/index.js +7 -0
  326. package/dist/sources/github/index.js.map +1 -0
  327. package/dist/sources/github/pushdown.d.ts +14 -0
  328. package/dist/sources/github/pushdown.js +47 -0
  329. package/dist/sources/github/pushdown.js.map +1 -0
  330. package/dist/sources/github/refs.d.ts +17 -0
  331. package/dist/sources/github/refs.js +61 -0
  332. package/dist/sources/github/refs.js.map +1 -0
  333. package/dist/sources/index.d.ts +6 -0
  334. package/dist/sources/index.js +7 -0
  335. package/dist/sources/index.js.map +1 -0
  336. package/dist/ui/index.d.ts +12 -0
  337. package/dist/ui/index.js +28 -0
  338. package/dist/ui/index.js.map +1 -0
  339. package/dist/ui/public/app.css +239 -0
  340. package/dist/ui/public/favicon.svg +1 -0
  341. package/dist/ui/public/index.html +27 -0
  342. package/dist/ui/public/js/client/api.js +68 -0
  343. package/dist/ui/public/js/client/app.js +146 -0
  344. package/dist/ui/public/js/client/dom.js +34 -0
  345. package/dist/ui/public/js/client/page.js +9 -0
  346. package/dist/ui/public/js/client/pages/actions.js +35 -0
  347. package/dist/ui/public/js/client/pages/blocked.js +58 -0
  348. package/dist/ui/public/js/client/pages/config.js +126 -0
  349. package/dist/ui/public/js/client/pages/dashboard.js +175 -0
  350. package/dist/ui/public/js/client/pages/run.js +367 -0
  351. package/dist/ui/public/js/client/pages/runs.js +72 -0
  352. package/dist/ui/public/js/client/pages/views.js +80 -0
  353. package/dist/ui/public/js/client/pages/workflows.js +291 -0
  354. package/dist/ui/public/js/client/types.js +1 -0
  355. package/dist/ui/public/js/client/ui.js +152 -0
  356. package/dist/ui/public/js/shared/dom.js +77 -0
  357. package/dist/ui/public/js/shared/events.js +88 -0
  358. package/dist/ui/public/js/shared/format.js +213 -0
  359. package/dist/ui/public/js/shared/logs.js +136 -0
  360. package/dist/ui/public/js/shared/params.js +90 -0
  361. package/dist/ui/public/js/shared/route.js +36 -0
  362. package/dist/ui/public/js/shared/sort.js +70 -0
  363. package/dist/ui/public/js/shared/sse.js +193 -0
  364. package/dist/ui/public/js/shared/throttle.js +65 -0
  365. package/dist/views/index.d.ts +6 -0
  366. package/dist/views/index.js +7 -0
  367. package/dist/views/index.js.map +1 -0
  368. package/dist/views/service.d.ts +131 -0
  369. package/dist/views/service.js +264 -0
  370. package/dist/views/service.js.map +1 -0
  371. package/docs/api.md +239 -0
  372. package/docs/configuration-reference.md +1785 -0
  373. package/docs/cron-semantics.md +96 -0
  374. package/docs/manual-tests/github-fixture/config.yaml +30 -0
  375. package/docs/manual-tests/github-fixture/helpers.sh +93 -0
  376. package/docs/manual-tests/github-fixture/identities.yaml +18 -0
  377. package/docs/manual-tests/github-fixture/scripts/test-worker.sh +100 -0
  378. package/docs/manual-tests/github-fixture/sources.yaml +13 -0
  379. package/docs/manual-tests/github-fixture/views/dependencies.yaml +17 -0
  380. package/docs/manual-tests/github-fixture/views/needs-maintainer.yaml +17 -0
  381. package/docs/manual-tests/github-fixture/workflows/coder-test.yaml +76 -0
  382. package/docs/manual-tests/github-fixture/workflows/commenter-test.yaml +63 -0
  383. package/docs/manual-tests/github-fixture/workflows/reviewer-test.yaml +60 -0
  384. package/docs/manual-tests/github.md +1308 -0
  385. package/docs/manual-tests/process-supervision.md +64 -0
  386. package/docs/manual-tests/systemd.md +68 -0
  387. package/docs/operations.md +271 -0
  388. package/docs/worker-protocol.md +145 -0
  389. package/examples/config/config.yaml +35 -0
  390. package/examples/config/identities.yaml +18 -0
  391. package/examples/config/sources.yaml +13 -0
  392. package/examples/config/views/needs-maintainer.yaml +15 -0
  393. package/examples/config/workflows/coder-project-a.yaml +86 -0
  394. package/examples/config/workflows/deploy.yaml +15 -0
  395. package/examples/config/workflows/nightly-maintenance.yaml +13 -0
  396. 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
+ };