@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.
- package/TODO.md +21 -0
- package/ai/AGENTS.md +79 -0
- package/ai/README.md +45 -0
- package/ai/claude/launch.json +12 -0
- package/ai/skills/stacks-actions/SKILL.md +128 -0
- package/ai/skills/stacks-ai/SKILL.md +276 -0
- package/ai/skills/stacks-alias/SKILL.md +94 -0
- package/ai/skills/stacks-analytics/SKILL.md +91 -0
- package/ai/skills/stacks-api/SKILL.md +738 -0
- package/ai/skills/stacks-arrays/SKILL.md +277 -0
- package/ai/skills/stacks-auth/SKILL.md +422 -0
- package/ai/skills/stacks-auto-imports/SKILL.md +138 -0
- package/ai/skills/stacks-brandkit/SKILL.md +811 -0
- package/ai/skills/stacks-browse/SKILL.md +127 -0
- package/ai/skills/stacks-browse/scripts/browse.ts +492 -0
- package/ai/skills/stacks-browser/SKILL.md +145 -0
- package/ai/skills/stacks-buddy/SKILL.md +676 -0
- package/ai/skills/stacks-build/SKILL.md +163 -0
- package/ai/skills/stacks-cache/SKILL.md +125 -0
- package/ai/skills/stacks-calendar/SKILL.md +113 -0
- package/ai/skills/stacks-chat/SKILL.md +371 -0
- package/ai/skills/stacks-cli/SKILL.md +112 -0
- package/ai/skills/stacks-cloud/SKILL.md +192 -0
- package/ai/skills/stacks-cms/SKILL.md +217 -0
- package/ai/skills/stacks-collections/SKILL.md +82 -0
- package/ai/skills/stacks-commerce/SKILL.md +114 -0
- package/ai/skills/stacks-composables/SKILL.md +161 -0
- package/ai/skills/stacks-config/SKILL.md +144 -0
- package/ai/skills/stacks-configuration/SKILL.md +154 -0
- package/ai/skills/stacks-cron/SKILL.md +123 -0
- package/ai/skills/stacks-crosswind/SKILL.md +190 -0
- package/ai/skills/stacks-dashboard/SKILL.md +173 -0
- package/ai/skills/stacks-database/SKILL.md +286 -0
- package/ai/skills/stacks-datetime/SKILL.md +269 -0
- package/ai/skills/stacks-dependencies/SKILL.md +111 -0
- package/ai/skills/stacks-deploy/SKILL.md +102 -0
- package/ai/skills/stacks-design-brutalist/SKILL.md +110 -0
- package/ai/skills/stacks-design-minimalist/SKILL.md +115 -0
- package/ai/skills/stacks-design-output/SKILL.md +55 -0
- package/ai/skills/stacks-design-soft/SKILL.md +156 -0
- package/ai/skills/stacks-design-taste/SKILL.md +1012 -0
- package/ai/skills/stacks-design-taste-codex/SKILL.md +294 -0
- package/ai/skills/stacks-desktop/SKILL.md +78 -0
- package/ai/skills/stacks-development/SKILL.md +298 -0
- package/ai/skills/stacks-dns/SKILL.md +149 -0
- package/ai/skills/stacks-docs/SKILL.md +109 -0
- package/ai/skills/stacks-email/SKILL.md +236 -0
- package/ai/skills/stacks-enums/SKILL.md +100 -0
- package/ai/skills/stacks-env/SKILL.md +181 -0
- package/ai/skills/stacks-error-handling/SKILL.md +137 -0
- package/ai/skills/stacks-events/SKILL.md +228 -0
- package/ai/skills/stacks-faker/SKILL.md +158 -0
- package/ai/skills/stacks-git/SKILL.md +158 -0
- package/ai/skills/stacks-guard/SKILL.md +98 -0
- package/ai/skills/stacks-health/SKILL.md +75 -0
- package/ai/skills/stacks-http/SKILL.md +316 -0
- package/ai/skills/stacks-i18n/SKILL.md +198 -0
- package/ai/skills/stacks-image-to-code/SKILL.md +629 -0
- package/ai/skills/stacks-imagegen-mobile/SKILL.md +1474 -0
- package/ai/skills/stacks-imagegen-web/SKILL.md +998 -0
- package/ai/skills/stacks-investigate/SKILL.md +119 -0
- package/ai/skills/stacks-jobs/SKILL.md +136 -0
- package/ai/skills/stacks-lint/SKILL.md +184 -0
- package/ai/skills/stacks-listeners/SKILL.md +99 -0
- package/ai/skills/stacks-logging/SKILL.md +78 -0
- package/ai/skills/stacks-mail/SKILL.md +120 -0
- package/ai/skills/stacks-middleware/SKILL.md +249 -0
- package/ai/skills/stacks-migrations/SKILL.md +105 -0
- package/ai/skills/stacks-models/SKILL.md +267 -0
- package/ai/skills/stacks-new-feature/SKILL.md +206 -0
- package/ai/skills/stacks-notifications/SKILL.md +210 -0
- package/ai/skills/stacks-objects/SKILL.md +205 -0
- package/ai/skills/stacks-office-hours/SKILL.md +134 -0
- package/ai/skills/stacks-orm/SKILL.md +342 -0
- package/ai/skills/stacks-path/SKILL.md +118 -0
- package/ai/skills/stacks-payments/SKILL.md +373 -0
- package/ai/skills/stacks-plan-review/SKILL.md +152 -0
- package/ai/skills/stacks-plugins/SKILL.md +149 -0
- package/ai/skills/stacks-push/SKILL.md +376 -0
- package/ai/skills/stacks-query-builder/SKILL.md +128 -0
- package/ai/skills/stacks-queue/SKILL.md +559 -0
- package/ai/skills/stacks-realtime/SKILL.md +442 -0
- package/ai/skills/stacks-redesign/SKILL.md +182 -0
- package/ai/skills/stacks-registry/SKILL.md +58 -0
- package/ai/skills/stacks-repl/SKILL.md +80 -0
- package/ai/skills/stacks-retro/SKILL.md +122 -0
- package/ai/skills/stacks-review/SKILL.md +135 -0
- package/ai/skills/stacks-router/SKILL.md +231 -0
- package/ai/skills/stacks-routes/SKILL.md +105 -0
- package/ai/skills/stacks-scaffolding/SKILL.md +162 -0
- package/ai/skills/stacks-scheduler/SKILL.md +363 -0
- package/ai/skills/stacks-search-engine/SKILL.md +112 -0
- package/ai/skills/stacks-security/SKILL.md +129 -0
- package/ai/skills/stacks-security-audit/SKILL.md +128 -0
- package/ai/skills/stacks-server/SKILL.md +458 -0
- package/ai/skills/stacks-shell/SKILL.md +62 -0
- package/ai/skills/stacks-slug/SKILL.md +87 -0
- package/ai/skills/stacks-sms/SKILL.md +385 -0
- package/ai/skills/stacks-socials/SKILL.md +303 -0
- package/ai/skills/stacks-storage/SKILL.md +576 -0
- package/ai/skills/stacks-strings/SKILL.md +416 -0
- package/ai/skills/stacks-stx/SKILL.md +176 -0
- package/ai/skills/stacks-technical-diagrams/LICENSE +22 -0
- package/ai/skills/stacks-technical-diagrams/SKILL.md +290 -0
- package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +4 -0
- package/ai/skills/stacks-technical-diagrams/assets/template.html +1252 -0
- package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +289 -0
- package/ai/skills/stacks-technical-diagrams/bunfig.toml +1 -0
- package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +64 -0
- package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +88 -0
- package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +75 -0
- package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +70 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +41 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +62 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +371 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +82 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +295 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +91 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +384 -0
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +84 -0
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +257 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +42 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +13 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +230 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +40 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +88 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +40 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +103 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +481 -0
- package/ai/skills/stacks-technical-diagrams/schemas/README.md +68 -0
- package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +121 -0
- package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +43 -0
- package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +221 -0
- package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +241 -0
- package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +186 -0
- package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +332 -0
- package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +297 -0
- package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +28 -0
- package/ai/skills/stacks-testing/SKILL.md +129 -0
- package/ai/skills/stacks-tunnel/SKILL.md +125 -0
- package/ai/skills/stacks-types/SKILL.md +178 -0
- package/ai/skills/stacks-ui/SKILL.md +246 -0
- package/ai/skills/stacks-utils/SKILL.md +444 -0
- package/ai/skills/stacks-validation/SKILL.md +371 -0
- package/ai/skills/stacks-whois/SKILL.md +315 -0
- package/assets/world-map-data.ts +121 -0
- package/bootstrap.ts +114 -0
- package/docs/api-examples.md +46 -0
- package/docs/index.md +24 -0
- package/docs/markdown-examples.md +87 -0
- package/functions/analytics.ts +97 -0
- package/functions/api.ts +143 -0
- package/functions/auth.ts +208 -0
- package/functions/billing/payments.ts +149 -0
- package/functions/blog.ts +120 -0
- package/functions/cms/categorizables.ts +107 -0
- package/functions/cms/pages.ts +108 -0
- package/functions/cms/posts.ts +100 -0
- package/functions/cms/taggables.ts +102 -0
- package/functions/commerce/coupons.ts +128 -0
- package/functions/commerce/customers.ts +128 -0
- package/functions/commerce/gift-cards.ts +119 -0
- package/functions/commerce/orders.ts +112 -0
- package/functions/commerce/payments.ts +119 -0
- package/functions/commerce/products/categories.ts +123 -0
- package/functions/commerce/products/manufacturers.ts +123 -0
- package/functions/commerce/products/products.ts +123 -0
- package/functions/commerce/products/reviews.ts +123 -0
- package/functions/commerce/products/units.ts +123 -0
- package/functions/commerce/products/variants.ts +123 -0
- package/functions/commerce/shippings/delivery-routes.ts +123 -0
- package/functions/commerce/shippings/digital-deliveries.ts +119 -0
- package/functions/commerce/shippings/drivers.ts +123 -0
- package/functions/commerce/shippings/license-keys.ts +123 -0
- package/functions/commerce/shippings/shipping-methods.ts +123 -0
- package/functions/commerce/shippings/shipping-rates.ts +119 -0
- package/functions/commerce/shippings/shipping-zones.ts +119 -0
- package/functions/commerce/taxes.ts +119 -0
- package/functions/commerce/waitlist/products.ts +123 -0
- package/functions/commerce/waitlist/restaurants.ts +123 -0
- package/functions/craft.ts +107 -0
- package/functions/dashboard.ts +153 -0
- package/functions/inbox.ts +95 -0
- package/functions/jobs.ts +86 -0
- package/functions/monitoring/errors.ts +295 -0
- package/functions/queue.ts +111 -0
- package/functions/realtime/websockets.ts +96 -0
- package/functions/realtime.ts +52 -0
- package/functions/releases.ts +49 -0
- package/functions/search.ts +126 -0
- package/functions/toasts.ts +95 -0
- package/functions/uploads.ts +216 -0
- package/ide/.gitignore +4 -0
- package/ide/cursor/rules/auto-imports.mdc +574 -0
- package/ide/cursor/rules/code-style.mdc +12 -0
- package/ide/cursor/rules/documentation.mdc +9 -0
- package/ide/cursor/rules/error-handling-validation.mdc +11 -0
- package/ide/cursor/rules/key-conventions.mdc +11 -0
- package/ide/cursor/rules/project-structure.mdc +169 -0
- package/ide/cursor/rules/readme.mdc +318 -0
- package/ide/cursor/rules/syntax-formatting.mdc +9 -0
- package/ide/cursor/rules/testing.mdc +10 -0
- package/ide/cursor/rules/typescript.mdc +9 -0
- package/ide/cursor/rules/ui-styling.mdc +0 -0
- package/ide/dictionary.txt +397 -0
- package/ide/jetbrains/.fleet/settings.json +4 -0
- package/ide/jetbrains/.idea/.gitignore +5 -0
- package/ide/jetbrains/.idea/.name +1 -0
- package/ide/jetbrains/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/ide/jetbrains/.idea/jsLinters/eslint.xml +7 -0
- package/ide/jetbrains/.idea/modules.xml +8 -0
- package/ide/jetbrains/.idea/stacks.iml +22 -0
- package/ide/jetbrains/.idea/vcs.xml +6 -0
- package/ide/jetbrains/.idea/webResources.xml +14 -0
- package/ide/jetbrains/IntelliJ IDEA Global Settings +0 -0
- package/ide/jetbrains/installed.txt +4 -0
- package/ide/jetbrains/options/github-copilot.xml +5 -0
- package/ide/jetbrains/options/nodejs.xml +7 -0
- package/ide/jetbrains/options/templates.xml +124 -0
- package/ide/jetbrains/templates/JavaScript.xml +6 -0
- package/ide/jetbrains/templates/Stacks.xml +8 -0
- package/ide/vscode/.vscode/README.md +23 -0
- package/ide/vscode/.vscode/extensions.json +5 -0
- package/ide/vscode/.vscode/settings.json +176 -0
- package/ide/vscode/.vscode/stacks.code-snippets +181 -0
- package/ide/vscode/LICENSE.md +21 -0
- package/ide/vscode/README.md +36 -0
- package/ide/vscode/logo.png +0 -0
- package/ide/vscode/package.json +79 -0
- package/ide/zed/.zed/settings.json +139 -0
- package/lang/README.md +28 -0
- package/lang/de.yml +14 -0
- package/lang/en.yml +15 -0
- package/package.json +19 -3
- package/project/bootstrap +161 -0
- package/project/buddy +175 -0
- package/project/storage/framework/tsconfig.app.json +50 -0
- package/project/storage/framework/tsconfig.base.json +58 -0
- package/routes/core.ts +78 -0
- package/routes/dashboard-api.ts +233 -0
- package/routes/dashboard.ts +739 -0
- package/routes/email.ts +129 -0
- package/stores/[name].stx +16 -0
- package/stores/git.ts +71 -0
- package/stores/payment.ts +474 -0
- package/stores/queue.ts +43 -0
- package/stores/user.ts +29 -0
- package/stx-components-plugin.ts +25 -0
- package/styles/design-tokens.css +597 -0
- package/types/dashboard.ts +70 -0
- package/types/defaults.ts +438 -0
- package/vcs/github/CODE_OF_CONDUCT.md +83 -0
- package/vcs/github/CONTRIBUTING.md +187 -0
- package/vcs/github/EXPLANATIONS.md +41 -0
- package/vcs/github/FUNDING.yml +1 -0
- package/vcs/github/ISSUE_TEMPLATE/bug_report.yml +49 -0
- package/vcs/github/ISSUE_TEMPLATE/config.yml +10 -0
- package/vcs/github/ISSUE_TEMPLATE/feature_request.yml +44 -0
- package/vcs/github/SECURITY.md +70 -0
- package/vcs/github/renovate.json +5 -0
- package/vcs/github/stale.yml +14 -0
- package/vcs/github/workflows/README.md +16 -0
- package/vcs/github/workflows/ci.yml +114 -0
- package/vcs/github/workflows/export-size.yml +25 -0
- package/vcs/github/workflows/labeler.yml +13 -0
- package/vcs/github/workflows/release.yml +219 -0
- package/views/[...all].stx +8 -0
- package/views/about.md +3 -0
- package/views/auth/login.stx +1 -0
- package/views/auth/signup.stx +85 -0
- package/views/dashboard/.discovered-models.json +542 -0
- package/views/dashboard/AUDIT.md +904 -0
- package/views/dashboard/[...all].stx +89 -0
- package/views/dashboard/access-tokens/index.stx +177 -0
- package/views/dashboard/actions/index.stx +129 -0
- package/views/dashboard/analytics/blog/index.stx +356 -0
- package/views/dashboard/analytics/browsers/index.stx +263 -0
- package/views/dashboard/analytics/commerce/sales/index.stx +329 -0
- package/views/dashboard/analytics/commerce/web/index.stx +379 -0
- package/views/dashboard/analytics/countries/index.stx +289 -0
- package/views/dashboard/analytics/devices/index.stx +305 -0
- package/views/dashboard/analytics/events/index.stx +536 -0
- package/views/dashboard/analytics/index.stx +88 -0
- package/views/dashboard/analytics/marketing/index.stx +315 -0
- package/views/dashboard/analytics/pages/index.stx +271 -0
- package/views/dashboard/analytics/referrers/index.stx +319 -0
- package/views/dashboard/analytics/web/index.stx +356 -0
- package/views/dashboard/buddy/index.stx +198 -0
- package/views/dashboard/ci/index.stx +462 -0
- package/views/dashboard/cloud/index.stx +452 -0
- package/views/dashboard/commands/index.stx +295 -0
- package/views/dashboard/commerce/categories/index.stx +406 -0
- package/views/dashboard/commerce/coupons/index.stx +485 -0
- package/views/dashboard/commerce/customers/index.stx +410 -0
- package/views/dashboard/commerce/dashboard/index.stx +287 -0
- package/views/dashboard/commerce/delivery/delivery-routes.stx +305 -0
- package/views/dashboard/commerce/delivery/digital-delivery.stx +332 -0
- package/views/dashboard/commerce/delivery/drivers.stx +397 -0
- package/views/dashboard/commerce/delivery/index.stx +18 -0
- package/views/dashboard/commerce/delivery/license-keys.stx +380 -0
- package/views/dashboard/commerce/delivery/shipping-methods.stx +346 -0
- package/views/dashboard/commerce/delivery/shipping-rates.stx +338 -0
- package/views/dashboard/commerce/delivery/shipping-zones.stx +302 -0
- package/views/dashboard/commerce/delivery/tabs.ts +25 -0
- package/views/dashboard/commerce/gift-cards/index.stx +561 -0
- package/views/dashboard/commerce/manufacturers/index.stx +308 -0
- package/views/dashboard/commerce/orders/index.stx +247 -0
- package/views/dashboard/commerce/payments/index.stx +535 -0
- package/views/dashboard/commerce/pos/index.stx +653 -0
- package/views/dashboard/commerce/printers/devices/index.stx +273 -0
- package/views/dashboard/commerce/printers/receipts/index.stx +308 -0
- package/views/dashboard/commerce/products/detail.stx +563 -0
- package/views/dashboard/commerce/products/index.stx +370 -0
- package/views/dashboard/commerce/reviews/index.stx +656 -0
- package/views/dashboard/commerce/reviews/index.ts +0 -0
- package/views/dashboard/commerce/taxes/index.stx +377 -0
- package/views/dashboard/commerce/units/index.stx +317 -0
- package/views/dashboard/commerce/variants/index.stx +319 -0
- package/views/dashboard/commerce/waitlist/products.stx +942 -0
- package/views/dashboard/commerce/waitlist/restaurant.stx +1024 -0
- package/views/dashboard/components/index.stx +330 -0
- package/views/dashboard/composables/index.ts +19 -0
- package/views/dashboard/composables/useChart.ts +77 -0
- package/views/dashboard/composables/useNavigation.ts +99 -0
- package/views/dashboard/composables/useRole.ts +58 -0
- package/views/dashboard/composables/useTheme.ts +74 -0
- package/views/dashboard/content/authors/index.stx +171 -0
- package/views/dashboard/content/blog/index.stx +493 -0
- package/views/dashboard/content/categories/index.stx +169 -0
- package/views/dashboard/content/comments/index.stx +189 -0
- package/views/dashboard/content/dashboard.stx +383 -0
- package/views/dashboard/content/files/index.stx +706 -0
- package/views/dashboard/content/pages/index.stx +174 -0
- package/views/dashboard/content/posts/index.stx +269 -0
- package/views/dashboard/content/seo/index.stx +240 -0
- package/views/dashboard/content/tags/index.stx +167 -0
- package/views/dashboard/data/activity/index.stx +261 -0
- package/views/dashboard/data/dashboard/index.stx +116 -0
- package/views/dashboard/data/subscribers.stx +270 -0
- package/views/dashboard/data/teams.stx +275 -0
- package/views/dashboard/data/users.stx +279 -0
- package/views/dashboard/dependencies/index.stx +178 -0
- package/views/dashboard/deployments/[id].stx +234 -0
- package/views/dashboard/deployments/index.stx +9 -0
- package/views/dashboard/dns/index.stx +163 -0
- package/views/dashboard/environment/index.stx +283 -0
- package/views/dashboard/errors/index.stx +274 -0
- package/views/dashboard/forgot-password.stx +57 -0
- package/views/dashboard/functions/index.stx +159 -0
- package/views/dashboard/health/index.stx +248 -0
- package/views/dashboard/inbox/activity.stx +350 -0
- package/views/dashboard/inbox/index.stx +452 -0
- package/views/dashboard/inbox/settings.stx +290 -0
- package/views/dashboard/index.stx +252 -0
- package/views/dashboard/insights/index.stx +529 -0
- package/views/dashboard/jobs/[id].stx +430 -0
- package/views/dashboard/jobs/history.stx +222 -0
- package/views/dashboard/jobs/index.stx +214 -0
- package/views/dashboard/kanban/[id].stx +899 -0
- package/views/dashboard/kanban/index.stx +260 -0
- package/views/dashboard/layouts/default.stx +565 -0
- package/views/dashboard/layouts/guest.stx +10 -0
- package/views/dashboard/login.stx +48 -0
- package/views/dashboard/logs/index.stx +540 -0
- package/views/dashboard/mailboxes/index.stx +149 -0
- package/views/dashboard/management/permissions/index.stx +490 -0
- package/views/dashboard/marketing/campaigns/index.stx +604 -0
- package/views/dashboard/marketing/lists/index.stx +514 -0
- package/views/dashboard/marketing/reviews/index.stx +475 -0
- package/views/dashboard/marketing/social-posts/index.stx +560 -0
- package/views/dashboard/models/[model].stx +195 -0
- package/views/dashboard/models/index.stx +159 -0
- package/views/dashboard/monitoring/errors/index.stx +415 -0
- package/views/dashboard/notifications/dashboard.stx +383 -0
- package/views/dashboard/notifications/email.stx +336 -0
- package/views/dashboard/notifications/history.stx +373 -0
- package/views/dashboard/notifications/sms.stx +332 -0
- package/views/dashboard/packages/index.stx +195 -0
- package/views/dashboard/queries/[id].stx +381 -0
- package/views/dashboard/queries/history.stx +227 -0
- package/views/dashboard/queries/index.stx +335 -0
- package/views/dashboard/queries/slow.stx +318 -0
- package/views/dashboard/queue/index.stx +427 -0
- package/views/dashboard/realtime/index.stx +429 -0
- package/views/dashboard/register.stx +46 -0
- package/views/dashboard/releases/index.stx +389 -0
- package/views/dashboard/requests/index.stx +462 -0
- package/views/dashboard/serverless/index.stx +475 -0
- package/views/dashboard/servers/[id].stx +299 -0
- package/views/dashboard/servers/index.stx +483 -0
- package/views/dashboard/settings/billing.stx +50 -0
- package/views/dashboard/settings/index.stx +256 -0
- package/views/dashboard/settings/mail.stx +252 -0
- package/views/dashboard/stores/auth.ts +123 -0
- package/views/dashboard/stores/ci.ts +342 -0
- package/views/dashboard/stores/index.ts +129 -0
- package/views/dashboard/stores/kanban.ts +723 -0
- package/views/dashboard/stores/rbac.ts +350 -0
- package/views/dashboard/stx.config.ts +34 -0
- package/views/dashboard/teams/[id].stx +170 -0
- package/views/forum/category.stx +92 -0
- package/views/forum/index.stx +209 -0
- package/views/forum/new-topic.stx +113 -0
- package/views/forum/profile.stx +1 -0
- package/views/forum/topic.stx +143 -0
- package/views/hello-world/[name].stx +47 -0
- package/views/index.stx +21 -0
- package/views/system-tray/index.stx +147 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
@extends('layouts/default')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
<script client>
|
|
5
|
+
interface ConfigFile {
|
|
6
|
+
name: string
|
|
7
|
+
title: string
|
|
8
|
+
size: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ConfigField {
|
|
12
|
+
key: string
|
|
13
|
+
value: string | number | boolean | null | unknown[] | Record<string, unknown>
|
|
14
|
+
type: string
|
|
15
|
+
editable: boolean
|
|
16
|
+
reason?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const files = state<ConfigFile[]>([])
|
|
20
|
+
const fields = state<ConfigField[]>([])
|
|
21
|
+
const drafts = state<Record<string, string | number | boolean>>({})
|
|
22
|
+
const selected = state('')
|
|
23
|
+
const query = state('')
|
|
24
|
+
const loading = state(true)
|
|
25
|
+
const saving = state(false)
|
|
26
|
+
const message = state('')
|
|
27
|
+
const error = state('')
|
|
28
|
+
|
|
29
|
+
const filteredFiles = derived(() => {
|
|
30
|
+
const needle = query().trim().toLowerCase()
|
|
31
|
+
if (!needle)
|
|
32
|
+
return files()
|
|
33
|
+
|
|
34
|
+
return files().filter(file => file.name.includes(needle) || file.title.toLowerCase().includes(needle))
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const selectedFile = derived(() => files().find(file => file.name === selected()))
|
|
38
|
+
const editableCount = derived(() => fields().filter(field => field.editable).length)
|
|
39
|
+
|
|
40
|
+
function displayValue(field: ConfigField): string | number {
|
|
41
|
+
if (field.editable)
|
|
42
|
+
return drafts()[field.key] as string | number
|
|
43
|
+
if (field.type === 'object' || field.type === 'array')
|
|
44
|
+
return JSON.stringify(field.value)
|
|
45
|
+
return String(field.value ?? '')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function setDraft(key: string, value: string | number | boolean): void {
|
|
49
|
+
drafts.set({ ...drafts(), [key]: value })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function loadFiles(): Promise<void> {
|
|
53
|
+
loading.set(true)
|
|
54
|
+
error.set('')
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch('/api/config/list')
|
|
57
|
+
const body = await response.json()
|
|
58
|
+
if (!response.ok || !body.ok)
|
|
59
|
+
throw new Error(body.error || 'Unable to list configuration files')
|
|
60
|
+
|
|
61
|
+
files.set(body.files)
|
|
62
|
+
const first = body.files[0]?.name
|
|
63
|
+
if (first)
|
|
64
|
+
await selectFile(first)
|
|
65
|
+
}
|
|
66
|
+
catch (cause) {
|
|
67
|
+
error.set(cause instanceof Error ? cause.message : 'Unable to load configuration')
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
loading.set(false)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function selectFile(name: string): Promise<void> {
|
|
75
|
+
selected.set(name)
|
|
76
|
+
fields.set([])
|
|
77
|
+
message.set('')
|
|
78
|
+
error.set('')
|
|
79
|
+
loading.set(true)
|
|
80
|
+
try {
|
|
81
|
+
const response = await fetch(`/api/config/read?name=${encodeURIComponent(name)}`)
|
|
82
|
+
const body = await response.json()
|
|
83
|
+
if (!response.ok || !body.ok)
|
|
84
|
+
throw new Error(body.error || `Unable to read config/${name}.ts`)
|
|
85
|
+
|
|
86
|
+
fields.set(body.fields)
|
|
87
|
+
drafts.set(Object.fromEntries(
|
|
88
|
+
body.fields
|
|
89
|
+
.filter((field: ConfigField) => field.editable)
|
|
90
|
+
.map((field: ConfigField) => [field.key, field.value]),
|
|
91
|
+
))
|
|
92
|
+
}
|
|
93
|
+
catch (cause) {
|
|
94
|
+
error.set(cause instanceof Error ? cause.message : `Unable to read config/${name}.ts`)
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
loading.set(false)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function save(): Promise<void> {
|
|
102
|
+
const file = selected()
|
|
103
|
+
if (!file || saving())
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
saving.set(true)
|
|
107
|
+
message.set('')
|
|
108
|
+
error.set('')
|
|
109
|
+
try {
|
|
110
|
+
const updates = fields()
|
|
111
|
+
.filter(field => field.editable)
|
|
112
|
+
.map(field => ({ key: field.key, value: drafts()[field.key] }))
|
|
113
|
+
const response = await fetch('/api/config/update', {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: { 'content-type': 'application/json' },
|
|
116
|
+
body: JSON.stringify({ file, updates }),
|
|
117
|
+
})
|
|
118
|
+
const body = await response.json()
|
|
119
|
+
if (!response.ok || !body.ok) {
|
|
120
|
+
const failed = body.results?.filter((result: { ok: boolean }) => !result.ok)
|
|
121
|
+
throw new Error(failed?.map((result: { error?: string }) => result.error).filter(Boolean).join(', ') || body.error || 'Unable to save configuration')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
message.set(`Saved config/${file}.ts`)
|
|
125
|
+
await selectFile(file)
|
|
126
|
+
message.set(`Saved config/${file}.ts`)
|
|
127
|
+
}
|
|
128
|
+
catch (cause) {
|
|
129
|
+
error.set(cause instanceof Error ? cause.message : 'Unable to save configuration')
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
saving.set(false)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
void loadFiles()
|
|
137
|
+
</script>
|
|
138
|
+
|
|
139
|
+
<div class="mx-auto pb-12 pt-6 px-6 space-y-6 max-w-7xl">
|
|
140
|
+
<PageHeader
|
|
141
|
+
title="Settings"
|
|
142
|
+
description="Review and edit project configuration without losing comments or environment bindings"
|
|
143
|
+
/>
|
|
144
|
+
|
|
145
|
+
<div class="grid gap-6 lg:grid-cols-[17rem_minmax(0,1fr)]">
|
|
146
|
+
<Card variant="default" padding="none">
|
|
147
|
+
<div class="p-4 border-b border-neutral-200/60 dark:border-white/5">
|
|
148
|
+
<label for="config-search" class="sr-only">Search configuration</label>
|
|
149
|
+
<div class="relative">
|
|
150
|
+
<span class="absolute left-3 top-1/2 h-4 w-4 text-neutral-400 pointer-events-none -translate-y-1/2 i-hugeicons-search-01"></span>
|
|
151
|
+
<input
|
|
152
|
+
id="config-search"
|
|
153
|
+
type="search"
|
|
154
|
+
placeholder="Search config"
|
|
155
|
+
:value="query()"
|
|
156
|
+
@input="query.set($event.target.value)"
|
|
157
|
+
class="block pl-9 pr-3 py-2 w-full text-neutral-900 text-sm dark:text-white placeholder:text-neutral-400 bg-white dark:bg-white/5 border border-black/10 rounded-lg dark:border-white/15 focus:border-blue-500/50 focus:outline-none focus:ring-2 focus:ring-blue-500/40"
|
|
158
|
+
>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<nav class="overflow-y-auto p-2 max-h-[68vh]" aria-label="Configuration files">
|
|
163
|
+
<template :for="file in filteredFiles">
|
|
164
|
+
<button
|
|
165
|
+
type="button"
|
|
166
|
+
:class="selected() === file.name ? 'flex items-center gap-3 px-3 py-2.5 w-full text-left text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-500/10 rounded-lg' : 'flex items-center gap-3 px-3 py-2.5 w-full text-left text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-white/5 rounded-lg'"
|
|
167
|
+
@click="selectFile(file.name)"
|
|
168
|
+
>
|
|
169
|
+
<span class="flex-none h-4 w-4 i-hugeicons-settings-02"></span>
|
|
170
|
+
<span class="flex-1 font-medium text-sm truncate">{{ file.title }}</span>
|
|
171
|
+
<span class="text-[10px] text-neutral-400">{{ file.size < 1024 ? file.size + ' B' : Math.round(file.size / 1024) + ' KB' }}</span>
|
|
172
|
+
</button>
|
|
173
|
+
</template>
|
|
174
|
+
</nav>
|
|
175
|
+
</Card>
|
|
176
|
+
|
|
177
|
+
<Card variant="default" padding="none">
|
|
178
|
+
@if(loading())
|
|
179
|
+
<div class="flex items-center justify-center min-h-80 text-neutral-500 dark:text-neutral-400">
|
|
180
|
+
<span class="mr-2 h-5 w-5 animate-spin i-hugeicons-loading-03"></span>
|
|
181
|
+
Loading configuration
|
|
182
|
+
</div>
|
|
183
|
+
@elseif(error() && !selectedFile())
|
|
184
|
+
<div class="p-8 text-center">
|
|
185
|
+
<span class="inline-block h-8 w-8 text-red-500 i-hugeicons-alert-02"></span>
|
|
186
|
+
<p class="mt-3 font-medium text-neutral-900 dark:text-white">Configuration unavailable</p>
|
|
187
|
+
<p class="mt-1 text-red-600 text-sm dark:text-red-400">{{ error() }}</p>
|
|
188
|
+
</div>
|
|
189
|
+
@elseif(selectedFile())
|
|
190
|
+
<div class="flex flex-wrap gap-4 items-center justify-between px-6 py-5 border-b border-neutral-200/60 dark:border-white/5">
|
|
191
|
+
<div>
|
|
192
|
+
<h2 class="font-semibold text-lg text-neutral-900 dark:text-white">{{ selectedFile().title }}</h2>
|
|
193
|
+
<p class="mt-1 font-mono text-neutral-500 text-xs dark:text-neutral-400">config/{{ selectedFile().name }}.ts</p>
|
|
194
|
+
</div>
|
|
195
|
+
<div class="flex gap-2 items-center">
|
|
196
|
+
<Badge variant="default" size="sm">{{ editableCount() }} editable</Badge>
|
|
197
|
+
<Button variant="primary" size="sm" :disabled="saving() || editableCount() === 0" @click="save()">
|
|
198
|
+
{{ saving() ? 'Saving...' : 'Save changes' }}
|
|
199
|
+
</Button>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
@if(message())
|
|
204
|
+
<div class="mt-5 mx-6 px-4 py-3 text-green-700 text-sm dark:text-green-300 bg-green-50 dark:bg-green-500/10 border border-green-200 rounded-lg dark:border-green-500/20">
|
|
205
|
+
{{ message() }}
|
|
206
|
+
</div>
|
|
207
|
+
@endif
|
|
208
|
+
@if(error())
|
|
209
|
+
<div class="mt-5 mx-6 px-4 py-3 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-500/10 border border-red-200 rounded-lg dark:border-red-500/20">
|
|
210
|
+
{{ error() }}
|
|
211
|
+
</div>
|
|
212
|
+
@endif
|
|
213
|
+
|
|
214
|
+
<form class="divide-neutral-200/60 divide-y dark:divide-white/5" @submit.prevent="save()">
|
|
215
|
+
<template :for="field in fields">
|
|
216
|
+
<div class="grid gap-3 sm:grid-cols-[minmax(10rem,0.8fr)_minmax(0,1.2fr)] sm:items-start px-6 py-5">
|
|
217
|
+
<div>
|
|
218
|
+
<label :for="'config-' + field.key" class="font-medium text-neutral-900 text-sm dark:text-white">{{ field.key }}</label>
|
|
219
|
+
<p class="mt-1 text-neutral-500 text-xs dark:text-neutral-400">{{ field.type }}</p>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
<div>
|
|
223
|
+
@if(field.type === 'boolean' && field.editable)
|
|
224
|
+
<label class="inline-flex gap-3 items-center cursor-pointer">
|
|
225
|
+
<input
|
|
226
|
+
:id="'config-' + field.key"
|
|
227
|
+
type="checkbox"
|
|
228
|
+
:checked="drafts()[field.key] === true"
|
|
229
|
+
@change="setDraft(field.key, $event.target.checked)"
|
|
230
|
+
class="h-4 w-4 text-blue-600 border-neutral-300 rounded focus:ring-blue-500"
|
|
231
|
+
>
|
|
232
|
+
<span class="text-neutral-700 text-sm dark:text-neutral-300">{{ drafts()[field.key] ? 'Enabled' : 'Disabled' }}</span>
|
|
233
|
+
</label>
|
|
234
|
+
@elseif(field.editable)
|
|
235
|
+
<input
|
|
236
|
+
:id="'config-' + field.key"
|
|
237
|
+
:type="field.type === 'number' ? 'number' : 'text'"
|
|
238
|
+
:value="displayValue(field)"
|
|
239
|
+
@input="setDraft(field.key, field.type === 'number' ? Number($event.target.value) : $event.target.value)"
|
|
240
|
+
class="block px-3 py-2 w-full text-neutral-900 text-sm dark:text-white bg-white dark:bg-white/5 border border-black/10 rounded-lg dark:border-white/15 focus:border-blue-500/50 focus:outline-none focus:ring-2 focus:ring-blue-500/40"
|
|
241
|
+
>
|
|
242
|
+
@else
|
|
243
|
+
<div class="px-3 py-2 break-all font-mono text-neutral-500 text-xs dark:text-neutral-400 bg-neutral-50 dark:bg-white/[0.03] border border-neutral-200 rounded-lg dark:border-white/10">
|
|
244
|
+
{{ displayValue(field) }}
|
|
245
|
+
</div>
|
|
246
|
+
<p class="mt-1.5 text-amber-700 text-xs dark:text-amber-300">{{ field.reason }}</p>
|
|
247
|
+
@endif
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</template>
|
|
251
|
+
</form>
|
|
252
|
+
@endif
|
|
253
|
+
</Card>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
@endsection
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
@extends('layouts/default')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
<script client>
|
|
5
|
+
// State-signal + directive style (matches the rest of the dashboard). The
|
|
6
|
+
// previous version used a Vue-style ref() composable, which stx's template
|
|
7
|
+
// runtime does not unwrap, so every binding on the page rendered empty; it
|
|
8
|
+
// also imported functions/api, whose auth chain runs useStorage() at module
|
|
9
|
+
// load and throws in the client bundle, killing the whole scope. So this
|
|
10
|
+
// talks to /api/settings/mail with a plain fetch, like the blog page.
|
|
11
|
+
const endpoint = '/api/settings/mail'
|
|
12
|
+
|
|
13
|
+
// CSRF header for writes, read from the cookie the GET on load seeds.
|
|
14
|
+
function writeHeaders() {
|
|
15
|
+
const headers = { 'Content-Type': 'application/json' }
|
|
16
|
+
const match = document.cookie.match(/(?:^|;\s*)X-CSRF-Token=([^;]*)/)
|
|
17
|
+
if (match)
|
|
18
|
+
headers['X-CSRF-Token'] = decodeURIComponent(match[1])
|
|
19
|
+
return headers
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const from = state('')
|
|
23
|
+
const to = state('')
|
|
24
|
+
const driver = state('SMTP')
|
|
25
|
+
const host = state('')
|
|
26
|
+
const port = state(587)
|
|
27
|
+
const username = state('')
|
|
28
|
+
const password = state('')
|
|
29
|
+
const isSaving = state(false)
|
|
30
|
+
const error = state('')
|
|
31
|
+
const success = state('')
|
|
32
|
+
|
|
33
|
+
async function load() {
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch(endpoint, { headers: { Accept: 'application/json' } })
|
|
36
|
+
if (!res.ok)
|
|
37
|
+
return
|
|
38
|
+
const json = await res.json()
|
|
39
|
+
const d = json && json.data ? json.data : json
|
|
40
|
+
if (d) {
|
|
41
|
+
from.set(d.from ?? '')
|
|
42
|
+
to.set(d.to ?? '')
|
|
43
|
+
driver.set(d.driver ?? 'SMTP')
|
|
44
|
+
host.set(d.host ?? '')
|
|
45
|
+
port.set(d.port ?? 587)
|
|
46
|
+
username.set(d.username ?? '')
|
|
47
|
+
password.set(d.password ?? '')
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
// Keep the defaults when the settings endpoint isn't available yet.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function save() {
|
|
56
|
+
isSaving.set(true)
|
|
57
|
+
error.set('')
|
|
58
|
+
success.set('')
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(endpoint, {
|
|
61
|
+
method: 'PUT',
|
|
62
|
+
headers: writeHeaders(),
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
from: from(),
|
|
65
|
+
to: to(),
|
|
66
|
+
driver: driver(),
|
|
67
|
+
host: host(),
|
|
68
|
+
port: port(),
|
|
69
|
+
username: username(),
|
|
70
|
+
password: password(),
|
|
71
|
+
}),
|
|
72
|
+
})
|
|
73
|
+
if (!res.ok)
|
|
74
|
+
throw new Error(`Save failed (${res.status})`)
|
|
75
|
+
success.set('Mail settings saved successfully.')
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
error.set(e instanceof Error ? e.message : 'Failed to save mail settings.')
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
isSaving.set(false)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onMount(() => {
|
|
86
|
+
load()
|
|
87
|
+
})
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<!-- Plain container instead of a component wrapper: a component slot severs the
|
|
91
|
+
page reactive scope from the markup, leaving every field binding empty. The
|
|
92
|
+
sibling settings pages avoid the wrapper for the same reason. -->
|
|
93
|
+
<div class="mx-auto pb-12 pt-6 px-6 space-y-6 max-w-7xl">
|
|
94
|
+
<PageHeader title="Email Settings" description="Configure how your application sends mail." />
|
|
95
|
+
|
|
96
|
+
<!-- Settings forms -->
|
|
97
|
+
<div class="divide-white/5 divide-y">
|
|
98
|
+
<div class="grid gap-x-8 gap-y-10 grid-cols-1 md:grid-cols-3 px-4 py-16 sm:px-6 lg:px-8 max-w-7xl">
|
|
99
|
+
<div>
|
|
100
|
+
<h2 class="font-semibold leading-7 text-base text-gray-900 dark:text-neutral-100">
|
|
101
|
+
Main Config
|
|
102
|
+
</h2>
|
|
103
|
+
<p class="mt-1 leading-6 text-gray-600 text-sm">
|
|
104
|
+
Update mail settings.
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<form class="md:col-span-2" @submit.prevent="save()">
|
|
109
|
+
<!-- Success message -->
|
|
110
|
+
<div x-show="success()" class="mb-6 p-4 bg-green-50 dark:bg-green-900/20 rounded-md">
|
|
111
|
+
<p class="text-green-700 text-sm dark:text-green-400">{{ success() }}</p>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<!-- Error message -->
|
|
115
|
+
<div x-show="error()" class="mb-6 p-4 bg-red-50 dark:bg-red-900/20 rounded-md">
|
|
116
|
+
<p class="text-red-700 text-sm dark:text-red-400">{{ error() }}</p>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div class="grid gap-x-6 gap-y-8 grid-cols-1 sm:grid-cols-6 sm:max-w-xl">
|
|
120
|
+
<div class="sm:col-span-full">
|
|
121
|
+
<label
|
|
122
|
+
for="mail-from"
|
|
123
|
+
class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
|
|
124
|
+
>Mail From</label>
|
|
125
|
+
<div class="mt-2">
|
|
126
|
+
<input
|
|
127
|
+
id="mail-from"
|
|
128
|
+
type="email"
|
|
129
|
+
x-model="from"
|
|
130
|
+
placeholder="no-reply@example.com"
|
|
131
|
+
class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
|
|
132
|
+
>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<div class="sm:col-span-full">
|
|
137
|
+
<label
|
|
138
|
+
for="mail-to"
|
|
139
|
+
class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
|
|
140
|
+
>Mail To</label>
|
|
141
|
+
<div class="mt-2">
|
|
142
|
+
<input
|
|
143
|
+
id="mail-to"
|
|
144
|
+
type="email"
|
|
145
|
+
x-model="to"
|
|
146
|
+
placeholder="admin@example.com"
|
|
147
|
+
class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
|
|
148
|
+
>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div class="sm:col-span-full">
|
|
153
|
+
<label
|
|
154
|
+
for="mail-driver"
|
|
155
|
+
class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
|
|
156
|
+
>Driver</label>
|
|
157
|
+
<div class="mt-2">
|
|
158
|
+
<select
|
|
159
|
+
id="mail-driver"
|
|
160
|
+
x-model="driver"
|
|
161
|
+
class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
|
|
162
|
+
>
|
|
163
|
+
<option value="SMTP">SMTP</option>
|
|
164
|
+
<option value="ses">Amazon SES</option>
|
|
165
|
+
<option value="sendgrid">SendGrid</option>
|
|
166
|
+
<option value="mailgun">Mailgun</option>
|
|
167
|
+
<option value="mailtrap">Mailtrap</option>
|
|
168
|
+
</select>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<div class="sm:col-span-3">
|
|
173
|
+
<label
|
|
174
|
+
for="mail-host"
|
|
175
|
+
class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
|
|
176
|
+
>Host</label>
|
|
177
|
+
<div class="mt-2">
|
|
178
|
+
<input
|
|
179
|
+
id="mail-host"
|
|
180
|
+
type="text"
|
|
181
|
+
x-model="host"
|
|
182
|
+
placeholder="smtp.example.com"
|
|
183
|
+
class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
|
|
184
|
+
>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<div class="sm:col-span-3">
|
|
189
|
+
<label
|
|
190
|
+
for="mail-port"
|
|
191
|
+
class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
|
|
192
|
+
>Port</label>
|
|
193
|
+
<div class="mt-2">
|
|
194
|
+
<input
|
|
195
|
+
id="mail-port"
|
|
196
|
+
type="number"
|
|
197
|
+
x-model.number="port"
|
|
198
|
+
placeholder="587"
|
|
199
|
+
class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
|
|
200
|
+
>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<div class="sm:col-span-3">
|
|
205
|
+
<label
|
|
206
|
+
for="mail-username"
|
|
207
|
+
class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
|
|
208
|
+
>Username</label>
|
|
209
|
+
<div class="mt-2">
|
|
210
|
+
<input
|
|
211
|
+
id="mail-username"
|
|
212
|
+
type="text"
|
|
213
|
+
x-model="username"
|
|
214
|
+
placeholder="your-username"
|
|
215
|
+
class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
|
|
216
|
+
>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div class="sm:col-span-3">
|
|
221
|
+
<label
|
|
222
|
+
for="mail-password"
|
|
223
|
+
class="block font-medium leading-6 text-gray-900 text-sm dark:text-neutral-100"
|
|
224
|
+
>Password</label>
|
|
225
|
+
<div class="mt-2">
|
|
226
|
+
<input
|
|
227
|
+
id="mail-password"
|
|
228
|
+
type="password"
|
|
229
|
+
x-model="password"
|
|
230
|
+
autocomplete="off"
|
|
231
|
+
placeholder="••••••••"
|
|
232
|
+
class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
|
|
233
|
+
>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<div class="flex mt-8">
|
|
239
|
+
<button
|
|
240
|
+
type="submit"
|
|
241
|
+
:disabled="isSaving()"
|
|
242
|
+
class="px-3 py-2 font-semibold 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 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
243
|
+
>
|
|
244
|
+
{{ isSaving() ? 'Saving...' : 'Save Settings' }}
|
|
245
|
+
</button>
|
|
246
|
+
</div>
|
|
247
|
+
</form>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
@endsection
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { defineStore, derived, registerStoresClient, state } from '@stacksjs/stx'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dashboard auth/identity store — single source of truth for "who is
|
|
5
|
+
* looking at the dashboard right now and what can they see?".
|
|
6
|
+
*
|
|
7
|
+
* Backs the `useRole()` composable
|
|
8
|
+
* (stacksjs/stacks#1843, plumbing landed for #1844). Fetches from
|
|
9
|
+
* `/api/dashboard/auth/me` on first use; subsequent consumers hit the
|
|
10
|
+
* cached signal.
|
|
11
|
+
*
|
|
12
|
+
* Auth modes the store is designed to handle:
|
|
13
|
+
*
|
|
14
|
+
* 1. **Local dev dashboard, no auth** — `/api/dashboard/auth/me`
|
|
15
|
+
* returns `{ user: null, roles: [], unauthenticated: true }`.
|
|
16
|
+
* We treat that as "dev mode" so dev-only surfaces (CI tracking,
|
|
17
|
+
* runner alerts, query inspector) stay visible without forcing
|
|
18
|
+
* anyone to authenticate against their own dev DB.
|
|
19
|
+
* 2. **Authenticated** — the endpoint returns the real user + their
|
|
20
|
+
* RBAC role names from the `user_roles` pivot. The role-derived
|
|
21
|
+
* flags (`isAdmin`, `isDev`, `isClient`) reflect that membership.
|
|
22
|
+
* 3. **Fetch failed** — network down, route 404, server error.
|
|
23
|
+
* `error` is set, but the role flags still default to "permissive
|
|
24
|
+
* dev" so a broken endpoint doesn't lock the dashboard.
|
|
25
|
+
*/
|
|
26
|
+
export const authStore = defineStore('auth', () => {
|
|
27
|
+
const userId = state<number | null>(null)
|
|
28
|
+
const userName = state<string | null>(null)
|
|
29
|
+
const userEmail = state<string | null>(null)
|
|
30
|
+
const roles = state<string[]>([])
|
|
31
|
+
const unauthenticated = state(true)
|
|
32
|
+
const loading = state(false)
|
|
33
|
+
const error = state<string | null>(null)
|
|
34
|
+
const loaded = state(false)
|
|
35
|
+
|
|
36
|
+
// Permissive-dev defaults: when nobody is signed in (or the endpoint
|
|
37
|
+
// failed), assume the viewer is a dev. The alternative — hiding every
|
|
38
|
+
// gated surface until auth is wired — would make the dashboard look
|
|
39
|
+
// broken on a fresh project.
|
|
40
|
+
const isAdmin = derived(() => {
|
|
41
|
+
if (unauthenticated()) return true
|
|
42
|
+
return roles().includes('admin')
|
|
43
|
+
})
|
|
44
|
+
const isDev = derived(() => {
|
|
45
|
+
if (unauthenticated()) return true
|
|
46
|
+
return roles().includes('admin') || roles().includes('dev')
|
|
47
|
+
})
|
|
48
|
+
const isClient = derived(() => {
|
|
49
|
+
if (unauthenticated()) return false
|
|
50
|
+
return roles().includes('client')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
async function load(): Promise<void> {
|
|
54
|
+
if (loading() || loaded()) return
|
|
55
|
+
loading.set(true)
|
|
56
|
+
error.set(null)
|
|
57
|
+
try {
|
|
58
|
+
const res = await fetch('/api/dashboard/auth/me', { headers: { accept: 'application/json' } })
|
|
59
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
60
|
+
const data = await res.json() as {
|
|
61
|
+
user: { id: number, name: string | null, email: string | null } | null
|
|
62
|
+
roles: string[]
|
|
63
|
+
unauthenticated?: true
|
|
64
|
+
}
|
|
65
|
+
if (data.user) {
|
|
66
|
+
userId.set(data.user.id)
|
|
67
|
+
userName.set(data.user.name)
|
|
68
|
+
userEmail.set(data.user.email)
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
userId.set(null)
|
|
72
|
+
userName.set(null)
|
|
73
|
+
userEmail.set(null)
|
|
74
|
+
}
|
|
75
|
+
roles.set(data.roles ?? [])
|
|
76
|
+
unauthenticated.set(Boolean(data.unauthenticated))
|
|
77
|
+
loaded.set(true)
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
error.set(e instanceof Error ? e.message : String(e))
|
|
81
|
+
// Keep the permissive defaults: don't mark `loaded` so a later
|
|
82
|
+
// explicit `refresh()` can retry.
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
loading.set(false)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function refresh(): Promise<void> {
|
|
90
|
+
loaded.set(false)
|
|
91
|
+
await load()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
userId,
|
|
96
|
+
userName,
|
|
97
|
+
userEmail,
|
|
98
|
+
roles,
|
|
99
|
+
unauthenticated,
|
|
100
|
+
loading,
|
|
101
|
+
error,
|
|
102
|
+
loaded,
|
|
103
|
+
isAdmin,
|
|
104
|
+
isDev,
|
|
105
|
+
isClient,
|
|
106
|
+
load,
|
|
107
|
+
refresh,
|
|
108
|
+
}
|
|
109
|
+
}, {
|
|
110
|
+
persist: {
|
|
111
|
+
storage: 'sessionStorage',
|
|
112
|
+
key: 'stacks-dashboard-auth',
|
|
113
|
+
// Don't persist `loaded` — every fresh tab should re-fetch identity
|
|
114
|
+
// (the user may have been assigned a new role, or signed out, since
|
|
115
|
+
// the last visit). Roles + user info ride along as a soft cache so
|
|
116
|
+
// the dashboard renders against last-known state before the
|
|
117
|
+
// round-trip resolves.
|
|
118
|
+
pick: ['userId', 'userName', 'userEmail', 'roles', 'unauthenticated'],
|
|
119
|
+
},
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
if (typeof window !== 'undefined')
|
|
123
|
+
registerStoresClient({ authStore })
|