@stacksjs/ts-cloud 0.7.56 → 0.7.58

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 (467) hide show
  1. package/dist/alerts/alerts.test.d.ts +1 -0
  2. package/dist/alerts/delivery.d.ts +45 -0
  3. package/dist/alerts/evaluator.d.ts +12 -0
  4. package/dist/alerts/health.d.ts +34 -0
  5. package/dist/alerts/index.d.ts +6 -0
  6. package/dist/alerts/model.d.ts +170 -0
  7. package/dist/alerts/store.d.ts +90 -0
  8. package/dist/alerts/telemetry.d.ts +3 -0
  9. package/dist/api/client.d.ts +197 -0
  10. package/dist/api/handler.d.ts +18 -0
  11. package/dist/api/handler.test.d.ts +1 -0
  12. package/dist/api/index.d.ts +6 -0
  13. package/dist/api/openapi.d.ts +2 -0
  14. package/dist/api/service.d.ts +28 -0
  15. package/dist/api/types.d.ts +119 -0
  16. package/dist/auth/encryption.d.ts +3 -0
  17. package/dist/auth/index.d.ts +8 -0
  18. package/dist/auth/mailer.d.ts +9 -0
  19. package/dist/auth/oidc.d.ts +36 -0
  20. package/dist/auth/oidc.test.d.ts +1 -0
  21. package/dist/auth/store.d.ts +121 -0
  22. package/dist/auth/store.test.d.ts +1 -0
  23. package/dist/auth/totp.d.ts +11 -0
  24. package/dist/auth/totp.test.d.ts +1 -0
  25. package/dist/auth/types.d.ts +138 -0
  26. package/dist/automation/index.d.ts +2 -0
  27. package/dist/automation/store.d.ts +46 -0
  28. package/dist/automation/store.test.d.ts +1 -0
  29. package/dist/automation/types.d.ts +66 -0
  30. package/dist/aws/acm.d.ts +0 -4
  31. package/dist/aws/application-autoscaling.d.ts +0 -5
  32. package/dist/aws/backup.d.ts +42 -0
  33. package/dist/aws/bedrock.d.ts +0 -5
  34. package/dist/aws/client.d.ts +3 -6
  35. package/dist/aws/cloudformation.d.ts +0 -4
  36. package/dist/aws/cloudfront-media.test.d.ts +1 -0
  37. package/dist/aws/cloudfront-origin.test.d.ts +1 -0
  38. package/dist/aws/cloudfront.d.ts +87 -3
  39. package/dist/aws/cloudwatch-logs.d.ts +2 -4
  40. package/dist/aws/cloudwatch.d.ts +1 -1
  41. package/dist/aws/comprehend.d.ts +0 -5
  42. package/dist/aws/connect.d.ts +0 -4
  43. package/dist/aws/cost-explorer-cache.d.ts +1 -15
  44. package/dist/aws/cost-explorer.d.ts +18 -9
  45. package/dist/aws/cost-explorer.test.d.ts +1 -0
  46. package/dist/aws/dynamodb.d.ts +0 -4
  47. package/dist/aws/ec2.d.ts +0 -4
  48. package/dist/aws/ecr.d.ts +0 -4
  49. package/dist/aws/ecs.d.ts +17 -7
  50. package/dist/aws/efs.d.ts +0 -4
  51. package/dist/aws/elasticache.d.ts +0 -4
  52. package/dist/aws/elbv2.d.ts +0 -7
  53. package/dist/aws/email.d.ts +0 -10
  54. package/dist/aws/eventbridge.d.ts +0 -4
  55. package/dist/aws/iam.d.ts +1 -5
  56. package/dist/aws/imap-server.d.ts +0 -7
  57. package/dist/aws/index.d.ts +4 -3
  58. package/dist/aws/index.js +31 -31
  59. package/dist/aws/kendra.d.ts +0 -5
  60. package/dist/aws/lambda.d.ts +3 -5
  61. package/dist/aws/opensearch.d.ts +0 -4
  62. package/dist/aws/personalize.d.ts +0 -5
  63. package/dist/aws/polly.d.ts +0 -5
  64. package/dist/aws/rds.d.ts +59 -4
  65. package/dist/aws/rekognition.d.ts +0 -5
  66. package/dist/aws/route53-domains.d.ts +0 -4
  67. package/dist/aws/route53.d.ts +0 -4
  68. package/dist/aws/s3.d.ts +10 -6
  69. package/dist/aws/scheduler.d.ts +0 -4
  70. package/dist/aws/secrets-manager.d.ts +0 -4
  71. package/dist/aws/setup-sms.d.ts +0 -14
  72. package/dist/aws/sms.d.ts +0 -10
  73. package/dist/aws/smtp-server.d.ts +0 -11
  74. package/dist/aws/sns.d.ts +0 -4
  75. package/dist/aws/sqs.d.ts +0 -4
  76. package/dist/aws/ssm.d.ts +0 -4
  77. package/dist/aws/sts.d.ts +0 -4
  78. package/dist/aws/support.d.ts +0 -4
  79. package/dist/aws/textract.d.ts +0 -5
  80. package/dist/aws/transcribe.d.ts +0 -4
  81. package/dist/aws/translate.d.ts +0 -5
  82. package/dist/aws/voice.d.ts +0 -11
  83. package/dist/aws/xray.d.ts +1 -0
  84. package/dist/backups/aws-database-source.d.ts +19 -0
  85. package/dist/backups/aws-database-source.test.d.ts +1 -0
  86. package/dist/backups/aws-infrastructure-source.d.ts +43 -0
  87. package/dist/backups/aws-infrastructure-source.test.d.ts +1 -0
  88. package/dist/backups/control-plane-source.d.ts +16 -0
  89. package/dist/backups/control-plane-source.test.d.ts +1 -0
  90. package/dist/backups/docker-volume-source.d.ts +31 -0
  91. package/dist/backups/docker-volume-source.test.d.ts +1 -0
  92. package/dist/backups/filesystem-source.d.ts +34 -0
  93. package/dist/backups/filesystem-source.test.d.ts +1 -0
  94. package/dist/backups/index.d.ts +10 -0
  95. package/dist/backups/logical-database-source.d.ts +16 -0
  96. package/dist/backups/logical-database-source.test.d.ts +1 -0
  97. package/dist/backups/model.d.ts +121 -0
  98. package/dist/backups/s3-destination.d.ts +85 -0
  99. package/dist/backups/s3-destination.test.d.ts +1 -0
  100. package/dist/backups/service.d.ts +80 -0
  101. package/dist/backups/service.test.d.ts +1 -0
  102. package/dist/backups/store.d.ts +30 -0
  103. package/dist/backups/store.test.d.ts +1 -0
  104. package/dist/bin/cli.js +3360 -1802
  105. package/dist/{chunk-tjjgajbh.js → chunk-01d86gt1.js} +301 -9
  106. package/dist/{chunk-73vcqg6z.js → chunk-0z43x8re.js} +7 -7
  107. package/dist/{chunk-tnztxpcb.js → chunk-32e7ya18.js} +61 -49
  108. package/dist/{chunk-qpj3edwz.js → chunk-4cjrg98a.js} +3 -7
  109. package/dist/{chunk-hsk6fe6x.js → chunk-50jpda9q.js} +2 -2
  110. package/dist/{chunk-93hjhs78.js → chunk-703nkybg.js} +1 -1
  111. package/dist/{chunk-d5vqwj8s.js → chunk-9m6b4m74.js} +755 -720
  112. package/dist/chunk-e05phx5f.js +10 -0
  113. package/dist/{chunk-f29w04w5.js → chunk-exbcsab2.js} +10 -4
  114. package/dist/{chunk-eq08r166.js → chunk-f55nkpvb.js} +2 -2
  115. package/dist/{chunk-3bfv1z4v.js → chunk-fv1bj3yt.js} +63 -56
  116. package/dist/chunk-fw3q88wk.js +48641 -0
  117. package/dist/{chunk-3knnr7wh.js → chunk-he9a874b.js} +170 -2
  118. package/dist/{chunk-zn0nxxa8.js → chunk-hpv68b4a.js} +1070 -1070
  119. package/dist/chunk-k114qwg9.js +12 -0
  120. package/dist/{chunk-r16e02bc.js → chunk-vdk53yyq.js} +3900 -4101
  121. package/dist/{chunk-jp2a8yad.js → chunk-vvkd46k1.js} +15 -13
  122. package/dist/{chunk-vd87cpvn.js → chunk-wj3s95p9.js} +62 -46
  123. package/dist/{chunk-mj1tmhte.js → chunk-x9s0cqfx.js} +1 -1
  124. package/dist/{chunk-3rsfns7x.js → chunk-ybcz6sxc.js} +2048 -387
  125. package/dist/{chunk-arsh1g5h.js → chunk-zqtpg06c.js} +14 -11
  126. package/dist/compose/index.d.ts +6 -0
  127. package/dist/compose/parser.d.ts +13 -0
  128. package/dist/compose/parser.test.d.ts +1 -0
  129. package/dist/compose/runtime.d.ts +10 -0
  130. package/dist/compose/service.d.ts +18 -0
  131. package/dist/compose/store.d.ts +59 -0
  132. package/dist/compose/store.test.d.ts +1 -0
  133. package/dist/compose/templates.d.ts +17 -0
  134. package/dist/compose/types.d.ts +164 -0
  135. package/dist/configuration/backends.d.ts +66 -0
  136. package/dist/configuration/backends.test.d.ts +1 -0
  137. package/dist/configuration/dotenv.d.ts +14 -0
  138. package/dist/configuration/dotenv.test.d.ts +1 -0
  139. package/dist/configuration/index.d.ts +6 -0
  140. package/dist/configuration/migrate.d.ts +10 -0
  141. package/dist/configuration/migrate.test.d.ts +1 -0
  142. package/dist/configuration/model.d.ts +50 -0
  143. package/dist/configuration/service.d.ts +144 -0
  144. package/dist/configuration/service.test.d.ts +1 -0
  145. package/dist/configuration/store.d.ts +33 -0
  146. package/dist/configuration/store.test.d.ts +1 -0
  147. package/dist/control-plane/authorization.d.ts +24 -0
  148. package/dist/control-plane/authorization.test.d.ts +1 -0
  149. package/dist/control-plane/index.d.ts +8 -0
  150. package/dist/control-plane/migrations.d.ts +7 -0
  151. package/dist/control-plane/search.d.ts +25 -0
  152. package/dist/control-plane/store.d.ts +117 -0
  153. package/dist/control-plane/store.test.d.ts +1 -0
  154. package/dist/control-plane/types.d.ts +381 -0
  155. package/dist/cost/reporting.d.ts +24 -0
  156. package/dist/cost/reporting.test.d.ts +1 -0
  157. package/dist/cost/resource-inventory.d.ts +32 -0
  158. package/dist/cost/resource-inventory.test.d.ts +1 -0
  159. package/dist/cost/resource-optimization.d.ts +59 -0
  160. package/dist/cost/resource-optimization.test.d.ts +1 -0
  161. package/dist/data-services/adapters.d.ts +39 -0
  162. package/dist/data-services/aws-transports.d.ts +29 -0
  163. package/dist/data-services/aws-transports.test.d.ts +1 -0
  164. package/dist/data-services/capabilities.d.ts +2 -0
  165. package/dist/data-services/data-services.test.d.ts +1 -0
  166. package/dist/data-services/docker-transport.d.ts +55 -0
  167. package/dist/data-services/docker-transport.test.d.ts +1 -0
  168. package/dist/data-services/index.d.ts +8 -0
  169. package/dist/data-services/model.d.ts +79 -0
  170. package/dist/data-services/secrets.d.ts +13 -0
  171. package/dist/data-services/secrets.test.d.ts +1 -0
  172. package/dist/data-services/service.d.ts +51 -0
  173. package/dist/data-services/store.d.ts +17 -0
  174. package/dist/deploy/container-image.d.ts +28 -0
  175. package/dist/deploy/container-image.test.d.ts +1 -0
  176. package/dist/deploy/dashboard-audit.test.d.ts +1 -0
  177. package/dist/deploy/dashboard-auth.d.ts +4 -23
  178. package/dist/deploy/dashboard-backups.integration.test.d.ts +1 -0
  179. package/dist/deploy/dashboard-configuration.integration.test.d.ts +1 -0
  180. package/dist/deploy/dashboard-control-plane.d.ts +37 -0
  181. package/dist/deploy/dashboard-control-plane.test.d.ts +1 -0
  182. package/dist/deploy/dashboard-data-services.integration.test.d.ts +1 -0
  183. package/dist/deploy/dashboard-environment-scope.test.d.ts +1 -0
  184. package/dist/deploy/dashboard-guard.d.ts +12 -0
  185. package/dist/deploy/dashboard-guard.test.d.ts +1 -0
  186. package/dist/deploy/dashboard-identities.d.ts +15 -0
  187. package/dist/deploy/dashboard-identities.test.d.ts +1 -0
  188. package/dist/deploy/dashboard-jobs.integration.test.d.ts +1 -0
  189. package/dist/deploy/dashboard-login-page.d.ts +5 -5
  190. package/dist/deploy/dashboard-login-page.test.d.ts +1 -0
  191. package/dist/deploy/dashboard-oidc.integration.test.d.ts +1 -0
  192. package/dist/deploy/dashboard-onboarding.integration.test.d.ts +1 -0
  193. package/dist/deploy/dashboard-policy.d.ts +4 -2
  194. package/dist/deploy/dashboard-queue-worker.integration.test.d.ts +1 -0
  195. package/dist/deploy/dashboard-release.integration.test.d.ts +1 -0
  196. package/dist/deploy/dashboard-route-manifest.d.ts +14 -0
  197. package/dist/deploy/dashboard-route-manifest.test.d.ts +1 -0
  198. package/dist/deploy/dashboard-security.integration.test.d.ts +1 -0
  199. package/dist/deploy/dashboard-session.d.ts +3 -14
  200. package/dist/deploy/dashboard-source.integration.test.d.ts +1 -0
  201. package/dist/deploy/dashboard-users.d.ts +3 -0
  202. package/dist/deploy/fullstack-container.d.ts +87 -0
  203. package/dist/deploy/fullstack-container.test.d.ts +1 -0
  204. package/dist/deploy/index.d.ts +7 -2
  205. package/dist/deploy/index.js +65 -25
  206. package/dist/deploy/local-dashboard-server.d.ts +30 -0
  207. package/dist/deploy/quick-deploy.d.ts +1 -1
  208. package/dist/deploy/serverless-operations.d.ts +1 -0
  209. package/dist/deploy/site-target.d.ts +16 -0
  210. package/dist/deploy/static-api-origin.d.ts +107 -0
  211. package/dist/deploy/static-api-origin.test.d.ts +1 -0
  212. package/dist/deploy/static-site-helper.d.ts +0 -16
  213. package/dist/deploy/telemetry-access.d.ts +5 -0
  214. package/dist/deploy/telemetry-access.test.d.ts +1 -0
  215. package/dist/deploy/telemetry-collection.d.ts +44 -0
  216. package/dist/deploy/telemetry-collection.test.d.ts +1 -0
  217. package/dist/dns/index.js +3 -3
  218. package/dist/dns/route53-adapter.d.ts +1 -1
  219. package/dist/drivers/hetzner/cloud-init.d.ts +1 -1
  220. package/dist/drivers/index.d.ts +2 -2
  221. package/dist/drivers/index.js +6 -6
  222. package/dist/drivers/shared/notifications.d.ts +1 -1
  223. package/dist/drivers/shared/rpx-gateway.d.ts +1 -0
  224. package/dist/fleet/fleet.test.d.ts +1 -0
  225. package/dist/fleet/index.d.ts +144 -0
  226. package/dist/fleet/queue.d.ts +3 -0
  227. package/dist/fleet/ssh-driver.d.ts +34 -0
  228. package/dist/index.d.ts +28 -4
  229. package/dist/index.js +1132 -424
  230. package/dist/jobs/adapters.d.ts +23 -0
  231. package/dist/jobs/index.d.ts +7 -0
  232. package/dist/jobs/jobs.test.d.ts +1 -0
  233. package/dist/jobs/model.d.ts +100 -0
  234. package/dist/jobs/provider-adapters.d.ts +49 -0
  235. package/dist/jobs/reconcile.d.ts +19 -0
  236. package/dist/jobs/schedule.d.ts +9 -0
  237. package/dist/jobs/service.d.ts +30 -0
  238. package/dist/jobs/store.d.ts +48 -0
  239. package/dist/maintenance/drivers.d.ts +117 -0
  240. package/dist/maintenance/index.d.ts +6 -0
  241. package/dist/maintenance/maintenance.test.d.ts +1 -0
  242. package/dist/maintenance/manifest.d.ts +15 -0
  243. package/dist/maintenance/queue.d.ts +9 -0
  244. package/dist/maintenance/service.d.ts +31 -0
  245. package/dist/maintenance/store.d.ts +84 -0
  246. package/dist/maintenance/types.d.ts +192 -0
  247. package/dist/media.d.ts +62 -0
  248. package/dist/media.test.d.ts +1 -0
  249. package/dist/object-storage/index.d.ts +0 -24
  250. package/dist/onboarding/archive.d.ts +12 -0
  251. package/dist/onboarding/artifact-store.d.ts +22 -0
  252. package/dist/onboarding/detection.d.ts +8 -0
  253. package/dist/onboarding/index.d.ts +9 -0
  254. package/dist/onboarding/migrations.d.ts +3 -0
  255. package/dist/onboarding/onboarding.test.d.ts +1 -0
  256. package/dist/onboarding/plan.d.ts +3 -0
  257. package/dist/onboarding/registry.d.ts +41 -0
  258. package/dist/onboarding/service.d.ts +16 -0
  259. package/dist/onboarding/store.d.ts +30 -0
  260. package/dist/onboarding/types.d.ts +204 -0
  261. package/dist/placement/builds.d.ts +20 -0
  262. package/dist/placement/drivers.d.ts +54 -0
  263. package/dist/placement/index.d.ts +6 -0
  264. package/dist/placement/placement.test.d.ts +1 -0
  265. package/dist/placement/queue.d.ts +4 -0
  266. package/dist/placement/service.d.ts +34 -0
  267. package/dist/placement/store.d.ts +49 -0
  268. package/dist/placement/types.d.ts +109 -0
  269. package/dist/preview/index.d.ts +3 -0
  270. package/dist/preview/service.d.ts +45 -0
  271. package/dist/preview/service.test.d.ts +1 -0
  272. package/dist/preview/store.d.ts +47 -0
  273. package/dist/preview/store.test.d.ts +1 -0
  274. package/dist/preview/types.d.ts +99 -0
  275. package/dist/push/apns.d.ts +0 -26
  276. package/dist/push/fcm.d.ts +0 -23
  277. package/dist/push/index.d.ts +2 -2
  278. package/dist/push/index.js +1 -1
  279. package/dist/queue/deployment-handlers.d.ts +27 -0
  280. package/dist/queue/deployment-handlers.test.d.ts +1 -0
  281. package/dist/queue/index.d.ts +4 -0
  282. package/dist/queue/queue.d.ts +57 -0
  283. package/dist/queue/queue.test.d.ts +1 -0
  284. package/dist/queue/types.d.ts +93 -0
  285. package/dist/queue/worker.d.ts +32 -0
  286. package/dist/queue/worker.test.d.ts +1 -0
  287. package/dist/regions/drivers.d.ts +139 -0
  288. package/dist/regions/index.d.ts +5 -0
  289. package/dist/regions/queue.d.ts +4 -0
  290. package/dist/regions/regions.test.d.ts +1 -0
  291. package/dist/regions/service.d.ts +23 -0
  292. package/dist/regions/store.d.ts +41 -0
  293. package/dist/regions/types.d.ts +132 -0
  294. package/dist/release/index.d.ts +6 -0
  295. package/dist/release/queue.d.ts +15 -0
  296. package/dist/release/queue.test.d.ts +1 -0
  297. package/dist/release/runtime.d.ts +35 -0
  298. package/dist/release/runtime.test.d.ts +1 -0
  299. package/dist/release/service.d.ts +28 -0
  300. package/dist/release/store.d.ts +88 -0
  301. package/dist/release/store.test.d.ts +1 -0
  302. package/dist/release/strategy.d.ts +8 -0
  303. package/dist/release/types.d.ts +103 -0
  304. package/dist/runtime/adapters/aws.d.ts +5 -0
  305. package/dist/runtime/adapters/docker.d.ts +53 -0
  306. package/dist/runtime/adapters/systemd.d.ts +26 -0
  307. package/dist/runtime/index.d.ts +9 -0
  308. package/dist/runtime/inventory.d.ts +6 -0
  309. package/dist/runtime/model.d.ts +130 -0
  310. package/dist/runtime/normalize.d.ts +5 -0
  311. package/dist/runtime/operations.d.ts +113 -0
  312. package/dist/runtime/service.d.ts +32 -0
  313. package/dist/runtime/streams.d.ts +49 -0
  314. package/dist/security/artifacts.d.ts +105 -0
  315. package/dist/security/artifacts.test.d.ts +1 -0
  316. package/dist/security/container-release.d.ts +35 -0
  317. package/dist/security/container-release.test.d.ts +1 -0
  318. package/dist/security/index.d.ts +8 -0
  319. package/dist/security/integration.d.ts +41 -0
  320. package/dist/security/integration.test.d.ts +1 -0
  321. package/dist/security/posture-store.d.ts +80 -0
  322. package/dist/security/posture-store.test.d.ts +1 -0
  323. package/dist/security/pre-deploy-scanner.d.ts +0 -4
  324. package/dist/security/scanners.d.ts +81 -0
  325. package/dist/security/scanners.test.d.ts +1 -0
  326. package/dist/security/types.d.ts +150 -0
  327. package/dist/source/git-workspace.d.ts +26 -0
  328. package/dist/source/git-workspace.test.d.ts +1 -0
  329. package/dist/source/index.d.ts +6 -0
  330. package/dist/source/providers.d.ts +199 -0
  331. package/dist/source/providers.test.d.ts +1 -0
  332. package/dist/source/service.d.ts +18 -0
  333. package/dist/source/store.d.ts +149 -0
  334. package/dist/source/store.test.d.ts +1 -0
  335. package/dist/source/types.d.ts +191 -0
  336. package/dist/source/webhooks.d.ts +33 -0
  337. package/dist/source/webhooks.test.d.ts +1 -0
  338. package/dist/ssl/acme-client.d.ts +0 -6
  339. package/dist/storage/compose.d.ts +4 -0
  340. package/dist/storage/compose.test.d.ts +1 -0
  341. package/dist/storage/drivers.d.ts +107 -0
  342. package/dist/storage/drivers.test.d.ts +1 -0
  343. package/dist/storage/index.d.ts +5 -0
  344. package/dist/storage/model.d.ts +112 -0
  345. package/dist/storage/service.d.ts +95 -0
  346. package/dist/storage/storage.test.d.ts +1 -0
  347. package/dist/storage/store.d.ts +40 -0
  348. package/dist/telemetry/correlation.test.d.ts +1 -0
  349. package/dist/telemetry/index.d.ts +4 -0
  350. package/dist/telemetry/model.d.ts +145 -0
  351. package/dist/telemetry/policy.d.ts +8 -0
  352. package/dist/telemetry/policy.test.d.ts +1 -0
  353. package/dist/telemetry/redaction.d.ts +9 -0
  354. package/dist/telemetry/store.d.ts +34 -0
  355. package/dist/telemetry/store.test.d.ts +1 -0
  356. package/dist/ui/404.html +1506 -17
  357. package/dist/ui/accept-invitation.html +1163 -0
  358. package/dist/ui/{server/backups.html → access-denied.html} +344 -55
  359. package/dist/ui/account/automation.html +1669 -0
  360. package/dist/ui/account/security.html +1646 -0
  361. package/dist/ui/applications/compose.html +1590 -0
  362. package/dist/ui/applications/new.html +1868 -0
  363. package/dist/ui/data/backups.html +1757 -0
  364. package/dist/ui/data/services.html +1723 -0
  365. package/dist/ui/data/volumes.html +1639 -0
  366. package/dist/ui/index.html +392 -29
  367. package/dist/ui/integrations.html +1865 -0
  368. package/dist/ui/operations/alerts.html +1610 -0
  369. package/dist/ui/operations/configuration.html +1746 -0
  370. package/dist/ui/operations/jobs.html +1709 -0
  371. package/dist/ui/operations/maintenance.html +1540 -0
  372. package/dist/ui/operations/observability.html +1742 -0
  373. package/dist/ui/{serverless/scheduler.html → operations/previews.html} +447 -46
  374. package/dist/ui/operations/queue.html +1615 -0
  375. package/dist/ui/{server/workers.html → operations/regions.html} +413 -49
  376. package/dist/ui/operations/releases.html +1596 -0
  377. package/dist/ui/operations/workloads.html +1775 -0
  378. package/dist/ui/security.html +1767 -0
  379. package/dist/ui/server/actions.html +446 -57
  380. package/dist/ui/server/activity.html +346 -1
  381. package/dist/ui/server/capacity.html +1542 -0
  382. package/dist/ui/server/database.html +443 -36
  383. package/dist/ui/server/deployments.html +439 -53
  384. package/dist/ui/server/diagnostics.html +346 -1
  385. package/dist/ui/server/firewall.html +476 -50
  386. package/dist/ui/server/fleet.html +1577 -0
  387. package/dist/ui/server/logs.html +389 -25
  388. package/dist/ui/server/metrics.html +346 -1
  389. package/dist/ui/server/security.html +346 -1
  390. package/dist/ui/server/services.html +421 -46
  391. package/dist/ui/server/sites.html +476 -95
  392. package/dist/ui/server/ssh-keys.html +476 -46
  393. package/dist/ui/server/team.html +700 -77
  394. package/dist/ui/server/terminal.html +444 -54
  395. package/dist/ui/serverless/alarms.html +430 -42
  396. package/dist/ui/serverless/assets.html +421 -46
  397. package/dist/ui/serverless/cost.html +346 -1
  398. package/dist/ui/serverless/data.html +422 -47
  399. package/dist/ui/serverless/deployments.html +421 -46
  400. package/dist/ui/serverless/firewall.html +346 -1
  401. package/dist/ui/serverless/functions.html +423 -48
  402. package/dist/ui/serverless/logs.html +389 -25
  403. package/dist/ui/serverless/metrics.html +346 -1
  404. package/dist/ui/serverless/queues.html +448 -62
  405. package/dist/ui/serverless/secrets.html +448 -42
  406. package/dist/ui/serverless/traces.html +350 -5
  407. package/dist/ui/serverless.html +434 -58
  408. package/dist/ui-src/pages/404.stx +18 -0
  409. package/dist/ui-src/pages/accept-invitation.stx +50 -0
  410. package/dist/ui-src/pages/access-denied.stx +18 -0
  411. package/dist/ui-src/pages/account/automation.stx +187 -0
  412. package/dist/ui-src/pages/account/security.stx +249 -0
  413. package/dist/ui-src/pages/applications/compose.stx +38 -0
  414. package/dist/ui-src/pages/applications/new.stx +76 -0
  415. package/dist/ui-src/pages/data/backups.stx +214 -0
  416. package/dist/ui-src/pages/data/services.stx +257 -0
  417. package/dist/ui-src/pages/data/volumes.stx +40 -0
  418. package/dist/ui-src/pages/index.stx +24 -10
  419. package/dist/ui-src/pages/integrations.stx +186 -0
  420. package/dist/ui-src/pages/operations/alerts.stx +40 -0
  421. package/dist/ui-src/pages/operations/configuration.stx +164 -0
  422. package/dist/ui-src/pages/operations/jobs.stx +238 -0
  423. package/dist/ui-src/pages/operations/maintenance.stx +15 -0
  424. package/dist/ui-src/pages/operations/observability.stx +76 -0
  425. package/dist/ui-src/pages/operations/previews.stx +62 -0
  426. package/dist/ui-src/pages/operations/queue.stx +158 -0
  427. package/dist/ui-src/pages/operations/regions.stx +12 -0
  428. package/dist/ui-src/pages/operations/releases.stx +39 -0
  429. package/dist/ui-src/pages/operations/workloads.stx +151 -0
  430. package/dist/ui-src/pages/partials/head.stx +148 -15
  431. package/dist/ui-src/pages/partials/nav.stx +491 -74
  432. package/dist/ui-src/pages/partials/op-confirm.stx +6 -6
  433. package/dist/ui-src/pages/security.stx +264 -0
  434. package/dist/ui-src/pages/server/actions.stx +17 -26
  435. package/dist/ui-src/pages/server/capacity.stx +16 -0
  436. package/dist/ui-src/pages/server/database.stx +31 -16
  437. package/dist/ui-src/pages/server/deployments.stx +28 -30
  438. package/dist/ui-src/pages/server/firewall.stx +25 -30
  439. package/dist/ui-src/pages/server/fleet.stx +10 -0
  440. package/dist/ui-src/pages/server/logs.stx +11 -9
  441. package/dist/ui-src/pages/server/metrics.stx +8 -16
  442. package/dist/ui-src/pages/server/services.stx +19 -28
  443. package/dist/ui-src/pages/server/sites.stx +75 -72
  444. package/dist/ui-src/pages/server/ssh-keys.stx +24 -30
  445. package/dist/ui-src/pages/server/team.stx +286 -149
  446. package/dist/ui-src/pages/server/terminal.stx +6 -28
  447. package/dist/ui-src/pages/serverless/alarms.stx +17 -31
  448. package/dist/ui-src/pages/serverless/assets.stx +13 -22
  449. package/dist/ui-src/pages/serverless/cost.stx +15 -3
  450. package/dist/ui-src/pages/serverless/data.stx +12 -25
  451. package/dist/ui-src/pages/serverless/deployments.stx +14 -23
  452. package/dist/ui-src/pages/serverless/firewall.stx +5 -1
  453. package/dist/ui-src/pages/serverless/functions.stx +20 -26
  454. package/dist/ui-src/pages/serverless/logs.stx +11 -10
  455. package/dist/ui-src/pages/serverless/metrics.stx +18 -17
  456. package/dist/ui-src/pages/serverless/queues.stx +34 -42
  457. package/dist/ui-src/pages/serverless/secrets.stx +31 -34
  458. package/dist/ui-src/pages/serverless/traces.stx +2 -6
  459. package/dist/ui-src/pages/serverless.stx +40 -74
  460. package/package.json +11 -11
  461. package/dist/chunk-b17vf1g1.js +0 -13787
  462. package/dist/chunk-b82pbxyp.js +0 -1572
  463. package/dist/chunk-pqfzdg68.js +0 -12
  464. package/dist/chunk-z58fqnjm.js +0 -10
  465. package/dist/ui-src/pages/server/backups.stx +0 -91
  466. package/dist/ui-src/pages/server/workers.stx +0 -99
  467. package/dist/ui-src/pages/serverless/scheduler.stx +0 -89
@@ -2,192 +2,329 @@
2
2
  let __d = null
3
3
  try { __d = JSON.parse(process.env.TSCLOUD_DASHBOARD_DATA || '') } catch { __d = null }
4
4
  const server = __d?.server ?? { name: 'acme-production-app' }
5
- // Only the box owner reaches this page (the server refuses /api/users for
6
- // anyone else), so the site list here is always the full one.
7
- const siteNames = (__d?.sitesDetail ?? __d?.sites ?? [{ name: 'main' }, { name: 'docs' }]).map(s => s.name)
8
5
  </script>
9
6
  <script client>
10
- // Collaborator management: list, invite, revoke. A member holds a role per site
11
- // - 'owner' may also change that site's settings, 'collaborator' may view and
12
- // deploy it. Neither ever reaches the box itself (shell, SSH keys, firewall,
13
- // databases): those are the box owner's alone, because each of them is root on
14
- // a server that hosts other people's sites.
15
- const users = state([])
16
- const sites = state(siteNames)
17
- const username = state('')
18
- const displayName = state('')
19
- const grants = state({})
7
+ import { useOpConfirm } from '../../functions/useOpConfirm.ts'
8
+ import { requestJson } from '../../functions/requestJson.ts'
9
+
10
+ const organization = state(null)
11
+ const project = state(null)
12
+ const environments = state([])
13
+ const resources = state([])
14
+ const memberships = state([])
15
+ const invitations = state([])
16
+ const email = state('')
17
+ const roleTemplate = state('deployer')
18
+ const scopeType = state('environment')
19
+ const scopeId = state('')
20
+ const ownerPassword = state('')
20
21
  const message = state('')
21
22
  const messageKind = state('')
22
- // Shown once after an invite - the password is generated server-side, hashed,
23
- // and never retrievable again.
24
23
  const issued = state(null)
24
+ const editing = state(null)
25
+ const editRole = state('viewer')
26
+ const editScopeType = state('organization')
27
+ const editScopeId = state('')
28
+ const editPassword = state('')
29
+ const editConfirm = state('')
30
+ const accessDiff = state(null)
31
+ const removing = state(null)
32
+ const removePassword = state('')
33
+ const oidcProviders = state([])
34
+ const oidcCallbackPattern = state('')
35
+ const oidcFormOpen = state(false)
36
+ const oidcEditing = state(null)
37
+ const oidcSlug = state('')
38
+ const oidcName = state('')
39
+ const oidcIssuer = state('')
40
+ const oidcClientId = state('')
41
+ const oidcClientSecret = state('')
42
+ const oidcDomains = state('')
43
+ const oidcDefaultRole = state('viewer')
44
+ const oidcEnforce = state(false)
45
+ const oidcPassword = state('')
46
+ const oidcCode = state('')
47
+ const pageBusy = state(false)
48
+
49
+ const { opOutput, opShown, pending, typed, busy, confirmTok, confirmVerb, confirmDanger, confirmLabel, canRun, askOp, cancelOp, runOp } = useOpConfirm(async p => {
50
+ await removeMembership(p.member, String(p.apiConfirm))
51
+ return message()
52
+ })
53
+
54
+ const roleSummary = {
55
+ owner: 'Everything, including ownership transfer',
56
+ admin: 'Everything except ownership transfer',
57
+ deployer: 'View resources and create or roll back deployments',
58
+ operator: 'View resources, restart runtime, and create backups',
59
+ viewer: 'Read-only resource and deployment access',
60
+ auditor: 'Read-only posture and audit history',
61
+ }
25
62
 
26
63
  function setMessage(text, kind) { message.set(text); messageKind.set(kind || '') }
27
- function grantFor(site) { return grants()[site] || 'none' }
28
- function setGrant(site, role) {
29
- const next = { ...grants() }
30
- if (role === 'none') delete next[site]
31
- else next[site] = role
32
- grants.set(next)
64
+ function actorName(member) { return member.actor?.displayName || member.actor?.externalId?.replace('dashboard:', '') || 'Unknown user' }
65
+ function scopeLabel(scope) {
66
+ if (!scope || scope.type === 'organization') return 'Entire organization'
67
+ if (scope.type === 'project') return 'Project: ' + (project()?.name || scope.id)
68
+ if (scope.type === 'environment') return 'Environment: ' + (environments().find(item => item.id === scope.id)?.name || scope.id)
69
+ const resource = resources().find(item => item.id === scope.id)
70
+ return 'Resource: ' + (resource ? resource.name + ' (' + resource.slug + ')' : scope.id)
71
+ }
72
+ function invitationState(invitation) {
73
+ if (invitation.state === 'pending') return 'Expires ' + new Date(invitation.expiresAt).toLocaleDateString()
74
+ return invitation.state
33
75
  }
34
- function siteList(user) {
35
- const entries = Object.entries(user.sites || {})
36
- if (!entries.length) return '-'
37
- return entries.map(([s, r]) => s + ' (' + r + ')').join(', ')
76
+ function setScope(type, idSignal) {
77
+ if (type === 'organization') idSignal.set('')
78
+ else if (type === 'project') idSignal.set(project()?.id || '')
79
+ else if (type === 'environment') idSignal.set(environments()[0]?.id || '')
80
+ else idSignal.set(resources()[0]?.id || '')
38
81
  }
39
82
 
40
83
  async function load() {
41
84
  try {
42
- const res = await fetch('/api/users')
43
- if (!res.ok) throw new Error('Could not read the team.')
44
- const data = await res.json()
45
- users.set(data.users || [])
46
- } catch (e) { setMessage((e && e.message) || 'Could not read the team.', 'bad') }
85
+ const response = await fetch('/api/organization')
86
+ const data = await response.json()
87
+ if (!response.ok) throw new Error(data.error || 'Could not read organization access.')
88
+ organization.set(data.organization); project.set(data.project); environments.set(data.environments || []); resources.set(data.resources || [])
89
+ memberships.set(data.memberships || []); invitations.set(data.invitations || [])
90
+ if (!scopeId()) setScope(scopeType(), scopeId)
91
+ } catch (error) { setMessage(error?.message || 'Could not read organization access.', 'bad') }
92
+ }
93
+
94
+ async function loadOidc() {
95
+ const response = await fetch('/api/auth/oidc/providers')
96
+ const data = await response.json().catch(() => ({}))
97
+ if (!response.ok) { setMessage(data.error || 'Could not read SSO providers.', 'bad'); return }
98
+ oidcProviders.set(data.providers || []); oidcCallbackPattern.set(data.callbackPattern || '')
99
+ }
100
+
101
+ function resetOidc(provider) {
102
+ oidcFormOpen.set(true)
103
+ oidcEditing.set(provider || null)
104
+ oidcSlug.set(provider?.slug || '')
105
+ oidcName.set(provider?.name || '')
106
+ oidcIssuer.set(provider?.issuer || '')
107
+ oidcClientId.set(provider?.clientId || '')
108
+ oidcClientSecret.set('')
109
+ oidcDomains.set((provider?.allowedDomains || []).join(', '))
110
+ oidcDefaultRole.set(provider?.defaultRole || 'viewer')
111
+ oidcEnforce.set(provider?.enforceSso === true)
112
+ }
113
+
114
+ async function oidcStepUp() {
115
+ const response = await fetch('/api/auth/step-up', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ password: oidcPassword(), code: oidcCode() }) })
116
+ const data = await response.json().catch(() => ({}))
117
+ if (!response.ok) throw new Error(data.error || 'Recent authentication is required.')
118
+ }
119
+
120
+ async function saveOidc() {
121
+ setMessage('Validating owner recovery and saving SSO…', '')
122
+ try {
123
+ await oidcStepUp()
124
+ const response = await fetch('/api/auth/oidc/providers', {
125
+ method: 'POST', headers: { 'content-type': 'application/json' },
126
+ body: JSON.stringify({
127
+ id: oidcEditing()?.id, slug: oidcSlug(), name: oidcName(), issuer: oidcIssuer(), clientId: oidcClientId(), clientSecret: oidcClientSecret(),
128
+ allowedDomains: oidcDomains().split(',').map(value => value.trim()).filter(Boolean), defaultRole: oidcDefaultRole(), enforceSso: oidcEnforce(), enabled: oidcEditing()?.enabled !== false,
129
+ }),
130
+ })
131
+ const data = await response.json().catch(() => ({}))
132
+ if (!response.ok) throw new Error(data.error || 'SSO provider could not be saved.')
133
+ oidcPassword.set(''); oidcCode.set(''); oidcEditing.set(null); oidcSlug.set(''); oidcFormOpen.set(false); setMessage('SSO provider saved. Test sign-in in a private browser before enforcing it.', 'ok'); loadOidc()
134
+ } catch (error) { setMessage(error?.message || 'SSO provider could not be saved.', 'bad') }
135
+ }
136
+
137
+ async function setOidcEnabled(provider, enabled) {
138
+ setMessage((enabled ? 'Enabling' : 'Disabling') + ' SSO provider…', '')
139
+ try {
140
+ await oidcStepUp()
141
+ const response = await fetch('/api/auth/oidc/providers', { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: provider.id, enabled }) })
142
+ const data = await response.json().catch(() => ({}))
143
+ if (!response.ok) throw new Error(data.error || 'SSO provider could not be updated.')
144
+ oidcPassword.set(''); oidcCode.set(''); oidcFormOpen.set(false); oidcEditing.set(null); setMessage(enabled ? 'SSO provider enabled.' : 'SSO provider disabled; enforcement and pending sign-ins were cancelled.', 'ok'); loadOidc()
145
+ } catch (error) { setMessage(error?.message || 'SSO provider could not be updated.', 'bad') }
47
146
  }
48
147
 
49
148
  async function invite() {
50
- setMessage('Saving...', ''); issued.set(null)
51
- const payload = { username: username(), name: displayName(), sites: grants() }
52
- const res = await fetch('/api/users', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) })
53
- const data = await res.json().catch(() => ({}))
54
- if (!res.ok || data.ok === false) { setMessage(data.error || 'Could not save.', 'bad'); return }
55
- if (data.password) issued.set({ username: data.user.username, password: data.password })
56
- setMessage(data.password ? 'Invited ' + data.user.username + '.' : 'Updated ' + data.user.username + '.', 'ok')
57
- username.set(''); displayName.set(''); grants.set({})
58
- load()
59
- }
60
-
61
- async function revoke(name) {
62
- setMessage('Removing...', '')
63
- const res = await fetch('/api/users', { method: 'DELETE', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ username: name }) })
64
- const data = await res.json().catch(() => ({}))
65
- if (!res.ok || data.ok === false) { setMessage(data.error || 'Could not remove.', 'bad'); return }
66
- setMessage('Removed ' + name + '.', 'ok')
67
- load()
68
- }
69
-
70
- // Removal runs through a typed-confirm bar (same pattern as firewall/secrets):
71
- // type the username to confirm. Revoking pulls someone's access to every site
72
- // on the box at once and invalidates their password, so it should never be one
73
- // misclick away.
74
- const pendingRevoke = state(null)
75
- const typedRevoke = state('')
76
- const canRevoke = derived(() => { const u = pendingRevoke(); return u !== null && typedRevoke() === String(u.username) })
77
- function askRevoke(user) { pendingRevoke.set(user); typedRevoke.set('') }
78
- function cancelRevoke() { pendingRevoke.set(null); typedRevoke.set('') }
79
- async function confirmRevoke() {
80
- const u = pendingRevoke()
81
- if (!u || typedRevoke() !== String(u.username)) return
82
- pendingRevoke.set(null); typedRevoke.set('')
83
- await revoke(u.username)
84
- }
85
-
86
- onMount(() => { load() })
149
+ if (pageBusy()) return
150
+ pageBusy.set(true)
151
+ issued.set(null); setMessage('Creating invitation…', '')
152
+ try {
153
+ const data = await requestJson('/api/organization/invitations', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ email: email(), roleTemplate: roleTemplate(), scopeType: scopeType(), scopeId: scopeId(), password: ownerPassword() }) })
154
+ issued.set(data); email.set(''); ownerPassword.set(''); setMessage('Invitation created. Copy the one-time link below.', 'ok'); load()
155
+ } catch (error) { setMessage(error?.message || 'Could not create invitation.', 'bad') }
156
+ finally { pageBusy.set(false) }
157
+ }
158
+
159
+ async function invitationAction(invitation, action) {
160
+ if (pageBusy()) return
161
+ pageBusy.set(true)
162
+ const endpoint = action === 'resend' ? '/api/organization/invitations/resend' : '/api/organization/invitations'
163
+ try {
164
+ const data = await requestJson(endpoint, { method: action === 'resend' ? 'POST' : 'DELETE', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: invitation.id }) })
165
+ if (data.acceptUrl) { issued.set(data); setMessage('Invitation reissued. Copy the new one-time link.', 'ok') }
166
+ else setMessage('Invitation revoked.', 'ok')
167
+ load()
168
+ } catch (error) { setMessage(error?.message || 'Invitation could not be updated.', 'bad') }
169
+ finally { pageBusy.set(false) }
170
+ }
171
+
172
+ function beginEdit(member) {
173
+ editing.set(member); editRole.set(member.roleTemplate); editScopeType.set(member.scope.type); editScopeId.set(member.scope.id || '')
174
+ editPassword.set(''); editConfirm.set(''); accessDiff.set(null)
175
+ }
176
+ async function changeMembership(preview) {
177
+ const member = editing(); if (!member) return
178
+ const response = await fetch('/api/organization/memberships', {
179
+ method: 'PATCH', headers: { 'content-type': 'application/json' },
180
+ body: JSON.stringify({ id: member.id, roleTemplate: editRole(), scopeType: editScopeType(), scopeId: editScopeId(), password: editPassword(), preview, confirm: editConfirm() }),
181
+ })
182
+ const data = await response.json().catch(() => ({}))
183
+ if (!response.ok) { accessDiff.set(data.accessDiff || null); setMessage(data.error || 'Membership could not be updated.', 'bad'); return }
184
+ accessDiff.set(data.accessDiff || null)
185
+ if (preview) { setMessage('Review the effective-access diff, then type the membership ID to apply.', '') ; return }
186
+ setMessage('Membership updated. Existing sessions were invalidated.', 'ok'); editing.set(null); load()
187
+ }
188
+
189
+ function beginRemove(member) {
190
+ removing.set(member); removePassword.set('')
191
+ askOp('membership:remove:' + member.id, member.id, 'remove ' + actorName(member) + ' from the organization', null, member.id, { member, danger: true, label: 'Remove membership' })
192
+ }
193
+ async function removeMembership(member, confirm) {
194
+ if (!member) return
195
+ const response = await fetch('/api/organization/memberships', {
196
+ method: 'DELETE', headers: { 'content-type': 'application/json' },
197
+ body: JSON.stringify({ id: member.id, confirm, password: removePassword() }),
198
+ })
199
+ const data = await response.json().catch(() => ({}))
200
+ if (!response.ok) { setMessage(data.error || 'Membership could not be removed.', 'bad'); return }
201
+ setMessage('Membership removed and active sessions invalidated.', 'ok'); removing.set(null); load()
202
+ }
203
+
204
+ onMount(() => { load(); loadOidc() })
87
205
  </script>
88
206
  <!DOCTYPE html>
89
207
  <html lang="en" data-theme="dark">
90
- <head>@include('../partials/head', { title: 'ts-cloud - Team' })</head>
208
+ <head>@include('../partials/head', { title: 'ts-cloud - People & access' })</head>
91
209
  <body>
92
210
  @include('../partials/nav', { active: 'team', name: server.name, env: 'production' })
93
211
  <div class="wrap">
94
- <div class="crumbs"><a href="/">Server</a> <span class="sep">/</span> Team</div>
212
+ <div class="crumbs"><a href="/">Dashboard</a> <span class="sep">/</span> Organization <span class="sep">/</span> People &amp; access</div>
95
213
  <header class="page">
96
- <div>
97
- <h1>Team</h1>
98
- <p>Invite people to individual sites. They see only the sites you grant them, and never the box.</p>
99
- </div>
214
+ <div><h1>People &amp; access</h1><p>Organization roles are inherited only within their assigned project, environment, or resource scope. Explicit denies always win.</p></div>
100
215
  </header>
101
216
 
217
+ <div class="access-message msg" :class="messageKind()" role="status">{{ message() }}</div>
218
+
102
219
  <div class="section">
103
- <h2>Invite</h2>
220
+ <h2>Single sign-on</h2>
221
+ <div class="panel sso-panel">
222
+ <div class="sso-intro"><div><strong>OpenID Connect providers</strong><p>Discovery, issuer binding, state, nonce, PKCE, signed ID tokens, and verified email-domain policy are enforced. Provider access and refresh tokens are discarded.</p></div><button class="btn" type="button" @click="resetOidc(null)">Add provider</button></div>
223
+ <div class="recovery-warning"><strong>Keep the recovery path proven.</strong><span>Organization owners can always use local sign-in. Before enforcement, verify the owner email, save MFA recovery codes, and test <code>cloud auth:oidc:disable &lt;slug&gt; --confirm &lt;slug&gt;</code> from the host.</span></div>
224
+ <div class="callback" @show="!!oidcCallbackPattern()"><span>Callback URI</span><code>{{ oidcCallbackPattern() }}</code></div>
225
+ <div class="provider-list"><template :for="provider in oidcProviders()"><div class="provider-row">
226
+ <div><strong>{{ provider.name }}</strong><span>{{ provider.issuer }}</span><small>{{ provider.allowedDomains.join(', ') }} · new users: {{ provider.defaultRole }} · {{ provider.enforceSso ? 'SSO required' : 'local sign-in allowed' }}</small></div>
227
+ <span class="tag" :class="provider.enabled ? 'ok' : ''">{{ provider.enabled ? 'Enabled' : 'Disabled' }}</span>
228
+ <div class="table-actions"><button class="btn ghost sm" type="button" @click="resetOidc(provider)">Configure</button></div>
229
+ </div></template><div class="compact empty" @show="oidcProviders().length === 0"><strong>No SSO providers</strong><span>Local authentication remains available until an OIDC provider is explicitly configured.</span></div></div>
230
+ </div>
231
+ </div>
232
+
233
+ <div class="section" @show="oidcFormOpen()">
234
+ <h2>{{ oidcEditing() ? 'Configure SSO provider' : 'Add SSO provider' }}</h2>
235
+ <div class="panel"><form class="form-grid" @submit.prevent="saveOidc()">
236
+ <div class="field-block"><label for="oidc-name">Display name</label><input id="oidc-name" :value="oidcName()" @input="oidcName.set($event.target.value)" placeholder="Acme Workforce" required></div>
237
+ <div class="field-block"><label for="oidc-slug">Slug</label><input id="oidc-slug" :value="oidcSlug()" @input="oidcSlug.set($event.target.value)" pattern="[a-z0-9][a-z0-9-]{1,47}" placeholder="workforce" required></div>
238
+ <div class="field-block"><label for="oidc-issuer">Exact issuer URL</label><input id="oidc-issuer" type="url" :value="oidcIssuer()" @input="oidcIssuer.set($event.target.value)" placeholder="https://identity.example.com" required><small>HTTPS discovery issuer must match this value exactly.</small></div>
239
+ <div class="field-block"><label for="oidc-client">Client ID</label><input id="oidc-client" :value="oidcClientId()" @input="oidcClientId.set($event.target.value)" autocomplete="off" required></div>
240
+ <div class="field-block"><label for="oidc-secret">Client secret</label><input id="oidc-secret" type="password" :value="oidcClientSecret()" @input="oidcClientSecret.set($event.target.value)" autocomplete="new-password" :required="!oidcEditing()"><small>{{ oidcEditing() ? 'Leave empty to preserve the encrypted secret.' : 'Encrypted at rest with the authentication key.' }}</small></div>
241
+ <div class="field-block"><label for="oidc-domains">Allowed email domains</label><input id="oidc-domains" :value="oidcDomains()" @input="oidcDomains.set($event.target.value)" placeholder="example.com, subsidiary.example.com" required><small>Exact domains only; wildcards are rejected.</small></div>
242
+ <div class="field-block"><label for="oidc-role">Provisioned-user role</label><select id="oidc-role" :value="oidcDefaultRole()" @change="oidcDefaultRole.set($event.target.value)"><option value="viewer">Viewer</option><option value="auditor">Auditor</option><option value="deployer">Developer / deployer</option><option value="operator">Operator</option><option value="admin">Admin</option></select></div>
243
+ <label class="check"><input type="checkbox" :checked="oidcEnforce()" @change="oidcEnforce.set($event.target.checked)"><span><b>Require SSO for matching non-owner accounts</b><small>Owners retain local emergency sign-in. Requires verified owner email and unused MFA recovery codes.</small></span></label>
244
+ <div class="field-block"><label for="oidc-password">Owner password</label><input id="oidc-password" type="password" autocomplete="current-password" :value="oidcPassword()" @input="oidcPassword.set($event.target.value)" required></div>
245
+ <div class="field-block"><label for="oidc-code">Authenticator or recovery code</label><input id="oidc-code" autocomplete="one-time-code" :value="oidcCode()" @input="oidcCode.set($event.target.value)"><small>Required when MFA is enabled.</small></div>
246
+ <div class="form-actions"><button class="btn" type="submit">Save provider</button><button class="btn danger" type="button" @show="oidcEditing()?.enabled" @click="setOidcEnabled(oidcEditing(), false)">Disable provider</button><button class="btn ghost" type="button" @show="oidcEditing() && !oidcEditing()?.enabled" @click="setOidcEnabled(oidcEditing(), true)">Enable provider</button><button class="btn ghost" type="button" @click="oidcFormOpen.set(false); oidcEditing.set(null); oidcSlug.set('')">Cancel</button></div>
247
+ </form></div>
248
+ </div>
249
+
250
+ <div class="section">
251
+ <h2>Invite a member</h2>
104
252
  <div class="panel">
105
253
  <form class="form-grid" @submit.prevent="invite()">
106
- <div class="field-block">
107
- <label for="team-username">Username</label>
108
- <input id="team-username" autocomplete="off" placeholder="dana" :value="username()" @input="username.set($event.target.value)" required>
109
- </div>
110
- <div class="field-block">
111
- <label for="team-name">Display name</label>
112
- <input id="team-name" autocomplete="off" placeholder="Dana Whitfield" :value="displayName()" @input="displayName.set($event.target.value)">
113
- </div>
114
- <div class="field-block wide">
115
- <label>Site access</label>
116
- <div class="grant-grid">
117
- <template :for="s in sites()">
118
- <div class="grant">
119
- <span class="mono">{{ s }}</span>
120
- <select :value="grantFor(s)" @change="setGrant(s, $event.target.value)">
121
- <option value="none">No access</option>
122
- <option value="collaborator">Collaborator - view and deploy</option>
123
- <option value="owner">Owner - view, deploy and edit settings</option>
124
- </select>
125
- </div>
126
- </template>
127
- </div>
128
- </div>
129
- <div class="form-actions">
130
- <button class="btn" type="submit">Send invite</button>
131
- <span class="msg" :class="messageKind()" role="status">{{ message() }}</span>
132
- </div>
254
+ <div class="field-block"><label for="invite-email">Email</label><input id="invite-email" type="email" autocomplete="email" placeholder="dana@example.com" :value="email()" @input="email.set($event.target.value)" required></div>
255
+ <div class="field-block"><label for="invite-role">Role template</label><select id="invite-role" :value="roleTemplate()" @change="roleTemplate.set($event.target.value)">
256
+ <option value="deployer">Developer / deployer</option><option value="operator">Operator</option><option value="viewer">Viewer</option><option value="auditor">Auditor</option><option value="admin">Admin</option><option value="owner">Owner</option>
257
+ </select><small>{{ roleSummary[roleTemplate()] }}</small></div>
258
+ <div class="field-block"><label for="invite-scope">Access scope</label><select id="invite-scope" :value="scopeType()" @change="scopeType.set($event.target.value); setScope($event.target.value, scopeId)">
259
+ <option value="organization">Entire organization</option><option value="project">Current project</option><option value="environment">One environment</option><option value="resource">One resource</option>
260
+ </select></div>
261
+ <div class="field-block" @show="scopeType() === 'environment'"><label for="invite-environment">Environment</label><select id="invite-environment" :value="scopeId()" @change="scopeId.set($event.target.value)"><template :for="item in environments()"><option :value="item.id">{{ item.name }}</option></template></select></div>
262
+ <div class="field-block" @show="scopeType() === 'resource'"><label for="invite-resource">Resource</label><select id="invite-resource" :value="scopeId()" @change="scopeId.set($event.target.value)"><template :for="item in resources()"><option :value="item.id">{{ item.name }} · {{ item.slug }}</option></template></select></div>
263
+ <div class="field-block" @show="roleTemplate() === 'owner'"><label for="invite-owner-password">Your password</label><input id="invite-owner-password" type="password" autocomplete="current-password" :value="ownerPassword()" @input="ownerPassword.set($event.target.value)"><small>Required when adding another owner.</small></div>
264
+ <div class="form-actions"><button class="btn" type="submit" :disabled="pageBusy()">Create invitation</button></div>
133
265
  </form>
134
-
135
- <div class="issued" @show="!!issued()">
136
- <strong>Password for {{ issued()?.username }}</strong>
137
- <code>{{ issued()?.password }}</code>
138
- <span>Copy it now and send it over a channel you trust. It is stored hashed and cannot be shown again.</span>
139
- </div>
266
+ <div class="issued" @show="!!issued()"><strong>One-time acceptance link</strong><code>{{ issued()?.acceptUrl }}</code><span>Copy this link now. Only its SHA-256 hash is stored, and resending replaces it.</span></div>
140
267
  </div>
141
268
  </div>
142
269
 
143
270
  <div class="section">
144
- <h2>People</h2>
271
+ <h2>Members</h2>
145
272
  <div class="panel">
146
- <table>
147
- <thead><tr><th>User</th><th>Role</th><th>Sites</th><th></th></tr></thead>
148
- <tbody>
149
- <template :for="u in users()">
150
- <tr>
151
- <td><b>{{ u.name }}</b><span class="mono uname">{{ u.username }}</span></td>
152
- <td><span class="tag">{{ u.role === 'admin' ? 'box owner' : 'member' }}</span></td>
153
- <td class="mono">{{ u.role === 'admin' ? 'every site' : siteList(u) }}</td>
154
- <td class="table-actions"><button type="button" class="btn danger sm" @click="askRevoke(u)">Remove</button></td>
155
- </tr>
156
- </template>
157
- </tbody>
158
- </table>
159
- <div class="compact empty" @show="users().length === 0"><strong>No one invited yet</strong><span>Invite someone above to give them access to a site.</span></div>
160
- <div class="op-confirm" @show="pendingRevoke() !== null" style="margin-top:14px">
161
- <span>Type <b class="mono">{{ pendingRevoke()?.username }}</b> to remove <b>{{ pendingRevoke()?.name }}</b> from every site:</span>
162
- <input class="op-confirm-input" :value="typedRevoke()" @input="typedRevoke.set($event.target.value)" @keydown.enter="confirmRevoke()" placeholder="confirm" autocomplete="off">
163
- <button class="btn danger sm" :disabled="!canRevoke()" @click="confirmRevoke()">Remove</button>
164
- <button class="btn ghost sm" @click="cancelRevoke()">Cancel</button>
165
- </div>
166
- <p class="note">Box owners reach everything on this server. Members reach only the sites listed here, and never the shell, SSH keys, firewall or databases. Access is checked on every request.</p>
273
+ <table><thead><tr><th scope="col">Person</th><th scope="col">Role</th><th scope="col">Effective scope</th><th scope="col">Last activity</th><th scope="col"></th></tr></thead><tbody>
274
+ <template :for="member in memberships()"><tr>
275
+ <td><b>{{ actorName(member) }}</b><span class="mono uname">{{ member.actor?.externalId?.replace('dashboard:', '') }}</span></td>
276
+ <td><span class="tag">{{ member.roleTemplate }}</span><small class="role-detail">{{ roleSummary[member.roleTemplate] }}</small></td>
277
+ <td><b>{{ scopeLabel(member.scope) }}</b><small class="role-detail">{{ member.source === 'legacy' ? 'Migrated direct access' : 'Inherited from membership' }}</small></td>
278
+ <td>{{ member.lastActiveAt ? new Date(member.lastActiveAt).toLocaleString() : 'Never' }}</td>
279
+ <td class="table-actions"><button class="btn ghost sm" type="button" :aria-label="'Edit access for ' + actorName(member)" @click="beginEdit(member)">Edit access</button><button class="btn danger sm" type="button" :aria-label="'Remove ' + actorName(member)" @click="beginRemove(member)">Remove</button></td>
280
+ </tr></template>
281
+ </tbody></table>
282
+ <div class="compact empty" @show="memberships().length === 0"><strong>No members</strong><span>Create an invitation to add someone.</span></div>
167
283
  </div>
168
284
  </div>
285
+
286
+ <div class="section" @show="editing() !== null">
287
+ <h2>Edit access</h2><div class="panel"><form class="form-grid" @submit.prevent="changeMembership(false)">
288
+ <div class="field-block"><label>Role template</label><select :value="editRole()" @change="editRole.set($event.target.value)"><option value="owner">Owner</option><option value="admin">Admin</option><option value="deployer">Developer / deployer</option><option value="operator">Operator</option><option value="viewer">Viewer</option><option value="auditor">Auditor</option></select></div>
289
+ <div class="field-block"><label>Scope</label><select :value="editScopeType()" @change="editScopeType.set($event.target.value); setScope($event.target.value, editScopeId)"><option value="organization">Entire organization</option><option value="project">Current project</option><option value="environment">One environment</option><option value="resource">One resource</option></select></div>
290
+ <div class="field-block" @show="editScopeType() === 'environment'"><label>Environment</label><select :value="editScopeId()" @change="editScopeId.set($event.target.value)"><template :for="item in environments()"><option :value="item.id">{{ item.name }}</option></template></select></div>
291
+ <div class="field-block" @show="editScopeType() === 'resource'"><label>Resource</label><select :value="editScopeId()" @change="editScopeId.set($event.target.value)"><template :for="item in resources()"><option :value="item.id">{{ item.name }} · {{ item.slug }}</option></template></select></div>
292
+ <div class="field-block"><label>Owner password</label><input type="password" autocomplete="current-password" :value="editPassword()" @input="editPassword.set($event.target.value)"><small>Required when adding, removing, or changing an owner.</small></div>
293
+ <div class="field-block"><label>Confirmation</label><input :placeholder="editing()?.id" :value="editConfirm()" @input="editConfirm.set($event.target.value)"><small>After previewing, type the membership ID.</small></div>
294
+ <div class="access-diff" @show="!!accessDiff()"><div><strong>Gained</strong><span>{{ accessDiff()?.gained?.join(', ') || 'None' }}</span></div><div><strong>Lost</strong><span>{{ accessDiff()?.lost?.join(', ') || 'None' }}</span></div></div>
295
+ <div class="form-actions"><button class="btn ghost" type="button" @click="changeMembership(true)">Preview effective access</button><button class="btn" type="submit">Apply change</button><button class="btn ghost" type="button" @click="editing.set(null)">Cancel</button></div>
296
+ </form></div>
297
+ </div>
298
+
299
+ <div class="section" @show="pending() !== null"><h2>Remove membership</h2><div class="panel"><div class="field-block"><label for="remove-owner-password">Your password (required when removing an owner)</label><input id="remove-owner-password" type="password" autocomplete="current-password" :value="removePassword()" @input="removePassword.set($event.target.value)"></div>@include('../partials/op-confirm')</div></div>
300
+
301
+ <div class="section"><h2>Pending &amp; previous invitations</h2><div class="panel"><table><thead><tr><th scope="col">Email</th><th scope="col">Role</th><th scope="col">Scope</th><th scope="col">State</th><th scope="col"></th></tr></thead><tbody><template :for="invitation in invitations()"><tr><td>{{ invitation.email }}</td><td><span class="tag">{{ invitation.roleTemplate }}</span></td><td>{{ scopeLabel(invitation.scope) }}</td><td>{{ invitationState(invitation) }}</td><td class="table-actions"><button class="btn ghost sm" type="button" :disabled="pageBusy()" @show="invitation.state !== 'accepted'" @click="invitationAction(invitation, 'resend')">Resend</button><button class="btn danger sm" type="button" :disabled="pageBusy()" @show="invitation.state === 'pending'" @click="invitationAction(invitation, 'revoke')">Revoke</button></td></tr></template></tbody></table><div class="compact empty" @show="invitations().length === 0"><strong>No invitations</strong><span>New and previous invitation states appear here.</span></div></div></div>
169
302
  </div>
170
303
  <style>
171
- .grant-grid { display: grid; gap: 10px; }
172
- .grant { display: grid; grid-template-columns: minmax(120px, 0.4fr) 1fr; gap: 12px; align-items: center; }
173
- .grant > span { color: var(--txt2); font-size: 13px; }
174
- .grant select {
175
- width: 100%; background: rgba(255,255,255,0.05); border: 1px solid var(--panel-br); border-radius: 10px;
176
- padding: 9px 12px; color: var(--txt); font-family: inherit; font-size: 13.5px;
177
- }
178
- .grant select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(90,139,224,0.12); }
179
- .issued {
180
- display: grid; gap: 7px; margin-top: 16px; padding: 14px 16px;
181
- border: 1px solid rgba(53,209,155,0.28); background: var(--okbg); border-radius: 12px;
182
- }
183
- .uname { margin-left: 8px; color: var(--txt3); }
184
- .issued strong { font-size: 13px; color: var(--ok); }
185
- .issued code {
186
- font-family: var(--mono); font-size: 14px; color: var(--txt); user-select: all;
187
- background: rgba(0,0,0,0.25); border-radius: 8px; padding: 8px 10px; overflow-wrap: anywhere;
188
- }
189
- .issued span { color: var(--txt2); font-size: 12px; line-height: 1.5; }
190
- @media (max-width: 640px) { .grant { grid-template-columns: 1fr; gap: 6px; } }
304
+ .access-message { min-height: 20px; margin-top: 12px; }
305
+ .issued { display:grid; gap:7px; margin-top:16px; padding:14px 16px; border:1px solid rgba(53,209,155,.28); background:var(--okbg); border-radius:12px; }
306
+ .issued code { user-select:all; overflow-wrap:anywhere; padding:8px 10px; border-radius:8px; background:rgba(0,0,0,.25); }
307
+ .uname { display:block; margin-top:3px; color:var(--txt3); }
308
+ .role-detail { display:block; margin-top:4px; max-width:260px; color:var(--txt3); }
309
+ .access-diff { grid-column:1/-1; display:grid; grid-template-columns:1fr 1fr; gap:10px; }
310
+ .access-diff div { display:grid; gap:4px; padding:12px; border:1px solid var(--panel-br); border-radius:10px; }
311
+ .access-diff span { color:var(--txt2); font-size:12px; line-height:1.5; }
312
+ .sso-panel,.provider-list { display:grid; gap:14px; }
313
+ .sso-intro { display:flex; align-items:flex-start; justify-content:space-between; gap:18px; }
314
+ .sso-intro p { color:var(--txt2); font-size:13px; line-height:1.55; max-width:720px; margin-top:4px; }
315
+ .recovery-warning { display:grid; gap:4px; padding:12px 14px; border:1px solid rgba(240,184,79,.3); background:rgba(240,184,79,.08); border-radius:10px; }
316
+ .recovery-warning span,.provider-row span,.provider-row small { color:var(--txt2); font-size:12px; line-height:1.5; }
317
+ .callback { display:grid; gap:5px; }
318
+ .callback span { color:var(--txt3); text-transform:uppercase; font-size:10px; font-weight:800; letter-spacing:.06em; }
319
+ .callback code { user-select:all; overflow-wrap:anywhere; }
320
+ .provider-row { display:grid; grid-template-columns:minmax(0,1fr) auto auto; align-items:center; gap:14px; padding:14px; border:1px solid var(--panel-br); border-radius:10px; }
321
+ .provider-row>div:first-child { min-width:0; display:grid; gap:3px; }
322
+ .provider-row>div:first-child span { overflow-wrap:anywhere; }
323
+ .check { display:flex; gap:10px; align-items:flex-start; padding:12px; border:1px solid var(--panel-br); border-radius:10px; }
324
+ .check input { width:auto; margin-top:3px; }
325
+ .check span { display:grid; gap:3px; }
326
+ .check small { color:var(--txt3); line-height:1.45; }
327
+ @media(max-width:640px){.access-diff{grid-template-columns:1fr}}
191
328
  </style>
192
329
  </body>
193
330
  </html>
@@ -4,35 +4,12 @@ try { __d = JSON.parse(process.env.TSCLOUD_DASHBOARD_DATA || '') } catch { __d =
4
4
  const server = __d?.server ?? { name: 'stacks-production-app' }
5
5
  </script>
6
6
  <script client>
7
+ import { useTerminal } from '../../functions/useTerminal.ts'
8
+
7
9
  // Web terminal: a WebSocket to /api/terminal streams a persistent shell running
8
10
  // on the dashboard host (the box, in box mode). Line-oriented: type a command
9
11
  // and press Enter; output streams back. Not a full PTY (no vim/htop).
10
- const buf = state('')
11
- const status = state('connecting')
12
- const cmd = state('')
13
- let ws = null
14
- function scrollBottom() {
15
- const el = document.getElementById('term-out')
16
- if (el) el.scrollTop = el.scrollHeight
17
- }
18
- function connect() {
19
- status.set('connecting')
20
- const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'
21
- ws = new WebSocket(proto + '//' + location.host + '/api/terminal')
22
- ws.onopen = () => status.set('connected')
23
- ws.onclose = () => status.set('disconnected')
24
- ws.onerror = () => status.set('error')
25
- ws.onmessage = (e) => { buf.set((buf() + e.data).slice(-40000)); scrollBottom() }
26
- }
27
- function send() {
28
- if (!ws || ws.readyState !== 1) return
29
- ws.send(cmd() + '\n')
30
- cmd.set('')
31
- }
32
- function reconnect() { try { if (ws) ws.close() } catch {} buf.set(''); connect() }
33
- function clearOut() { buf.set('') }
34
- function statusTone(s) { return s === 'connected' ? 'ok' : (s === 'disconnected' || s === 'error' ? 'bad' : 'warn') }
35
- onMount(() => { connect(); return () => { try { if (ws) ws.close() } catch {} } })
12
+ const { buf, cmd, status, send, reconnect, clearOut, statusTone } = useTerminal()
36
13
  </script>
37
14
  <!DOCTYPE html>
38
15
  <html lang="en" data-theme="dark">
@@ -52,9 +29,10 @@ onMount(() => { connect(); return () => { try { if (ws) ws.close() } catch {} }
52
29
 
53
30
  <div class="section">
54
31
  <div class="panel">
55
- <pre id="term-out" class="logs" style="max-height:520px; white-space:pre-wrap; overflow-wrap:anywhere;">{{ buf() }}</pre>
32
+ <pre @ref="terminalOutput" id="term-out" class="logs" aria-live="polite" aria-label="Terminal output" style="max-height:520px; white-space:pre-wrap; overflow-wrap:anywhere;">{{ buf() }}</pre>
56
33
  <div class="field" style="margin-top:12px;">
57
- <input :value="cmd()" @input="cmd.set($event.target.value)" @keydown.enter="send()" placeholder="e.g. systemctl status rpx-gateway, tail -n 50 /var/log/syslog" autocomplete="off" spellcheck="false" style="font-family:ui-monospace, SFMono-Regular, Menlo, monospace;">
34
+ <label class="sr-only" for="terminal-command">Command</label>
35
+ <input id="terminal-command" :value="cmd()" @input="cmd.set($event.target.value)" @keydown.enter="send()" placeholder="e.g. systemctl status rpx-gateway, tail -n 50 /var/log/syslog" autocomplete="off" spellcheck="false" style="font-family:ui-monospace, SFMono-Regular, Menlo, monospace;">
58
36
  <button class="btn" type="button" @click="send()">Run</button>
59
37
  </div>
60
38
  <p class="note">The shell runs on the machine serving this dashboard (in box mode, the provisioned server) and is protected by the same Basic auth. It is line-oriented, so full-screen programs like vim or htop are not supported. Disable it with <span class="mono">TS_CLOUD_DASHBOARD_TERMINAL=0</span>.</p>