@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,330 @@
|
|
|
1
|
+
@extends('layouts/default')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
@title('Dashboard - Components')
|
|
5
|
+
<div class="py-4 lg:py-8 min-h-screen dark:bg-neutral-800">
|
|
6
|
+
<div class="mb-8 px-4 sm:px-6 lg:px-8">
|
|
7
|
+
<div>
|
|
8
|
+
<h3 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Last 30 days</h3>
|
|
9
|
+
|
|
10
|
+
<dl class="grid gap-5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 mt-5">
|
|
11
|
+
<div class="overflow-hidden relative pt-5 px-4 sm:pt-6 sm:px-6 bg-white dark:bg-neutral-700 rounded-lg shadow">
|
|
12
|
+
<dt>
|
|
13
|
+
<div class="absolute p-3 bg-blue-500 rounded-md">
|
|
14
|
+
<div class="h-6 w-6 text-white i-hugeicons-cloud-download"></div>
|
|
15
|
+
</div>
|
|
16
|
+
<p class="ml-16 font-medium text-gray-500 text-sm truncate dark:text-neutral-400">Total Downloads</p>
|
|
17
|
+
</dt>
|
|
18
|
+
<dd class="flex items-baseline ml-16 pb-6 sm:pb-7">
|
|
19
|
+
<p class="font-semibold text-2xl text-gray-900 dark:text-neutral-100">71,897</p>
|
|
20
|
+
<p class="flex items-baseline ml-2 font-semibold text-green-600 text-sm dark:text-green-400">
|
|
21
|
+
<svg class="flex-shrink-0 self-center h-5 w-5 text-green-500 dark:text-green-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
22
|
+
<path fill-rule="evenodd" d="M10 17a.75.75 0 01-.75-.75V5.612L5.29 9.77a.75.75 0 01-1.08-1.04l5.25-5.5a.75.75 0 011.08 0l5.25 5.5a.75.75 0 11-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0110 17z" clip-rule="evenodd" />
|
|
23
|
+
</svg>
|
|
24
|
+
<span class="sr-only"> Increased by </span>
|
|
25
|
+
122
|
|
26
|
+
</p>
|
|
27
|
+
</dd>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="overflow-hidden relative pt-5 px-4 sm:pt-6 sm:px-6 bg-white dark:bg-neutral-700 rounded-lg shadow">
|
|
31
|
+
<dt>
|
|
32
|
+
<div class="absolute p-3 bg-blue-500 rounded-md">
|
|
33
|
+
<div class="h-6 w-6 text-white i-hugeicons-clock-01"></div>
|
|
34
|
+
</div>
|
|
35
|
+
<p class="ml-16 font-medium text-gray-500 text-sm truncate dark:text-neutral-400">Avg. Release Time</p>
|
|
36
|
+
</dt>
|
|
37
|
+
<dd class="flex items-baseline ml-16 pb-6 sm:pb-7">
|
|
38
|
+
<p class="font-semibold text-2xl text-gray-900 dark:text-neutral-100">3.65 mins</p>
|
|
39
|
+
<p class="flex items-baseline ml-2 font-semibold text-green-600 text-sm dark:text-green-400">
|
|
40
|
+
<svg class="flex-shrink-0 self-center h-5 w-5 text-green-500 dark:text-green-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
41
|
+
<path fill-rule="evenodd" d="M10 17a.75.75 0 01-.75-.75V5.612L5.29 9.77a.75.75 0 01-1.08-1.04l5.25-5.5a.75.75 0 011.08 0l5.25 5.5a.75.75 0 11-1.08 1.04l-3.96-4.158V16.25A.75.75 0 0110 17z" clip-rule="evenodd" />
|
|
42
|
+
</svg>
|
|
43
|
+
<span class="sr-only"> Increased by </span>
|
|
44
|
+
12 s
|
|
45
|
+
</p>
|
|
46
|
+
</dd>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="overflow-hidden relative pt-5 px-4 sm:pt-6 sm:px-6 bg-white dark:bg-neutral-700 rounded-lg shadow">
|
|
50
|
+
<dt>
|
|
51
|
+
<div class="absolute p-3 bg-blue-500 rounded-md">
|
|
52
|
+
<div class="h-6 w-6 text-white i-hugeicons-rocket-01"></div>
|
|
53
|
+
</div>
|
|
54
|
+
<p class="ml-16 font-medium text-gray-500 text-sm truncate dark:text-neutral-400">Releases</p>
|
|
55
|
+
</dt>
|
|
56
|
+
<dd class="flex items-baseline ml-16 pb-6 sm:pb-7">
|
|
57
|
+
<p class="font-semibold text-2xl text-gray-900 dark:text-neutral-100">420</p>
|
|
58
|
+
<p class="flex items-baseline ml-2 font-semibold text-red-600 text-sm dark:text-red-400">
|
|
59
|
+
<svg class="flex-shrink-0 self-center h-5 w-5 text-red-500 dark:text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
60
|
+
<path fill-rule="evenodd" d="M10 3a.75.75 0 01.75.75v10.638l3.96-4.158a.75.75 0 111.08 1.04l-5.25 5.5a.75.75 0 01-1.08 0l-5.25-5.5a.75.75 0 111.08-1.04l3.96 4.158V3.75A.75.75 0 0110 3z" clip-rule="evenodd" />
|
|
61
|
+
</svg>
|
|
62
|
+
<span class="sr-only"> Decreased by </span>
|
|
63
|
+
3.2%
|
|
64
|
+
</p>
|
|
65
|
+
</dd>
|
|
66
|
+
</div>
|
|
67
|
+
</dl>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="mb-8 px-4 sm:px-6 lg:px-8">
|
|
72
|
+
<div class="bg-white dark:bg-neutral-700 rounded-lg shadow">
|
|
73
|
+
<div class="p-6">
|
|
74
|
+
<div class="flex items-center justify-between mb-6">
|
|
75
|
+
<div>
|
|
76
|
+
<h3 class="font-medium text-base text-gray-900 dark:text-neutral-100">Download Activity</h3>
|
|
77
|
+
<p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Compare package downloads over time</p>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="flex items-center space-x-4">
|
|
80
|
+
<div class="flex items-center space-x-2">
|
|
81
|
+
<input x-model="comparePackage" type="text" placeholder="Compare with package..." @keyup.enter="handleCompare()" class="block py-1.5 w-64 text-gray-900 sm:leading-6 sm:text-sm dark:text-neutral-100 placeholder:text-gray-400 dark:bg-neutral-600 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600 focus:ring-inset shadow-sm" />
|
|
82
|
+
<button @click="handleCompare()" 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">Compare</button>
|
|
83
|
+
</div>
|
|
84
|
+
<select x-model="timeRange" @change="onTimeRangeChange()" class="pl-3 pr-8 py-1.5 text-gray-900 text-sm dark:text-neutral-100 bg-gray-50 dark:bg-neutral-600 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600">
|
|
85
|
+
<option value="7">Last 7 days</option>
|
|
86
|
+
<option value="30">Last 30 days</option>
|
|
87
|
+
<option value="90">Last 90 days</option>
|
|
88
|
+
<option value="365">Last year</option>
|
|
89
|
+
</select>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="h-[400px]">
|
|
93
|
+
<canvas id="components_downloads_chart"></canvas>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div class="pt-12 px-4 sm:px-6 lg:px-8">
|
|
100
|
+
<div class="sm:flex sm:items-center">
|
|
101
|
+
<div class="sm:flex-auto">
|
|
102
|
+
<h1 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Components</h1>
|
|
103
|
+
<p class="mt-2 text-gray-700 text-sm dark:text-neutral-300">A list of all the components in your library.</p>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="sm:flex-none mt-4 sm:ml-16 sm:mt-0">
|
|
107
|
+
<button type="button" 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">Create Component</button>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<div class="flow-root mt-8">
|
|
112
|
+
<div class="overflow-x-auto -mx-4 -my-2 sm:-mx-6 lg:-mx-8">
|
|
113
|
+
<div class="inline-block py-2 sm:px-6 lg:px-8 min-w-full align-middle">
|
|
114
|
+
<div class="overflow-hidden ring-1 ring-black ring-opacity-5 sm:rounded-lg dark:ring-neutral-600 shadow">
|
|
115
|
+
<table class="min-w-full divide-gray-300 divide-y dark:divide-neutral-600">
|
|
116
|
+
<thead class="bg-gray-50 dark:bg-neutral-700">
|
|
117
|
+
<tr>
|
|
118
|
+
<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">Name</th>
|
|
119
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Size</th>
|
|
120
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Path</th>
|
|
121
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-right text-sm dark:text-neutral-100">Updated At</th>
|
|
122
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-right text-sm dark:text-neutral-100">Created At</th>
|
|
123
|
+
<th scope="col" class="relative pl-3 pr-4 py-3.5 sm:pr-6"><span class="sr-only">Edit</span></th>
|
|
124
|
+
</tr>
|
|
125
|
+
</thead>
|
|
126
|
+
|
|
127
|
+
<tbody class="bg-white dark:bg-neutral-700 divide-gray-200 divide-y dark:divide-neutral-600">
|
|
128
|
+
<template :for="component in componentList">
|
|
129
|
+
<tr>
|
|
130
|
+
<td class="pl-4 pr-3 py-4 sm:pl-6 font-medium text-gray-900 text-sm whitespace-nowrap dark:text-neutral-100">{{ component.name }}</td>
|
|
131
|
+
<td class="px-3 py-4 font-mono text-gray-500 text-sm whitespace-nowrap dark:text-neutral-300">{{ component.size }}</td>
|
|
132
|
+
<td class="px-3 py-4 font-mono text-gray-500 text-sm whitespace-nowrap dark:text-neutral-300">{{ component.path }}</td>
|
|
133
|
+
<td class="px-3 py-4 text-gray-500 text-right text-sm whitespace-nowrap dark:text-neutral-300">{{ component.updated_at }}</td>
|
|
134
|
+
<td class="px-3 py-4 text-gray-500 text-right text-sm whitespace-nowrap dark:text-neutral-300">{{ component.created_at }}</td>
|
|
135
|
+
<td class="relative pl-3 pr-4 py-4 sm:pr-6 font-medium text-right text-sm whitespace-nowrap">
|
|
136
|
+
<a href="#" class="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300">Edit<span class="sr-only">, {{ component.name }}, in IDE</span></a>
|
|
137
|
+
</td>
|
|
138
|
+
</tr>
|
|
139
|
+
</template>
|
|
140
|
+
</tbody>
|
|
141
|
+
</table>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<script client>
|
|
150
|
+
// Demo component rows — moved out of the former `<script server>` block
|
|
151
|
+
// (stacksjs/stacks#1838). Replace with a real component scan when the
|
|
152
|
+
// API lands.
|
|
153
|
+
const componentList = [
|
|
154
|
+
{ name: 'Component A', size: '30kb', path: './resources/components/component-a.stx', updated_at: '01-03-2023 08:23:15', created_at: '04-19-2022 09:04:20' },
|
|
155
|
+
{ name: 'Component B', size: '30kb', path: './resources/components/component-b.stx', updated_at: '01-03-2023 08:23:15', created_at: '04-19-2022 09:04:20' },
|
|
156
|
+
{ name: 'Component C', size: '30kb', path: './resources/components/component-c.stx', updated_at: '01-03-2023 08:23:15', created_at: '04-19-2022 09:04:20' },
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
interface DownloadStats {
|
|
160
|
+
labels: string[]
|
|
161
|
+
data: number[]
|
|
162
|
+
compareData: number[]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
interface ChartLike { destroy: () => void, update: () => void, data: { labels: string[], datasets: Array<{ label?: string, data: number[], borderColor?: string, backgroundColor?: string, fill?: boolean }> } }
|
|
166
|
+
type ChartCtor = new (canvas: HTMLElement, config: { type: string, data: unknown, options?: unknown }) => ChartLike
|
|
167
|
+
|
|
168
|
+
function formatDate(daysAgo: number): string {
|
|
169
|
+
const date = new Date()
|
|
170
|
+
date.setDate(date.getDate() - daysAgo)
|
|
171
|
+
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function generateDateLabels(days: number): string[] {
|
|
175
|
+
return Array.from({ length: days }, (_, i) => formatDate(i))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const downloadStats = state<Record<string, DownloadStats>>({
|
|
179
|
+
'7': {
|
|
180
|
+
labels: generateDateLabels(7),
|
|
181
|
+
data: [12500, 15800, 14200, 16800, 19200, 18100, 17200],
|
|
182
|
+
compareData: [9500, 11800, 10200, 12800, 15200, 14100, 13200],
|
|
183
|
+
},
|
|
184
|
+
'30': {
|
|
185
|
+
labels: generateDateLabels(30),
|
|
186
|
+
data: Array.from({ length: 30 }, () => Math.floor(Math.random() * 20000) + 10000),
|
|
187
|
+
compareData: Array.from({ length: 30 }, () => Math.floor(Math.random() * 15000) + 8000),
|
|
188
|
+
},
|
|
189
|
+
'90': {
|
|
190
|
+
labels: generateDateLabels(90),
|
|
191
|
+
data: Array.from({ length: 90 }, () => Math.floor(Math.random() * 20000) + 10000),
|
|
192
|
+
compareData: Array.from({ length: 90 }, () => Math.floor(Math.random() * 15000) + 8000),
|
|
193
|
+
},
|
|
194
|
+
'365': {
|
|
195
|
+
labels: generateDateLabels(365),
|
|
196
|
+
data: Array.from({ length: 365 }, () => Math.floor(Math.random() * 20000) + 10000),
|
|
197
|
+
compareData: Array.from({ length: 365 }, () => Math.floor(Math.random() * 15000) + 8000),
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const comparePackage = state('')
|
|
202
|
+
const timeRange = state('7')
|
|
203
|
+
const isLoading = state(false)
|
|
204
|
+
const fetchError = state('')
|
|
205
|
+
|
|
206
|
+
let chartHandle: ChartLike | null = null
|
|
207
|
+
|
|
208
|
+
function downloadsData() {
|
|
209
|
+
const range = downloadStats()[timeRange()]
|
|
210
|
+
if (!range)
|
|
211
|
+
return { labels: [], datasets: [] }
|
|
212
|
+
const datasets: Array<{ label?: string, data: number[], borderColor?: string, backgroundColor?: string, fill?: boolean }> = [
|
|
213
|
+
{ label: '@your/package', data: range.data, borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true },
|
|
214
|
+
]
|
|
215
|
+
if (comparePackage()) {
|
|
216
|
+
datasets.push({ label: comparePackage(), data: range.compareData, borderColor: 'rgb(234, 179, 8)', backgroundColor: 'rgba(234, 179, 8, 0.1)', fill: true })
|
|
217
|
+
}
|
|
218
|
+
return { labels: range.labels, datasets }
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const chartOptions = {
|
|
222
|
+
responsive: true,
|
|
223
|
+
maintainAspectRatio: false,
|
|
224
|
+
scales: {
|
|
225
|
+
y: {
|
|
226
|
+
beginAtZero: true,
|
|
227
|
+
grid: { color: 'rgba(200, 200, 200, 0.1)' },
|
|
228
|
+
ticks: {
|
|
229
|
+
color: 'rgb(156, 163, 175)',
|
|
230
|
+
font: { family: "'JetBrains Mono', monospace" },
|
|
231
|
+
callback: (value: number | string) => {
|
|
232
|
+
const v = Number(value)
|
|
233
|
+
if (v >= 1000000)
|
|
234
|
+
return `${(v / 1000000).toFixed(1)}M`
|
|
235
|
+
if (v >= 1000)
|
|
236
|
+
return `${(v / 1000).toFixed(1)}k`
|
|
237
|
+
return v
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
x: { grid: { display: false }, ticks: { color: 'rgb(156, 163, 175)', font: { family: "'JetBrains Mono', monospace" } } },
|
|
242
|
+
},
|
|
243
|
+
plugins: {
|
|
244
|
+
legend: { display: true, position: 'top', align: 'end', labels: { color: 'rgb(156, 163, 175)', font: { family: "'JetBrains Mono', monospace" }, boxWidth: 12, padding: 15 } },
|
|
245
|
+
tooltip: {
|
|
246
|
+
callbacks: {
|
|
247
|
+
label: (context: { dataset: { label?: string }, parsed: { y: number | null } }) => {
|
|
248
|
+
let label = context.dataset.label || ''
|
|
249
|
+
if (label)
|
|
250
|
+
label += ': '
|
|
251
|
+
if (context.parsed.y !== null) {
|
|
252
|
+
const v = context.parsed.y
|
|
253
|
+
if (v >= 1000000)
|
|
254
|
+
label += `${(v / 1000000).toFixed(1)}M`
|
|
255
|
+
else if (v >= 1000)
|
|
256
|
+
label += `${(v / 1000).toFixed(1)}k`
|
|
257
|
+
else label += v
|
|
258
|
+
}
|
|
259
|
+
return `${label} downloads`
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
elements: { line: { tension: 0.4 } },
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function fetchDownloadData(days: string): Promise<void> {
|
|
268
|
+
isLoading.set(true)
|
|
269
|
+
fetchError.set('')
|
|
270
|
+
try {
|
|
271
|
+
const baseUrl = `http://localhost:${(globalThis as { process?: { env?: { PORT_API?: string } } }).process?.env?.PORT_API || '3008'}`
|
|
272
|
+
const response = await fetch(`${baseUrl}/components/stats?days=${days}`, {
|
|
273
|
+
headers: { Accept: 'application/json' },
|
|
274
|
+
})
|
|
275
|
+
if (response.ok) {
|
|
276
|
+
const data = await response.json()
|
|
277
|
+
if (data.stats) {
|
|
278
|
+
const next = { ...downloadStats() }
|
|
279
|
+
next[timeRange()] = data.stats
|
|
280
|
+
downloadStats.set(next)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
fetchError.set('Failed to load component data. Using cached data.')
|
|
286
|
+
// eslint-disable-next-line no-console
|
|
287
|
+
console.error('Failed to fetch component data:', error)
|
|
288
|
+
}
|
|
289
|
+
finally {
|
|
290
|
+
isLoading.set(false)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function refreshChart(): void {
|
|
295
|
+
if (!chartHandle)
|
|
296
|
+
return
|
|
297
|
+
const next = downloadsData()
|
|
298
|
+
chartHandle.data.labels = next.labels
|
|
299
|
+
chartHandle.data.datasets = next.datasets
|
|
300
|
+
chartHandle.update()
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function onTimeRangeChange(): Promise<void> {
|
|
304
|
+
await fetchDownloadData(timeRange())
|
|
305
|
+
refreshChart()
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function handleCompare(): Promise<void> {
|
|
309
|
+
if (comparePackage()) {
|
|
310
|
+
await fetchDownloadData(timeRange())
|
|
311
|
+
refreshChart()
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
onMount(async () => {
|
|
316
|
+
await fetchDownloadData(timeRange())
|
|
317
|
+
const { Chart, registerables } = await import('@stacksjs/charts')
|
|
318
|
+
Chart.register(...registerables)
|
|
319
|
+
const Ctor = Chart as unknown as ChartCtor
|
|
320
|
+
const handle = useChart({ Chart: Ctor, id: 'components_downloads_chart', type: 'line', data: downloadsData(), options: chartOptions })
|
|
321
|
+
chartHandle = handle.instance as ChartLike | null
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
onDestroy(() => {
|
|
325
|
+
chartHandle?.destroy()
|
|
326
|
+
chartHandle = null
|
|
327
|
+
})
|
|
328
|
+
</script>
|
|
329
|
+
|
|
330
|
+
@endsection
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard-local composables.
|
|
3
|
+
*
|
|
4
|
+
* Most reactive primitives are auto-imported by stx in `<script client>`
|
|
5
|
+
* blocks (`state`, `derived`, `effect`, `onMount`, `onDestroy`, `navigate`,
|
|
6
|
+
* `goBack`, `useColorMode`, `useDark`, `useFetch`, `useEventListener`, …) —
|
|
7
|
+
* reach for those first. This barrel only ships things stx doesn't.
|
|
8
|
+
*
|
|
9
|
+
* See stacksjs/stacks#1838 for the wider sweep this is part of.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type { ChartCtor, ChartHandle, ChartLike, UseChartOptions } from './useChart'
|
|
13
|
+
export { useChart, useCharts } from './useChart'
|
|
14
|
+
export type { NavigationSnapshot } from './useNavigation'
|
|
15
|
+
export { useNavigation } from './useNavigation'
|
|
16
|
+
export type { RoleSnapshot } from './useRole'
|
|
17
|
+
export { useRole } from './useRole'
|
|
18
|
+
export type { ThemeMode, ThemeSnapshot } from './useTheme'
|
|
19
|
+
export { isDarkTheme, useTheme } from './useTheme'
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useChart — wires `@stacksjs/charts` to a canvas without leaking
|
|
3
|
+
* `document.getElementById` into pages.
|
|
4
|
+
*
|
|
5
|
+
* The dashboard repeats the same shape across feature pages:
|
|
6
|
+
*
|
|
7
|
+
* const ctx = document.getElementById('chart_1')
|
|
8
|
+
* if (ctx) new Chart(ctx, { type, data, options })
|
|
9
|
+
*
|
|
10
|
+
* `useChart` packages that into one call, returns a destroyer so SPA
|
|
11
|
+
* navigation can tear charts down, and tolerates SSR (no-ops on the
|
|
12
|
+
* server). It does *not* import the chart lib eagerly — pages can
|
|
13
|
+
* still `await import('@stacksjs/charts')` to keep the browser bundle
|
|
14
|
+
* lazy, then pass the `Chart` constructor in.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface ChartLike {
|
|
18
|
+
destroy: () => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Constructor signature mirrors Chart.js — `new Chart(canvas, config)`.
|
|
22
|
+
// We don't import `@stacksjs/charts` here to keep this composable
|
|
23
|
+
// dep-free at the type level; pages pass the constructor in.
|
|
24
|
+
export type ChartCtor = new (
|
|
25
|
+
canvas: HTMLCanvasElement | HTMLElement,
|
|
26
|
+
config: { type: string, data: unknown, options?: unknown },
|
|
27
|
+
) => ChartLike
|
|
28
|
+
|
|
29
|
+
export interface UseChartOptions {
|
|
30
|
+
/** Chart constructor (`new Chart(...)`). Pages import lazily and pass it in. */
|
|
31
|
+
Chart: ChartCtor
|
|
32
|
+
/** Element id, e.g. `'chart_1'`. */
|
|
33
|
+
id: string
|
|
34
|
+
type: string
|
|
35
|
+
data: unknown
|
|
36
|
+
options?: unknown
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ChartHandle {
|
|
40
|
+
/** The constructed chart, or `null` if the element was missing or we're SSR. */
|
|
41
|
+
readonly instance: ChartLike | null
|
|
42
|
+
destroy: () => void
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function useChart(opts: UseChartOptions): ChartHandle {
|
|
46
|
+
// SSR-safe — no document means no canvas.
|
|
47
|
+
if (typeof document === 'undefined')
|
|
48
|
+
return { instance: null, destroy: () => {} }
|
|
49
|
+
|
|
50
|
+
const el = document.getElementById(opts.id)
|
|
51
|
+
if (!el) {
|
|
52
|
+
// Canvas not in the DOM yet (or wrong id). Returning a no-op handle
|
|
53
|
+
// is friendlier than throwing — pages stay rendered, devs see the
|
|
54
|
+
// missing chart and can grep the id.
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.warn(`[useChart] no element with id "${opts.id}" — chart not initialised`)
|
|
57
|
+
return { instance: null, destroy: () => {} }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const instance = new opts.Chart(el, { type: opts.type, data: opts.data, options: opts.options })
|
|
61
|
+
return {
|
|
62
|
+
instance,
|
|
63
|
+
destroy: () => instance.destroy(),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Convenience for the common "init N charts at once" pattern. Returns
|
|
69
|
+
* a single destroyer that tears them all down.
|
|
70
|
+
*/
|
|
71
|
+
export function useCharts(specs: UseChartOptions[]): { handles: ChartHandle[], destroyAll: () => void } {
|
|
72
|
+
const handles = specs.map(useChart)
|
|
73
|
+
return {
|
|
74
|
+
handles,
|
|
75
|
+
destroyAll: () => handles.forEach(h => h.destroy()),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useNavigation — wraps `window.location` / `history.pushState` so
|
|
3
|
+
* dashboard pages can read URL state and navigate without leaking
|
|
4
|
+
* vanilla DOM access into `.stx` files (stacksjs/stacks#1838).
|
|
5
|
+
*
|
|
6
|
+
* stx already exposes a top-level `navigate(url)` for SPA navigation, so
|
|
7
|
+
* this composable focuses on the *read* side (current path, segment
|
|
8
|
+
* extraction, query params) and on a couple of write helpers that stx's
|
|
9
|
+
* primitive doesn't cover (hash setters, opening external URLs).
|
|
10
|
+
*
|
|
11
|
+
* All accessors are SSR-safe — they return sensible defaults when
|
|
12
|
+
* `window` is undefined so importing this in a page that hydrates
|
|
13
|
+
* server-side doesn't crash.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface NavigationSnapshot {
|
|
17
|
+
/**
|
|
18
|
+
* Current pathname, e.g. `/kanban/12`. Empty string on the server.
|
|
19
|
+
*/
|
|
20
|
+
path: () => string
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Path segment by index, supporting negative indexes (Pythonic). The
|
|
24
|
+
* pathname is split on `/` with empty segments dropped — so
|
|
25
|
+
* `urlSegment(-1)` on `/kanban/12/cards` returns `cards`, and
|
|
26
|
+
* `urlSegment(0)` returns `kanban`. Returns an empty string when the
|
|
27
|
+
* index is out of bounds or on the server.
|
|
28
|
+
*
|
|
29
|
+
* Replaces the recurring `window.location.pathname.split('/').pop()`
|
|
30
|
+
* pattern from feature pages.
|
|
31
|
+
*/
|
|
32
|
+
urlSegment: (index: number) => string
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Numeric route parameter — convenience wrapper for the very common
|
|
36
|
+
* `/feature/[id]` shape. Parses `urlSegment(-1)` as a positive integer
|
|
37
|
+
* and returns `null` when it isn't one.
|
|
38
|
+
*/
|
|
39
|
+
urlIdParam: () => number | null
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Query string value, e.g. `urlQuery('q')` on `/search?q=foo` → `foo`.
|
|
43
|
+
* Returns `null` when the param is absent or on the server.
|
|
44
|
+
*/
|
|
45
|
+
urlQuery: (name: string) => string | null
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Replace the current URL without a history entry (uses
|
|
49
|
+
* `history.replaceState`). Useful for tab/state changes that
|
|
50
|
+
* shouldn't pollute the back button.
|
|
51
|
+
*/
|
|
52
|
+
replacePath: (next: string) => void
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Open a URL in a new tab. Forwards `noopener,noreferrer` automatically
|
|
56
|
+
* — pages shouldn't have to remember the security flags every time.
|
|
57
|
+
*/
|
|
58
|
+
openExternal: (url: string) => void
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function useNavigation(): NavigationSnapshot {
|
|
62
|
+
return {
|
|
63
|
+
path: () => {
|
|
64
|
+
if (typeof window === 'undefined') return ''
|
|
65
|
+
return window.location.pathname
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
urlSegment: (index: number) => {
|
|
69
|
+
if (typeof window === 'undefined') return ''
|
|
70
|
+
const parts = window.location.pathname.split('/').filter(Boolean)
|
|
71
|
+
if (parts.length === 0) return ''
|
|
72
|
+
const i = index < 0 ? parts.length + index : index
|
|
73
|
+
return parts[i] ?? ''
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
urlIdParam: () => {
|
|
77
|
+
if (typeof window === 'undefined') return null
|
|
78
|
+
const last = window.location.pathname.split('/').filter(Boolean).pop()
|
|
79
|
+
if (!last) return null
|
|
80
|
+
const n = Number(last)
|
|
81
|
+
return Number.isFinite(n) && n > 0 ? n : null
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
urlQuery: (name: string) => {
|
|
85
|
+
if (typeof window === 'undefined') return null
|
|
86
|
+
return new URL(window.location.href).searchParams.get(name)
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
replacePath: (next: string) => {
|
|
90
|
+
if (typeof window === 'undefined') return
|
|
91
|
+
window.history.replaceState({}, '', next)
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
openExternal: (url: string) => {
|
|
95
|
+
if (typeof window === 'undefined') return
|
|
96
|
+
window.open(url, '_blank', 'noopener,noreferrer')
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role-aware visibility hook for dashboard features.
|
|
3
|
+
*
|
|
4
|
+
* Backs gating decisions across the dashboard
|
|
5
|
+
* (stacksjs/stacks#1843, first consumer: CI tracking in #1844).
|
|
6
|
+
*
|
|
7
|
+
* Resolution chain (in order):
|
|
8
|
+
*
|
|
9
|
+
* 1. **Authenticated user with roles** — Reads the role names returned
|
|
10
|
+
* by `/api/dashboard/auth/me` (Action: `Dashboard/Auth/MeAction` →
|
|
11
|
+
* `@stacksjs/auth` → `BqbRbacStore` → `user_roles` pivot). Role
|
|
12
|
+
* membership is the authoritative answer.
|
|
13
|
+
* 2. **Authenticated user with no role records** — The user exists but
|
|
14
|
+
* has nothing in `user_roles`. Treated as `client` (most restrictive)
|
|
15
|
+
* so a brand-new account doesn't accidentally see dev tools before
|
|
16
|
+
* an admin assigns a role.
|
|
17
|
+
* 3. **Unauthenticated** — No bearer token, no session. The dev
|
|
18
|
+
* dashboard on localhost lands here. Defaults to `isDev: true` so
|
|
19
|
+
* the surface stays usable without forcing anyone to log in against
|
|
20
|
+
* a local DB. Production deployments that gate `/api/dashboard/*`
|
|
21
|
+
* behind the `auth` middleware never hit this branch — the endpoint
|
|
22
|
+
* returns a real user/roles and case (1) applies.
|
|
23
|
+
*
|
|
24
|
+
* Consumers should `derived(() => isDev())` so the signal flips
|
|
25
|
+
* reactively if the user's roles change (admin assigns/revokes via the
|
|
26
|
+
* RBAC UI without a page reload).
|
|
27
|
+
*/
|
|
28
|
+
import { useStore } from '@stacksjs/stx'
|
|
29
|
+
|
|
30
|
+
export interface RoleSnapshot {
|
|
31
|
+
/** Admin: every dashboard surface is visible. */
|
|
32
|
+
isAdmin: () => boolean
|
|
33
|
+
/** Dev: infra/dev-mode surfaces (CI tracking, runner alerts, queries). */
|
|
34
|
+
isDev: () => boolean
|
|
35
|
+
/** Client: minimal surface — content / orders / billing only. */
|
|
36
|
+
isClient: () => boolean
|
|
37
|
+
/** Trigger the identity round-trip. Safe to call repeatedly; the store
|
|
38
|
+
* dedupes in-flight + cached responses. */
|
|
39
|
+
load: () => Promise<void>
|
|
40
|
+
/** Force a re-fetch (e.g. after an admin assigns the viewer a new role). */
|
|
41
|
+
refresh: () => Promise<void>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function useRole(): RoleSnapshot {
|
|
45
|
+
const auth = useStore('auth')
|
|
46
|
+
|
|
47
|
+
// Fire the load lazily on first consumer. The store guards against
|
|
48
|
+
// double-fetch so multiple `useRole()` calls in one page are safe.
|
|
49
|
+
auth.load()
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
isAdmin: () => auth.isAdmin(),
|
|
53
|
+
isDev: () => auth.isDev(),
|
|
54
|
+
isClient: () => auth.isClient(),
|
|
55
|
+
load: () => auth.load(),
|
|
56
|
+
refresh: () => auth.refresh(),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useTheme — single owner of the OS dark-mode preference signal
|
|
3
|
+
* (stacksjs/stacks#1838).
|
|
4
|
+
*
|
|
5
|
+
* The original incident: `dashboardStore.isDark` derived its OS
|
|
6
|
+
* preference by calling `window.matchMedia(...)` *inside the store
|
|
7
|
+
* factory* — a store that imported window for its own state was one of
|
|
8
|
+
* the audit's main offenders. This composable lifts that wiring out so
|
|
9
|
+
* the store consumes a clean signal and pages can ask `useTheme()`
|
|
10
|
+
* directly without re-reading `matchMedia` per call site.
|
|
11
|
+
*
|
|
12
|
+
* Behaviour:
|
|
13
|
+
*
|
|
14
|
+
* - On the server, `prefersDark()` resolves to `false`. The dashboard
|
|
15
|
+
* defaults to its light palette during SSR; once the page hydrates
|
|
16
|
+
* the listener attaches and `prefersDark()` flips if the OS theme
|
|
17
|
+
* is dark, so the first repaint reflects reality.
|
|
18
|
+
* - In the browser, the listener is installed once per module load.
|
|
19
|
+
* Subsequent `useTheme()` calls reuse the same signal — no
|
|
20
|
+
* listener storm.
|
|
21
|
+
*
|
|
22
|
+
* Note: stx exposes its own `useDark()` / `useColorMode()` composables
|
|
23
|
+
* via auto-import. Those are the right primitives for "what theme is
|
|
24
|
+
* the user currently *viewing*" (which considers user preference +
|
|
25
|
+
* stored override). `useTheme().prefersDark` is specifically the *OS
|
|
26
|
+
* preference* — that's what `dashboardStore` resolves against when its
|
|
27
|
+
* own `theme` setting is `'system'`.
|
|
28
|
+
*/
|
|
29
|
+
import { state } from '@stacksjs/stx'
|
|
30
|
+
|
|
31
|
+
export type ThemeMode = 'light' | 'dark' | 'system'
|
|
32
|
+
|
|
33
|
+
export interface ThemeSnapshot {
|
|
34
|
+
/**
|
|
35
|
+
* OS-level dark-mode preference. Tracks `prefers-color-scheme: dark`
|
|
36
|
+
* via a single `matchMedia` listener installed lazily on first import.
|
|
37
|
+
*/
|
|
38
|
+
prefersDark: () => boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const prefersDarkSignal = state(false)
|
|
42
|
+
let installed = false
|
|
43
|
+
|
|
44
|
+
function install(): void {
|
|
45
|
+
if (installed) return
|
|
46
|
+
installed = true
|
|
47
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function')
|
|
48
|
+
return
|
|
49
|
+
const mql = window.matchMedia('(prefers-color-scheme: dark)')
|
|
50
|
+
prefersDarkSignal.set(mql.matches)
|
|
51
|
+
mql.addEventListener('change', e => prefersDarkSignal.set(e.matches))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Install at module load. The window guard inside `install()` keeps this
|
|
55
|
+
// safe for SSR — the signal stays at its `false` default until the
|
|
56
|
+
// page hydrates and re-imports the composable in the browser.
|
|
57
|
+
install()
|
|
58
|
+
|
|
59
|
+
export function useTheme(): ThemeSnapshot {
|
|
60
|
+
return {
|
|
61
|
+
prefersDark: () => prefersDarkSignal(),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the *effective* dark state given a user theme mode +
|
|
67
|
+
* the OS preference. Pulled out as a small pure helper so consumers
|
|
68
|
+
* (e.g. `dashboardStore.isDark`) can compose it without re-deriving
|
|
69
|
+
* the same `'system' ? prefersDark : mode === 'dark'` ternary.
|
|
70
|
+
*/
|
|
71
|
+
export function isDarkTheme(mode: ThemeMode, prefersDark: boolean): boolean {
|
|
72
|
+
if (mode === 'system') return prefersDark
|
|
73
|
+
return mode === 'dark'
|
|
74
|
+
}
|