plum-e2e 1.1.1 → 1.2.1

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 (334) hide show
  1. package/README.md +142 -70
  2. package/backend/Dockerfile +4 -2
  3. package/backend/_scaffold/utils/browser.ts +1 -0
  4. package/backend/app.js +4 -2
  5. package/backend/config/scripts/generate-report.js +38 -30
  6. package/{.prettierignore → backend/entrypoint.sh} +7 -6
  7. package/backend/package-lock.json +453 -10
  8. package/backend/package.json +5 -2
  9. package/backend/prisma/migrations/20260614000000_init/migration.sql +35 -0
  10. package/backend/prisma/migrations/20260614000001_add_project/migration.sql +8 -0
  11. package/backend/prisma/migrations/migration_lock.toml +3 -0
  12. package/backend/prisma/schema.prisma +53 -0
  13. package/backend/routes/backup.routes.js +50 -0
  14. package/backend/routes/cron.routes.js +9 -60
  15. package/backend/routes/reports.routes.js +39 -6
  16. package/backend/routes/settings.routes.js +43 -0
  17. package/backend/server.js +52 -1
  18. package/backend/services/backupService.js +88 -0
  19. package/backend/services/cronService.js +68 -78
  20. package/backend/{routes/schedules.routes.js → services/prisma.js} +3 -13
  21. package/backend/services/reportService.js +48 -20
  22. package/backend/services/settingsService.js +36 -0
  23. package/bin/plum.js +216 -34
  24. package/docker-compose.yml +24 -0
  25. package/frontend/.svelte-kit/adapter-node/.vite/manifest.json +190 -0
  26. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/Badge.DLLowvEA.css +17 -0
  27. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/Button.cBruH0aD.css +17 -0
  28. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_layout.D7eM-6MV.css +17 -0
  29. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.BVnUajEa.css +17 -0
  30. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.CGnCsn5q.css +17 -0
  31. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.DBhBrHFz.css +17 -0
  32. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.DOqo0UR4.css +17 -0
  33. package/frontend/.svelte-kit/adapter-node/chunks/Badge.js +30 -0
  34. package/frontend/.svelte-kit/adapter-node/chunks/Button.js +32 -0
  35. package/frontend/.svelte-kit/adapter-node/chunks/attributes.js +34 -0
  36. package/frontend/.svelte-kit/adapter-node/chunks/client.js +59 -0
  37. package/{backend/services/scheduleService.js → frontend/.svelte-kit/adapter-node/chunks/equality.js} +13 -16
  38. package/frontend/.svelte-kit/adapter-node/chunks/escaping.js +36 -0
  39. package/frontend/.svelte-kit/adapter-node/chunks/exports.js +247 -0
  40. package/frontend/.svelte-kit/adapter-node/chunks/index.js +1135 -0
  41. package/frontend/.svelte-kit/adapter-node/chunks/internal.js +695 -0
  42. package/frontend/.svelte-kit/adapter-node/chunks/reports.js +57 -0
  43. package/frontend/.svelte-kit/adapter-node/entries/fallbacks/error.svelte.js +44 -0
  44. package/frontend/.svelte-kit/adapter-node/entries/pages/_layout.svelte.js +181 -0
  45. package/frontend/.svelte-kit/adapter-node/entries/pages/_page.svelte.js +164 -0
  46. package/frontend/.svelte-kit/adapter-node/entries/pages/reports/_page.svelte.js +141 -0
  47. package/frontend/.svelte-kit/adapter-node/entries/pages/reports/_slug_/_page.svelte.js +42 -0
  48. package/frontend/.svelte-kit/adapter-node/entries/pages/scheduled-tests/_page.svelte.js +179 -0
  49. package/frontend/.svelte-kit/adapter-node/index.js +3144 -0
  50. package/frontend/.svelte-kit/adapter-node/internal.js +30 -0
  51. package/frontend/.svelte-kit/adapter-node/manifest-full.js +77 -0
  52. package/frontend/.svelte-kit/adapter-node/manifest.js +81 -0
  53. package/frontend/.svelte-kit/adapter-node/nodes/0.js +25 -0
  54. package/frontend/.svelte-kit/adapter-node/nodes/1.js +25 -0
  55. package/frontend/.svelte-kit/adapter-node/nodes/2.js +25 -0
  56. package/frontend/.svelte-kit/adapter-node/nodes/3.js +25 -0
  57. package/frontend/.svelte-kit/adapter-node/nodes/4.js +25 -0
  58. package/frontend/.svelte-kit/adapter-node/nodes/5.js +25 -0
  59. package/frontend/.svelte-kit/ambient.d.ts +240 -0
  60. package/frontend/.svelte-kit/generated/client/app.js +51 -0
  61. package/frontend/.svelte-kit/generated/client/matchers.js +18 -0
  62. package/frontend/.svelte-kit/generated/client/nodes/0.js +18 -0
  63. package/frontend/.svelte-kit/generated/client/nodes/1.js +18 -0
  64. package/frontend/.svelte-kit/generated/client/nodes/2.js +18 -0
  65. package/frontend/.svelte-kit/generated/client/nodes/3.js +18 -0
  66. package/frontend/.svelte-kit/generated/client/nodes/4.js +18 -0
  67. package/frontend/.svelte-kit/generated/client/nodes/5.js +18 -0
  68. package/frontend/.svelte-kit/generated/client-optimized/app.js +51 -0
  69. package/frontend/.svelte-kit/generated/client-optimized/matchers.js +18 -0
  70. package/frontend/.svelte-kit/generated/client-optimized/nodes/0.js +18 -0
  71. package/frontend/.svelte-kit/generated/client-optimized/nodes/1.js +18 -0
  72. package/frontend/.svelte-kit/generated/client-optimized/nodes/2.js +18 -0
  73. package/frontend/.svelte-kit/generated/client-optimized/nodes/3.js +18 -0
  74. package/frontend/.svelte-kit/generated/client-optimized/nodes/4.js +18 -0
  75. package/frontend/.svelte-kit/generated/client-optimized/nodes/5.js +18 -0
  76. package/frontend/.svelte-kit/generated/root.js +20 -0
  77. package/frontend/.svelte-kit/generated/root.svelte +83 -0
  78. package/frontend/.svelte-kit/generated/server/internal.js +65 -0
  79. package/frontend/.svelte-kit/non-ambient.d.ts +42 -0
  80. package/frontend/.svelte-kit/output/client/.vite/manifest.json +289 -0
  81. package/frontend/.svelte-kit/output/client/_app/immutable/assets/0.CnXRuPt4.css +17 -0
  82. package/frontend/.svelte-kit/output/client/_app/immutable/assets/2.CGnCsn5q.css +17 -0
  83. package/frontend/.svelte-kit/output/client/_app/immutable/assets/3.BVnUajEa.css +17 -0
  84. package/frontend/.svelte-kit/output/client/_app/immutable/assets/4.DBhBrHFz.css +17 -0
  85. package/frontend/.svelte-kit/output/client/_app/immutable/assets/5.D93VAB-w.css +17 -0
  86. package/frontend/.svelte-kit/output/client/_app/immutable/assets/Badge.DLLowvEA.css +17 -0
  87. package/frontend/.svelte-kit/output/client/_app/immutable/assets/Button.cBruH0aD.css +17 -0
  88. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_layout.D7eM-6MV.css +17 -0
  89. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.BVnUajEa.css +17 -0
  90. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.CGnCsn5q.css +17 -0
  91. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.DBhBrHFz.css +17 -0
  92. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.DOqo0UR4.css +17 -0
  93. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/B0bn91RF.js +18 -0
  94. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/B1kE5jmt.js +23 -0
  95. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BKz6FZdB.js +18 -0
  96. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/Be4iUAB0.js +18 -0
  97. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BpaMqo9O.js +18 -0
  98. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/Bt3-Z7H1.js +18 -0
  99. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CHV2KnPr.js +18 -0
  100. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CJ8q7g9Y.js +18 -0
  101. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/Cit9MuSg.js +20 -0
  102. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CwBTqcbj.js +18 -0
  103. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DBk3zeq4.js +18 -0
  104. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DFJLQFic.js +18 -0
  105. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DTUE7kkY.js +18 -0
  106. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/IYPwzKs_.js +20 -0
  107. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/oc0we5Us.js +18 -0
  108. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/rVEKg0Ak.js +18 -0
  109. package/frontend/.svelte-kit/output/client/_app/immutable/entry/app.C2MbX06K.js +19 -0
  110. package/frontend/.svelte-kit/output/client/_app/immutable/entry/start.GiBry-lw.js +18 -0
  111. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/0.BsCYK-Tn.js +21 -0
  112. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/1.N4gjrCnX.js +18 -0
  113. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/2.D9yQNzK8.js +18 -0
  114. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/3.CBC5WvNb.js +18 -0
  115. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/4.BU6UZHSE.js +19 -0
  116. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/5.Dzzfrntl.js +18 -0
  117. package/frontend/.svelte-kit/output/client/_app/version.json +1 -0
  118. package/frontend/.svelte-kit/output/client/favicon.png +0 -0
  119. package/frontend/.svelte-kit/output/server/.vite/manifest.json +190 -0
  120. package/frontend/.svelte-kit/output/server/_app/immutable/assets/Badge.DLLowvEA.css +17 -0
  121. package/frontend/.svelte-kit/output/server/_app/immutable/assets/Button.cBruH0aD.css +17 -0
  122. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_layout.D7eM-6MV.css +17 -0
  123. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.BVnUajEa.css +17 -0
  124. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.CGnCsn5q.css +17 -0
  125. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.DBhBrHFz.css +17 -0
  126. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.DOqo0UR4.css +17 -0
  127. package/frontend/.svelte-kit/output/server/chunks/Badge.js +30 -0
  128. package/frontend/.svelte-kit/output/server/chunks/Button.js +32 -0
  129. package/frontend/.svelte-kit/output/server/chunks/attributes.js +34 -0
  130. package/frontend/.svelte-kit/output/server/chunks/client.js +59 -0
  131. package/frontend/.svelte-kit/output/server/chunks/equality.js +31 -0
  132. package/frontend/.svelte-kit/output/server/chunks/escaping.js +36 -0
  133. package/frontend/.svelte-kit/output/server/chunks/exports.js +247 -0
  134. package/frontend/.svelte-kit/output/server/chunks/index.js +1135 -0
  135. package/frontend/.svelte-kit/output/server/chunks/internal.js +695 -0
  136. package/frontend/.svelte-kit/output/server/chunks/reports.js +57 -0
  137. package/frontend/.svelte-kit/output/server/entries/fallbacks/error.svelte.js +44 -0
  138. package/frontend/.svelte-kit/output/server/entries/pages/_layout.svelte.js +181 -0
  139. package/frontend/.svelte-kit/output/server/entries/pages/_page.svelte.js +164 -0
  140. package/frontend/.svelte-kit/output/server/entries/pages/reports/_page.svelte.js +141 -0
  141. package/frontend/.svelte-kit/output/server/entries/pages/reports/_slug_/_page.svelte.js +42 -0
  142. package/frontend/.svelte-kit/output/server/entries/pages/scheduled-tests/_page.svelte.js +179 -0
  143. package/frontend/.svelte-kit/output/server/index.js +3144 -0
  144. package/frontend/.svelte-kit/output/server/internal.js +30 -0
  145. package/frontend/.svelte-kit/output/server/manifest-full.js +77 -0
  146. package/frontend/.svelte-kit/output/server/manifest.js +77 -0
  147. package/frontend/.svelte-kit/output/server/nodes/0.js +25 -0
  148. package/frontend/.svelte-kit/output/server/nodes/1.js +25 -0
  149. package/frontend/.svelte-kit/output/server/nodes/2.js +25 -0
  150. package/frontend/.svelte-kit/output/server/nodes/3.js +25 -0
  151. package/frontend/.svelte-kit/output/server/nodes/4.js +25 -0
  152. package/frontend/.svelte-kit/output/server/nodes/5.js +25 -0
  153. package/frontend/.svelte-kit/tsconfig.json +49 -0
  154. package/frontend/build/client/_app/immutable/assets/0.CnXRuPt4.css +17 -0
  155. package/frontend/build/client/_app/immutable/assets/0.CnXRuPt4.css.br +0 -0
  156. package/frontend/build/client/_app/immutable/assets/0.CnXRuPt4.css.gz +0 -0
  157. package/frontend/build/client/_app/immutable/assets/2.CGnCsn5q.css +17 -0
  158. package/frontend/build/client/_app/immutable/assets/2.CGnCsn5q.css.br +0 -0
  159. package/frontend/build/client/_app/immutable/assets/2.CGnCsn5q.css.gz +0 -0
  160. package/frontend/build/client/_app/immutable/assets/3.BVnUajEa.css +17 -0
  161. package/frontend/build/client/_app/immutable/assets/3.BVnUajEa.css.br +0 -0
  162. package/frontend/build/client/_app/immutable/assets/3.BVnUajEa.css.gz +0 -0
  163. package/frontend/build/client/_app/immutable/assets/4.DBhBrHFz.css +17 -0
  164. package/frontend/build/client/_app/immutable/assets/4.DBhBrHFz.css.br +0 -0
  165. package/frontend/build/client/_app/immutable/assets/4.DBhBrHFz.css.gz +0 -0
  166. package/frontend/build/client/_app/immutable/assets/5.D93VAB-w.css +17 -0
  167. package/frontend/build/client/_app/immutable/assets/5.D93VAB-w.css.br +0 -0
  168. package/frontend/build/client/_app/immutable/assets/5.D93VAB-w.css.gz +0 -0
  169. package/frontend/build/client/_app/immutable/assets/Badge.DLLowvEA.css +17 -0
  170. package/frontend/build/client/_app/immutable/assets/Badge.DLLowvEA.css.br +0 -0
  171. package/frontend/build/client/_app/immutable/assets/Badge.DLLowvEA.css.gz +0 -0
  172. package/frontend/build/client/_app/immutable/assets/Button.cBruH0aD.css +17 -0
  173. package/frontend/build/client/_app/immutable/assets/Button.cBruH0aD.css.br +0 -0
  174. package/frontend/build/client/_app/immutable/assets/Button.cBruH0aD.css.gz +0 -0
  175. package/frontend/build/client/_app/immutable/assets/_layout.D7eM-6MV.css +17 -0
  176. package/frontend/build/client/_app/immutable/assets/_layout.D7eM-6MV.css.br +0 -0
  177. package/frontend/build/client/_app/immutable/assets/_layout.D7eM-6MV.css.gz +0 -0
  178. package/frontend/build/client/_app/immutable/assets/_page.BVnUajEa.css +17 -0
  179. package/frontend/build/client/_app/immutable/assets/_page.BVnUajEa.css.br +0 -0
  180. package/frontend/build/client/_app/immutable/assets/_page.BVnUajEa.css.gz +0 -0
  181. package/frontend/build/client/_app/immutable/assets/_page.CGnCsn5q.css +17 -0
  182. package/frontend/build/client/_app/immutable/assets/_page.CGnCsn5q.css.br +0 -0
  183. package/frontend/build/client/_app/immutable/assets/_page.CGnCsn5q.css.gz +0 -0
  184. package/frontend/build/client/_app/immutable/assets/_page.DBhBrHFz.css +17 -0
  185. package/frontend/build/client/_app/immutable/assets/_page.DBhBrHFz.css.br +0 -0
  186. package/frontend/build/client/_app/immutable/assets/_page.DBhBrHFz.css.gz +0 -0
  187. package/frontend/build/client/_app/immutable/assets/_page.DOqo0UR4.css +17 -0
  188. package/frontend/build/client/_app/immutable/assets/_page.DOqo0UR4.css.br +0 -0
  189. package/frontend/build/client/_app/immutable/assets/_page.DOqo0UR4.css.gz +0 -0
  190. package/frontend/build/client/_app/immutable/chunks/B0bn91RF.js +18 -0
  191. package/frontend/build/client/_app/immutable/chunks/B0bn91RF.js.br +0 -0
  192. package/frontend/build/client/_app/immutable/chunks/B0bn91RF.js.gz +0 -0
  193. package/frontend/build/client/_app/immutable/chunks/B1kE5jmt.js +23 -0
  194. package/frontend/build/client/_app/immutable/chunks/B1kE5jmt.js.br +0 -0
  195. package/frontend/build/client/_app/immutable/chunks/B1kE5jmt.js.gz +0 -0
  196. package/frontend/build/client/_app/immutable/chunks/BKz6FZdB.js +18 -0
  197. package/frontend/build/client/_app/immutable/chunks/BKz6FZdB.js.br +0 -0
  198. package/frontend/build/client/_app/immutable/chunks/BKz6FZdB.js.gz +0 -0
  199. package/frontend/build/client/_app/immutable/chunks/Be4iUAB0.js +18 -0
  200. package/frontend/build/client/_app/immutable/chunks/Be4iUAB0.js.br +0 -0
  201. package/frontend/build/client/_app/immutable/chunks/Be4iUAB0.js.gz +0 -0
  202. package/frontend/build/client/_app/immutable/chunks/BpaMqo9O.js +18 -0
  203. package/frontend/build/client/_app/immutable/chunks/BpaMqo9O.js.br +0 -0
  204. package/frontend/build/client/_app/immutable/chunks/BpaMqo9O.js.gz +0 -0
  205. package/frontend/build/client/_app/immutable/chunks/Bt3-Z7H1.js +18 -0
  206. package/frontend/build/client/_app/immutable/chunks/Bt3-Z7H1.js.br +0 -0
  207. package/frontend/build/client/_app/immutable/chunks/Bt3-Z7H1.js.gz +0 -0
  208. package/frontend/build/client/_app/immutable/chunks/CHV2KnPr.js +18 -0
  209. package/frontend/build/client/_app/immutable/chunks/CHV2KnPr.js.br +18 -0
  210. package/frontend/build/client/_app/immutable/chunks/CHV2KnPr.js.gz +0 -0
  211. package/frontend/build/client/_app/immutable/chunks/CJ8q7g9Y.js +18 -0
  212. package/{resources/comments-format.text → frontend/build/client/_app/immutable/chunks/CJ8q7g9Y.js.br} +0 -0
  213. package/frontend/build/client/_app/immutable/chunks/CJ8q7g9Y.js.gz +0 -0
  214. package/frontend/build/client/_app/immutable/chunks/Cit9MuSg.js +20 -0
  215. package/frontend/build/client/_app/immutable/chunks/Cit9MuSg.js.br +0 -0
  216. package/frontend/build/client/_app/immutable/chunks/Cit9MuSg.js.gz +0 -0
  217. package/frontend/build/client/_app/immutable/chunks/CwBTqcbj.js +18 -0
  218. package/frontend/build/client/_app/immutable/chunks/CwBTqcbj.js.br +0 -0
  219. package/frontend/build/client/_app/immutable/chunks/CwBTqcbj.js.gz +0 -0
  220. package/frontend/build/client/_app/immutable/chunks/DBk3zeq4.js +18 -0
  221. package/frontend/build/client/_app/immutable/chunks/DBk3zeq4.js.br +0 -0
  222. package/frontend/build/client/_app/immutable/chunks/DBk3zeq4.js.gz +0 -0
  223. package/frontend/build/client/_app/immutable/chunks/DFJLQFic.js +18 -0
  224. package/frontend/build/client/_app/immutable/chunks/DFJLQFic.js.br +0 -0
  225. package/frontend/build/client/_app/immutable/chunks/DFJLQFic.js.gz +0 -0
  226. package/frontend/build/client/_app/immutable/chunks/DTUE7kkY.js +18 -0
  227. package/frontend/build/client/_app/immutable/chunks/DTUE7kkY.js.br +0 -0
  228. package/frontend/build/client/_app/immutable/chunks/DTUE7kkY.js.gz +0 -0
  229. package/frontend/build/client/_app/immutable/chunks/IYPwzKs_.js +20 -0
  230. package/frontend/build/client/_app/immutable/chunks/IYPwzKs_.js.br +0 -0
  231. package/frontend/build/client/_app/immutable/chunks/IYPwzKs_.js.gz +0 -0
  232. package/frontend/build/client/_app/immutable/chunks/oc0we5Us.js +18 -0
  233. package/frontend/build/client/_app/immutable/chunks/oc0we5Us.js.br +0 -0
  234. package/frontend/build/client/_app/immutable/chunks/oc0we5Us.js.gz +0 -0
  235. package/frontend/build/client/_app/immutable/chunks/rVEKg0Ak.js +18 -0
  236. package/frontend/build/client/_app/immutable/chunks/rVEKg0Ak.js.br +0 -0
  237. package/frontend/build/client/_app/immutable/chunks/rVEKg0Ak.js.gz +0 -0
  238. package/frontend/build/client/_app/immutable/entry/app.C2MbX06K.js +19 -0
  239. package/frontend/build/client/_app/immutable/entry/app.C2MbX06K.js.br +0 -0
  240. package/frontend/build/client/_app/immutable/entry/app.C2MbX06K.js.gz +0 -0
  241. package/frontend/build/client/_app/immutable/entry/start.GiBry-lw.js +18 -0
  242. package/frontend/build/client/_app/immutable/entry/start.GiBry-lw.js.br +18 -0
  243. package/frontend/build/client/_app/immutable/entry/start.GiBry-lw.js.gz +0 -0
  244. package/frontend/build/client/_app/immutable/nodes/0.BsCYK-Tn.js +21 -0
  245. package/frontend/build/client/_app/immutable/nodes/0.BsCYK-Tn.js.br +0 -0
  246. package/frontend/build/client/_app/immutable/nodes/0.BsCYK-Tn.js.gz +0 -0
  247. package/frontend/build/client/_app/immutable/nodes/1.N4gjrCnX.js +18 -0
  248. package/frontend/build/client/_app/immutable/nodes/1.N4gjrCnX.js.br +0 -0
  249. package/frontend/build/client/_app/immutable/nodes/1.N4gjrCnX.js.gz +0 -0
  250. package/frontend/build/client/_app/immutable/nodes/2.D9yQNzK8.js +18 -0
  251. package/frontend/build/client/_app/immutable/nodes/2.D9yQNzK8.js.br +0 -0
  252. package/frontend/build/client/_app/immutable/nodes/2.D9yQNzK8.js.gz +0 -0
  253. package/frontend/build/client/_app/immutable/nodes/3.CBC5WvNb.js +18 -0
  254. package/frontend/build/client/_app/immutable/nodes/3.CBC5WvNb.js.br +0 -0
  255. package/frontend/build/client/_app/immutable/nodes/3.CBC5WvNb.js.gz +0 -0
  256. package/frontend/build/client/_app/immutable/nodes/4.BU6UZHSE.js +19 -0
  257. package/frontend/build/client/_app/immutable/nodes/4.BU6UZHSE.js.br +0 -0
  258. package/frontend/build/client/_app/immutable/nodes/4.BU6UZHSE.js.gz +0 -0
  259. package/frontend/build/client/_app/immutable/nodes/5.Dzzfrntl.js +18 -0
  260. package/frontend/build/client/_app/immutable/nodes/5.Dzzfrntl.js.br +0 -0
  261. package/frontend/build/client/_app/immutable/nodes/5.Dzzfrntl.js.gz +0 -0
  262. package/frontend/build/client/_app/version.json +1 -0
  263. package/frontend/build/client/_app/version.json.br +0 -0
  264. package/frontend/build/client/_app/version.json.gz +0 -0
  265. package/frontend/build/client/favicon.png +0 -0
  266. package/frontend/build/env.js +111 -0
  267. package/frontend/build/handler.js +1511 -0
  268. package/frontend/build/index.js +362 -0
  269. package/frontend/build/server/chunks/0-BFfqmlsS.js +26 -0
  270. package/frontend/build/server/chunks/0-BFfqmlsS.js.map +17 -0
  271. package/frontend/build/server/chunks/1-hso3H4Ax.js +26 -0
  272. package/frontend/build/server/chunks/1-hso3H4Ax.js.map +17 -0
  273. package/frontend/build/server/chunks/2-BSZWXCWp.js +26 -0
  274. package/frontend/build/server/chunks/2-BSZWXCWp.js.map +17 -0
  275. package/frontend/build/server/chunks/3-BYumY9dv.js +26 -0
  276. package/frontend/build/server/chunks/3-BYumY9dv.js.map +17 -0
  277. package/frontend/build/server/chunks/4-2UUrCVtM.js +26 -0
  278. package/frontend/build/server/chunks/4-2UUrCVtM.js.map +17 -0
  279. package/frontend/build/server/chunks/5-C2_y_fwT.js +26 -0
  280. package/frontend/build/server/chunks/5-C2_y_fwT.js.map +17 -0
  281. package/frontend/build/server/chunks/Badge-CC4U7C0c.js +30 -0
  282. package/frontend/build/server/chunks/Badge-CC4U7C0c.js.map +17 -0
  283. package/frontend/build/server/chunks/Button-BLkPicwS.js +33 -0
  284. package/frontend/build/server/chunks/Button-BLkPicwS.js.map +17 -0
  285. package/frontend/build/server/chunks/_layout.svelte-Bfxcj9C6.js +183 -0
  286. package/frontend/build/server/chunks/_layout.svelte-Bfxcj9C6.js.map +17 -0
  287. package/frontend/build/server/chunks/_page.svelte-DEK6UvaI.js +45 -0
  288. package/frontend/build/server/chunks/_page.svelte-DEK6UvaI.js.map +17 -0
  289. package/frontend/build/server/chunks/_page.svelte-DScVdHOq.js +142 -0
  290. package/frontend/build/server/chunks/_page.svelte-DScVdHOq.js.map +17 -0
  291. package/frontend/build/server/chunks/_page.svelte-Ns7w2sZt.js +180 -0
  292. package/frontend/build/server/chunks/_page.svelte-Ns7w2sZt.js.map +17 -0
  293. package/frontend/build/server/chunks/_page.svelte-s6ZIOitT.js +165 -0
  294. package/frontend/build/server/chunks/_page.svelte-s6ZIOitT.js.map +17 -0
  295. package/frontend/build/server/chunks/attributes-BeaNKpgU.js +34 -0
  296. package/frontend/build/server/chunks/attributes-BeaNKpgU.js.map +17 -0
  297. package/frontend/build/server/chunks/client-C9IaEwNo.js +52 -0
  298. package/frontend/build/server/chunks/client-C9IaEwNo.js.map +17 -0
  299. package/frontend/build/server/chunks/error.svelte-HKBv5sQT.js +45 -0
  300. package/frontend/build/server/chunks/error.svelte-HKBv5sQT.js.map +17 -0
  301. package/frontend/build/server/chunks/escaping-CqgfEcN3.js +36 -0
  302. package/frontend/build/server/chunks/escaping-CqgfEcN3.js.map +17 -0
  303. package/frontend/build/server/chunks/exports-OxlCJ8yE.js +192 -0
  304. package/frontend/build/server/chunks/exports-OxlCJ8yE.js.map +17 -0
  305. package/frontend/build/server/chunks/index-CjHlq1kt.js +1076 -0
  306. package/frontend/build/server/chunks/index-CjHlq1kt.js.map +17 -0
  307. package/frontend/build/server/chunks/reports-D3ZeYSmi.js +56 -0
  308. package/frontend/build/server/chunks/reports-D3ZeYSmi.js.map +17 -0
  309. package/frontend/build/server/index.js +5015 -0
  310. package/frontend/build/server/index.js.map +17 -0
  311. package/frontend/build/server/manifest.js +84 -0
  312. package/frontend/build/server/manifest.js.map +17 -0
  313. package/frontend/build/shims.js +49 -0
  314. package/frontend/package-lock.json +2 -2
  315. package/frontend/package.json +1 -1
  316. package/frontend/src/lib/api/reports.js +38 -27
  317. package/frontend/src/lib/api/schedules.js +9 -25
  318. package/frontend/src/lib/api/settings.js +48 -0
  319. package/frontend/src/lib/components/layout/Nav.svelte +2 -1
  320. package/frontend/src/lib/components/layout/RunnerPanel.svelte +160 -21
  321. package/frontend/src/lib/components/ui/Terminal.svelte +2 -2
  322. package/frontend/src/lib/stores/runner.js +9 -0
  323. package/frontend/src/routes/+page.svelte +10 -3
  324. package/frontend/src/routes/reports/+page.svelte +342 -51
  325. package/frontend/src/routes/reports/[slug]/+page.svelte +2 -0
  326. package/frontend/src/routes/scheduled-tests/+page.svelte +247 -11
  327. package/frontend/src/routes/settings/+page.svelte +410 -0
  328. package/package.json +6 -2
  329. package/.claude/settings.local.json +0 -27
  330. package/.husky/pre-commit +0 -2
  331. package/.prettierrc +0 -15
  332. package/.vscode/settings.json +0 -10
  333. package/backend/config/scripts/create-settings.js +0 -53
  334. package/license-config.json +0 -37
@@ -18,17 +18,27 @@
18
18
  <script>
19
19
  import { onMount } from 'svelte';
20
20
  import { fly } from 'svelte/transition';
21
- import { fetchSchedules, fetchCronJobs, saveCronJob, deleteCronJob } from '$lib/api/schedules';
21
+ import { fetchCronJobs, saveCronJob, deleteCronJob } from '$lib/api/schedules';
22
+ import { activeCronJobs } from '$lib/stores/runner';
22
23
  import Button from '$lib/components/ui/Button.svelte';
23
24
  import Badge from '$lib/components/ui/Badge.svelte';
24
25
  import Modal from '$lib/components/ui/Modal.svelte';
25
26
 
26
27
  const CRON_REGEX =
27
- /^(\*|([0-5]?[0-9])) (\*|([01]?[0-9]|2[0-3])) (\*|([01]?[0-9]|3[01])) (\*|([1-9]|1[0-2])) (\*|[0-6])$/;
28
+ /^(\*|([0-5]?[0-9])|\*\/[0-9]+) (\*|([01]?[0-9]|2[0-3])) (\*|([01]?[0-9]|3[01])) (\*|([1-9]|1[0-2])) (\*|[0-6](-[0-6])?)$/;
29
+
30
+ const CUSTOM_SENTINEL = '__custom__';
31
+ const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
32
+
33
+ const scheduleOptions = [
34
+ { label: 'Every minute', value: '* * * * *' },
35
+ { label: 'Every hour', value: '0 * * * *' },
36
+ { label: 'Every midnight', value: '0 0 * * *' },
37
+ { label: 'Every Sunday', value: '0 0 * * 0' }
38
+ ];
28
39
 
29
40
  let cronJobs = [];
30
- let scheduleOptions = [];
31
- let toast = null; // { type: 'success' | 'error', message }
41
+ let toast = null;
32
42
 
33
43
  let modalOpen = false;
34
44
  let deleteModalOpen = false;
@@ -36,18 +46,68 @@
36
46
  let editTaskName = '';
37
47
  let taskToDelete = '';
38
48
 
39
- let form = { taskName: '', cronExpression: '', tags: '' };
49
+ const WORKER_OPTIONS = [1, 2, 4, 8];
50
+
51
+ let form = { taskName: '', cronExpression: '', tags: '', workers: 1 };
52
+ let selectedSchedule = '';
53
+ let useCustomCron = false;
40
54
  let formError = '';
41
55
 
56
+ function describeCron(expr) {
57
+ if (!expr) return '';
58
+ const parts = expr.trim().split(/\s+/);
59
+ if (parts.length !== 5) return '';
60
+ const [min, hour, dom, month, dow] = parts;
61
+
62
+ const fmt = (h, m) => {
63
+ const ap = h >= 12 ? 'PM' : 'AM';
64
+ return `${h % 12 || 12}:${String(m).padStart(2, '0')} ${ap}`;
65
+ };
66
+ const isNum = (s) => /^\d+$/.test(s);
67
+ const h = isNum(hour) ? +hour : null;
68
+ const m = isNum(min) ? +min : null;
69
+
70
+ if (expr === '* * * * *') return 'Every minute';
71
+ const everyN = min.match(/^\*\/(\d+)$/);
72
+ if (everyN && hour === '*' && dom === '*' && month === '*' && dow === '*')
73
+ return `Every ${everyN[1]} minutes`;
74
+ if (m !== null && hour === '*' && dom === '*' && month === '*' && dow === '*')
75
+ return `Every hour at :${String(m).padStart(2, '0')}`;
76
+ if (m !== null && h !== null && dom === '*' && month === '*' && dow === '1-5')
77
+ return `Weekdays at ${fmt(h, m)}`;
78
+ if (m !== null && h !== null && dom === '*' && month === '*' && dow === '*')
79
+ return `Daily at ${fmt(h, m)}`;
80
+ if (m !== null && h !== null && dom === '*' && month === '*' && /^\d$/.test(dow))
81
+ return `Every ${DAYS[+dow]} at ${fmt(h, m)}`;
82
+ const d = isNum(dom) ? +dom : null;
83
+ if (m !== null && h !== null && d !== null && month === '*' && dow === '*') {
84
+ const sfx = d === 1 ? 'st' : d === 2 ? 'nd' : d === 3 ? 'rd' : 'th';
85
+ return `Monthly on the ${d}${sfx} at ${fmt(h, m)}`;
86
+ }
87
+ return '';
88
+ }
89
+
42
90
  function showToast(type, message) {
43
91
  toast = { type, message };
44
92
  setTimeout(() => (toast = null), 4000);
45
93
  }
46
94
 
95
+ function handleScheduleChange(e) {
96
+ if (e.target.value === CUSTOM_SENTINEL) {
97
+ useCustomCron = true;
98
+ form.cronExpression = '';
99
+ } else {
100
+ useCustomCron = false;
101
+ form.cronExpression = e.target.value;
102
+ }
103
+ }
104
+
47
105
  function openAddModal() {
48
106
  isEditing = false;
49
107
  editTaskName = '';
50
- form = { taskName: '', cronExpression: '', tags: '' };
108
+ form = { taskName: '', cronExpression: '', tags: '', workers: 1 };
109
+ selectedSchedule = '';
110
+ useCustomCron = false;
51
111
  formError = '';
52
112
  modalOpen = true;
53
113
  }
@@ -55,7 +115,15 @@
55
115
  function openEditModal(job) {
56
116
  isEditing = true;
57
117
  editTaskName = job.taskName;
58
- form = { taskName: job.taskName, cronExpression: job.cronExpression, tags: job.tags };
118
+ form = {
119
+ taskName: job.taskName,
120
+ cronExpression: job.cronExpression,
121
+ tags: job.tags,
122
+ workers: job.workers ?? 1
123
+ };
124
+ const isPreset = scheduleOptions.some((o) => o.value === job.cronExpression);
125
+ useCustomCron = !isPreset;
126
+ selectedSchedule = isPreset ? job.cronExpression : CUSTOM_SENTINEL;
59
127
  formError = '';
60
128
  modalOpen = true;
61
129
  }
@@ -110,10 +178,12 @@
110
178
  }
111
179
 
112
180
  onMount(async () => {
113
- [cronJobs, scheduleOptions] = await Promise.all([fetchCronJobs(), fetchSchedules()]);
181
+ cronJobs = await fetchCronJobs();
114
182
  });
115
183
  </script>
116
184
 
185
+ <svelte:head><title>Scheduled Tests — Plum</title></svelte:head>
186
+
117
187
  <!-- Add/Edit modal -->
118
188
  <Modal bind:open={modalOpen} title={isEditing ? 'Edit Cron Job' : 'New Cron Job'}>
119
189
  <form on:submit|preventDefault={handleSave} class="modal-form">
@@ -138,12 +208,35 @@
138
208
  <div class="field-label">
139
209
  <span>Schedule</span>
140
210
  </div>
141
- <select class="field-input" bind:value={form.cronExpression} required>
142
- <option value="" disabled selected>Select a schedule</option>
211
+ <select
212
+ class="field-input"
213
+ bind:value={selectedSchedule}
214
+ on:change={handleScheduleChange}
215
+ required
216
+ >
217
+ <option value="" disabled>Select a schedule</option>
143
218
  {#each scheduleOptions as opt}
144
219
  <option value={opt.value}>{opt.label}</option>
145
220
  {/each}
221
+ <option value={CUSTOM_SENTINEL}>Custom…</option>
146
222
  </select>
223
+
224
+ {#if useCustomCron}
225
+ <input
226
+ type="text"
227
+ class="field-input cron-input"
228
+ bind:value={form.cronExpression}
229
+ placeholder="0 9 * * 1-5"
230
+ spellcheck="false"
231
+ />
232
+ {#if form.cronExpression}
233
+ <p class="cron-desc" class:cron-invalid={!CRON_REGEX.test(form.cronExpression)}>
234
+ {CRON_REGEX.test(form.cronExpression)
235
+ ? describeCron(form.cronExpression) || form.cronExpression
236
+ : 'Invalid cron expression'}
237
+ </p>
238
+ {/if}
239
+ {/if}
147
240
  </div>
148
241
 
149
242
  <div class="field">
@@ -160,6 +253,26 @@
160
253
  />
161
254
  </div>
162
255
 
256
+ <div class="field">
257
+ <div class="field-label">
258
+ <span>Runners</span>
259
+ <span class="field-hint">Parallel workers for this job</span>
260
+ </div>
261
+ <div class="seg-control">
262
+ {#each WORKER_OPTIONS as n}
263
+ <button
264
+ type="button"
265
+ class="seg-btn"
266
+ class:active={form.workers === n}
267
+ on:click={() => (form.workers = n)}
268
+ >
269
+ <span class="seg-num">{n}</span>
270
+ <span class="seg-label">{n === 1 ? 'runner' : 'runners'}</span>
271
+ </button>
272
+ {/each}
273
+ </div>
274
+ </div>
275
+
163
276
  {#if formError}
164
277
  <p class="form-error">{formError}</p>
165
278
  {/if}
@@ -209,19 +322,30 @@
209
322
  <th>Name</th>
210
323
  <th>Schedule</th>
211
324
  <th>Tags</th>
325
+ <th>Runners</th>
212
326
  <th>Actions</th>
213
327
  </tr>
214
328
  </thead>
215
329
  <tbody>
216
330
  {#each cronJobs as job, i}
217
331
  <tr style="animation-delay: {i * 45}ms" class="job-row">
218
- <td class="job-name">{job.taskName}</td>
332
+ <td class="job-name">
333
+ {#if $activeCronJobs[job.taskName]}
334
+ <span class="running-dot" title="Running now"></span>
335
+ {/if}
336
+ {job.taskName}
337
+ </td>
219
338
  <td>
220
339
  <Badge variant="schedule">{cronLabel(job.cronExpression)}</Badge>
221
340
  </td>
222
341
  <td>
223
342
  <span class="tag-text">{job.tags}</span>
224
343
  </td>
344
+ <td>
345
+ <span class="workers-badge" class:multi={job.workers > 1}>
346
+ ×{job.workers}
347
+ </span>
348
+ </td>
225
349
  <td class="actions-cell">
226
350
  <Button variant="ghost" size="sm" on:click={() => openEditModal(job)}>Edit</Button>
227
351
  <Button variant="danger" size="sm" on:click={() => openDeleteModal(job.taskName)}
@@ -276,6 +400,31 @@
276
400
  .job-name {
277
401
  font-weight: 400;
278
402
  font-size: 0.875rem;
403
+ display: flex;
404
+ align-items: center;
405
+ gap: 0.5rem;
406
+ }
407
+
408
+ .running-dot {
409
+ display: inline-block;
410
+ width: 7px;
411
+ height: 7px;
412
+ border-radius: 50%;
413
+ background: var(--pass);
414
+ flex-shrink: 0;
415
+ animation: statusPulse 1.6s ease-in-out infinite;
416
+ }
417
+
418
+ @keyframes statusPulse {
419
+ 0%,
420
+ 100% {
421
+ opacity: 1;
422
+ transform: scale(1);
423
+ }
424
+ 50% {
425
+ opacity: 0.4;
426
+ transform: scale(0.65);
427
+ }
279
428
  }
280
429
 
281
430
  .tag-text {
@@ -324,4 +473,91 @@
324
473
  gap: 0.625rem;
325
474
  padding-top: 0.25rem;
326
475
  }
476
+
477
+ /* Custom cron input */
478
+ .cron-input {
479
+ margin-top: 0.5rem;
480
+ font-family: 'JetBrains Mono', monospace;
481
+ font-size: 0.875rem;
482
+ letter-spacing: 0.03em;
483
+ }
484
+
485
+ .cron-desc {
486
+ margin-top: 0.375rem;
487
+ font-size: 0.8rem;
488
+ color: var(--pass);
489
+ }
490
+
491
+ .cron-desc.cron-invalid {
492
+ color: var(--fail);
493
+ }
494
+
495
+ /* Segmented control */
496
+ .seg-control {
497
+ display: flex;
498
+ border: 1px solid var(--border);
499
+ border-radius: var(--radius-sm);
500
+ overflow: hidden;
501
+ width: fit-content;
502
+ }
503
+
504
+ .seg-btn {
505
+ display: flex;
506
+ flex-direction: column;
507
+ align-items: center;
508
+ padding: 0.375rem 0.875rem;
509
+ font-family: inherit;
510
+ background: var(--bg-elevated);
511
+ color: var(--text-muted);
512
+ border: none;
513
+ border-right: 1px solid var(--border);
514
+ cursor: pointer;
515
+ transition:
516
+ background var(--duration-fast),
517
+ color var(--duration-fast);
518
+ }
519
+
520
+ .seg-num {
521
+ font-size: 0.875rem;
522
+ font-weight: 600;
523
+ line-height: 1.2;
524
+ }
525
+
526
+ .seg-label {
527
+ font-size: 0.625rem;
528
+ letter-spacing: 0.04em;
529
+ text-transform: uppercase;
530
+ opacity: 0.75;
531
+ line-height: 1.2;
532
+ }
533
+
534
+ .seg-btn:last-child {
535
+ border-right: none;
536
+ }
537
+
538
+ .seg-btn:hover {
539
+ background: var(--bg-subtle);
540
+ color: var(--text);
541
+ }
542
+
543
+ .seg-btn.active {
544
+ background: var(--accent);
545
+ color: #fff;
546
+ }
547
+
548
+ .seg-btn.active .seg-label {
549
+ opacity: 0.85;
550
+ }
551
+
552
+ /* Workers badge in table */
553
+ .workers-badge {
554
+ font-family: 'JetBrains Mono', monospace;
555
+ font-size: 0.75rem;
556
+ color: var(--text-muted);
557
+ }
558
+
559
+ .workers-badge.multi {
560
+ color: var(--accent);
561
+ font-weight: 500;
562
+ }
327
563
  </style>