@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,283 @@
|
|
|
1
|
+
@extends('layouts/default')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
<div class="py-4 lg:py-8 min-h-screen dark:bg-neutral-800">
|
|
5
|
+
<div class="px-4 sm:px-6 lg:px-8">
|
|
6
|
+
<div class="sm:flex sm:items-center">
|
|
7
|
+
<div class="sm:flex-auto">
|
|
8
|
+
<h1 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Environment Variables</h1>
|
|
9
|
+
<p class="mt-2 text-gray-700 text-sm dark:text-neutral-300">
|
|
10
|
+
This is your project's <code class="text-red-400">.env</code> file. Be careful when editing production values.
|
|
11
|
+
</p>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="sm:flex-none mt-4 space-x-3 sm:ml-16 sm:mt-0">
|
|
14
|
+
<button @click="openAddModal()" 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">Add Variable</button>
|
|
15
|
+
<button :show="hasBackup" @click="openBackupModal()" type="button" class="px-3 py-2 font-semibold text-sm text-white bg-gray-600 hover:bg-gray-500 rounded-md focus-visible:outline focus-visible:outline-2 focus-visible:outline-gray-600 focus-visible:outline-offset-2 shadow-sm">Show Backup</button>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="flow-root mt-8">
|
|
20
|
+
<div class="overflow-x-auto -mx-4 -my-2 sm:-mx-6 lg:-mx-8">
|
|
21
|
+
<div class="inline-block py-2 sm:px-6 lg:px-8 min-w-full align-middle">
|
|
22
|
+
<div class="overflow-hidden sm:rounded-lg">
|
|
23
|
+
<form @submit.prevent="handleSave()" class="relative">
|
|
24
|
+
<CodeEditor :value="envValues" :loading="isLoading" language="env" :lineNumbers="true" :keyboardShortcuts="true" @save="handleSave" @format="formatContent" />
|
|
25
|
+
|
|
26
|
+
<div class="flex items-center mb-4 mt-2 space-x-4 text-gray-500 text-sm dark:text-neutral-400">
|
|
27
|
+
<div class="flex items-center space-x-1">
|
|
28
|
+
<kbd class="px-1.5 py-0.5 font-semibold text-gray-800 text-xs dark:text-neutral-100 bg-gray-100 dark:bg-neutral-700 border border-gray-200 rounded dark:border-neutral-600">{{ modKey() }}</kbd>
|
|
29
|
+
<span>+</span>
|
|
30
|
+
<kbd class="px-1.5 py-0.5 font-semibold text-gray-800 text-xs dark:text-neutral-100 bg-gray-100 dark:bg-neutral-700 border border-gray-200 rounded dark:border-neutral-600">S</kbd>
|
|
31
|
+
<span class="text-xs">save</span>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="flex items-center space-x-1">
|
|
34
|
+
<kbd class="px-1.5 py-0.5 font-semibold text-gray-800 text-xs dark:text-neutral-100 bg-gray-100 dark:bg-neutral-700 border border-gray-200 rounded dark:border-neutral-600">{{ modKey() }}</kbd>
|
|
35
|
+
<span>+</span>
|
|
36
|
+
<kbd class="px-1.5 py-0.5 font-semibold text-gray-800 text-xs dark:text-neutral-100 bg-gray-100 dark:bg-neutral-700 border border-gray-200 rounded dark:border-neutral-600">N</kbd>
|
|
37
|
+
<span class="text-xs">new</span>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="flex items-center space-x-1">
|
|
40
|
+
<kbd class="px-1.5 py-0.5 font-semibold text-gray-800 text-xs dark:text-neutral-100 bg-gray-100 dark:bg-neutral-700 border border-gray-200 rounded dark:border-neutral-600">{{ modKey() }}</kbd>
|
|
41
|
+
<span>+</span>
|
|
42
|
+
<kbd class="px-1.5 py-0.5 font-semibold text-gray-800 text-xs dark:text-neutral-100 bg-gray-100 dark:bg-neutral-700 border border-gray-200 rounded dark:border-neutral-600">⇧</kbd>
|
|
43
|
+
<span>+</span>
|
|
44
|
+
<kbd class="px-1.5 py-0.5 font-semibold text-gray-800 text-xs dark:text-neutral-100 bg-gray-100 dark:bg-neutral-700 border border-gray-200 rounded dark:border-neutral-600">F</kbd>
|
|
45
|
+
<span class="text-xs">format</span>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="flex justify-end mt-4 space-x-3">
|
|
50
|
+
<button type="button" @click="formatContent()" class="px-3 py-2 font-semibold text-gray-900 text-sm dark:text-neutral-100 hover:bg-gray-50 dark:hover:bg-neutral-600 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:outline-none focus:ring-2 focus:ring-blue-600 shadow-sm">Format</button>
|
|
51
|
+
<button type="button" @click="clearContent()" class="px-3 py-2 font-semibold text-gray-900 text-sm dark:text-neutral-100 hover:bg-gray-50 dark:hover:bg-neutral-600 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:outline-none focus:ring-2 focus:ring-blue-600 shadow-sm">Clear</button>
|
|
52
|
+
<button type="submit" :disabled="isLoading" 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">
|
|
53
|
+
<span :show="isLoading" class="flex items-center space-x-2">
|
|
54
|
+
<div class="h-4 w-4 animate-spin i-hugeicons-arrow-path"></div>
|
|
55
|
+
<span>Saving...</span>
|
|
56
|
+
</span>
|
|
57
|
+
<span :show="isIdle">Save Changes</span>
|
|
58
|
+
</button>
|
|
59
|
+
</div>
|
|
60
|
+
</form>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div :show="showAddModal" class="relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
|
68
|
+
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 dark:bg-neutral-900 dark:bg-opacity-75 transition-opacity"></div>
|
|
69
|
+
|
|
70
|
+
<div class="fixed inset-0 overflow-y-auto z-10">
|
|
71
|
+
<div class="flex items-end justify-center sm:items-center p-4 sm:p-0 min-h-full text-center">
|
|
72
|
+
<div class="overflow-hidden relative pb-4 pt-5 px-4 sm:my-8 sm:p-6 sm:max-w-lg sm:w-full text-left bg-white dark:bg-neutral-700 rounded-lg shadow-xl transition-all transform">
|
|
73
|
+
<div>
|
|
74
|
+
<h3 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Add Environment Variable</h3>
|
|
75
|
+
<div class="mt-4 space-y-4">
|
|
76
|
+
<div>
|
|
77
|
+
<label class="block font-medium text-gray-700 text-sm dark:text-neutral-300">Key</label>
|
|
78
|
+
<input x-model="newVarKey" type="text" placeholder="APP_NAME" class="block mt-1 py-1.5 w-full 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" />
|
|
79
|
+
</div>
|
|
80
|
+
<div>
|
|
81
|
+
<label class="block font-medium text-gray-700 text-sm dark:text-neutral-300">Value</label>
|
|
82
|
+
<input x-model="newVarValue" type="text" placeholder="My App" class="block mt-1 py-1.5 w-full 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" />
|
|
83
|
+
</div>
|
|
84
|
+
<div>
|
|
85
|
+
<label class="block font-medium text-gray-700 text-sm dark:text-neutral-300">Description (optional)</label>
|
|
86
|
+
<input x-model="newVarDescription" type="text" placeholder="Application name used in emails and UI" class="block mt-1 py-1.5 w-full 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" />
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="sm:grid sm:gap-3 sm:grid-cols-2 sm:grid-flow-row-dense mt-5 sm:mt-6">
|
|
91
|
+
<button type="button" @click="addVariable()" class="inline-flex justify-center sm:col-start-2 px-3 py-2 w-full 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">Add</button>
|
|
92
|
+
<button type="button" @click="closeAddModal()" class="inline-flex justify-center sm:col-start-1 mt-3 px-3 py-2 sm:mt-0 w-full font-semibold text-gray-900 text-sm dark:text-neutral-100 bg-white dark:bg-neutral-600 hover:bg-gray-50 dark:hover:bg-neutral-500 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 shadow-sm">Cancel</button>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div :show="showBackupModal" class="relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true">
|
|
100
|
+
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 dark:bg-neutral-900 dark:bg-opacity-75 transition-opacity"></div>
|
|
101
|
+
|
|
102
|
+
<div class="fixed inset-0 overflow-y-auto z-10">
|
|
103
|
+
<div class="flex items-end justify-center sm:items-center p-4 sm:p-0 min-h-full text-center">
|
|
104
|
+
<div class="overflow-hidden relative pb-4 pt-5 px-4 sm:my-8 sm:p-6 sm:max-w-lg sm:w-full text-left bg-white dark:bg-neutral-700 rounded-lg shadow-xl transition-all transform">
|
|
105
|
+
<div>
|
|
106
|
+
<h3 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Backup Content</h3>
|
|
107
|
+
<div class="mt-4">
|
|
108
|
+
<textarea rows="10" readonly class="block py-1.5 w-full font-mono text-gray-900 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">{{ backupContent }}</textarea>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="sm:grid sm:gap-3 sm:grid-cols-2 sm:grid-flow-row-dense mt-5 sm:mt-6">
|
|
112
|
+
<button type="button" @click="restoreBackup()" class="inline-flex justify-center sm:col-start-2 px-3 py-2 w-full 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">Restore Backup</button>
|
|
113
|
+
<button type="button" @click="closeBackupModal()" class="inline-flex justify-center sm:col-start-1 mt-3 px-3 py-2 sm:mt-0 w-full font-semibold text-gray-900 text-sm dark:text-neutral-100 bg-white dark:bg-neutral-600 hover:bg-gray-50 dark:hover:bg-neutral-500 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 shadow-sm">Close</button>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<script client>
|
|
122
|
+
const envValues = state('')
|
|
123
|
+
const searchQuery = state('')
|
|
124
|
+
const showAddModal = state(false)
|
|
125
|
+
const showBackupModal = state(false)
|
|
126
|
+
const isLoading = state(false)
|
|
127
|
+
const backupContent = state('')
|
|
128
|
+
|
|
129
|
+
const newVarKey = state('')
|
|
130
|
+
const newVarValue = state('')
|
|
131
|
+
const newVarDescription = state('')
|
|
132
|
+
|
|
133
|
+
const hasBackup = derived(() => backupContent() !== '')
|
|
134
|
+
const isIdle = derived(() => !isLoading())
|
|
135
|
+
|
|
136
|
+
function isMac(): boolean {
|
|
137
|
+
return (typeof window !== 'undefined' && window.navigator?.platform?.toLowerCase().includes('mac')) ?? false
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function modKey(): string {
|
|
141
|
+
return isMac() ? '⌘' : 'Ctrl'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function openAddModal(): void {
|
|
145
|
+
newVarKey.set('')
|
|
146
|
+
newVarValue.set('')
|
|
147
|
+
newVarDescription.set('')
|
|
148
|
+
showAddModal.set(true)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function closeAddModal(): void {
|
|
152
|
+
showAddModal.set(false)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function openBackupModal(): void {
|
|
156
|
+
showBackupModal.set(true)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function closeBackupModal(): void {
|
|
160
|
+
showBackupModal.set(false)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function clearContent(): void {
|
|
164
|
+
envValues.set('')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function formatContent(): void {
|
|
168
|
+
const current = envValues()
|
|
169
|
+
if (!current)
|
|
170
|
+
return
|
|
171
|
+
const formatted = current.split('\n').map((line) => {
|
|
172
|
+
if (!line.trim() || line.trim().startsWith('#'))
|
|
173
|
+
return line
|
|
174
|
+
const parts = line.split('=')
|
|
175
|
+
if (parts.length < 2)
|
|
176
|
+
return line
|
|
177
|
+
const key = parts[0]?.trim() ?? ''
|
|
178
|
+
const value = parts.slice(1).join('=').trim()
|
|
179
|
+
return `${key}=${value}`
|
|
180
|
+
}).join('\n')
|
|
181
|
+
envValues.set(formatted)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function validateEnv(content: string): string[] {
|
|
185
|
+
const lines = content.split('\n')
|
|
186
|
+
const errors: string[] = []
|
|
187
|
+
const keys = new Set<string>()
|
|
188
|
+
|
|
189
|
+
lines.forEach((line, index) => {
|
|
190
|
+
if (line.trim() && !line.startsWith('#')) {
|
|
191
|
+
if (!line.includes('=')) {
|
|
192
|
+
errors.push(`Line ${index + 1}: Missing '=' separator`)
|
|
193
|
+
}
|
|
194
|
+
else if (line.trim().endsWith('=')) {
|
|
195
|
+
errors.push(`Line ${index + 1}: Empty value`)
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const parts = line.split('=')
|
|
199
|
+
const key = parts[0]?.trim() ?? ''
|
|
200
|
+
if (keys.has(key))
|
|
201
|
+
errors.push(`Line ${index + 1}: Duplicate key '${key}'`)
|
|
202
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(key))
|
|
203
|
+
errors.push(`Line ${index + 1}: Invalid key format '${key}'. Use uppercase letters, numbers, and underscores only`)
|
|
204
|
+
keys.add(key)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
return errors
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function handleSave(): Promise<void> {
|
|
213
|
+
isLoading.set(true)
|
|
214
|
+
try {
|
|
215
|
+
const errors = validateEnv(envValues())
|
|
216
|
+
if (errors.length)
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
formatContent()
|
|
220
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
221
|
+
backupContent.set(envValues())
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// swallow — UI handles loading state
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
isLoading.set(false)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function addVariable(): void {
|
|
232
|
+
const key = newVarKey()
|
|
233
|
+
const value = newVarValue()
|
|
234
|
+
if (!key || !value)
|
|
235
|
+
return
|
|
236
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(key))
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
const description = newVarDescription() ? `# ${newVarDescription()}\n` : ''
|
|
240
|
+
const newLine = `${key}=${value}`
|
|
241
|
+
envValues.set(`${envValues().trim()}\n\n${description}${newLine}`)
|
|
242
|
+
closeAddModal()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function restoreBackup(): void {
|
|
246
|
+
const backup = backupContent()
|
|
247
|
+
if (backup) {
|
|
248
|
+
envValues.set(backup)
|
|
249
|
+
closeBackupModal()
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function handleKeyboardShortcuts(e: KeyboardEvent): void {
|
|
254
|
+
const isCmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
|
|
255
|
+
if (!isCmdOrCtrl)
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
if (e.key === 's') {
|
|
259
|
+
e.preventDefault()
|
|
260
|
+
void handleSave()
|
|
261
|
+
}
|
|
262
|
+
else if (e.key === 'n') {
|
|
263
|
+
e.preventDefault()
|
|
264
|
+
openAddModal()
|
|
265
|
+
}
|
|
266
|
+
else if (e.key === 'F' && e.shiftKey) {
|
|
267
|
+
e.preventDefault()
|
|
268
|
+
formatContent()
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
onMount(() => {
|
|
273
|
+
if (typeof window !== 'undefined')
|
|
274
|
+
window.addEventListener('keydown', handleKeyboardShortcuts)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
onDestroy(() => {
|
|
278
|
+
if (typeof window !== 'undefined')
|
|
279
|
+
window.removeEventListener('keydown', handleKeyboardShortcuts)
|
|
280
|
+
})
|
|
281
|
+
</script>
|
|
282
|
+
|
|
283
|
+
@endsection
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
@extends('layouts/default')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
@title('Dashboard - Errors')
|
|
5
|
+
<div class="py-8 min-h-screen dark:bg-neutral-800">
|
|
6
|
+
<div class="px-4 sm:px-6 lg:px-8">
|
|
7
|
+
<div class="sm:flex sm:items-center">
|
|
8
|
+
<div class="sm:flex-auto">
|
|
9
|
+
<h1 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Error Tracking</h1>
|
|
10
|
+
<p class="mt-2 text-gray-700 text-sm dark:text-neutral-300">Last 30 days error tracking and monitoring.</p>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<dl class="grid gap-5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 mt-5">
|
|
15
|
+
<div class="overflow-hidden relative pt-5 px-4 sm:pt-6 sm:px-6 bg-white dark:bg-neutral-800 rounded-lg shadow">
|
|
16
|
+
<dt>
|
|
17
|
+
<div class="absolute p-3 bg-red-500 rounded-md">
|
|
18
|
+
<div class="h-6 w-6 text-white i-hugeicons-alert-02"></div>
|
|
19
|
+
</div>
|
|
20
|
+
<p class="ml-16 font-medium text-gray-500 text-sm truncate">Total Unresolved Errors</p>
|
|
21
|
+
</dt>
|
|
22
|
+
<dd class="flex items-baseline ml-16 pb-6 sm:pb-7">
|
|
23
|
+
<p class="font-semibold text-2xl text-gray-900 dark:text-neutral-100">{{ totalLabel() }}</p>
|
|
24
|
+
<p class="flex items-baseline ml-2 font-semibold text-red-600 text-sm">
|
|
25
|
+
<svg class="flex-shrink-0 self-center h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
26
|
+
<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" />
|
|
27
|
+
</svg>
|
|
28
|
+
<span class="sr-only">Increased by</span>
|
|
29
|
+
24%
|
|
30
|
+
</p>
|
|
31
|
+
</dd>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="overflow-hidden relative pt-5 px-4 sm:pt-6 sm:px-6 bg-white dark:bg-neutral-800 rounded-lg shadow">
|
|
35
|
+
<dt>
|
|
36
|
+
<div class="absolute p-3 bg-green-500 rounded-md">
|
|
37
|
+
<div class="h-6 w-6 text-white i-hugeicons-checkmark-circle-02"></div>
|
|
38
|
+
</div>
|
|
39
|
+
<p class="ml-16 font-medium text-gray-500 text-sm truncate">Resolved Issues</p>
|
|
40
|
+
</dt>
|
|
41
|
+
<dd class="flex items-baseline ml-16 pb-6 sm:pb-7">
|
|
42
|
+
<p class="font-semibold text-2xl text-gray-900 dark:text-neutral-100">{{ resolvedLabel() }}</p>
|
|
43
|
+
<p class="flex items-baseline ml-2 font-semibold text-green-600 text-sm">
|
|
44
|
+
<svg class="flex-shrink-0 self-center h-5 w-5 text-green-500" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
|
45
|
+
<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" />
|
|
46
|
+
</svg>
|
|
47
|
+
<span class="sr-only">Increased by</span>
|
|
48
|
+
12%
|
|
49
|
+
</p>
|
|
50
|
+
</dd>
|
|
51
|
+
</div>
|
|
52
|
+
</dl>
|
|
53
|
+
|
|
54
|
+
<div class="mt-8 border-b border-gray-200 dark:border-neutral-600">
|
|
55
|
+
<div class="flex items-center justify-between">
|
|
56
|
+
<nav class="flex -mb-px space-x-8" aria-label="Tabs">
|
|
57
|
+
<button @click="setActiveTab('active')" class="whitespace-nowrap border-b-2 py-4 px-1 text-sm font-medium {{ activeTab() === 'active' ? 'border-gray-300 text-gray-700 dark:border-neutral-600 dark:text-neutral-300' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-neutral-400 dark:hover:text-gray-300' }}">
|
|
58
|
+
Active Errors
|
|
59
|
+
<span class="ml-3 rounded-full py-0.5 px-2.5 text-xs font-medium {{ activeTab() === 'active' ? 'bg-red-100 text-red-600 dark:bg-red-400/10 dark:text-red-400' : 'bg-gray-100 text-gray-900 dark:bg-neutral-600 dark:text-neutral-300' }}">{{ activeCount() }}</span>
|
|
60
|
+
</button>
|
|
61
|
+
|
|
62
|
+
<button @click="setActiveTab('resolved')" class="whitespace-nowrap border-b-2 py-4 px-1 text-sm font-medium {{ activeTab() === 'resolved' ? 'border-gray-300 text-gray-700 dark:border-neutral-600 dark:text-neutral-300' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-neutral-400 dark:hover:text-gray-300' }}">
|
|
63
|
+
Resolved
|
|
64
|
+
<span class="ml-3 rounded-full py-0.5 px-2.5 text-xs font-medium {{ activeTab() === 'resolved' ? 'bg-blue-100 text-blue-600 dark:bg-blue-400/10 dark:text-blue-400' : 'bg-gray-100 text-gray-900 dark:bg-neutral-600 dark:text-neutral-300' }}">{{ resolvedCount() }}</span>
|
|
65
|
+
</button>
|
|
66
|
+
</nav>
|
|
67
|
+
|
|
68
|
+
<div class="flex items-center">
|
|
69
|
+
<label for="environment-filter" class="mr-2 font-medium text-gray-700 text-sm dark:text-neutral-300">Environment:</label>
|
|
70
|
+
<select id="environment-filter" x-model="selectedEnvironment" class="block pl-3 pr-10 py-1.5 w-32 text-gray-900 sm:leading-6 sm:text-sm dark:text-neutral-100 dark:bg-neutral-700 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600">
|
|
71
|
+
<option value="all">All</option>
|
|
72
|
+
<option value="production">Production</option>
|
|
73
|
+
<option value="staging">Staging</option>
|
|
74
|
+
</select>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="flow-root mt-8">
|
|
80
|
+
<div class="overflow-x-auto -mx-4 -my-2 sm:-mx-6 lg:-mx-8">
|
|
81
|
+
<div class="inline-block py-2 sm:px-6 lg:px-8 min-w-full align-middle">
|
|
82
|
+
<div class="overflow-hidden ring-1 ring-black ring-opacity-5 rounded-lg dark:ring-neutral-700 shadow">
|
|
83
|
+
<table class="min-w-full divide-gray-300 divide-y dark:divide-neutral-600">
|
|
84
|
+
<thead class="bg-gray-50 dark:bg-neutral-700">
|
|
85
|
+
<tr>
|
|
86
|
+
<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">Error</th>
|
|
87
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Type</th>
|
|
88
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Environment</th>
|
|
89
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Location</th>
|
|
90
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-right text-sm dark:text-neutral-100">Last Seen</th>
|
|
91
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-right text-sm dark:text-neutral-100">Count</th>
|
|
92
|
+
<th :show="isResolvedTab" scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Resolved</th>
|
|
93
|
+
<th scope="col" class="relative pl-3 pr-4 py-3.5 sm:pr-6"><span class="sr-only">Actions</span></th>
|
|
94
|
+
</tr>
|
|
95
|
+
</thead>
|
|
96
|
+
<tbody class="bg-white dark:bg-neutral-800 divide-gray-200 divide-y dark:divide-neutral-600">
|
|
97
|
+
<template :for="error in visibleErrors">
|
|
98
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-neutral-700/50">
|
|
99
|
+
<td class="pl-4 pr-3 py-4 sm:pl-6 font-medium text-gray-900 text-sm whitespace-nowrap dark:text-neutral-100">{{ error.message }}</td>
|
|
100
|
+
<td class="px-3 py-4 text-sm whitespace-nowrap">
|
|
101
|
+
<span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium {{ getTypeColor(error.type) }}">{{ error.type }}</span>
|
|
102
|
+
</td>
|
|
103
|
+
<td class="px-3 py-4 text-sm whitespace-nowrap">
|
|
104
|
+
<span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium {{ getEnvironmentColor(error.environment) }}">{{ error.environment }}</span>
|
|
105
|
+
</td>
|
|
106
|
+
<td class="px-3 py-4 font-mono text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">
|
|
107
|
+
<button @click="openInEditor('{{ error.location }}')" class="hover:text-blue-600 hover:underline dark:hover:text-blue-400 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">{{ error.location }}</button>
|
|
108
|
+
</td>
|
|
109
|
+
<td class="px-3 py-4 text-gray-500 text-right text-sm whitespace-nowrap dark:text-neutral-400">{{ error.lastSeen }}</td>
|
|
110
|
+
<td class="px-3 py-4 text-gray-500 text-right text-sm whitespace-nowrap dark:text-neutral-400">{{ error.count }}</td>
|
|
111
|
+
<td :show="isResolvedTab" class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">
|
|
112
|
+
<div class="flex flex-col">
|
|
113
|
+
<span>{{ error.resolvedAt }}</span>
|
|
114
|
+
<span class="text-gray-400 text-xs">by {{ error.resolvedBy }}</span>
|
|
115
|
+
</div>
|
|
116
|
+
</td>
|
|
117
|
+
<td class="relative pl-3 pr-4 py-4 sm:pr-6 font-medium text-right text-sm whitespace-nowrap">
|
|
118
|
+
<div class="inline-flex rounded-md shadow-sm" role="group">
|
|
119
|
+
<Tooltip :show="isActiveTab" text="Mark as resolved" position="top" :dark="isDark" :usePortal="true">
|
|
120
|
+
<button @click="resolveError('{{ error.id }}')" type="button" class="inline-flex relative focus:z-10 items-center px-3 py-2 font-semibold text-gray-600 text-sm dark:text-neutral-400 hover:text-green-600 dark:hover:text-green-400 hover:bg-gray-50 dark:hover:bg-neutral-700 ring-1 ring-gray-300 ring-inset rounded-l-md dark:ring-neutral-600" aria-label="Mark error as resolved">
|
|
121
|
+
<div class="h-4 w-4 i-hugeicons-checkmark-circle-02"></div>
|
|
122
|
+
</button>
|
|
123
|
+
</Tooltip>
|
|
124
|
+
<Tooltip :show="isActiveTab" text="Analyze with AI" position="top" :dark="isDark" :usePortal="true">
|
|
125
|
+
<button @click="analyzeWithAI('{{ error.id }}')" type="button" :disabled="isAiAnalyzing" class="relative -ml-px inline-flex items-center px-3 py-2 text-sm font-semibold ring-1 ring-inset ring-gray-300 hover:bg-gray-50 hover:text-purple-600 focus:z-10 dark:ring-neutral-600 dark:hover:bg-neutral-700 dark:hover:text-purple-400 {{ isAiAnalyzing() ? 'text-gray-400 cursor-not-allowed' : 'text-gray-600 dark:text-neutral-400' }}" aria-label="Analyze error with AI">
|
|
126
|
+
<div class="h-4 w-4 {{ isAiAnalyzing() ? 'i-hugeicons-arrow-path animate-spin' : 'i-hugeicons-sparkles' }}"></div>
|
|
127
|
+
</button>
|
|
128
|
+
</Tooltip>
|
|
129
|
+
<Tooltip text="Share error details" position="top" :dark="isDark" :usePortal="true">
|
|
130
|
+
<button type="button" class="inline-flex relative focus:z-10 items-center -ml-px px-3 py-2 font-semibold text-gray-600 text-sm dark:text-neutral-400 hover:text-blue-600 dark:hover:text-blue-400 hover:bg-gray-50 dark:hover:bg-neutral-700 ring-1 ring-gray-300 ring-inset dark:ring-neutral-600 {{ activeTab() === 'resolved' ? 'rounded-l-md' : '' }}" aria-label="Share error details">
|
|
131
|
+
<div class="h-4 w-4 i-hugeicons-share-01"></div>
|
|
132
|
+
</button>
|
|
133
|
+
</Tooltip>
|
|
134
|
+
<Tooltip text="View error details" position="top" :dark="isDark" :usePortal="true" alignment="right">
|
|
135
|
+
<button type="button" class="inline-flex relative focus:z-10 items-center -ml-px px-3 py-2 font-semibold text-gray-600 text-sm dark:text-neutral-400 hover:text-indigo-600 dark:hover:text-indigo-400 hover:bg-gray-50 dark:hover:bg-neutral-700 ring-1 ring-gray-300 ring-inset rounded-r-md dark:ring-neutral-600" aria-label="View error details">
|
|
136
|
+
<div class="h-4 w-4 i-hugeicons-view"></div>
|
|
137
|
+
</button>
|
|
138
|
+
</Tooltip>
|
|
139
|
+
</div>
|
|
140
|
+
</td>
|
|
141
|
+
</tr>
|
|
142
|
+
</template>
|
|
143
|
+
</tbody>
|
|
144
|
+
</table>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<script client>
|
|
153
|
+
interface ErrorEntry {
|
|
154
|
+
id: string
|
|
155
|
+
message: string
|
|
156
|
+
type: string
|
|
157
|
+
environment: string
|
|
158
|
+
location: string
|
|
159
|
+
lastSeen: string
|
|
160
|
+
count: number
|
|
161
|
+
status: 'active' | 'resolved'
|
|
162
|
+
resolvedAt?: string
|
|
163
|
+
resolvedBy?: string
|
|
164
|
+
aiAnalysis?: string
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const activeTab = state<'active' | 'resolved'>('active')
|
|
168
|
+
const selectedEnvironment = state('all')
|
|
169
|
+
const isAiAnalyzing = state(false)
|
|
170
|
+
const isDark = state(false)
|
|
171
|
+
|
|
172
|
+
const allErrors = state<ErrorEntry[]>([
|
|
173
|
+
{ id: '1', message: "Cannot read property 'length' of undefined", type: 'JavaScript Error', environment: 'production', location: 'main.js:156', lastSeen: '2 minutes ago', count: 36, status: 'active' },
|
|
174
|
+
{ id: '2', message: "Cannot read property 'length' of undefined", type: 'JavaScript Error', environment: 'staging', location: 'main.js:156', lastSeen: '15 minutes ago', count: 22, status: 'active' },
|
|
175
|
+
{ id: '3', message: 'Database connection failed', type: 'Fatal Error', environment: 'production', location: 'UserAction.ts:42', lastSeen: '1 hour ago', count: 15, status: 'active' },
|
|
176
|
+
{ id: '4', message: 'Invalid API response format', type: 'API Error', environment: 'production', location: 'api/handlers.ts:89', lastSeen: '2 days ago', count: 56, status: 'resolved', resolvedAt: '1 day ago', resolvedBy: 'John Doe' },
|
|
177
|
+
])
|
|
178
|
+
|
|
179
|
+
const activeErrors = derived(() => {
|
|
180
|
+
const list = allErrors().filter(e => e.status === 'active')
|
|
181
|
+
const env = selectedEnvironment()
|
|
182
|
+
return env === 'all' ? list : list.filter(e => e.environment === env)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const resolvedErrors = derived(() => {
|
|
186
|
+
const list = allErrors().filter(e => e.status === 'resolved')
|
|
187
|
+
const env = selectedEnvironment()
|
|
188
|
+
return env === 'all' ? list : list.filter(e => e.environment === env)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const visibleErrors = derived(() => activeTab() === 'active' ? activeErrors() : resolvedErrors())
|
|
192
|
+
const activeCount = derived(() => activeErrors().length)
|
|
193
|
+
const resolvedCount = derived(() => resolvedErrors().length)
|
|
194
|
+
const isActiveTab = derived(() => activeTab() === 'active')
|
|
195
|
+
const isResolvedTab = derived(() => activeTab() === 'resolved')
|
|
196
|
+
|
|
197
|
+
function totalLabel(): string { return '2,847' }
|
|
198
|
+
function resolvedLabel(): string { return '1,624' }
|
|
199
|
+
|
|
200
|
+
function setActiveTab(tab: 'active' | 'resolved'): void {
|
|
201
|
+
activeTab.set(tab)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function getTypeColor(type: string): string {
|
|
205
|
+
const colors: Record<string, string> = {
|
|
206
|
+
'TypeScript Error': 'text-purple-600 bg-purple-50 dark:text-purple-400 dark:bg-purple-400/10',
|
|
207
|
+
'JavaScript Error': 'text-yellow-600 bg-yellow-50 dark:text-yellow-400 dark:bg-yellow-400/10',
|
|
208
|
+
'Fatal Error': 'text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-400/10',
|
|
209
|
+
'API Error': 'text-blue-600 bg-blue-50 dark:text-blue-400 dark:bg-blue-400/10',
|
|
210
|
+
}
|
|
211
|
+
return colors[type] || 'text-gray-600 bg-gray-50 dark:text-neutral-400 dark:bg-neutral-400/10'
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function getEnvironmentColor(env: string): string {
|
|
215
|
+
const colors: Record<string, string> = {
|
|
216
|
+
production: 'text-red-600 bg-red-50 dark:text-red-400 dark:bg-red-400/10',
|
|
217
|
+
staging: 'text-yellow-600 bg-yellow-50 dark:text-yellow-400 dark:bg-yellow-400/10',
|
|
218
|
+
development: 'text-green-600 bg-green-50 dark:text-green-400 dark:bg-green-400/10',
|
|
219
|
+
}
|
|
220
|
+
return colors[env] || 'text-gray-600 bg-gray-50 dark:text-neutral-400 dark:bg-neutral-400/10'
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function openInEditor(location: string): void {
|
|
224
|
+
fetch('/api/editor/open', {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
headers: { 'Content-Type': 'application/json' },
|
|
227
|
+
body: JSON.stringify({ location }),
|
|
228
|
+
}).catch(() => {
|
|
229
|
+
// editor may not be available
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function resolveError(errorId: string): void {
|
|
234
|
+
allErrors.set(allErrors().map(e => e.id === errorId
|
|
235
|
+
? { ...e, status: 'resolved' as const, resolvedAt: 'Just now', resolvedBy: 'Current User' }
|
|
236
|
+
: e))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function analyzeWithAI(errorId: string): Promise<void> {
|
|
240
|
+
const error = allErrors().find(e => e.id === errorId)
|
|
241
|
+
if (!error)
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
isAiAnalyzing.set(true)
|
|
245
|
+
try {
|
|
246
|
+
const response = await fetch('/api/ai/analyze-error', {
|
|
247
|
+
method: 'POST',
|
|
248
|
+
headers: { 'Content-Type': 'application/json' },
|
|
249
|
+
body: JSON.stringify({
|
|
250
|
+
errorId,
|
|
251
|
+
message: error.message,
|
|
252
|
+
type: error.type,
|
|
253
|
+
location: error.location,
|
|
254
|
+
}),
|
|
255
|
+
})
|
|
256
|
+
if (!response.ok)
|
|
257
|
+
throw new Error('AI analysis failed')
|
|
258
|
+
const data = await response.json()
|
|
259
|
+
if (data.analysis) {
|
|
260
|
+
allErrors.set(allErrors().map(e => e.id === errorId ? { ...e, aiAnalysis: data.analysis } : e))
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
// eslint-disable-next-line no-console
|
|
265
|
+
console.error('AI analysis error:', err)
|
|
266
|
+
allErrors.set(allErrors().map(e => e.id === errorId ? { ...e, aiAnalysis: 'AI analysis failed. Please try again later.' } : e))
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
isAiAnalyzing.set(false)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
</script>
|
|
273
|
+
|
|
274
|
+
@endsection
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
@extends('layouts/guest')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
<script client>
|
|
5
|
+
// Forgot-password form (stacksjs/stacks#1838) — same broken-const fix as
|
|
6
|
+
// login/register. Original `<script server>` had:
|
|
7
|
+
// 1. `const` reassignments (never worked in strict mode)
|
|
8
|
+
// 2. A missing `)` on the fetch options object (would never parse)
|
|
9
|
+
// 3. `process.env.PORT_API` for a hardcoded `localhost:3008` URL,
|
|
10
|
+
// which won't survive a deploy
|
|
11
|
+
// Now uses signals + a relative `/forgot-password` path, so the form
|
|
12
|
+
// hits whatever origin served the page.
|
|
13
|
+
const isLoading = state(false)
|
|
14
|
+
const error = state('')
|
|
15
|
+
const success = state(false)
|
|
16
|
+
|
|
17
|
+
async function handleForgotPassword(data) {
|
|
18
|
+
isLoading.set(true)
|
|
19
|
+
error.set('')
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetch('/forgot-password', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
'Accept': 'application/json',
|
|
27
|
+
},
|
|
28
|
+
body: JSON.stringify(data),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
const errorData = await response.json()
|
|
33
|
+
throw new Error(errorData.message || 'Failed to send reset link')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
success.set(true)
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
error.set(err.message || 'An error occurred')
|
|
40
|
+
// eslint-disable-next-line no-console
|
|
41
|
+
console.error(err)
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
isLoading.set(false)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
@title('Forgot Password - Stacks Dashboard')
|
|
50
|
+
<ForgotPassword
|
|
51
|
+
@submit="handleForgotPassword"
|
|
52
|
+
:isLoading="isLoading()"
|
|
53
|
+
:error="error()"
|
|
54
|
+
:success="success()"
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
@endsection
|