@stacksjs/defaults 0.70.206 → 0.70.208

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 (408) hide show
  1. package/TODO.md +21 -0
  2. package/ai/AGENTS.md +79 -0
  3. package/ai/README.md +45 -0
  4. package/ai/claude/launch.json +12 -0
  5. package/ai/skills/stacks-actions/SKILL.md +128 -0
  6. package/ai/skills/stacks-ai/SKILL.md +276 -0
  7. package/ai/skills/stacks-alias/SKILL.md +94 -0
  8. package/ai/skills/stacks-analytics/SKILL.md +91 -0
  9. package/ai/skills/stacks-api/SKILL.md +738 -0
  10. package/ai/skills/stacks-arrays/SKILL.md +277 -0
  11. package/ai/skills/stacks-auth/SKILL.md +422 -0
  12. package/ai/skills/stacks-auto-imports/SKILL.md +138 -0
  13. package/ai/skills/stacks-brandkit/SKILL.md +811 -0
  14. package/ai/skills/stacks-browse/SKILL.md +127 -0
  15. package/ai/skills/stacks-browse/scripts/browse.ts +492 -0
  16. package/ai/skills/stacks-browser/SKILL.md +145 -0
  17. package/ai/skills/stacks-buddy/SKILL.md +676 -0
  18. package/ai/skills/stacks-build/SKILL.md +163 -0
  19. package/ai/skills/stacks-cache/SKILL.md +125 -0
  20. package/ai/skills/stacks-calendar/SKILL.md +113 -0
  21. package/ai/skills/stacks-chat/SKILL.md +371 -0
  22. package/ai/skills/stacks-cli/SKILL.md +112 -0
  23. package/ai/skills/stacks-cloud/SKILL.md +192 -0
  24. package/ai/skills/stacks-cms/SKILL.md +217 -0
  25. package/ai/skills/stacks-collections/SKILL.md +82 -0
  26. package/ai/skills/stacks-commerce/SKILL.md +114 -0
  27. package/ai/skills/stacks-composables/SKILL.md +161 -0
  28. package/ai/skills/stacks-config/SKILL.md +144 -0
  29. package/ai/skills/stacks-configuration/SKILL.md +154 -0
  30. package/ai/skills/stacks-cron/SKILL.md +123 -0
  31. package/ai/skills/stacks-crosswind/SKILL.md +190 -0
  32. package/ai/skills/stacks-dashboard/SKILL.md +173 -0
  33. package/ai/skills/stacks-database/SKILL.md +286 -0
  34. package/ai/skills/stacks-datetime/SKILL.md +269 -0
  35. package/ai/skills/stacks-dependencies/SKILL.md +111 -0
  36. package/ai/skills/stacks-deploy/SKILL.md +102 -0
  37. package/ai/skills/stacks-design-brutalist/SKILL.md +110 -0
  38. package/ai/skills/stacks-design-minimalist/SKILL.md +115 -0
  39. package/ai/skills/stacks-design-output/SKILL.md +55 -0
  40. package/ai/skills/stacks-design-soft/SKILL.md +156 -0
  41. package/ai/skills/stacks-design-taste/SKILL.md +1012 -0
  42. package/ai/skills/stacks-design-taste-codex/SKILL.md +294 -0
  43. package/ai/skills/stacks-desktop/SKILL.md +78 -0
  44. package/ai/skills/stacks-development/SKILL.md +298 -0
  45. package/ai/skills/stacks-dns/SKILL.md +149 -0
  46. package/ai/skills/stacks-docs/SKILL.md +109 -0
  47. package/ai/skills/stacks-email/SKILL.md +236 -0
  48. package/ai/skills/stacks-enums/SKILL.md +100 -0
  49. package/ai/skills/stacks-env/SKILL.md +181 -0
  50. package/ai/skills/stacks-error-handling/SKILL.md +137 -0
  51. package/ai/skills/stacks-events/SKILL.md +228 -0
  52. package/ai/skills/stacks-faker/SKILL.md +158 -0
  53. package/ai/skills/stacks-git/SKILL.md +158 -0
  54. package/ai/skills/stacks-guard/SKILL.md +98 -0
  55. package/ai/skills/stacks-health/SKILL.md +75 -0
  56. package/ai/skills/stacks-http/SKILL.md +316 -0
  57. package/ai/skills/stacks-i18n/SKILL.md +198 -0
  58. package/ai/skills/stacks-image-to-code/SKILL.md +629 -0
  59. package/ai/skills/stacks-imagegen-mobile/SKILL.md +1474 -0
  60. package/ai/skills/stacks-imagegen-web/SKILL.md +998 -0
  61. package/ai/skills/stacks-investigate/SKILL.md +119 -0
  62. package/ai/skills/stacks-jobs/SKILL.md +136 -0
  63. package/ai/skills/stacks-lint/SKILL.md +184 -0
  64. package/ai/skills/stacks-listeners/SKILL.md +99 -0
  65. package/ai/skills/stacks-logging/SKILL.md +78 -0
  66. package/ai/skills/stacks-mail/SKILL.md +120 -0
  67. package/ai/skills/stacks-middleware/SKILL.md +249 -0
  68. package/ai/skills/stacks-migrations/SKILL.md +105 -0
  69. package/ai/skills/stacks-models/SKILL.md +267 -0
  70. package/ai/skills/stacks-new-feature/SKILL.md +206 -0
  71. package/ai/skills/stacks-notifications/SKILL.md +210 -0
  72. package/ai/skills/stacks-objects/SKILL.md +205 -0
  73. package/ai/skills/stacks-office-hours/SKILL.md +134 -0
  74. package/ai/skills/stacks-orm/SKILL.md +342 -0
  75. package/ai/skills/stacks-path/SKILL.md +118 -0
  76. package/ai/skills/stacks-payments/SKILL.md +373 -0
  77. package/ai/skills/stacks-plan-review/SKILL.md +152 -0
  78. package/ai/skills/stacks-plugins/SKILL.md +149 -0
  79. package/ai/skills/stacks-push/SKILL.md +376 -0
  80. package/ai/skills/stacks-query-builder/SKILL.md +128 -0
  81. package/ai/skills/stacks-queue/SKILL.md +559 -0
  82. package/ai/skills/stacks-realtime/SKILL.md +442 -0
  83. package/ai/skills/stacks-redesign/SKILL.md +182 -0
  84. package/ai/skills/stacks-registry/SKILL.md +58 -0
  85. package/ai/skills/stacks-repl/SKILL.md +80 -0
  86. package/ai/skills/stacks-retro/SKILL.md +122 -0
  87. package/ai/skills/stacks-review/SKILL.md +135 -0
  88. package/ai/skills/stacks-router/SKILL.md +231 -0
  89. package/ai/skills/stacks-routes/SKILL.md +105 -0
  90. package/ai/skills/stacks-scaffolding/SKILL.md +162 -0
  91. package/ai/skills/stacks-scheduler/SKILL.md +363 -0
  92. package/ai/skills/stacks-search-engine/SKILL.md +112 -0
  93. package/ai/skills/stacks-security/SKILL.md +129 -0
  94. package/ai/skills/stacks-security-audit/SKILL.md +128 -0
  95. package/ai/skills/stacks-server/SKILL.md +458 -0
  96. package/ai/skills/stacks-shell/SKILL.md +62 -0
  97. package/ai/skills/stacks-slug/SKILL.md +87 -0
  98. package/ai/skills/stacks-sms/SKILL.md +385 -0
  99. package/ai/skills/stacks-socials/SKILL.md +303 -0
  100. package/ai/skills/stacks-storage/SKILL.md +576 -0
  101. package/ai/skills/stacks-strings/SKILL.md +416 -0
  102. package/ai/skills/stacks-stx/SKILL.md +176 -0
  103. package/ai/skills/stacks-technical-diagrams/LICENSE +22 -0
  104. package/ai/skills/stacks-technical-diagrams/SKILL.md +290 -0
  105. package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +4 -0
  106. package/ai/skills/stacks-technical-diagrams/assets/template.html +1252 -0
  107. package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +289 -0
  108. package/ai/skills/stacks-technical-diagrams/bunfig.toml +1 -0
  109. package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +64 -0
  110. package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +88 -0
  111. package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +75 -0
  112. package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +70 -0
  113. package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +41 -0
  114. package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +62 -0
  115. package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +371 -0
  116. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +82 -0
  117. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +295 -0
  118. package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +91 -0
  119. package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +384 -0
  120. package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +84 -0
  121. package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +257 -0
  122. package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +42 -0
  123. package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +13 -0
  124. package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +230 -0
  125. package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +40 -0
  126. package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +88 -0
  127. package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +40 -0
  128. package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +103 -0
  129. package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +481 -0
  130. package/ai/skills/stacks-technical-diagrams/schemas/README.md +68 -0
  131. package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +121 -0
  132. package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +43 -0
  133. package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +221 -0
  134. package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +241 -0
  135. package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +186 -0
  136. package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +332 -0
  137. package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +297 -0
  138. package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +28 -0
  139. package/ai/skills/stacks-testing/SKILL.md +129 -0
  140. package/ai/skills/stacks-tunnel/SKILL.md +125 -0
  141. package/ai/skills/stacks-types/SKILL.md +178 -0
  142. package/ai/skills/stacks-ui/SKILL.md +246 -0
  143. package/ai/skills/stacks-utils/SKILL.md +444 -0
  144. package/ai/skills/stacks-validation/SKILL.md +371 -0
  145. package/ai/skills/stacks-whois/SKILL.md +315 -0
  146. package/assets/world-map-data.ts +121 -0
  147. package/bootstrap.ts +114 -0
  148. package/docs/api-examples.md +46 -0
  149. package/docs/index.md +24 -0
  150. package/docs/markdown-examples.md +87 -0
  151. package/functions/analytics.ts +97 -0
  152. package/functions/api.ts +143 -0
  153. package/functions/auth.ts +208 -0
  154. package/functions/billing/payments.ts +149 -0
  155. package/functions/blog.ts +120 -0
  156. package/functions/cms/categorizables.ts +107 -0
  157. package/functions/cms/pages.ts +108 -0
  158. package/functions/cms/posts.ts +100 -0
  159. package/functions/cms/taggables.ts +102 -0
  160. package/functions/commerce/coupons.ts +128 -0
  161. package/functions/commerce/customers.ts +128 -0
  162. package/functions/commerce/gift-cards.ts +119 -0
  163. package/functions/commerce/orders.ts +112 -0
  164. package/functions/commerce/payments.ts +119 -0
  165. package/functions/commerce/products/categories.ts +123 -0
  166. package/functions/commerce/products/manufacturers.ts +123 -0
  167. package/functions/commerce/products/products.ts +123 -0
  168. package/functions/commerce/products/reviews.ts +123 -0
  169. package/functions/commerce/products/units.ts +123 -0
  170. package/functions/commerce/products/variants.ts +123 -0
  171. package/functions/commerce/shippings/delivery-routes.ts +123 -0
  172. package/functions/commerce/shippings/digital-deliveries.ts +119 -0
  173. package/functions/commerce/shippings/drivers.ts +123 -0
  174. package/functions/commerce/shippings/license-keys.ts +123 -0
  175. package/functions/commerce/shippings/shipping-methods.ts +123 -0
  176. package/functions/commerce/shippings/shipping-rates.ts +119 -0
  177. package/functions/commerce/shippings/shipping-zones.ts +119 -0
  178. package/functions/commerce/taxes.ts +119 -0
  179. package/functions/commerce/waitlist/products.ts +123 -0
  180. package/functions/commerce/waitlist/restaurants.ts +123 -0
  181. package/functions/craft.ts +107 -0
  182. package/functions/dashboard.ts +153 -0
  183. package/functions/inbox.ts +95 -0
  184. package/functions/jobs.ts +86 -0
  185. package/functions/monitoring/errors.ts +295 -0
  186. package/functions/queue.ts +111 -0
  187. package/functions/realtime/websockets.ts +96 -0
  188. package/functions/realtime.ts +52 -0
  189. package/functions/releases.ts +49 -0
  190. package/functions/search.ts +126 -0
  191. package/functions/toasts.ts +95 -0
  192. package/functions/uploads.ts +216 -0
  193. package/ide/.gitignore +4 -0
  194. package/ide/cursor/rules/auto-imports.mdc +574 -0
  195. package/ide/cursor/rules/code-style.mdc +12 -0
  196. package/ide/cursor/rules/documentation.mdc +9 -0
  197. package/ide/cursor/rules/error-handling-validation.mdc +11 -0
  198. package/ide/cursor/rules/key-conventions.mdc +11 -0
  199. package/ide/cursor/rules/project-structure.mdc +169 -0
  200. package/ide/cursor/rules/readme.mdc +318 -0
  201. package/ide/cursor/rules/syntax-formatting.mdc +9 -0
  202. package/ide/cursor/rules/testing.mdc +10 -0
  203. package/ide/cursor/rules/typescript.mdc +9 -0
  204. package/ide/cursor/rules/ui-styling.mdc +0 -0
  205. package/ide/dictionary.txt +397 -0
  206. package/ide/jetbrains/.fleet/settings.json +4 -0
  207. package/ide/jetbrains/.idea/.gitignore +5 -0
  208. package/ide/jetbrains/.idea/.name +1 -0
  209. package/ide/jetbrains/.idea/inspectionProfiles/Project_Default.xml +6 -0
  210. package/ide/jetbrains/.idea/jsLinters/eslint.xml +7 -0
  211. package/ide/jetbrains/.idea/modules.xml +8 -0
  212. package/ide/jetbrains/.idea/stacks.iml +22 -0
  213. package/ide/jetbrains/.idea/vcs.xml +6 -0
  214. package/ide/jetbrains/.idea/webResources.xml +14 -0
  215. package/ide/jetbrains/IntelliJ IDEA Global Settings +0 -0
  216. package/ide/jetbrains/installed.txt +4 -0
  217. package/ide/jetbrains/options/github-copilot.xml +5 -0
  218. package/ide/jetbrains/options/nodejs.xml +7 -0
  219. package/ide/jetbrains/options/templates.xml +124 -0
  220. package/ide/jetbrains/templates/JavaScript.xml +6 -0
  221. package/ide/jetbrains/templates/Stacks.xml +8 -0
  222. package/ide/vscode/.vscode/README.md +23 -0
  223. package/ide/vscode/.vscode/extensions.json +5 -0
  224. package/ide/vscode/.vscode/settings.json +176 -0
  225. package/ide/vscode/.vscode/stacks.code-snippets +181 -0
  226. package/ide/vscode/LICENSE.md +21 -0
  227. package/ide/vscode/README.md +36 -0
  228. package/ide/vscode/logo.png +0 -0
  229. package/ide/vscode/package.json +79 -0
  230. package/ide/zed/.zed/settings.json +139 -0
  231. package/lang/README.md +28 -0
  232. package/lang/de.yml +14 -0
  233. package/lang/en.yml +15 -0
  234. package/package.json +19 -3
  235. package/project/bootstrap +161 -0
  236. package/project/buddy +175 -0
  237. package/project/storage/framework/tsconfig.app.json +50 -0
  238. package/project/storage/framework/tsconfig.base.json +58 -0
  239. package/routes/core.ts +78 -0
  240. package/routes/dashboard-api.ts +233 -0
  241. package/routes/dashboard.ts +739 -0
  242. package/routes/email.ts +129 -0
  243. package/stores/[name].stx +16 -0
  244. package/stores/git.ts +71 -0
  245. package/stores/payment.ts +474 -0
  246. package/stores/queue.ts +43 -0
  247. package/stores/user.ts +29 -0
  248. package/stx-components-plugin.ts +25 -0
  249. package/styles/design-tokens.css +597 -0
  250. package/types/dashboard.ts +70 -0
  251. package/types/defaults.ts +438 -0
  252. package/vcs/github/CODE_OF_CONDUCT.md +83 -0
  253. package/vcs/github/CONTRIBUTING.md +187 -0
  254. package/vcs/github/EXPLANATIONS.md +41 -0
  255. package/vcs/github/FUNDING.yml +1 -0
  256. package/vcs/github/ISSUE_TEMPLATE/bug_report.yml +49 -0
  257. package/vcs/github/ISSUE_TEMPLATE/config.yml +10 -0
  258. package/vcs/github/ISSUE_TEMPLATE/feature_request.yml +44 -0
  259. package/vcs/github/SECURITY.md +70 -0
  260. package/vcs/github/renovate.json +5 -0
  261. package/vcs/github/stale.yml +14 -0
  262. package/vcs/github/workflows/README.md +16 -0
  263. package/vcs/github/workflows/ci.yml +114 -0
  264. package/vcs/github/workflows/export-size.yml +25 -0
  265. package/vcs/github/workflows/labeler.yml +13 -0
  266. package/vcs/github/workflows/release.yml +219 -0
  267. package/views/[...all].stx +8 -0
  268. package/views/about.md +3 -0
  269. package/views/auth/login.stx +1 -0
  270. package/views/auth/signup.stx +85 -0
  271. package/views/dashboard/.discovered-models.json +542 -0
  272. package/views/dashboard/AUDIT.md +904 -0
  273. package/views/dashboard/[...all].stx +89 -0
  274. package/views/dashboard/access-tokens/index.stx +177 -0
  275. package/views/dashboard/actions/index.stx +129 -0
  276. package/views/dashboard/analytics/blog/index.stx +356 -0
  277. package/views/dashboard/analytics/browsers/index.stx +263 -0
  278. package/views/dashboard/analytics/commerce/sales/index.stx +329 -0
  279. package/views/dashboard/analytics/commerce/web/index.stx +379 -0
  280. package/views/dashboard/analytics/countries/index.stx +289 -0
  281. package/views/dashboard/analytics/devices/index.stx +305 -0
  282. package/views/dashboard/analytics/events/index.stx +536 -0
  283. package/views/dashboard/analytics/index.stx +88 -0
  284. package/views/dashboard/analytics/marketing/index.stx +315 -0
  285. package/views/dashboard/analytics/pages/index.stx +271 -0
  286. package/views/dashboard/analytics/referrers/index.stx +319 -0
  287. package/views/dashboard/analytics/web/index.stx +356 -0
  288. package/views/dashboard/buddy/index.stx +198 -0
  289. package/views/dashboard/ci/index.stx +462 -0
  290. package/views/dashboard/cloud/index.stx +452 -0
  291. package/views/dashboard/commands/index.stx +295 -0
  292. package/views/dashboard/commerce/categories/index.stx +406 -0
  293. package/views/dashboard/commerce/coupons/index.stx +485 -0
  294. package/views/dashboard/commerce/customers/index.stx +410 -0
  295. package/views/dashboard/commerce/dashboard/index.stx +287 -0
  296. package/views/dashboard/commerce/delivery/delivery-routes.stx +305 -0
  297. package/views/dashboard/commerce/delivery/digital-delivery.stx +332 -0
  298. package/views/dashboard/commerce/delivery/drivers.stx +397 -0
  299. package/views/dashboard/commerce/delivery/index.stx +18 -0
  300. package/views/dashboard/commerce/delivery/license-keys.stx +380 -0
  301. package/views/dashboard/commerce/delivery/shipping-methods.stx +346 -0
  302. package/views/dashboard/commerce/delivery/shipping-rates.stx +338 -0
  303. package/views/dashboard/commerce/delivery/shipping-zones.stx +302 -0
  304. package/views/dashboard/commerce/delivery/tabs.ts +25 -0
  305. package/views/dashboard/commerce/gift-cards/index.stx +561 -0
  306. package/views/dashboard/commerce/manufacturers/index.stx +308 -0
  307. package/views/dashboard/commerce/orders/index.stx +247 -0
  308. package/views/dashboard/commerce/payments/index.stx +535 -0
  309. package/views/dashboard/commerce/pos/index.stx +653 -0
  310. package/views/dashboard/commerce/printers/devices/index.stx +273 -0
  311. package/views/dashboard/commerce/printers/receipts/index.stx +308 -0
  312. package/views/dashboard/commerce/products/detail.stx +563 -0
  313. package/views/dashboard/commerce/products/index.stx +370 -0
  314. package/views/dashboard/commerce/reviews/index.stx +656 -0
  315. package/views/dashboard/commerce/reviews/index.ts +0 -0
  316. package/views/dashboard/commerce/taxes/index.stx +377 -0
  317. package/views/dashboard/commerce/units/index.stx +317 -0
  318. package/views/dashboard/commerce/variants/index.stx +319 -0
  319. package/views/dashboard/commerce/waitlist/products.stx +942 -0
  320. package/views/dashboard/commerce/waitlist/restaurant.stx +1024 -0
  321. package/views/dashboard/components/index.stx +330 -0
  322. package/views/dashboard/composables/index.ts +19 -0
  323. package/views/dashboard/composables/useChart.ts +77 -0
  324. package/views/dashboard/composables/useNavigation.ts +99 -0
  325. package/views/dashboard/composables/useRole.ts +58 -0
  326. package/views/dashboard/composables/useTheme.ts +74 -0
  327. package/views/dashboard/content/authors/index.stx +171 -0
  328. package/views/dashboard/content/blog/index.stx +493 -0
  329. package/views/dashboard/content/categories/index.stx +169 -0
  330. package/views/dashboard/content/comments/index.stx +189 -0
  331. package/views/dashboard/content/dashboard.stx +383 -0
  332. package/views/dashboard/content/files/index.stx +706 -0
  333. package/views/dashboard/content/pages/index.stx +174 -0
  334. package/views/dashboard/content/posts/index.stx +269 -0
  335. package/views/dashboard/content/seo/index.stx +240 -0
  336. package/views/dashboard/content/tags/index.stx +167 -0
  337. package/views/dashboard/data/activity/index.stx +261 -0
  338. package/views/dashboard/data/dashboard/index.stx +116 -0
  339. package/views/dashboard/data/subscribers.stx +270 -0
  340. package/views/dashboard/data/teams.stx +275 -0
  341. package/views/dashboard/data/users.stx +279 -0
  342. package/views/dashboard/dependencies/index.stx +178 -0
  343. package/views/dashboard/deployments/[id].stx +234 -0
  344. package/views/dashboard/deployments/index.stx +9 -0
  345. package/views/dashboard/dns/index.stx +163 -0
  346. package/views/dashboard/environment/index.stx +283 -0
  347. package/views/dashboard/errors/index.stx +274 -0
  348. package/views/dashboard/forgot-password.stx +57 -0
  349. package/views/dashboard/functions/index.stx +159 -0
  350. package/views/dashboard/health/index.stx +248 -0
  351. package/views/dashboard/inbox/activity.stx +350 -0
  352. package/views/dashboard/inbox/index.stx +452 -0
  353. package/views/dashboard/inbox/settings.stx +290 -0
  354. package/views/dashboard/index.stx +252 -0
  355. package/views/dashboard/insights/index.stx +529 -0
  356. package/views/dashboard/jobs/[id].stx +430 -0
  357. package/views/dashboard/jobs/history.stx +222 -0
  358. package/views/dashboard/jobs/index.stx +214 -0
  359. package/views/dashboard/kanban/[id].stx +899 -0
  360. package/views/dashboard/kanban/index.stx +260 -0
  361. package/views/dashboard/layouts/default.stx +565 -0
  362. package/views/dashboard/layouts/guest.stx +10 -0
  363. package/views/dashboard/login.stx +48 -0
  364. package/views/dashboard/logs/index.stx +540 -0
  365. package/views/dashboard/mailboxes/index.stx +149 -0
  366. package/views/dashboard/management/permissions/index.stx +490 -0
  367. package/views/dashboard/marketing/campaigns/index.stx +604 -0
  368. package/views/dashboard/marketing/lists/index.stx +514 -0
  369. package/views/dashboard/marketing/reviews/index.stx +475 -0
  370. package/views/dashboard/marketing/social-posts/index.stx +560 -0
  371. package/views/dashboard/models/[model].stx +195 -0
  372. package/views/dashboard/models/index.stx +159 -0
  373. package/views/dashboard/monitoring/errors/index.stx +415 -0
  374. package/views/dashboard/notifications/dashboard.stx +383 -0
  375. package/views/dashboard/notifications/email.stx +336 -0
  376. package/views/dashboard/notifications/history.stx +373 -0
  377. package/views/dashboard/notifications/sms.stx +332 -0
  378. package/views/dashboard/packages/index.stx +195 -0
  379. package/views/dashboard/queries/[id].stx +381 -0
  380. package/views/dashboard/queries/history.stx +227 -0
  381. package/views/dashboard/queries/index.stx +335 -0
  382. package/views/dashboard/queries/slow.stx +318 -0
  383. package/views/dashboard/queue/index.stx +427 -0
  384. package/views/dashboard/realtime/index.stx +429 -0
  385. package/views/dashboard/register.stx +46 -0
  386. package/views/dashboard/releases/index.stx +389 -0
  387. package/views/dashboard/requests/index.stx +462 -0
  388. package/views/dashboard/serverless/index.stx +475 -0
  389. package/views/dashboard/servers/[id].stx +299 -0
  390. package/views/dashboard/servers/index.stx +483 -0
  391. package/views/dashboard/settings/billing.stx +50 -0
  392. package/views/dashboard/settings/index.stx +256 -0
  393. package/views/dashboard/settings/mail.stx +252 -0
  394. package/views/dashboard/stores/auth.ts +123 -0
  395. package/views/dashboard/stores/ci.ts +342 -0
  396. package/views/dashboard/stores/index.ts +129 -0
  397. package/views/dashboard/stores/kanban.ts +723 -0
  398. package/views/dashboard/stores/rbac.ts +350 -0
  399. package/views/dashboard/stx.config.ts +34 -0
  400. package/views/dashboard/teams/[id].stx +170 -0
  401. package/views/forum/category.stx +92 -0
  402. package/views/forum/index.stx +209 -0
  403. package/views/forum/new-topic.stx +113 -0
  404. package/views/forum/profile.stx +1 -0
  405. package/views/forum/topic.stx +143 -0
  406. package/views/hello-world/[name].stx +47 -0
  407. package/views/index.stx +21 -0
  408. package/views/system-tray/index.stx +147 -0
@@ -0,0 +1,483 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ @title('Dashboard - Servers')
5
+ <div class="py-4 lg:py-8 min-h-screen dark:bg-neutral-800">
6
+ <div class="px-4 sm:px-6 lg:px-8">
7
+ <div class="flex items-center justify-between mb-8">
8
+ <PageHeader title="Servers" description="Manage all your application servers." />
9
+ <div class="sm:flex-none mt-4 sm:ml-16 sm:mt-0">
10
+ <button type="button" class="block px-3 py-2 font-semibold text-center text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-600 focus-visible:outline-offset-2 shadow-sm">Add Server</button>
11
+ </div>
12
+ </div>
13
+
14
+ <div class="mb-8 bg-white dark:bg-neutral-700 rounded-lg shadow">
15
+ <div class="p-6">
16
+ <div class="flex items-center justify-between mb-4">
17
+ <h4 class="font-medium text-base text-gray-900 dark:text-neutral-100">Infrastructure overview</h4>
18
+ <span class="text-gray-500 text-sm dark:text-neutral-400">{{ serversRaw.length }} servers</span>
19
+ </div>
20
+
21
+ <div class="grid gap-6 grid-cols-1 lg:grid-cols-12">
22
+ <div class="lg:col-span-3">
23
+ <div class="flex flex-col items-center p-4 bg-pink-50 dark:bg-pink-900/30 rounded-lg">
24
+ <div class="flex items-center justify-center mb-2 h-12 w-12 text-pink-600 dark:text-pink-300 bg-pink-200 dark:bg-pink-900 rounded-full">
25
+ <div class="h-6 w-6 i-hugeicons-globe"></div>
26
+ </div>
27
+ <p class="font-medium text-gray-900 text-sm dark:text-white">DNS</p>
28
+ <p class="text-gray-500 text-xs dark:text-neutral-400">Entry point</p>
29
+ </div>
30
+ </div>
31
+
32
+ <div class="hidden lg:flex items-center justify-center lg:col-span-1 text-gray-400 dark:text-neutral-500">
33
+ <div class="h-6 w-6 i-hugeicons-arrow-right-01"></div>
34
+ </div>
35
+
36
+ <div class="lg:col-span-3">
37
+ <div class="flex flex-col items-center p-4 bg-blue-50 dark:bg-blue-900/30 rounded-lg">
38
+ <div class="flex items-center justify-center mb-2 h-12 w-12 text-blue-600 dark:text-blue-300 bg-blue-200 dark:bg-blue-900 rounded-full">
39
+ <div class="h-6 w-6 i-hugeicons-balance"></div>
40
+ </div>
41
+ <p class="font-medium text-gray-900 text-sm dark:text-white">Load balancer</p>
42
+ <p class="text-gray-500 text-xs dark:text-neutral-400">{{ useLoadBalancer ? 'Enabled' : 'Disabled' }}</p>
43
+ </div>
44
+ </div>
45
+
46
+ <div class="hidden lg:flex items-center justify-center lg:col-span-1 text-gray-400 dark:text-neutral-500">
47
+ <div class="h-6 w-6 i-hugeicons-arrow-right-01"></div>
48
+ </div>
49
+
50
+ <div class="lg:col-span-4">
51
+ <div class="space-y-2">
52
+ <template :for="group in serversByType">
53
+ <div class="flex items-center justify-between p-3 bg-gray-50 dark:bg-neutral-800 rounded-lg">
54
+ <div class="flex gap-2 items-center">
55
+ <span :class="typeBadgeClassFor(group.type)">{{ group.label }}</span>
56
+ </div>
57
+ <span class="font-mono text-gray-900 text-sm dark:text-white">×{{ group.servers.length }}</span>
58
+ </div>
59
+ </template>
60
+ </div>
61
+ </div>
62
+ </div>
63
+
64
+ <div class="flex items-center justify-between mb-3 mt-6">
65
+ <h5 class="font-medium text-gray-900 text-sm dark:text-neutral-100">Topology</h5>
66
+ <p class="text-gray-500 text-xs dark:text-neutral-400">Drag nodes · scroll to zoom</p>
67
+ </div>
68
+ <div id="servers_topology" class="h-[420px] w-full bg-neutral-50 dark:bg-neutral-800 rounded-lg"></div>
69
+ </div>
70
+ </div>
71
+
72
+ <div class="mb-6">
73
+ <div class="relative max-w-md">
74
+ <div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
75
+ <div class="h-5 w-5 text-gray-400 i-hugeicons-search-01"></div>
76
+ </div>
77
+ <input x-model="searchQuery" type="text" placeholder="Search servers..." class="block placeholder-gray-500 pl-10 pr-3 py-2 w-full leading-5 text-gray-900 sm:text-sm dark:text-neutral-100 bg-white dark:bg-neutral-700 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500" />
78
+ </div>
79
+ </div>
80
+
81
+ <div class="overflow-hidden bg-white dark:bg-neutral-700 rounded-lg shadow">
82
+ <div class="overflow-x-auto">
83
+ <table class="min-w-full divide-gray-200 divide-y dark:divide-neutral-600">
84
+ <thead class="bg-gray-50 dark:bg-neutral-800">
85
+ <tr>
86
+ <th scope="col" @click="toggleSort('name')" class="px-6 py-3 font-medium text-gray-500 text-left text-xs tracking-wider uppercase dark:text-neutral-400 hover:text-gray-700 dark:hover:text-gray-300 cursor-pointer">
87
+ <div class="flex gap-1 items-center">Name <div :class="sortIconClass('name')"></div></div>
88
+ </th>
89
+ <th scope="col" @click="toggleSort('type')" class="px-6 py-3 font-medium text-gray-500 text-left text-xs tracking-wider uppercase dark:text-neutral-400 hover:text-gray-700 dark:hover:text-gray-300 cursor-pointer">
90
+ <div class="flex gap-1 items-center">Type <div :class="sortIconClass('type')"></div></div>
91
+ </th>
92
+ <th scope="col" @click="toggleSort('region')" class="px-6 py-3 font-medium text-gray-500 text-left text-xs tracking-wider uppercase dark:text-neutral-400 hover:text-gray-700 dark:hover:text-gray-300 cursor-pointer">
93
+ <div class="flex gap-1 items-center">Region <div :class="sortIconClass('region')"></div></div>
94
+ </th>
95
+ <th scope="col" @click="toggleSort('size')" class="px-6 py-3 font-medium text-gray-500 text-left text-xs tracking-wider uppercase dark:text-neutral-400 hover:text-gray-700 dark:hover:text-gray-300 cursor-pointer">
96
+ <div class="flex gap-1 items-center">Size <div :class="sortIconClass('size')"></div></div>
97
+ </th>
98
+ <th scope="col" class="px-6 py-3 font-medium text-gray-500 text-left text-xs tracking-wider uppercase dark:text-neutral-400">OS</th>
99
+ <th scope="col" class="px-6 py-3 font-medium text-gray-500 text-left text-xs tracking-wider uppercase dark:text-neutral-400">Database</th>
100
+ <th scope="col" class="px-6 py-3 font-medium text-gray-500 text-left text-xs tracking-wider uppercase dark:text-neutral-400">Search</th>
101
+ <th scope="col" class="relative px-6 py-3"><span class="sr-only">Actions</span></th>
102
+ </tr>
103
+ </thead>
104
+ <tbody class="bg-white dark:bg-neutral-700 divide-gray-200 divide-y dark:divide-neutral-600">
105
+ <template :for="server in filteredServers">
106
+ <tr class="hover:bg-gray-50 dark:hover:bg-neutral-600">
107
+ <td class="px-6 py-4 whitespace-nowrap">
108
+ <div class="font-medium text-gray-900 text-sm dark:text-neutral-100">{{ server.name }}</div>
109
+ </td>
110
+ <td class="px-6 py-4 whitespace-nowrap">
111
+ <span :class="typeBadgeClassFor(server.type)">{{ server.type }}</span>
112
+ </td>
113
+ <td class="px-6 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ server.region }}</td>
114
+ <td class="px-6 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ server.size }}</td>
115
+ <td class="px-6 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ server.serverOS }}</td>
116
+ <td class="px-6 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ server.database || 'None' }}</td>
117
+ <td class="px-6 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ server.searchEngine || 'None' }}</td>
118
+ <td class="px-6 py-4 font-medium text-right text-sm whitespace-nowrap">
119
+ <div class="flex gap-4 items-center justify-end">
120
+ <button @click="selectServer(server.key)" class="text-gray-600 dark:text-neutral-300 hover:text-gray-900 dark:hover:text-white">View</button>
121
+ <a :href="serverEditHref(server.key)" class="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300">Edit</a>
122
+ </div>
123
+ </td>
124
+ </tr>
125
+ </template>
126
+ <tr :show="filteredServers().length === 0">
127
+ <td colspan="8" class="px-6 py-8 text-center text-gray-500 text-sm dark:text-neutral-400">No servers match your search.</td>
128
+ </tr>
129
+ </tbody>
130
+ </table>
131
+ </div>
132
+ </div>
133
+
134
+ <div :show="selectedServer() !== null" @click.self="closeServer()" class="flex fixed inset-0 z-50 items-center justify-center p-4 bg-black bg-opacity-50">
135
+ <div class="overflow-y-auto p-6 max-h-[90vh] max-w-2xl w-full bg-white dark:bg-neutral-800 rounded-lg shadow-xl">
136
+ <div class="flex items-start justify-between mb-4">
137
+ <div>
138
+ <h3 class="font-semibold text-gray-900 text-lg dark:text-white">{{ selectedServer()?.name }}</h3>
139
+ <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">{{ selectedServer()?.domain }}</p>
140
+ </div>
141
+ <button @click="closeServer()" class="text-gray-400 dark:text-neutral-300 hover:text-gray-600 dark:hover:text-white">
142
+ <i class="h-5 w-5 i-hugeicons-cancel-01"></i>
143
+ </button>
144
+ </div>
145
+
146
+ <dl class="grid gap-3 grid-cols-1 sm:grid-cols-2">
147
+ <template :for="entry in selectedDetails">
148
+ <div class="flex gap-4 justify-between py-2 border-gray-100 border-t dark:border-neutral-700">
149
+ <dt class="text-gray-500 text-sm dark:text-neutral-400">{{ entry.label }}</dt>
150
+ <dd class="font-medium text-gray-900 text-right text-sm dark:text-white">{{ entry.value }}</dd>
151
+ </div>
152
+ </template>
153
+ </dl>
154
+ </div>
155
+ </div>
156
+
157
+ <div class="mt-12">
158
+ <div class="flex items-center justify-between mb-4">
159
+ <div>
160
+ <h3 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Recent Events</h3>
161
+ <p class="mt-2 text-gray-700 text-sm dark:text-neutral-400">Latest server events and notifications.</p>
162
+ </div>
163
+ </div>
164
+
165
+ <div class="inline-block min-w-full align-middle">
166
+ <div class="overflow-hidden ring-1 ring-black ring-opacity-5 sm:rounded-lg shadow">
167
+ <table class="min-w-full divide-gray-300 divide-y dark:divide-neutral-600">
168
+ <thead class="bg-gray-50 dark:bg-neutral-600">
169
+ <tr>
170
+ <th scope="col" class="pl-4 pr-3 py-3.5 sm:pl-6 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Type</th>
171
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Service</th>
172
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Message</th>
173
+ <th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-right text-sm dark:text-neutral-100">Timestamp</th>
174
+ </tr>
175
+ </thead>
176
+ <tbody class="bg-white dark:bg-neutral-700 divide-gray-200 divide-y dark:divide-neutral-700">
177
+ <template :for="event in serverEventsRaw">
178
+ <tr class="hover:bg-gray-50 dark:hover:bg-neutral-600">
179
+ <td class="pl-4 pr-3 py-4 sm:pl-6 text-sm whitespace-nowrap">
180
+ <div :class="eventTypeIcon(event.type) + ' ' + eventTypeColor(event.type)" class="h-5 w-5"></div>
181
+ </td>
182
+ <td class="px-3 py-4 text-gray-900 text-sm whitespace-nowrap dark:text-neutral-100">{{ event.service }}</td>
183
+ <td class="px-3 py-4 text-gray-500 text-sm dark:text-neutral-400">{{ event.message }}</td>
184
+ <td class="px-3 py-4 text-gray-500 text-right text-sm whitespace-nowrap dark:text-neutral-400">{{ event.timestamp }}</td>
185
+ </tr>
186
+ </template>
187
+ </tbody>
188
+ </table>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ </div>
194
+
195
+ <script client>
196
+ interface ServerConfig {
197
+ key: string
198
+ name: string
199
+ domain: string
200
+ region: string
201
+ type: string
202
+ size: string
203
+ diskSize: number
204
+ serverOS: string
205
+ bunVersion: string
206
+ database: string
207
+ databaseName: string
208
+ searchEngine: string
209
+ }
210
+
211
+ const serversData: ServerConfig[] = [
212
+ { key: 'app1', name: 'app-cosmic-nebula', domain: 'stacksjs.com', region: 'us-east-1', type: 'app', size: 't3.micro', diskSize: 20, serverOS: 'ubuntu-24-lts-x86_64', bunVersion: 'v1.2.4', database: 'sqlite', databaseName: 'stacks', searchEngine: 'meilisearch' },
213
+ { key: 'worker1', name: 'worker-cosmic-nebula', domain: 'stacksjs.com', region: 'us-east-1', type: 'worker', size: 't3.micro', diskSize: 20, serverOS: 'ubuntu-24-lts-x86_64', bunVersion: 'v1.2.4', database: '', databaseName: '', searchEngine: '' },
214
+ { key: 'worker2', name: 'worker2-cosmic-nebula', domain: 'stacksjs.com', region: 'us-east-1', type: 'worker', size: 't3.micro', diskSize: 20, serverOS: 'ubuntu-24-lts-x86_64', bunVersion: 'v1.2.4', database: '', databaseName: '', searchEngine: '' },
215
+ { key: 'worker3', name: 'worker3-cosmic-nebula', domain: 'stacksjs.com', region: 'us-east-1', type: 'worker', size: 't3.micro', diskSize: 20, serverOS: 'ubuntu-24-lts-x86_64', bunVersion: 'v1.2.4', database: '', databaseName: '', searchEngine: '' },
216
+ { key: 'cache1', name: 'cache-cosmic-nebula', domain: 'stacksjs.com', region: 'us-east-1', type: 'cache', size: 't3.micro', diskSize: 20, serverOS: 'ubuntu-24-lts-x86_64', bunVersion: 'v1.2.4', database: '', databaseName: '', searchEngine: '' },
217
+ { key: 'database1', name: 'database-cosmic-nebula', domain: 'stacksjs.com', region: 'us-east-1', type: 'database', size: 't3.micro', diskSize: 20, serverOS: 'ubuntu-24-lts-x86_64', bunVersion: '', database: 'sqlite', databaseName: 'stacks', searchEngine: '' },
218
+ { key: 'search1', name: 'search-cosmic-nebula', domain: 'stacksjs.com', region: 'us-east-1', type: 'search', size: 't3.micro', diskSize: 20, serverOS: 'ubuntu-24-lts-x86_64', bunVersion: 'v1.2.4', database: '', databaseName: '', searchEngine: 'meilisearch' },
219
+ ]
220
+
221
+ // Grouped view + static events — moved out of the former
222
+ // `<script server>` block (stacksjs/stacks#1838).
223
+ const serversByType = ['app', 'worker', 'cache', 'database', 'search']
224
+ .map(type => ({
225
+ type,
226
+ label: type.charAt(0).toUpperCase() + type.slice(1),
227
+ servers: serversData.filter(s => s.type === type),
228
+ }))
229
+ .filter(group => group.servers.length > 0)
230
+
231
+ const useLoadBalancer = true
232
+
233
+ const serverEventsRaw = [
234
+ { id: '1', service: 'App Server', type: 'success', message: 'Server deployment completed successfully', timestamp: '2024-03-21 14:23:45' },
235
+ { id: '2', service: 'Worker', type: 'info', message: 'New worker instance launched', timestamp: '2024-03-21 14:20:12' },
236
+ { id: '3', service: 'Database', type: 'warning', message: 'High CPU utilization detected', timestamp: '2024-03-21 14:15:30' },
237
+ { id: '4', service: 'Cache', type: 'error', message: 'Connection timeout', timestamp: '2024-03-21 14:10:55' },
238
+ { id: '5', service: 'Search', type: 'success', message: 'Index rebuilt successfully', timestamp: '2024-03-21 14:05:22' },
239
+ ]
240
+
241
+ function eventTypeColor(type: string): string {
242
+ switch (type) {
243
+ case 'success': return 'text-green-500'
244
+ case 'error': return 'text-red-500'
245
+ case 'warning': return 'text-yellow-500'
246
+ default: return 'text-blue-500'
247
+ }
248
+ }
249
+
250
+ function eventTypeIcon(type: string): string {
251
+ switch (type) {
252
+ case 'success': return 'i-hugeicons-checkmark-circle-02'
253
+ case 'error': return 'i-hugeicons-cancel-circle'
254
+ case 'warning': return 'i-hugeicons-alert-02'
255
+ default: return 'i-hugeicons-information-circle'
256
+ }
257
+ }
258
+
259
+ const searchQuery = state('')
260
+ const sortKey = state<keyof ServerConfig>('name')
261
+ const sortOrder = state<'asc' | 'desc'>('asc')
262
+ const selectedServerKey = state<string | null>(null)
263
+
264
+ const filteredServers = derived(() => {
265
+ const q = searchQuery().toLowerCase()
266
+ const key = sortKey()
267
+ const order = sortOrder()
268
+ const result = serversData.filter((server) => {
269
+ if (!q)
270
+ return true
271
+ return server.name.toLowerCase().includes(q)
272
+ || server.domain.toLowerCase().includes(q)
273
+ || server.type.toLowerCase().includes(q)
274
+ || server.region.toLowerCase().includes(q)
275
+ })
276
+ result.sort((a, b) => {
277
+ const av = String(a[key] ?? '')
278
+ const bv = String(b[key] ?? '')
279
+ if (av === bv)
280
+ return 0
281
+ return order === 'asc' ? (av < bv ? -1 : 1) : (av < bv ? 1 : -1)
282
+ })
283
+ return result
284
+ })
285
+
286
+ const selectedServer = derived(() => serversData.find(s => s.key === selectedServerKey()) ?? null)
287
+
288
+ const selectedDetails = derived(() => {
289
+ const server = selectedServer()
290
+ if (!server)
291
+ return []
292
+ const entries: Array<{ label: string, value: string }> = [
293
+ { label: 'Domain', value: server.domain },
294
+ { label: 'Region', value: server.region },
295
+ { label: 'Type', value: server.type },
296
+ { label: 'Size', value: server.size },
297
+ { label: 'Disk', value: `${server.diskSize}GB` },
298
+ { label: 'OS', value: server.serverOS },
299
+ ]
300
+ if (server.bunVersion)
301
+ entries.push({ label: 'Bun version', value: server.bunVersion })
302
+ if (server.database)
303
+ entries.push({ label: 'Database', value: `${server.database}${server.databaseName ? ` (${server.databaseName})` : ''}` })
304
+ if (server.searchEngine)
305
+ entries.push({ label: 'Search engine', value: server.searchEngine })
306
+ return entries
307
+ })
308
+
309
+ function selectServer(key: string) {
310
+ selectedServerKey.set(key)
311
+ }
312
+
313
+ function closeServer() {
314
+ selectedServerKey.set(null)
315
+ }
316
+
317
+ function toggleSort(key: keyof ServerConfig) {
318
+ if (sortKey() === key)
319
+ sortOrder.set(sortOrder() === 'asc' ? 'desc' : 'asc')
320
+ else {
321
+ sortKey.set(key)
322
+ sortOrder.set('asc')
323
+ }
324
+ }
325
+
326
+ function sortIconClass(key: keyof ServerConfig) {
327
+ if (sortKey() !== key)
328
+ return 'h-4 w-4 opacity-0'
329
+ return sortOrder() === 'asc' ? 'h-4 w-4 i-hugeicons-chevron-down' : 'h-4 w-4 i-hugeicons-chevron-up'
330
+ }
331
+
332
+ function typeBadgeClassFor(type: string): string {
333
+ const base = 'inline-flex px-2 font-semibold leading-5 text-xs rounded-full'
334
+ switch (type) {
335
+ case 'app': return `${base} bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200`
336
+ case 'worker': return `${base} bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200`
337
+ case 'cache': return `${base} bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200`
338
+ case 'database': return `${base} bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200`
339
+ case 'search': return `${base} bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200`
340
+ case 'web': return `${base} bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200`
341
+ default: return `${base} bg-gray-100 text-gray-800 dark:bg-neutral-700 dark:text-neutral-200`
342
+ }
343
+ }
344
+
345
+ function serverEditHref(key: string): string {
346
+ return `/servers/${key}`
347
+ }
348
+
349
+ function nodeFill(type: 'dns' | 'lb' | 'app' | 'worker' | 'cache' | 'database' | 'search' | 'web'): { ring: string, fill: string } {
350
+ switch (type) {
351
+ case 'dns': return { ring: '#EC4899', fill: '#FCE7F3' }
352
+ case 'lb': return { ring: '#3B82F6', fill: '#DBEAFE' }
353
+ case 'app': return { ring: '#10B981', fill: '#D1FAE5' }
354
+ case 'worker': return { ring: '#F59E0B', fill: '#FEF3C7' }
355
+ case 'cache': return { ring: '#A855F7', fill: '#F3E8FF' }
356
+ case 'database': return { ring: '#22C55E', fill: '#DCFCE7' }
357
+ case 'search': return { ring: '#6366F1', fill: '#E0E7FF' }
358
+ default: return { ring: '#6B7280', fill: '#F3F4F6' }
359
+ }
360
+ }
361
+
362
+ let topologyCleanup: (() => void) | null = null
363
+
364
+ interface TopoNode { id: string, type: 'dns' | 'lb' | 'app' | 'worker' | 'cache' | 'database' | 'search' | 'web', label: string, key?: string, x?: number, y?: number, fx?: number | null, fy?: number | null }
365
+ interface TopoLink { source: string | TopoNode, target: string | TopoNode }
366
+
367
+ async function renderTopology() {
368
+ const container = document.getElementById('servers_topology')
369
+ if (!container)
370
+ return
371
+ topologyCleanup?.()
372
+ container.innerHTML = ''
373
+
374
+ const d3 = await import('d3')
375
+ const width = container.clientWidth || 800
376
+ const height = container.clientHeight || 420
377
+
378
+ const nodes: TopoNode[] = [
379
+ { id: 'dns', type: 'dns', label: 'DNS' },
380
+ { id: 'lb', type: 'lb', label: 'Load balancer' },
381
+ ...serversData.map(s => ({ id: s.key, type: s.type as TopoNode['type'], label: s.name, key: s.key })),
382
+ ]
383
+ const links: TopoLink[] = [
384
+ { source: 'dns', target: 'lb' },
385
+ ...serversData.map(s => ({ source: 'lb', target: s.key })),
386
+ ]
387
+
388
+ const svg = d3.select(container)
389
+ .append('svg')
390
+ .attr('width', width)
391
+ .attr('height', height)
392
+ .attr('viewBox', [0, 0, width, height])
393
+ .attr('style', 'max-width: 100%; height: auto;')
394
+
395
+ const zoomGroup = svg.append('g')
396
+ const zoom = d3.zoom<SVGSVGElement, unknown>()
397
+ .scaleExtent([0.3, 3])
398
+ .on('zoom', event => zoomGroup.attr('transform', event.transform.toString()))
399
+ svg.call(zoom as any)
400
+
401
+ const link = zoomGroup.append('g')
402
+ .attr('stroke', '#94A3B8')
403
+ .attr('stroke-opacity', 0.5)
404
+ .attr('stroke-dasharray', '4,4')
405
+ .selectAll('line')
406
+ .data(links)
407
+ .join('line')
408
+ .attr('stroke-width', 2)
409
+
410
+ const node = zoomGroup.append('g')
411
+ .selectAll<SVGGElement, TopoNode>('g')
412
+ .data(nodes)
413
+ .join('g')
414
+ .attr('cursor', d => d.key ? 'pointer' : 'default')
415
+ .on('click', (_event, d) => {
416
+ if (d.key)
417
+ selectServer(d.key)
418
+ })
419
+
420
+ node.append('circle')
421
+ .attr('r', 26)
422
+ .attr('fill', d => nodeFill(d.type).fill)
423
+ .attr('stroke', d => nodeFill(d.type).ring)
424
+ .attr('stroke-width', 3)
425
+
426
+ node.append('text')
427
+ .text(d => d.label.length > 14 ? `${d.label.slice(0, 12)}…` : d.label)
428
+ .attr('text-anchor', 'middle')
429
+ .attr('dy', 44)
430
+ .attr('fill', 'currentColor')
431
+ .attr('font-size', '11px')
432
+ .attr('font-weight', '500')
433
+ .attr('class', 'text-gray-700 dark:text-neutral-200')
434
+ .attr('pointer-events', 'none')
435
+
436
+ const drag = d3.drag<SVGGElement, TopoNode>()
437
+ .on('start', (event, d) => {
438
+ if (!event.active)
439
+ simulation.alphaTarget(0.3).restart()
440
+ d.fx = d.x
441
+ d.fy = d.y
442
+ })
443
+ .on('drag', (event, d) => { d.fx = event.x; d.fy = event.y })
444
+ .on('end', (event, d) => {
445
+ if (!event.active)
446
+ simulation.alphaTarget(0)
447
+ d.fx = null
448
+ d.fy = null
449
+ })
450
+ node.call(drag as any)
451
+
452
+ const simulation = d3.forceSimulation<TopoNode>(nodes)
453
+ .force('link', d3.forceLink<TopoNode, TopoLink>(links).id(d => d.id).distance(120).strength(1))
454
+ .force('charge', d3.forceManyBody().strength(-380))
455
+ .force('x', d3.forceX<TopoNode>(d => d.type === 'dns' ? width * 0.15 : d.type === 'lb' ? width * 0.4 : width * 0.75).strength(0.6))
456
+ .force('y', d3.forceY<TopoNode>(height / 2).strength(0.15))
457
+ .force('collide', d3.forceCollide<TopoNode>().radius(38))
458
+ .on('tick', () => {
459
+ link
460
+ .attr('x1', d => (d.source as TopoNode).x ?? 0)
461
+ .attr('y1', d => (d.source as TopoNode).y ?? 0)
462
+ .attr('x2', d => (d.target as TopoNode).x ?? 0)
463
+ .attr('y2', d => (d.target as TopoNode).y ?? 0)
464
+ node.attr('transform', d => `translate(${d.x ?? 0},${d.y ?? 0})`)
465
+ })
466
+
467
+ topologyCleanup = () => {
468
+ simulation.stop()
469
+ container.innerHTML = ''
470
+ }
471
+ }
472
+
473
+ onMount(() => {
474
+ void renderTopology()
475
+ })
476
+
477
+ onDestroy(() => {
478
+ topologyCleanup?.()
479
+ topologyCleanup = null
480
+ })
481
+ </script>
482
+
483
+ @endsection
@@ -0,0 +1,50 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ <div class="container mx-auto px-4 py-8 lg:px-8">
5
+ <div id="subscribed">
6
+ <TransactionHistory />
7
+ <div class="flex space-x-8">
8
+ <div class="mt-16 px-8 py-6 w-2/3 bg-white dark:bg-neutral-800 ring-1 ring-black ring-opacity-5 sm:rounded-lg shadow">
9
+ @if(paymentStore.isStateLoading('fetchActivePlan') && paymentStore.isStateLoading('fetchStripeCustomer'))
10
+ <LoadingDetails :height="24" />
11
+ @else
12
+ <div class="w-full">
13
+ @if(showCurrentPlan)
14
+ <ActivePlan />
15
+ @else
16
+ <Plans />
17
+ @endif
18
+ </div>
19
+ @endif
20
+ </div>
21
+ <PaymentMethod />
22
+ </div>
23
+ </div>
24
+ </div>
25
+ <script client>
26
+ import { useAuth } from '../../../functions/auth'
27
+ import { useBillable } from '../../../functions/billing/payments'
28
+
29
+ // Moved out of the former `<script server>` block (stacksjs/stacks#1838).
30
+ // `usePaymentStore` is auto-imported by stx.
31
+ const { isEmpty, showCurrentPlan } = useBillable()
32
+ const paymentStore = usePaymentStore()
33
+
34
+ onMount(async () => {
35
+ const { user } = useAuth()
36
+ const userId = user.value?.id
37
+
38
+ if (!userId) return
39
+
40
+ await paymentStore.fetchStripeCustomer(userId).then(async () => {
41
+ if (!isEmpty(paymentStore.getStripeCustomer)) {
42
+ await paymentStore.fetchDefaultPaymentMethod(userId)
43
+ await paymentStore.fetchUserActivePlan(userId)
44
+ await paymentStore.fetchTransactionHistory(userId)
45
+ }
46
+ })
47
+ })
48
+ </script>
49
+
50
+ @endsection