devpush 0.0.0-stage → 1.0.0-beta.3

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 (456) hide show
  1. package/README.md +6 -2
  2. package/agent-release.json +37 -0
  3. package/dist/package/LICENSE.md +21 -0
  4. package/dist/package/migrate.mjs +182 -0
  5. package/dist/package/plugins.mjs +125 -0
  6. package/dist/package/vite.mjs +439 -0
  7. package/package.json +170 -4
  8. package/public/apple-touch-icon.png +0 -0
  9. package/public/favicon.svg +1 -0
  10. package/public/logo-email.png +0 -0
  11. package/public/robots.txt +3 -0
  12. package/public/social.png +0 -0
  13. package/src/cloudflare-runtime.d.ts +78 -0
  14. package/src/components/about-dialog.tsx +70 -0
  15. package/src/components/copy-button.tsx +74 -0
  16. package/src/components/logo-mark.tsx +20 -0
  17. package/src/components/pending-collection.tsx +103 -0
  18. package/src/components/resource-avatar.tsx +65 -0
  19. package/src/components/route-error-state.tsx +95 -0
  20. package/src/components/section-navigation.tsx +39 -0
  21. package/src/components/settings-layout.tsx +111 -0
  22. package/src/components/storage-capabilities.tsx +11 -0
  23. package/src/components/table-pager.tsx +101 -0
  24. package/src/components/ui/alert-dialog.tsx +180 -0
  25. package/src/components/ui/alert.tsx +78 -0
  26. package/src/components/ui/avatar.tsx +58 -0
  27. package/src/components/ui/badge.tsx +49 -0
  28. package/src/components/ui/button-group.tsx +38 -0
  29. package/src/components/ui/button.tsx +67 -0
  30. package/src/components/ui/card.tsx +82 -0
  31. package/src/components/ui/chart.tsx +307 -0
  32. package/src/components/ui/checkbox.tsx +30 -0
  33. package/src/components/ui/combobox.tsx +256 -0
  34. package/src/components/ui/dialog.tsx +163 -0
  35. package/src/components/ui/dropdown-menu.tsx +110 -0
  36. package/src/components/ui/empty.tsx +65 -0
  37. package/src/components/ui/field.tsx +192 -0
  38. package/src/components/ui/input-group.tsx +118 -0
  39. package/src/components/ui/input-otp.tsx +70 -0
  40. package/src/components/ui/input.tsx +19 -0
  41. package/src/components/ui/label.tsx +24 -0
  42. package/src/components/ui/pagination.tsx +34 -0
  43. package/src/components/ui/popover.tsx +86 -0
  44. package/src/components/ui/select.tsx +145 -0
  45. package/src/components/ui/skeleton.tsx +19 -0
  46. package/src/components/ui/sonner.tsx +47 -0
  47. package/src/components/ui/switch.tsx +31 -0
  48. package/src/components/ui/table.tsx +81 -0
  49. package/src/components/ui/textarea.tsx +18 -0
  50. package/src/components/ui/tooltip.tsx +55 -0
  51. package/src/features/admin/admin-settings.tsx +1048 -0
  52. package/src/features/auth/current-user-context-query.ts +11 -0
  53. package/src/features/auth/login-form.tsx +442 -0
  54. package/src/features/auth/sign-out-menu-item.tsx +42 -0
  55. package/src/features/deployments/deployment-actions.tsx +336 -0
  56. package/src/features/deployments/deployment-alias-badge.tsx +35 -0
  57. package/src/features/deployments/deployment-detail.tsx +596 -0
  58. package/src/features/deployments/deployment-filters.ts +66 -0
  59. package/src/features/deployments/deployment-list.tsx +465 -0
  60. package/src/features/deployments/deployment-visit-links.tsx +104 -0
  61. package/src/features/deployments/deployments-page.tsx +376 -0
  62. package/src/features/logs/log-scroll-area.tsx +69 -0
  63. package/src/features/notifications/email-preferences.tsx +232 -0
  64. package/src/features/onboarding/profile-onboarding.tsx +162 -0
  65. package/src/features/plugins/contributions.tsx +118 -0
  66. package/src/features/projects/backup-job-logs.tsx +143 -0
  67. package/src/features/projects/backup-policy-delete-dialog.tsx +82 -0
  68. package/src/features/projects/backup-restore-actions.tsx +573 -0
  69. package/src/features/projects/backup-status-icon.tsx +33 -0
  70. package/src/features/projects/backup-target-list.tsx +222 -0
  71. package/src/features/projects/backup-timestamp.tsx +32 -0
  72. package/src/features/projects/backup-volume-picker.tsx +158 -0
  73. package/src/features/projects/compose-service-input.tsx +64 -0
  74. package/src/features/projects/deploy-readiness.ts +28 -0
  75. package/src/features/projects/environment-dot.tsx +91 -0
  76. package/src/features/projects/log-keyword-filter.tsx +48 -0
  77. package/src/features/projects/manual-deploy-dialog.tsx +563 -0
  78. package/src/features/projects/pending-section.ts +26 -0
  79. package/src/features/projects/project-backup-details-pending.tsx +305 -0
  80. package/src/features/projects/project-backup-details.tsx +936 -0
  81. package/src/features/projects/project-backup-policy.tsx +644 -0
  82. package/src/features/projects/project-backup-utils.ts +190 -0
  83. package/src/features/projects/project-backups-pending.tsx +136 -0
  84. package/src/features/projects/project-backups.tsx +876 -0
  85. package/src/features/projects/project-environment-settings.tsx +1128 -0
  86. package/src/features/projects/project-heading.tsx +68 -0
  87. package/src/features/projects/project-layout.tsx +188 -0
  88. package/src/features/projects/project-logs.tsx +461 -0
  89. package/src/features/projects/project-overview.tsx +280 -0
  90. package/src/features/projects/project-route-pending.tsx +1064 -0
  91. package/src/features/projects/project-routing-settings.tsx +663 -0
  92. package/src/features/projects/project-settings-section.tsx +64 -0
  93. package/src/features/projects/project-settings-utils.ts +101 -0
  94. package/src/features/projects/project-settings.tsx +1472 -0
  95. package/src/features/projects/repository-picker-dialog.tsx +404 -0
  96. package/src/features/repositories/new-project-dialog-launcher.tsx +44 -0
  97. package/src/features/repositories/new-project-dialog.tsx +1204 -0
  98. package/src/features/repositories/new-project-pending.tsx +28 -0
  99. package/src/features/repositories/repository-list-pending.tsx +59 -0
  100. package/src/features/servers/server-availability-alert.tsx +44 -0
  101. package/src/features/servers/server-availability.ts +29 -0
  102. package/src/features/servers/server-avatar.tsx +52 -0
  103. package/src/features/servers/server-detail-pending.tsx +315 -0
  104. package/src/features/servers/server-detail.tsx +855 -0
  105. package/src/features/servers/server-metric-chart-pending.tsx +38 -0
  106. package/src/features/servers/server-metric-chart.tsx +182 -0
  107. package/src/features/servers/server-metric-format.ts +50 -0
  108. package/src/features/servers/server-metrics-pending.tsx +75 -0
  109. package/src/features/servers/server-metrics.tsx +136 -0
  110. package/src/features/servers/team-servers.tsx +432 -0
  111. package/src/features/settings/settings-route-pending.tsx +406 -0
  112. package/src/features/teams/backup-storage-settings.tsx +483 -0
  113. package/src/features/teams/navigation.ts +51 -0
  114. package/src/features/teams/new-team-dialog.tsx +132 -0
  115. package/src/features/teams/pending-section.ts +19 -0
  116. package/src/features/teams/team-header.tsx +1286 -0
  117. package/src/features/teams/team-heading.tsx +57 -0
  118. package/src/features/teams/team-home.tsx +391 -0
  119. package/src/features/teams/team-invite.tsx +155 -0
  120. package/src/features/teams/team-route-pending.tsx +505 -0
  121. package/src/features/teams/team-settings.tsx +1555 -0
  122. package/src/features/teams/use-workspace-updates.ts +126 -0
  123. package/src/features/users/api-keys-settings.tsx +544 -0
  124. package/src/features/users/api-scope-selection.tsx +269 -0
  125. package/src/features/users/connected-apps-settings.tsx +449 -0
  126. package/src/features/users/mcp-consent.tsx +220 -0
  127. package/src/features/users/profile-picture.tsx +171 -0
  128. package/src/features/users/user-settings.tsx +1064 -0
  129. package/src/lib/account-settings-schema.ts +78 -0
  130. package/src/lib/action-feedback.ts +81 -0
  131. package/src/lib/admin-search-schema.ts +8 -0
  132. package/src/lib/agent-release.d.mts +21 -0
  133. package/src/lib/agent-release.mjs +90 -0
  134. package/src/lib/allowlist-schema.ts +79 -0
  135. package/src/lib/api-key-schema.ts +176 -0
  136. package/src/lib/auth/client.ts +20 -0
  137. package/src/lib/avatar-schema.ts +11 -0
  138. package/src/lib/backup-cron.ts +118 -0
  139. package/src/lib/backup-database.ts +41 -0
  140. package/src/lib/backup-feature.ts +2 -0
  141. package/src/lib/backup-paths.ts +7 -0
  142. package/src/lib/backup-policy-schema.ts +85 -0
  143. package/src/lib/backup-resource-schema.ts +12 -0
  144. package/src/lib/backup-restore-schema.ts +15 -0
  145. package/src/lib/backup-storage-schema.ts +55 -0
  146. package/src/lib/deferred-module.ts +38 -0
  147. package/src/lib/deployment-logs-query.ts +63 -0
  148. package/src/lib/display-name.ts +7 -0
  149. package/src/lib/error-message.ts +119 -0
  150. package/src/lib/form-errors.ts +10 -0
  151. package/src/lib/internal-redirect.ts +25 -0
  152. package/src/lib/manual-deploy-options-query.ts +21 -0
  153. package/src/lib/notification-preferences.ts +43 -0
  154. package/src/lib/onboarding.ts +11 -0
  155. package/src/lib/operation-logs-query.ts +51 -0
  156. package/src/lib/page-head.ts +14 -0
  157. package/src/lib/project-context-query.ts +257 -0
  158. package/src/lib/project-creation-schema.ts +64 -0
  159. package/src/lib/project-env-schema.ts +23 -0
  160. package/src/lib/project-environments-schema.ts +56 -0
  161. package/src/lib/project-routing-schema.ts +94 -0
  162. package/src/lib/project-runtime-config.ts +131 -0
  163. package/src/lib/project-settings-schema.ts +75 -0
  164. package/src/lib/public-api-schema.ts +128 -0
  165. package/src/lib/public-error.ts +337 -0
  166. package/src/lib/query.ts +13 -0
  167. package/src/lib/repository-selection-query.ts +11 -0
  168. package/src/lib/resource-cache.ts +30 -0
  169. package/src/lib/runtime-id.ts +19 -0
  170. package/src/lib/search.ts +14 -0
  171. package/src/lib/server-context-query.ts +43 -0
  172. package/src/lib/server-name-schema.ts +11 -0
  173. package/src/lib/settings-conflict.ts +45 -0
  174. package/src/lib/settings-version.ts +7 -0
  175. package/src/lib/team-context-query.ts +126 -0
  176. package/src/lib/team-context-schema.ts +8 -0
  177. package/src/lib/team-forms-schema.ts +45 -0
  178. package/src/lib/team-role.ts +13 -0
  179. package/src/lib/team-settings-schema.ts +48 -0
  180. package/src/lib/urls.ts +3 -0
  181. package/src/lib/use-avatar-input.ts +61 -0
  182. package/src/lib/use-refresh-error.ts +18 -0
  183. package/src/lib/use-settings-version.ts +23 -0
  184. package/src/lib/utils.ts +6 -0
  185. package/src/lib/validation.ts +5 -0
  186. package/src/lib/workspace-updates.ts +53 -0
  187. package/src/package/config.ts +10 -0
  188. package/src/package/env.mjs +54 -0
  189. package/src/package/migrate-api.d.ts +5 -0
  190. package/src/package/migrate.ts +56 -0
  191. package/src/package/plugins.ts +141 -0
  192. package/src/package/sqlite.mjs +2 -0
  193. package/src/package/vite-api.d.ts +5 -0
  194. package/src/package/vite.ts +324 -0
  195. package/src/package/watch-guard.cjs +26 -0
  196. package/src/plugins/client.ts +57 -0
  197. package/src/plugins/contract.ts +188 -0
  198. package/src/plugins/default.server.ts +4 -0
  199. package/src/plugins/default.ui.ts +4 -0
  200. package/src/plugins/operations.ts +45 -0
  201. package/src/plugins/server.ts +197 -0
  202. package/src/plugins/version.ts +1 -0
  203. package/src/routeTree.gen.ts +1275 -0
  204. package/src/router.tsx +46 -0
  205. package/src/routes/$teamSlug.index.tsx +53 -0
  206. package/src/routes/$teamSlug.new-project.tsx +13 -0
  207. package/src/routes/$teamSlug.projects.$projectName.backups.$backupId.tsx +40 -0
  208. package/src/routes/$teamSlug.projects.$projectName.backups.tsx +82 -0
  209. package/src/routes/$teamSlug.projects.$projectName.deployments.$deploymentId.tsx +52 -0
  210. package/src/routes/$teamSlug.projects.$projectName.deployments.tsx +86 -0
  211. package/src/routes/$teamSlug.projects.$projectName.logs.tsx +55 -0
  212. package/src/routes/$teamSlug.projects.$projectName.settings.tsx +51 -0
  213. package/src/routes/$teamSlug.projects.$projectName.tsx +106 -0
  214. package/src/routes/$teamSlug.projects.tsx +40 -0
  215. package/src/routes/$teamSlug.servers.$serverId.tsx +31 -0
  216. package/src/routes/$teamSlug.servers.index.tsx +59 -0
  217. package/src/routes/$teamSlug.servers.tsx +7 -0
  218. package/src/routes/$teamSlug.settings.tsx +56 -0
  219. package/src/routes/$teamSlug.tsx +86 -0
  220. package/src/routes/[.]well-known.oauth-authorization-server.api.auth.ts +13 -0
  221. package/src/routes/[.]well-known.oauth-protected-resource.mcp.ts +6 -0
  222. package/src/routes/__root.tsx +155 -0
  223. package/src/routes/admin.tsx +55 -0
  224. package/src/routes/api/agents/certificates.ts +21 -0
  225. package/src/routes/api/agents/credentials/rotate.ts +22 -0
  226. package/src/routes/api/agents/heartbeat.ts +42 -0
  227. package/src/routes/api/agents/job-logs.ts +22 -0
  228. package/src/routes/api/agents/jobs/complete.ts +22 -0
  229. package/src/routes/api/agents/jobs/next.ts +29 -0
  230. package/src/routes/api/agents/jobs/progress.ts +32 -0
  231. package/src/routes/api/agents/jobs/status.ts +22 -0
  232. package/src/routes/api/agents/register.ts +45 -0
  233. package/src/routes/api/agents/runtime-logs.ts +22 -0
  234. package/src/routes/api/agents/switch.ts +25 -0
  235. package/src/routes/api/auth/$.ts +38 -0
  236. package/src/routes/api/cron/backups.ts +33 -0
  237. package/src/routes/api/cron/maintenance.ts +30 -0
  238. package/src/routes/api/github/install.callback.ts +51 -0
  239. package/src/routes/api/github/install.ts +41 -0
  240. package/src/routes/api/github/webhook.ts +11 -0
  241. package/src/routes/api/health.ts +19 -0
  242. package/src/routes/api/install/agent.$token.ts +50 -0
  243. package/src/routes/api/install/agent.binary.ts +83 -0
  244. package/src/routes/api/install/agent.checksum.ts +45 -0
  245. package/src/routes/api/install/agent.ts +26 -0
  246. package/src/routes/api/plugins/$.ts +11 -0
  247. package/src/routes/api/v1/$.ts +15 -0
  248. package/src/routes/api/workspaces/updates.ts +8 -0
  249. package/src/routes/auth.login.tsx +50 -0
  250. package/src/routes/deployments.$deploymentId.unavailable.tsx +21 -0
  251. package/src/routes/index.tsx +23 -0
  252. package/src/routes/invites.$token.tsx +35 -0
  253. package/src/routes/mcp.ts +13 -0
  254. package/src/routes/oauth.consent.tsx +54 -0
  255. package/src/routes/onboarding.tsx +37 -0
  256. package/src/routes/onboarding.workspace.tsx +44 -0
  257. package/src/routes/settings.tsx +30 -0
  258. package/src/routes/uploads/avatars/$fileName.ts +44 -0
  259. package/src/server/admin/access.ts +67 -0
  260. package/src/server/admin/functions.ts +122 -0
  261. package/src/server/admin/service.ts +260 -0
  262. package/src/server/agents/backup-state.ts +210 -0
  263. package/src/server/agents/certificates.ts +96 -0
  264. package/src/server/agents/claims.ts +116 -0
  265. package/src/server/agents/cleanup.ts +52 -0
  266. package/src/server/agents/compatibility.ts +116 -0
  267. package/src/server/agents/completion.ts +83 -0
  268. package/src/server/agents/container-cleanup-state.ts +51 -0
  269. package/src/server/agents/deployment-state.ts +111 -0
  270. package/src/server/agents/job-payloads.ts +159 -0
  271. package/src/server/agents/jobs.ts +1293 -0
  272. package/src/server/agents/protocol.ts +400 -0
  273. package/src/server/agents/reconciliation.ts +43 -0
  274. package/src/server/agents/runtime-logs.ts +337 -0
  275. package/src/server/agents/runtime-state.ts +125 -0
  276. package/src/server/api/admission.ts +12 -0
  277. package/src/server/api/authorization.ts +115 -0
  278. package/src/server/api/credentials.ts +569 -0
  279. package/src/server/api/cursors.ts +72 -0
  280. package/src/server/api/errors.ts +24 -0
  281. package/src/server/api/functions.ts +60 -0
  282. package/src/server/api/handler.ts +274 -0
  283. package/src/server/api/mutation-operations.ts +913 -0
  284. package/src/server/api/operation-context.ts +139 -0
  285. package/src/server/api/operation-contract.ts +1099 -0
  286. package/src/server/api/operations.ts +64 -0
  287. package/src/server/api/read-operations.ts +891 -0
  288. package/src/server/api/retention.ts +33 -0
  289. package/src/server/auth/access.ts +109 -0
  290. package/src/server/auth/auth-methods.ts +28 -0
  291. package/src/server/auth/better-auth.ts +518 -0
  292. package/src/server/auth/context.ts +28 -0
  293. package/src/server/auth/email-change.ts +47 -0
  294. package/src/server/auth/email-delivery.ts +51 -0
  295. package/src/server/auth/provider-identity.ts +19 -0
  296. package/src/server/auth/provider-tokens.ts +27 -0
  297. package/src/server/auth/session.server.ts +19 -0
  298. package/src/server/backups/database-commands.ts +40 -0
  299. package/src/server/backups/destination.ts +12 -0
  300. package/src/server/backups/feature.ts +6 -0
  301. package/src/server/backups/policy-validation.ts +51 -0
  302. package/src/server/backups/queue-state.ts +137 -0
  303. package/src/server/backups/service.ts +918 -0
  304. package/src/server/backups/storage-functions.ts +42 -0
  305. package/src/server/backups/storage-state.ts +67 -0
  306. package/src/server/backups/storage.ts +237 -0
  307. package/src/server/config.ts +274 -0
  308. package/src/server/db/atomic.ts +16 -0
  309. package/src/server/db/d1-connection.ts +83 -0
  310. package/src/server/db/errors.ts +17 -0
  311. package/src/server/db/index.cloudflare.ts +15 -0
  312. package/src/server/db/index.ts +35 -0
  313. package/src/server/db/libsql-connection.ts +72 -0
  314. package/src/server/db/log-schema.ts +102 -0
  315. package/src/server/db/migrate-libsql.ts +29 -0
  316. package/src/server/db/migrate-sqlite.ts +47 -0
  317. package/src/server/db/migration-history.ts +12 -0
  318. package/src/server/db/migrations-logs/0000_initial.sql +48 -0
  319. package/src/server/db/migrations-logs/0001_runtime_log_order.sql +2 -0
  320. package/src/server/db/migrations-logs/meta/0000_snapshot.json +327 -0
  321. package/src/server/db/migrations-logs/meta/0001_snapshot.json +329 -0
  322. package/src/server/db/migrations-logs/meta/_journal.json +20 -0
  323. package/src/server/db/migrations-sqlite/0000_initial.sql +894 -0
  324. package/src/server/db/migrations-sqlite/0001_agent_indexes.sql +5 -0
  325. package/src/server/db/migrations-sqlite/meta/0000_snapshot.json +5816 -0
  326. package/src/server/db/migrations-sqlite/meta/0001_snapshot.json +5841 -0
  327. package/src/server/db/migrations-sqlite/meta/_journal.json +20 -0
  328. package/src/server/db/oauth-schema.ts +216 -0
  329. package/src/server/db/remote-config.ts +47 -0
  330. package/src/server/db/schema.ts +1218 -0
  331. package/src/server/db/sqlite-connection.ts +94 -0
  332. package/src/server/db/sqlite-files.ts +27 -0
  333. package/src/server/db/sqlite-params.ts +19 -0
  334. package/src/server/db/worker-connection.ts +47 -0
  335. package/src/server/deleted-resources.ts +133 -0
  336. package/src/server/deletion-recovery.ts +272 -0
  337. package/src/server/email/layout.tsx +85 -0
  338. package/src/server/email/notification-template.tsx +62 -0
  339. package/src/server/email/otp-template.tsx +48 -0
  340. package/src/server/email/service.ts +251 -0
  341. package/src/server/email/team-invite-template.tsx +59 -0
  342. package/src/server/email/theme.ts +10 -0
  343. package/src/server/github/http.ts +14 -0
  344. package/src/server/github/install-state.ts +48 -0
  345. package/src/server/github/service.ts +831 -0
  346. package/src/server/github/webhook-handler.ts +198 -0
  347. package/src/server/github/webhooks.ts +205 -0
  348. package/src/server/http/auth.ts +11 -0
  349. package/src/server/http/error-diagnostics.ts +66 -0
  350. package/src/server/http/plugin-request.ts +15 -0
  351. package/src/server/http/proxy-request.ts +53 -0
  352. package/src/server/http/public-errors.ts +48 -0
  353. package/src/server/http/rate-limit.ts +59 -0
  354. package/src/server/http/request-body.ts +54 -0
  355. package/src/server/http/request-ip.ts +54 -0
  356. package/src/server/http/security-headers.ts +36 -0
  357. package/src/server/install/agent-binary.ts +230 -0
  358. package/src/server/install/agent-script.ts +565 -0
  359. package/src/server/install/release-binary.ts +65 -0
  360. package/src/server/logs/budget.ts +72 -0
  361. package/src/server/logs/cleanup.ts +64 -0
  362. package/src/server/logs/deployments.ts +98 -0
  363. package/src/server/logs/operations.ts +145 -0
  364. package/src/server/logs/retention.ts +39 -0
  365. package/src/server/maintenance.ts +61 -0
  366. package/src/server/mcp/connections.ts +358 -0
  367. package/src/server/mcp/functions.ts +63 -0
  368. package/src/server/mcp/handler.ts +295 -0
  369. package/src/server/mcp/oauth-discovery.cloudflare.ts +6 -0
  370. package/src/server/mcp/oauth-discovery.ts +8 -0
  371. package/src/server/mcp/oauth-provider.ts +220 -0
  372. package/src/server/mcp/urls.ts +20 -0
  373. package/src/server/notifications/functions.ts +51 -0
  374. package/src/server/notifications/observe.ts +303 -0
  375. package/src/server/notifications/outbox.ts +335 -0
  376. package/src/server/notifications/preferences.ts +131 -0
  377. package/src/server/notifications/query.ts +16 -0
  378. package/src/server/plugins/scope.ts +17 -0
  379. package/src/server/plugins/server-context.ts +157 -0
  380. package/src/server/plugins/services.server.ts +10 -0
  381. package/src/server/plugins/workspace-access.ts +74 -0
  382. package/src/server/projects/backup-policy-state.ts +68 -0
  383. package/src/server/projects/backups.ts +219 -0
  384. package/src/server/projects/compose-metadata.ts +174 -0
  385. package/src/server/projects/configuration-state.ts +90 -0
  386. package/src/server/projects/context.ts +909 -0
  387. package/src/server/projects/creation-state.ts +46 -0
  388. package/src/server/projects/deletion-state.ts +69 -0
  389. package/src/server/projects/deployment-config.ts +139 -0
  390. package/src/server/projects/deployment-logs.ts +97 -0
  391. package/src/server/projects/deployments.ts +1123 -0
  392. package/src/server/projects/domain-state.ts +71 -0
  393. package/src/server/projects/environment-branches.ts +75 -0
  394. package/src/server/projects/environment-state.ts +158 -0
  395. package/src/server/projects/environments.ts +424 -0
  396. package/src/server/projects/functions.ts +748 -0
  397. package/src/server/projects/generated-hostname.ts +77 -0
  398. package/src/server/projects/hostnames.ts +244 -0
  399. package/src/server/projects/mappers.ts +457 -0
  400. package/src/server/projects/names.ts +41 -0
  401. package/src/server/projects/project-env-vars.ts +28 -0
  402. package/src/server/projects/queue-state.ts +118 -0
  403. package/src/server/projects/repository.ts +368 -0
  404. package/src/server/projects/route-state.ts +37 -0
  405. package/src/server/projects/runtime-logs.ts +97 -0
  406. package/src/server/projects/settings-context.ts +58 -0
  407. package/src/server/projects/settings.ts +738 -0
  408. package/src/server/projects/types.ts +355 -0
  409. package/src/server/realtime/agent-events.ts +32 -0
  410. package/src/server/realtime/node-broadcaster.ts +71 -0
  411. package/src/server/realtime/node-handler.ts +55 -0
  412. package/src/server/realtime/service.ts +125 -0
  413. package/src/server/realtime/workspace-object.cloudflare.ts +62 -0
  414. package/src/server/realtime/workspace-object.ts +3 -0
  415. package/src/server/resources/color.ts +13 -0
  416. package/src/server/runtime/background.ts +21 -0
  417. package/src/server/runtime/node-asset-cache-plugin.ts +12 -0
  418. package/src/server/runtime/node-scheduler-plugin.ts +17 -0
  419. package/src/server/runtime/node-scheduler.ts +51 -0
  420. package/src/server/runtime/platform-env.cloudflare.ts +17 -0
  421. package/src/server/runtime/platform-env.ts +19 -0
  422. package/src/server/runtime/scheduled-task-error.ts +9 -0
  423. package/src/server/scheduled-tasks.ts +28 -0
  424. package/src/server/secrets/crypto.ts +56 -0
  425. package/src/server/servers/functions.ts +233 -0
  426. package/src/server/servers/lifecycle.ts +122 -0
  427. package/src/server/servers/metric-history-schema.ts +24 -0
  428. package/src/server/servers/metric-history.ts +55 -0
  429. package/src/server/servers/monitoring.ts +592 -0
  430. package/src/server/servers/service.ts +1211 -0
  431. package/src/server/servers/status.ts +32 -0
  432. package/src/server/servers/switch.ts +96 -0
  433. package/src/server/settings-version.ts +43 -0
  434. package/src/server/storage/capabilities.ts +6 -0
  435. package/src/server/storage/local-object-store.cloudflare.ts +7 -0
  436. package/src/server/storage/local-object-store.ts +50 -0
  437. package/src/server/storage/object-store.ts +160 -0
  438. package/src/server/teams/creation.ts +51 -0
  439. package/src/server/teams/delete-policy.ts +13 -0
  440. package/src/server/teams/deletion-state.ts +80 -0
  441. package/src/server/teams/functions.ts +324 -0
  442. package/src/server/teams/invitations.ts +38 -0
  443. package/src/server/teams/membership.ts +86 -0
  444. package/src/server/teams/queries.ts +261 -0
  445. package/src/server/teams/service.ts +1127 -0
  446. package/src/server/teams/types.ts +101 -0
  447. package/src/server/uploads/avatar.ts +133 -0
  448. package/src/server/users/deletion-state.ts +93 -0
  449. package/src/server/users/functions.ts +160 -0
  450. package/src/server/users/onboarding.ts +17 -0
  451. package/src/server/users/service.ts +644 -0
  452. package/src/server.ts +55 -0
  453. package/src/styles/shadcn.css +653 -0
  454. package/src/styles/theme.css +76 -0
  455. package/src/styles.css +177 -0
  456. package/wrangler.template.jsonc +37 -0
@@ -0,0 +1,855 @@
1
+ import { mutationErrorOptions, afterMutation } from '#devpush/lib/action-feedback'
2
+ import { useSettingsVersion } from '#devpush/lib/use-settings-version'
3
+ import { showSettingsConflict } from '#devpush/lib/settings-conflict'
4
+ import { CopyButton } from '#devpush/components/copy-button'
5
+ import { useQueryClient } from '@tanstack/react-query'
6
+ import { invalidateResourceQueries } from '#devpush/lib/resource-cache'
7
+ import { serverNameSchema } from '#devpush/lib/server-name-schema'
8
+ import { Link, useRouter } from '@tanstack/react-router'
9
+ import {
10
+ BanIcon,
11
+ LoaderIcon,
12
+ SquareTerminalIcon,
13
+ Trash2Icon,
14
+ } from 'lucide-react'
15
+ import { useRef, useState, lazy, Suspense } from 'react'
16
+ import { toast } from 'sonner'
17
+
18
+ import {
19
+ Alert,
20
+ AlertDescription,
21
+ AlertTitle,
22
+ } from '#devpush/components/ui/alert'
23
+ import {
24
+ AlertDialog,
25
+ AlertDialogAction,
26
+ AlertDialogCancel,
27
+ AlertDialogContent,
28
+ AlertDialogDescription,
29
+ AlertDialogFooter,
30
+ AlertDialogHeader,
31
+ AlertDialogTitle,
32
+ AlertDialogTrigger,
33
+ } from '#devpush/components/ui/alert-dialog'
34
+ import { Button } from '#devpush/components/ui/button'
35
+ import { Badge } from '#devpush/components/ui/badge'
36
+ import { Checkbox } from '#devpush/components/ui/checkbox'
37
+
38
+ import {
39
+ Field,
40
+ FieldDescription,
41
+ FieldError,
42
+ FieldGroup,
43
+ FieldLabel,
44
+ } from '#devpush/components/ui/field'
45
+ import { Input } from '#devpush/components/ui/input'
46
+ import {
47
+ InputGroup,
48
+ InputGroupAddon,
49
+ InputGroupInput,
50
+ } from '#devpush/components/ui/input-group'
51
+ import {
52
+ SettingsLayout,
53
+ SettingsSection,
54
+ } from '#devpush/components/settings-layout'
55
+ import { TeamHeading } from '#devpush/features/teams/team-heading'
56
+ import { ServerAvatar } from '#devpush/features/servers/server-avatar'
57
+ import {
58
+ ServerMetricsPending,
59
+ ServerMetricsEmpty,
60
+ } from './server-metrics-pending'
61
+ import { formatBytes } from './server-metric-format'
62
+
63
+ import { getPublicErrorMessage } from '#devpush/lib/public-error'
64
+ import { canManageTeamSettings } from '#devpush/lib/team-role'
65
+ import {
66
+ formatAge,
67
+ formatServerStatus,
68
+ getInstallDescription,
69
+ InstallCommand,
70
+ } from '#devpush/features/servers/team-servers'
71
+ import {
72
+ createInstallCommand,
73
+ deleteServer,
74
+ disableServer,
75
+ enableServer,
76
+ getDeleteServerDiagnostic,
77
+ updateServer,
78
+ } from '#devpush/server/servers/functions'
79
+
80
+ import type {
81
+ ServerDeleteDiagnostic,
82
+ ServerDetail as ServerDetailData,
83
+ ServerInstallCommand,
84
+ } from '#devpush/server/servers/service'
85
+ import type { TeamSummary } from '#devpush/server/teams/types'
86
+
87
+ const ServerMetrics = lazy(() =>
88
+ import('./server-metrics').then((module) => ({
89
+ default: module.ServerMetrics,
90
+ })),
91
+ )
92
+
93
+ export function ServerDetail({
94
+ server,
95
+ team,
96
+ }: {
97
+ server: ServerDetailData
98
+ team: TeamSummary
99
+ }) {
100
+ const router = useRouter()
101
+ const queryClient = useQueryClient()
102
+ const [savedName, setSavedName] = useState(server.name)
103
+ const [nameError, setNameError] = useState<string | null>(null)
104
+ const [name, setName] = useState(server.name)
105
+ const [isSaving, setIsSaving] = useState(false)
106
+ const canManageServer = canManageTeamSettings(team.role)
107
+ const hasChanges = name.trim() !== savedName
108
+
109
+ const settingsVersion = useSettingsVersion({
110
+ version: server.settingsVersion,
111
+ dirty: hasChanges,
112
+ saving: isSaving,
113
+ refresh: () => {
114
+ setName(server.name)
115
+ setSavedName(server.name)
116
+ setNameError(null)
117
+ },
118
+ })
119
+
120
+ async function save() {
121
+ if (isSaving || !canManageServer || !hasChanges) return
122
+ const parsed = serverNameSchema.safeParse(name)
123
+ if (!parsed.success) {
124
+ setNameError(
125
+ parsed.error.issues[0]?.message ?? 'Enter a valid server name.',
126
+ )
127
+ document.getElementById('server-name')?.focus()
128
+ return
129
+ }
130
+ setNameError(null)
131
+
132
+ setIsSaving(true)
133
+
134
+ try {
135
+ const result = await updateServer({
136
+ data: {
137
+ teamSlug: team.slug,
138
+ serverId: server.id,
139
+ settingsVersion: settingsVersion.current,
140
+ name,
141
+ },
142
+ })
143
+ settingsVersion.current = result.settingsVersion
144
+ const nextName = name.trim()
145
+ setName(nextName)
146
+ setSavedName(nextName)
147
+ toast.success('Server saved.')
148
+ await afterMutation(async () => {
149
+ await invalidateResourceQueries(queryClient, team.slug)
150
+ await router.invalidate()
151
+ }, 'The action completed, but refreshing the page failed. Please reload.')
152
+ } catch (error) {
153
+ if (showSettingsConflict(error)) return
154
+ toast.error(
155
+ getPublicErrorMessage(error, 'Unable to save server.'),
156
+ ...mutationErrorOptions(error),
157
+ )
158
+ } finally {
159
+ setIsSaving(false)
160
+ }
161
+ }
162
+
163
+ function reset() {
164
+ setNameError(null)
165
+ setName(savedName)
166
+ }
167
+
168
+ return (
169
+ <div className="space-y-8">
170
+ <TeamHeading
171
+ team={team}
172
+ crumb={
173
+ <>
174
+ <Link
175
+ className="hover:underline"
176
+ to="/$teamSlug/servers"
177
+ params={{ teamSlug: team.slug }}
178
+ >
179
+ Servers
180
+ </Link>
181
+ <span className="text-muted-foreground/25 font-normal">/</span>
182
+ <span className="truncate">{savedName}</span>
183
+ </>
184
+ }
185
+ />
186
+
187
+ <SettingsLayout
188
+ navigation={[
189
+ { href: '#general', label: 'General' },
190
+ { href: '#host', label: 'Host' },
191
+ { href: '#metrics', label: 'Metrics' },
192
+ ...(canManageServer
193
+ ? [
194
+ {
195
+ href: '#danger' as const,
196
+ label: 'Danger',
197
+ destructive: true,
198
+ },
199
+ ]
200
+ : []),
201
+ ]}
202
+ >
203
+ {server.status === 'connected' || !canManageServer ? null : (
204
+ <ServerInstallCallout server={server} teamSlug={team.slug} />
205
+ )}
206
+
207
+ {server.agentUpdate.status === 'current' ? null : (
208
+ <Alert
209
+ variant={
210
+ server.agentUpdate.status === 'required'
211
+ ? 'destructive'
212
+ : 'default'
213
+ }
214
+ >
215
+ <AlertTitle className="flex items-center gap-2">
216
+ Agent update {server.agentUpdate.status}
217
+ {server.agentUpdate.latestVersion ? (
218
+ <Badge variant="outline">
219
+ {server.agentUpdate.latestVersion}
220
+ </Badge>
221
+ ) : null}
222
+ </AlertTitle>
223
+ <AlertDescription>
224
+ {server.agentUpdate.reason}{' '}
225
+ {canManageServer ? (
226
+ <>
227
+ Update manually with{' '}
228
+ <code>sudo /usr/local/lib/devpush/update-agent</code>.
229
+ </>
230
+ ) : (
231
+ 'Contact a workspace owner or admin to update the agent.'
232
+ )}
233
+ </AlertDescription>
234
+ </Alert>
235
+ )}
236
+
237
+ <SettingsSection
238
+ id="general"
239
+ title="General"
240
+ description="Basic details for this server."
241
+ contentClassName="space-y-6"
242
+ footer={
243
+ canManageServer ? (
244
+ <>
245
+ <Button
246
+ variant="ghost"
247
+ type="button"
248
+ onClick={reset}
249
+ disabled={!hasChanges || isSaving}
250
+ >
251
+ Cancel
252
+ </Button>
253
+ <Button
254
+ type="submit"
255
+ form="server-general-form"
256
+ disabled={!hasChanges || isSaving}
257
+ >
258
+ {isSaving ? (
259
+ <LoaderIcon
260
+ data-icon="inline-start"
261
+ className="animate-spin"
262
+ />
263
+ ) : null}
264
+ Save
265
+ </Button>
266
+ </>
267
+ ) : undefined
268
+ }
269
+ >
270
+ <div className="space-y-2">
271
+ <p className="text-sm font-medium">Status</p>
272
+ <div className="flex items-center gap-2 text-sm">
273
+ <ServerAvatar name={savedName} status={server.status} size="sm" />
274
+ <span>{formatServerStatus(server.status)}</span>
275
+ <span className="text-muted-foreground">
276
+ ·{' '}
277
+ {server.lastSeenAt
278
+ ? formatAge(server.lastSeenAt)
279
+ : 'Never contacted'}
280
+ </span>
281
+ </div>
282
+ </div>
283
+
284
+ <form
285
+ id="server-general-form"
286
+ noValidate
287
+ onSubmit={(event) => {
288
+ event.preventDefault()
289
+ void save()
290
+ }}
291
+ >
292
+ <FieldGroup>
293
+ <Field data-invalid={Boolean(nameError)}>
294
+ <FieldLabel htmlFor="server-name">Name</FieldLabel>
295
+ <Input
296
+ id="server-name"
297
+ required
298
+ maxLength={100}
299
+ disabled={isSaving}
300
+ aria-invalid={Boolean(nameError)}
301
+ aria-describedby="server-name-error"
302
+ value={name}
303
+ readOnly={!canManageServer}
304
+ onChange={(event) => {
305
+ setName(event.target.value)
306
+ setNameError(null)
307
+ }}
308
+ />
309
+ <FieldError id="server-name-error">{nameError}</FieldError>
310
+ </Field>
311
+ <Field>
312
+ <FieldLabel htmlFor="server-id">Server ID</FieldLabel>
313
+ <InputGroup>
314
+ <InputGroupInput
315
+ id="server-id"
316
+ className="font-mono text-[13px] text-muted-foreground"
317
+ value={server.id}
318
+ readOnly
319
+ />
320
+ <InputGroupAddon align="inline-end">
321
+ <CopyButton
322
+ value={server.id}
323
+ label="Copy server ID"
324
+ inputGroup
325
+ />
326
+ </InputGroupAddon>
327
+ </InputGroup>
328
+ <FieldDescription>
329
+ A unique identifier used by the API and agent.
330
+ </FieldDescription>
331
+ </Field>
332
+ </FieldGroup>
333
+ </form>
334
+ </SettingsSection>
335
+
336
+ <SettingsSection
337
+ id="host"
338
+ title="Host"
339
+ description="System information reported by the agent."
340
+ >
341
+ <dl className="grid gap-6 text-sm sm:grid-cols-2">
342
+ <ServerDetailField label="Public IPv4">
343
+ {server.publicIpv4 ?? 'Not reported'}
344
+ </ServerDetailField>
345
+ <ServerDetailField label="Public IPv6">
346
+ {server.publicIpv6 ?? 'Not reported'}
347
+ </ServerDetailField>
348
+ <ServerDetailField label="Hostname">
349
+ {server.hostname ?? 'Not reported'}
350
+ </ServerDetailField>
351
+ <ServerDetailField label="Operating system">
352
+ {server.operatingSystem ?? 'Not reported'}
353
+ </ServerDetailField>
354
+ <ServerDetailField label="CPU">
355
+ {server.cpuCoreCount
356
+ ? formatCount(server.cpuCoreCount, 'core')
357
+ : 'Not reported'}
358
+ </ServerDetailField>
359
+ <ServerDetailField label="Memory">
360
+ {server.monitoring.latestMetrics?.memoryTotalBytes
361
+ ? formatBytes(server.monitoring.latestMetrics.memoryTotalBytes)
362
+ : 'Not reported'}
363
+ </ServerDetailField>
364
+ <ServerDetailField label="Architecture">
365
+ {server.architecture ?? 'Not reported'}
366
+ </ServerDetailField>
367
+ <ServerDetailField label="Host uptime">
368
+ {server.status === 'connected' &&
369
+ server.hostUptimeSeconds !== null
370
+ ? formatDuration(server.hostUptimeSeconds)
371
+ : 'Not reported'}
372
+ </ServerDetailField>
373
+ <ServerDetailField label="Kernel">
374
+ {server.kernelVersion ?? 'Not reported'}
375
+ </ServerDetailField>
376
+ <ServerDetailField label="Agent version">
377
+ {server.agentVersion ?? 'Not reported'}
378
+ </ServerDetailField>
379
+ </dl>
380
+ </SettingsSection>
381
+
382
+ <SettingsSection
383
+ id="metrics"
384
+ title="Metrics"
385
+ description="Up to 24 hours of host resource history. Missing samples remain visible as gaps."
386
+ contentClassName="space-y-4"
387
+ >
388
+ {server.monitoring.latestMetrics ? (
389
+ <Suspense
390
+ fallback={
391
+ <ServerMetricsPending
392
+ latestMetrics={server.monitoring.latestMetrics}
393
+ />
394
+ }
395
+ >
396
+ <ServerMetrics
397
+ serverId={server.id}
398
+ teamSlug={team.slug}
399
+ latestMetrics={server.monitoring.latestMetrics}
400
+ />
401
+ </Suspense>
402
+ ) : (
403
+ <ServerMetricsEmpty />
404
+ )}
405
+ </SettingsSection>
406
+
407
+ {canManageServer ? (
408
+ <ServerDangerSection server={server} teamSlug={team.slug} />
409
+ ) : null}
410
+ </SettingsLayout>
411
+ </div>
412
+ )
413
+ }
414
+
415
+ export function ServerInstallCallout({
416
+ server,
417
+ teamSlug,
418
+ }: {
419
+ server: Pick<ServerDetailData, 'id' | 'name' | 'status'>
420
+ teamSlug: string
421
+ }) {
422
+ const [installCommand, setInstallCommand] = useState<string | null>(null)
423
+ const [isCreating, setIsCreating] = useState(false)
424
+
425
+ async function createCommand() {
426
+ if (isCreating || server.status === 'disabled') return
427
+ setIsCreating(true)
428
+
429
+ try {
430
+ const result: ServerInstallCommand = await createInstallCommand({
431
+ data: {
432
+ teamSlug,
433
+ serverId: server.id,
434
+ },
435
+ })
436
+ setInstallCommand(result.installCommand)
437
+ } catch (cause) {
438
+ if (showSettingsConflict(cause)) return
439
+ toast.error(
440
+ getPublicErrorMessage(cause, 'Unable to create install command.'),
441
+ ...mutationErrorOptions(cause),
442
+ )
443
+ } finally {
444
+ setIsCreating(false)
445
+ }
446
+ }
447
+
448
+ return (
449
+ <Alert className="gap-y-2">
450
+ <SquareTerminalIcon />
451
+ <AlertTitle>Install agent</AlertTitle>
452
+ <AlertDescription className="col-start-2 space-y-3 [&_p:not(:last-child)]:mb-0">
453
+ <p>{getInstallDescription(server.name)}</p>
454
+ {server.status === 'disabled' ? (
455
+ <p className="text-sm text-muted-foreground">
456
+ Enable this server before creating a new install command.
457
+ </p>
458
+ ) : installCommand ? (
459
+ <InstallCommand className="mt-2 max-w-xl" command={installCommand} />
460
+ ) : (
461
+ <div className="space-y-3">
462
+ <Button
463
+ className="mt-2"
464
+ disabled={isCreating}
465
+ onClick={() => void createCommand()}
466
+ type="button"
467
+ variant="outline"
468
+ >
469
+ {isCreating ? (
470
+ <LoaderIcon data-icon="inline-start" className="animate-spin" />
471
+ ) : null}
472
+ Generate install command
473
+ </Button>
474
+ </div>
475
+ )}
476
+ </AlertDescription>
477
+ </Alert>
478
+ )
479
+ }
480
+
481
+ export function ServerDangerSection({
482
+ server,
483
+ teamSlug,
484
+ }: {
485
+ server: Pick<ServerDetailData, 'id' | 'name' | 'status'>
486
+ teamSlug: string
487
+ }) {
488
+ const router = useRouter()
489
+ const queryClient = useQueryClient()
490
+ const [isChangingStatus, setIsChangingStatus] = useState(false)
491
+ const [statusDialogOpen, setStatusDialogOpen] = useState(false)
492
+ const [disableConfirmation, setDisableConfirmation] = useState('')
493
+ const [deleteOpen, setDeleteOpen] = useState(false)
494
+ const [deleteConfirmation, setDeleteConfirmation] = useState('')
495
+ const [cancelOngoingDeployments, setCancelOngoingDeployments] = useState(true)
496
+ const [deleteDiagnostic, setDeleteDiagnostic] =
497
+ useState<ServerDeleteDiagnostic | null>(null)
498
+ const [deleteDiagnosticError, setDeleteDiagnosticError] = useState<
499
+ string | null
500
+ >(null)
501
+ const [isLoadingDeleteDiagnostic, setIsLoadingDeleteDiagnostic] =
502
+ useState(false)
503
+ const diagnosticRequest = useRef(0)
504
+ const [serverDeleted, setServerDeleted] = useState(false)
505
+ const [isDeleting, setIsDeleting] = useState(false)
506
+ const isDisabled = server.status === 'disabled'
507
+ const canDisable =
508
+ !isChangingStatus && (isDisabled || disableConfirmation === server.name)
509
+ const canDelete =
510
+ !isDeleting && !serverDeleted && deleteConfirmation.trim() === server.name
511
+
512
+ async function toggleServerStatus() {
513
+ if (
514
+ isChangingStatus ||
515
+ isDeleting ||
516
+ serverDeleted ||
517
+ (!isDisabled && disableConfirmation !== server.name)
518
+ )
519
+ return
520
+
521
+ setIsChangingStatus(true)
522
+
523
+ try {
524
+ if (isDisabled) {
525
+ await enableServer({
526
+ data: { teamSlug, serverId: server.id },
527
+ })
528
+ } else {
529
+ await disableServer({
530
+ data: {
531
+ teamSlug,
532
+ serverId: server.id,
533
+ confirmationName: disableConfirmation,
534
+ },
535
+ })
536
+ }
537
+ toast.success(isDisabled ? 'Server enabled.' : 'Server disabled.')
538
+ setStatusDialogOpen(false)
539
+ setDisableConfirmation('')
540
+ await afterMutation(async () => {
541
+ await invalidateResourceQueries(queryClient, teamSlug)
542
+ await router.invalidate()
543
+ }, 'The action completed, but refreshing the page failed. Please reload.')
544
+ } catch (error) {
545
+ if (showSettingsConflict(error)) return
546
+ toast.error(
547
+ getPublicErrorMessage(
548
+ error,
549
+ isDisabled ? 'Unable to enable server.' : 'Unable to disable server.',
550
+ ),
551
+ ...mutationErrorOptions(error),
552
+ )
553
+ } finally {
554
+ setIsChangingStatus(false)
555
+ }
556
+ }
557
+
558
+ async function loadDeleteDiagnostic() {
559
+ const request = ++diagnosticRequest.current
560
+ setIsLoadingDeleteDiagnostic(true)
561
+ setDeleteDiagnostic(null)
562
+ setDeleteDiagnosticError(null)
563
+
564
+ try {
565
+ const diagnostic = await getDeleteServerDiagnostic({
566
+ data: { teamSlug, serverId: server.id },
567
+ })
568
+ if (request !== diagnosticRequest.current) return
569
+ setDeleteDiagnostic(diagnostic)
570
+ } catch (error) {
571
+ if (showSettingsConflict(error)) return
572
+ if (request !== diagnosticRequest.current) return
573
+ setDeleteDiagnosticError(
574
+ getPublicErrorMessage(error, 'Unable to inspect server usage.'),
575
+ )
576
+ } finally {
577
+ if (request === diagnosticRequest.current)
578
+ setIsLoadingDeleteDiagnostic(false)
579
+ }
580
+ }
581
+
582
+ async function deleteCurrentServer() {
583
+ if (
584
+ isDeleting ||
585
+ isChangingStatus ||
586
+ serverDeleted ||
587
+ deleteConfirmation.trim() !== server.name
588
+ )
589
+ return
590
+
591
+ setIsDeleting(true)
592
+
593
+ try {
594
+ await deleteServer({
595
+ data: {
596
+ teamSlug,
597
+ serverId: server.id,
598
+ confirmationName: deleteConfirmation,
599
+ cancelOngoingDeployments,
600
+ },
601
+ })
602
+ setServerDeleted(true)
603
+ setDeleteOpen(false)
604
+ toast.success('Server deleted.')
605
+ await afterMutation(async () => {
606
+ await invalidateResourceQueries(queryClient, teamSlug)
607
+ await router.navigate({
608
+ to: '/$teamSlug/servers',
609
+ params: { teamSlug },
610
+ })
611
+ }, 'The action completed, but opening the next page failed. Please reload.')
612
+ } catch (error) {
613
+ if (showSettingsConflict(error)) return
614
+ toast.error(
615
+ getPublicErrorMessage(error, 'Unable to delete server.'),
616
+ ...mutationErrorOptions(error),
617
+ )
618
+ } finally {
619
+ setIsDeleting(false)
620
+ }
621
+ }
622
+
623
+ function onDeleteOpenChange(open: boolean) {
624
+ if (isDeleting || isChangingStatus || serverDeleted) return
625
+ diagnosticRequest.current += 1
626
+
627
+ setDeleteOpen(open)
628
+
629
+ if (!open) {
630
+ setDeleteConfirmation('')
631
+ setDeleteDiagnostic(null)
632
+ setDeleteDiagnosticError(null)
633
+ setCancelOngoingDeployments(true)
634
+ return
635
+ }
636
+
637
+ void loadDeleteDiagnostic()
638
+ }
639
+
640
+ function onStatusOpenChange(open: boolean) {
641
+ if (isChangingStatus || isDeleting || serverDeleted) return
642
+
643
+ setStatusDialogOpen(open)
644
+
645
+ if (!open) {
646
+ setDisableConfirmation('')
647
+ }
648
+ }
649
+
650
+ return (
651
+ <SettingsSection
652
+ id="danger"
653
+ title="Danger"
654
+ description="Administrative actions that affect this server."
655
+ contentClassName="flex flex-col items-start gap-2"
656
+ >
657
+ <AlertDialog open={statusDialogOpen} onOpenChange={onStatusOpenChange}>
658
+ <AlertDialogTrigger asChild>
659
+ <Button
660
+ variant={isDisabled ? 'default' : 'destructive'}
661
+ disabled={isChangingStatus}
662
+ >
663
+ {isChangingStatus ? (
664
+ <LoaderIcon data-icon="inline-start" className="animate-spin" />
665
+ ) : !isDisabled ? (
666
+ <BanIcon data-icon="inline-start" />
667
+ ) : null}
668
+ {isDisabled ? 'Enable server' : 'Disable server'}
669
+ </Button>
670
+ </AlertDialogTrigger>
671
+ <AlertDialogContent>
672
+ <AlertDialogHeader>
673
+ <AlertDialogTitle>
674
+ {isDisabled ? 'Enable this server?' : 'Disable this server?'}
675
+ </AlertDialogTitle>
676
+ <AlertDialogDescription>
677
+ {isDisabled
678
+ ? `${server.name} will accept its existing agent credential again. Reinstallation is not required.`
679
+ : `This blocks agent communication and new work for ${server.name}. Running apps stay online; queued work waits and active jobs may time out. Type "${server.name}" to confirm.`}
680
+ </AlertDialogDescription>
681
+ </AlertDialogHeader>
682
+ {!isDisabled ? (
683
+ <Field>
684
+ <Input
685
+ aria-label={`Type ${server.name} to confirm`}
686
+ required
687
+ id="disable-server-confirmation"
688
+ placeholder={server.name}
689
+ value={disableConfirmation}
690
+ onChange={(event) => setDisableConfirmation(event.target.value)}
691
+ onKeyDown={(event) => {
692
+ if (event.key === 'Enter' && canDisable) {
693
+ event.preventDefault()
694
+ void toggleServerStatus()
695
+ }
696
+ }}
697
+ disabled={isChangingStatus}
698
+ />
699
+ </Field>
700
+ ) : null}
701
+ <AlertDialogFooter>
702
+ <AlertDialogCancel disabled={isChangingStatus}>
703
+ Cancel
704
+ </AlertDialogCancel>
705
+ <AlertDialogAction
706
+ variant={isDisabled ? 'default' : 'destructive'}
707
+ disabled={!canDisable}
708
+ onClick={(event) => {
709
+ event.preventDefault()
710
+ void toggleServerStatus()
711
+ }}
712
+ >
713
+ {isChangingStatus ? (
714
+ <LoaderIcon className="animate-spin" />
715
+ ) : !isDisabled ? (
716
+ <BanIcon />
717
+ ) : null}
718
+ {isDisabled ? 'Enable server' : 'Disable server'}
719
+ </AlertDialogAction>
720
+ </AlertDialogFooter>
721
+ </AlertDialogContent>
722
+ </AlertDialog>
723
+
724
+ <AlertDialog open={deleteOpen} onOpenChange={onDeleteOpenChange}>
725
+ <AlertDialogTrigger asChild>
726
+ <Button variant="destructive" disabled={isDeleting || serverDeleted}>
727
+ {isDeleting ? (
728
+ <LoaderIcon data-icon="inline-start" className="animate-spin" />
729
+ ) : (
730
+ <Trash2Icon data-icon="inline-start" />
731
+ )}
732
+ Delete server
733
+ </Button>
734
+ </AlertDialogTrigger>
735
+ <AlertDialogContent>
736
+ <AlertDialogHeader>
737
+ <AlertDialogTitle>Delete this server?</AlertDialogTitle>
738
+ <AlertDialogDescription>
739
+ This permanently removes {server.name} from server lists and
740
+ assignment pickers, deletes its monitoring data, and cannot be
741
+ undone. {`Type "${server.name}" to confirm.`}
742
+ </AlertDialogDescription>
743
+ </AlertDialogHeader>
744
+
745
+ <div className="space-y-4">
746
+ {isLoadingDeleteDiagnostic ? (
747
+ <p className="text-sm text-muted-foreground">
748
+ Inspecting server usage...
749
+ </p>
750
+ ) : deleteDiagnosticError ? (
751
+ <div className="space-y-2">
752
+ <p role="alert" className="text-sm text-destructive">
753
+ {deleteDiagnosticError}
754
+ </p>
755
+ <Button
756
+ type="button"
757
+ variant="outline"
758
+ onClick={() => void loadDeleteDiagnostic()}
759
+ >
760
+ Retry inspection
761
+ </Button>
762
+ </div>
763
+ ) : deleteDiagnostic ? (
764
+ <p className="text-sm">
765
+ {deleteDiagnostic.projectCount === 0
766
+ ? 'This will impact no active projects or environments.'
767
+ : `This will impact ${formatCount(deleteDiagnostic.projectCount, 'project')} and ${formatCount(deleteDiagnostic.environmentCount, 'environment')}.`}
768
+ </p>
769
+ ) : null}
770
+
771
+ <Field orientation="horizontal">
772
+ <Checkbox
773
+ id="delete-server-cancel-deployments"
774
+ checked={cancelOngoingDeployments}
775
+ onCheckedChange={(checked) =>
776
+ setCancelOngoingDeployments(checked === true)
777
+ }
778
+ disabled={isDeleting || serverDeleted}
779
+ />
780
+ <FieldLabel htmlFor="delete-server-cancel-deployments">
781
+ Cancel ongoing deployments on this server
782
+ </FieldLabel>
783
+ </Field>
784
+
785
+ <Field>
786
+ <Input
787
+ aria-label={`Type ${server.name} to confirm`}
788
+ required
789
+ id="delete-server-confirmation"
790
+ placeholder={server.name}
791
+ value={deleteConfirmation}
792
+ onChange={(event) => setDeleteConfirmation(event.target.value)}
793
+ onKeyDown={(event) => {
794
+ if (event.key === 'Enter' && canDelete) {
795
+ event.preventDefault()
796
+ void deleteCurrentServer()
797
+ }
798
+ }}
799
+ disabled={isDeleting || serverDeleted}
800
+ />
801
+ </Field>
802
+ </div>
803
+ <AlertDialogFooter>
804
+ <AlertDialogCancel disabled={isDeleting || serverDeleted}>
805
+ Cancel
806
+ </AlertDialogCancel>
807
+ <AlertDialogAction
808
+ variant="destructive"
809
+ disabled={!canDelete}
810
+ onClick={(event) => {
811
+ event.preventDefault()
812
+ void deleteCurrentServer()
813
+ }}
814
+ >
815
+ {isDeleting ? <LoaderIcon className="animate-spin" /> : null}
816
+ Delete server
817
+ </AlertDialogAction>
818
+ </AlertDialogFooter>
819
+ </AlertDialogContent>
820
+ </AlertDialog>
821
+ </SettingsSection>
822
+ )
823
+ }
824
+
825
+ function formatCount(count: number, singular: string) {
826
+ return `${count.toLocaleString()} ${singular}${count === 1 ? '' : 's'}`
827
+ }
828
+
829
+ function ServerDetailField({
830
+ children,
831
+ label,
832
+ }: {
833
+ children: string
834
+ label: string
835
+ }) {
836
+ return (
837
+ <div className="min-w-0 space-y-1.5">
838
+ <dt className="text-xs text-muted-foreground">{label}</dt>
839
+ <dd className="text-sm break-words">{children}</dd>
840
+ </div>
841
+ )
842
+ }
843
+
844
+ function formatDuration(totalSeconds: number) {
845
+ const days = Math.floor(totalSeconds / 86_400)
846
+ const hours = Math.floor((totalSeconds % 86_400) / 3_600)
847
+ const minutes = Math.floor((totalSeconds % 3_600) / 60)
848
+ if (days > 0) {
849
+ return `${days}d ${hours}h`
850
+ }
851
+ if (hours > 0) {
852
+ return `${hours}h ${minutes}m`
853
+ }
854
+ return `${minutes}m`
855
+ }