@stacksjs/defaults 0.70.207 → 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,430 @@
|
|
|
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="mb-8">
|
|
7
|
+
<div class="flex items-center">
|
|
8
|
+
<button type="button" class="inline-flex items-center text-gray-500 text-sm dark:text-neutral-400 hover:text-gray-700 dark:hover:text-gray-300" @click="goBack()">
|
|
9
|
+
<div class="mr-1 h-5 w-5 i-hugeicons-arrow-left"></div>
|
|
10
|
+
Back
|
|
11
|
+
</button>
|
|
12
|
+
<h1 class="ml-4 font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Job Details #{{ jobId() }}</h1>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div :show="isLoading" class="flex justify-center py-12">
|
|
17
|
+
<div class="h-8 w-8 border-b-2 border-blue-500 rounded-full animate-spin"></div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div :show="notFound" class="py-12 text-center">
|
|
21
|
+
<div class="mx-auto h-12 w-12 text-gray-400 i-hugeicons-exclamation-circle"></div>
|
|
22
|
+
<h3 class="mt-2 font-semibold text-gray-900 text-sm dark:text-neutral-100">Job not found</h3>
|
|
23
|
+
<p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">The job you're looking for doesn't exist or has been deleted.</p>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div :show="hasJob" class="grid gap-6 grid-cols-1 lg:grid-cols-2">
|
|
27
|
+
<div class="bg-white dark:bg-neutral-700 rounded-lg shadow">
|
|
28
|
+
<div class="px-4 py-5 sm:p-6">
|
|
29
|
+
<h3 class="mb-4 font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Overview</h3>
|
|
30
|
+
|
|
31
|
+
<dl class="grid gap-4 grid-cols-1">
|
|
32
|
+
<div class="grid gap-4 grid-cols-3">
|
|
33
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Name</dt>
|
|
34
|
+
<dd class="col-span-2 font-mono text-gray-900 text-sm dark:text-neutral-100">{{ jobName() }}</dd>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="grid gap-4 grid-cols-3">
|
|
37
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Connection</dt>
|
|
38
|
+
<dd class="col-span-2 capitalize text-gray-900 text-sm dark:text-neutral-100">{{ jobConnection() }}</dd>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="grid gap-4 grid-cols-3">
|
|
41
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Queue</dt>
|
|
42
|
+
<dd class="col-span-2 capitalize text-gray-900 text-sm dark:text-neutral-100">{{ jobQueue() }}</dd>
|
|
43
|
+
</div>
|
|
44
|
+
<div class="grid gap-4 grid-cols-3">
|
|
45
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Status</dt>
|
|
46
|
+
<dd class="col-span-2">
|
|
47
|
+
<span class="{{ getJobStatusColor(jobStatus()) }} inline-flex items-center px-2 py-1 font-medium text-xs ring-1 ring-inset rounded-md">{{ jobStatus() }}</span>
|
|
48
|
+
</dd>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="grid gap-4 grid-cols-3">
|
|
51
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Attempts</dt>
|
|
52
|
+
<dd class="col-span-2 text-gray-900 text-sm dark:text-neutral-100">{{ jobAttempts() }} / {{ jobMaxTries() }}</dd>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="grid gap-4 grid-cols-3">
|
|
55
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Timeout</dt>
|
|
56
|
+
<dd class="col-span-2 font-mono text-gray-900 text-sm dark:text-neutral-100">{{ jobTimeout() }}s</dd>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="grid gap-4 grid-cols-3">
|
|
59
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Started At</dt>
|
|
60
|
+
<dd class="col-span-2 text-gray-900 text-sm dark:text-neutral-100">{{ jobStartedAt() }}</dd>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="grid gap-4 grid-cols-3">
|
|
63
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Finished At</dt>
|
|
64
|
+
<dd class="col-span-2 text-gray-900 text-sm dark:text-neutral-100">{{ jobFinishedAt() }}</dd>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="grid gap-4 grid-cols-3">
|
|
67
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Runtime</dt>
|
|
68
|
+
<dd class="col-span-2 font-mono text-gray-900 text-sm dark:text-neutral-100">{{ jobRuntime() }}</dd>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="grid gap-4 grid-cols-3">
|
|
71
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Worker</dt>
|
|
72
|
+
<dd class="col-span-2 font-mono text-gray-900 text-sm dark:text-neutral-100">{{ jobWorker() }}</dd>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="grid gap-4 grid-cols-3">
|
|
75
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Tags</dt>
|
|
76
|
+
<dd class="col-span-2 text-gray-900 text-sm dark:text-neutral-100">
|
|
77
|
+
<div class="flex flex-wrap gap-2">
|
|
78
|
+
<template :for="tag in jobTags">
|
|
79
|
+
<span class="inline-flex items-center px-2 py-1 font-medium text-gray-600 text-xs dark:text-neutral-300 bg-gray-50 dark:bg-neutral-600 ring-1 ring-gray-500/10 ring-inset rounded-md">{{ tag }}</span>
|
|
80
|
+
</template>
|
|
81
|
+
</div>
|
|
82
|
+
</dd>
|
|
83
|
+
</div>
|
|
84
|
+
</dl>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="space-y-6">
|
|
89
|
+
<div :show="hasChain" class="bg-white dark:bg-neutral-700 rounded-lg shadow">
|
|
90
|
+
<div class="px-4 py-5 sm:p-6">
|
|
91
|
+
<h3 class="mb-6 font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Job Chain</h3>
|
|
92
|
+
<div class="relative">
|
|
93
|
+
<div class="absolute bottom-4 left-4 top-4 w-px bg-gray-200 dark:bg-neutral-600"></div>
|
|
94
|
+
<ul class="relative space-y-6">
|
|
95
|
+
<template :for="(chainJob, index) in jobChain">
|
|
96
|
+
<li class="relative pl-10">
|
|
97
|
+
<div class="flex absolute left-0 top-1/2 items-center justify-center h-8 w-8 bg-white dark:bg-neutral-700 border-2 border-gray-200 rounded-full dark:border-neutral-600 -translate-y-1/2">
|
|
98
|
+
<span class="font-medium text-gray-500 text-sm dark:text-neutral-400">{{ index + 1 }}</span>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="py-2 font-mono text-gray-900 text-sm dark:text-neutral-100">{{ chainJob.replace('App\\Jobs\\', '') }}</div>
|
|
101
|
+
</li>
|
|
102
|
+
</template>
|
|
103
|
+
</ul>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div :show="hasLogs" class="bg-white dark:bg-neutral-700 rounded-lg shadow">
|
|
109
|
+
<div class="px-4 py-5 sm:p-6">
|
|
110
|
+
<h3 class="mb-4 font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Logs</h3>
|
|
111
|
+
<div class="space-y-2">
|
|
112
|
+
<template :for="log in jobLogs">
|
|
113
|
+
<div class="flex gap-2 items-start font-mono text-sm">
|
|
114
|
+
<span class="shrink-0 text-gray-500 dark:text-neutral-400">{{ log.timestamp }}</span>
|
|
115
|
+
<span class="shrink-0 {{ getLogLevelColor(log.level) }}">[{{ log.level }}]</span>
|
|
116
|
+
<span class="text-gray-900 dark:text-neutral-100">{{ log.message }}</span>
|
|
117
|
+
</div>
|
|
118
|
+
</template>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div class="bg-white dark:bg-neutral-700 rounded-lg shadow">
|
|
124
|
+
<div class="px-4 py-5 sm:p-6">
|
|
125
|
+
<h3 class="mb-6 font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Performance</h3>
|
|
126
|
+
|
|
127
|
+
<dl class="grid gap-8 grid-cols-1">
|
|
128
|
+
<div>
|
|
129
|
+
<dt class="flex items-center justify-between mb-2 font-medium text-gray-500 text-sm dark:text-neutral-400">
|
|
130
|
+
<span>Memory Usage</span>
|
|
131
|
+
<span class="font-mono">{{ memoryUsageLabel() }}</span>
|
|
132
|
+
</dt>
|
|
133
|
+
<dd>
|
|
134
|
+
<div class="overflow-hidden h-2.5 bg-gray-100 dark:bg-neutral-600 rounded-full">
|
|
135
|
+
<div class="h-full bg-blue-500 rounded-full duration-300 transition-all" style="width: {{ memoryPercent() }}%"></div>
|
|
136
|
+
</div>
|
|
137
|
+
</dd>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div>
|
|
141
|
+
<dt class="flex items-center justify-between mb-2 font-medium text-gray-500 text-sm dark:text-neutral-400">
|
|
142
|
+
<span>CPU Usage</span>
|
|
143
|
+
<span class="font-mono">{{ cpuUsageLabel() }}</span>
|
|
144
|
+
</dt>
|
|
145
|
+
<dd>
|
|
146
|
+
<div class="overflow-hidden h-2.5 bg-gray-100 dark:bg-neutral-600 rounded-full">
|
|
147
|
+
<div class="h-full bg-green-500 rounded-full duration-300 transition-all" style="width: {{ cpuPercent() }}%"></div>
|
|
148
|
+
</div>
|
|
149
|
+
</dd>
|
|
150
|
+
</div>
|
|
151
|
+
</dl>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div class="lg:col-span-2 bg-white dark:bg-neutral-700 rounded-lg shadow">
|
|
157
|
+
<div class="px-4 py-5 sm:p-6">
|
|
158
|
+
<div class="flex items-center justify-between mb-4">
|
|
159
|
+
<h3 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Payload</h3>
|
|
160
|
+
<button type="button" class="text-blue-600 text-sm dark:text-blue-400 hover:text-blue-500 dark:hover:text-blue-300" @click="togglePayload()">{{ payloadToggleLabel() }} Raw</button>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div :show="showPayload" class="mt-4">
|
|
164
|
+
<pre class="overflow-x-auto p-4 font-mono text-gray-900 text-sm dark:text-neutral-100 bg-gray-50 dark:bg-neutral-600 rounded-lg">{{ payloadJson() }}</pre>
|
|
165
|
+
</div>
|
|
166
|
+
<div :show="hidePayload" class="mt-4">
|
|
167
|
+
<dl class="grid gap-4 grid-cols-1 sm:grid-cols-2">
|
|
168
|
+
<template :for="entry in payloadEntries">
|
|
169
|
+
<div class="grid gap-4 grid-cols-3">
|
|
170
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">{{ entry.key }}</dt>
|
|
171
|
+
<dd class="col-span-2 font-mono text-gray-900 text-sm dark:text-neutral-100">{{ entry.value }}</dd>
|
|
172
|
+
</div>
|
|
173
|
+
</template>
|
|
174
|
+
</dl>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div :show="isFailed" class="lg:col-span-2 bg-white dark:bg-neutral-700 rounded-lg shadow">
|
|
180
|
+
<div class="px-4 py-5 sm:p-6">
|
|
181
|
+
<div class="flex items-center justify-between mb-4">
|
|
182
|
+
<h3 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Error Information</h3>
|
|
183
|
+
<div class="flex items-center space-x-3">
|
|
184
|
+
<button type="button" class="text-blue-600 text-sm dark:text-blue-400 hover:text-blue-500 dark:hover:text-blue-300" @click="toggleTrace()">{{ traceToggleLabel() }} Full Trace</button>
|
|
185
|
+
<button type="button" class="inline-flex items-center 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" @click="handleRetry()">Retry Job</button>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div class="mt-4">
|
|
190
|
+
<h4 class="font-medium text-gray-500 text-sm dark:text-neutral-400">Error Message</h4>
|
|
191
|
+
<p class="mt-1 text-red-600 text-sm dark:text-red-400">{{ exceptionMessage() }}</p>
|
|
192
|
+
|
|
193
|
+
<div class="flex items-center mt-4 text-gray-500 text-sm dark:text-neutral-400">
|
|
194
|
+
<button type="button" class="font-mono hover:text-blue-600 dark:hover:text-blue-400" @click="openExceptionFile()">{{ exceptionFileLine() }}</button>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<h4 class="mt-4 font-medium text-gray-500 text-sm dark:text-neutral-400">Stack Trace</h4>
|
|
198
|
+
<div class="overflow-hidden mt-1 bg-gray-50 dark:bg-neutral-600 rounded-lg">
|
|
199
|
+
<div :show="hideTrace" class="p-4">
|
|
200
|
+
<div class="font-mono text-red-600 text-sm dark:text-red-400 hover:text-blue-600 dark:hover:text-blue-400 cursor-pointer" @click="openTraceLine(0)">{{ firstTraceLine() }}</div>
|
|
201
|
+
</div>
|
|
202
|
+
<div :show="showTrace" class="divide-gray-200 divide-y dark:divide-neutral-500">
|
|
203
|
+
<template :for="(line, index) in exceptionTrace">
|
|
204
|
+
<div class="p-4 font-mono text-red-600 text-sm dark:text-red-400 hover:text-blue-600 dark:hover:text-blue-400 cursor-pointer" @click="openTraceLine({{ index }})">{{ line }}</div>
|
|
205
|
+
</template>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<script client>
|
|
216
|
+
interface JobLogEntry {
|
|
217
|
+
timestamp: string
|
|
218
|
+
level: 'info' | 'warning' | 'error'
|
|
219
|
+
message: string
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
interface JobException {
|
|
223
|
+
message: string
|
|
224
|
+
trace: string[]
|
|
225
|
+
file: string
|
|
226
|
+
line: number
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
interface JobDetails {
|
|
230
|
+
id: string
|
|
231
|
+
name: string
|
|
232
|
+
queue: string
|
|
233
|
+
connection: string
|
|
234
|
+
status: 'queued' | 'processing' | 'failed' | 'completed'
|
|
235
|
+
attempts: number
|
|
236
|
+
max_tries: number
|
|
237
|
+
timeout: number
|
|
238
|
+
runtime?: number
|
|
239
|
+
started_at?: string
|
|
240
|
+
finished_at?: string
|
|
241
|
+
error?: string
|
|
242
|
+
worker: string
|
|
243
|
+
memory_usage: number
|
|
244
|
+
cpu_usage: number
|
|
245
|
+
payload: Record<string, unknown>
|
|
246
|
+
tags: string[]
|
|
247
|
+
chain?: string[]
|
|
248
|
+
exception?: JobException
|
|
249
|
+
logs?: JobLogEntry[]
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const route = useRoute()
|
|
253
|
+
const job = state<JobDetails | null>(null)
|
|
254
|
+
const isLoading = state(true)
|
|
255
|
+
const showPayload = state(false)
|
|
256
|
+
const showTrace = state(false)
|
|
257
|
+
|
|
258
|
+
const hasJob = derived(() => !isLoading() && job() !== null)
|
|
259
|
+
const notFound = derived(() => !isLoading() && job() === null)
|
|
260
|
+
const isFailed = derived(() => job()?.status === 'failed')
|
|
261
|
+
const hasChain = derived(() => (job()?.chain?.length ?? 0) > 0)
|
|
262
|
+
const hasLogs = derived(() => (job()?.logs?.length ?? 0) > 0)
|
|
263
|
+
const hidePayload = derived(() => !showPayload())
|
|
264
|
+
const hideTrace = derived(() => !showTrace())
|
|
265
|
+
|
|
266
|
+
function jobId(): string { return (route.params.id as string) ?? '' }
|
|
267
|
+
function jobName(): string { return job()?.name ?? '' }
|
|
268
|
+
function jobConnection(): string { return job()?.connection ?? '' }
|
|
269
|
+
function jobQueue(): string { return job()?.queue ?? '' }
|
|
270
|
+
function jobStatus(): string { return job()?.status ?? '' }
|
|
271
|
+
function jobAttempts(): number { return job()?.attempts ?? 0 }
|
|
272
|
+
function jobMaxTries(): number { return job()?.max_tries ?? 0 }
|
|
273
|
+
function jobTimeout(): number { return job()?.timeout ?? 0 }
|
|
274
|
+
function jobStartedAt(): string { return job()?.started_at ?? '' }
|
|
275
|
+
function jobFinishedAt(): string { return job()?.finished_at ?? '-' }
|
|
276
|
+
function jobRuntime(): string {
|
|
277
|
+
const r = job()?.runtime
|
|
278
|
+
return r !== undefined ? `${r.toFixed(1)}s` : '-'
|
|
279
|
+
}
|
|
280
|
+
function jobWorker(): string { return job()?.worker ?? '' }
|
|
281
|
+
function jobTags(): string[] { return job()?.tags ?? [] }
|
|
282
|
+
function jobChain(): string[] { return job()?.chain ?? [] }
|
|
283
|
+
function jobLogs(): JobLogEntry[] { return job()?.logs ?? [] }
|
|
284
|
+
function exceptionMessage(): string { return job()?.exception?.message ?? '' }
|
|
285
|
+
function exceptionFileLine(): string {
|
|
286
|
+
const ex = job()?.exception
|
|
287
|
+
return ex ? `${ex.file}:${ex.line}` : ''
|
|
288
|
+
}
|
|
289
|
+
function exceptionTrace(): string[] { return job()?.exception?.trace ?? [] }
|
|
290
|
+
function firstTraceLine(): string { return exceptionTrace()[0] ?? '' }
|
|
291
|
+
|
|
292
|
+
function memoryUsageLabel(): string { return formatBytes(job()?.memory_usage ?? 0) }
|
|
293
|
+
function memoryPercent(): number {
|
|
294
|
+
const mem = job()?.memory_usage ?? 0
|
|
295
|
+
return Math.min(100, (mem / (512 * 1024 * 1024)) * 100)
|
|
296
|
+
}
|
|
297
|
+
function cpuUsageLabel(): string { return `${(job()?.cpu_usage ?? 0).toFixed(1)}%` }
|
|
298
|
+
function cpuPercent(): number { return Math.min(100, job()?.cpu_usage ?? 0) }
|
|
299
|
+
|
|
300
|
+
function payloadJson(): string { return JSON.stringify(job()?.payload ?? {}, null, 2) }
|
|
301
|
+
function payloadEntries(): Array<{ key: string, value: string }> {
|
|
302
|
+
const p = job()?.payload ?? {}
|
|
303
|
+
return Object.entries(p).map(([key, value]) => ({ key, value: String(value) }))
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function payloadToggleLabel(): string { return showPayload() ? 'Hide' : 'Show' }
|
|
307
|
+
function traceToggleLabel(): string { return showTrace() ? 'Hide' : 'Show' }
|
|
308
|
+
|
|
309
|
+
const jobStatusColors: Record<string, string> = {
|
|
310
|
+
queued: 'text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-900/50 ring-blue-600/20',
|
|
311
|
+
processing: 'text-yellow-700 dark:text-yellow-300 bg-yellow-50 dark:bg-yellow-900/50 ring-yellow-600/20',
|
|
312
|
+
completed: 'text-green-700 dark:text-green-300 bg-green-50 dark:bg-green-900/50 ring-green-600/20',
|
|
313
|
+
failed: 'text-red-700 dark:text-red-300 bg-red-50 dark:bg-red-900/50 ring-red-600/20',
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function getJobStatusColor(status: string): string {
|
|
317
|
+
return jobStatusColors[status] || 'text-gray-700 dark:text-neutral-300 bg-gray-50 dark:bg-neutral-900/50 ring-gray-600/20'
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function getLogLevelColor(level?: string): string {
|
|
321
|
+
if (!level)
|
|
322
|
+
return 'text-gray-600 dark:text-neutral-400'
|
|
323
|
+
switch (level) {
|
|
324
|
+
case 'error': return 'text-red-600 dark:text-red-400'
|
|
325
|
+
case 'warning': return 'text-yellow-600 dark:text-yellow-400'
|
|
326
|
+
default: return 'text-gray-600 dark:text-neutral-400'
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function formatBytes(bytes: number): string {
|
|
331
|
+
if (bytes === 0)
|
|
332
|
+
return '0 B'
|
|
333
|
+
const k = 1024
|
|
334
|
+
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
335
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
336
|
+
return `${Number.parseFloat((bytes / k ** i).toFixed(1))} ${sizes[i]}`
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function goBack(): void {
|
|
340
|
+
if (typeof window !== 'undefined' && window.history.length > 1)
|
|
341
|
+
window.history.back()
|
|
342
|
+
else
|
|
343
|
+
navigate('/jobs')
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function togglePayload(): void {
|
|
347
|
+
showPayload.update(v => !v)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function toggleTrace(): void {
|
|
351
|
+
showTrace.update(v => !v)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function handleRetry(): void {
|
|
355
|
+
// TODO: implement retry via queue API
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function openTraceLine(index: number): void {
|
|
359
|
+
const line = exceptionTrace()[index]
|
|
360
|
+
if (line)
|
|
361
|
+
openFile(line)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function openExceptionFile(): void {
|
|
365
|
+
const ex = job()?.exception
|
|
366
|
+
if (ex)
|
|
367
|
+
openFile(ex.file)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function openFile(file: string): void {
|
|
371
|
+
if (!file)
|
|
372
|
+
return
|
|
373
|
+
const match = file.match(/\(([^:]+):(\d+)/)
|
|
374
|
+
void (match ? match[1] : file)
|
|
375
|
+
// TODO: implement editor integration via API
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
onMount(async () => {
|
|
379
|
+
isLoading.set(true)
|
|
380
|
+
try {
|
|
381
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
382
|
+
|
|
383
|
+
job.set({
|
|
384
|
+
id: route.params.id as string,
|
|
385
|
+
name: 'ProcessPayment',
|
|
386
|
+
queue: 'high',
|
|
387
|
+
connection: 'redis',
|
|
388
|
+
status: 'failed',
|
|
389
|
+
attempts: 3,
|
|
390
|
+
max_tries: 3,
|
|
391
|
+
timeout: 60,
|
|
392
|
+
runtime: 5.2,
|
|
393
|
+
started_at: '2024-03-14 10:15:23',
|
|
394
|
+
finished_at: '2024-03-14 10:15:28',
|
|
395
|
+
error: 'Connection timeout at ProcessPayment.handle (/app/jobs/ProcessPayment.ts:25:7)',
|
|
396
|
+
worker: 'worker-1',
|
|
397
|
+
memory_usage: 128.5 * 1024 * 1024,
|
|
398
|
+
cpu_usage: 23.4,
|
|
399
|
+
tags: ['payment', 'order:12345', 'user:1234'],
|
|
400
|
+
chain: ['App\\Jobs\\ProcessPayment', 'App\\Jobs\\SendPaymentConfirmation', 'App\\Jobs\\UpdateOrderStatus'],
|
|
401
|
+
payload: { order_id: '12345', amount: 99.99, currency: 'USD' },
|
|
402
|
+
exception: {
|
|
403
|
+
message: 'Connection timeout',
|
|
404
|
+
file: '/app/jobs/ProcessPayment.ts',
|
|
405
|
+
line: 25,
|
|
406
|
+
trace: [
|
|
407
|
+
'#0 /app/jobs/ProcessPayment.ts(25): ProcessPayment->handle()',
|
|
408
|
+
'#1 /vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(59): ProcessPayment->handle()',
|
|
409
|
+
'#2 /vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php(88): CallQueuedHandler->call()',
|
|
410
|
+
],
|
|
411
|
+
},
|
|
412
|
+
logs: [
|
|
413
|
+
{ timestamp: '2024-03-14 10:15:23', level: 'info', message: 'Starting payment processing' },
|
|
414
|
+
{ timestamp: '2024-03-14 10:15:24', level: 'info', message: 'Validating payment details' },
|
|
415
|
+
{ timestamp: '2024-03-14 10:15:25', level: 'warning', message: 'Slow response from payment gateway' },
|
|
416
|
+
{ timestamp: '2024-03-14 10:15:28', level: 'error', message: 'Connection timeout while processing payment' },
|
|
417
|
+
],
|
|
418
|
+
})
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
// eslint-disable-next-line no-console
|
|
422
|
+
console.error('Failed to load job details:', error)
|
|
423
|
+
}
|
|
424
|
+
finally {
|
|
425
|
+
isLoading.set(false)
|
|
426
|
+
}
|
|
427
|
+
})
|
|
428
|
+
</script>
|
|
429
|
+
|
|
430
|
+
@endsection
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
@extends('layouts/default')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
@title('Dashboard - Jobs History')
|
|
5
|
+
<div class="py-4 lg:py-8 min-h-screen dark:bg-neutral-800">
|
|
6
|
+
<div class="px-4 sm:px-6 lg:px-8">
|
|
7
|
+
<div class="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">Jobs History</h1>
|
|
10
|
+
<p class="mt-2 text-gray-700 text-sm dark:text-neutral-300">A complete history of all jobs processed by the system</p>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="flex flex-col sm:flex-row sm:items-center mt-4 space-y-4 sm:space-x-4 sm:space-y-0">
|
|
15
|
+
<div class="flex-1 min-w-0">
|
|
16
|
+
<label for="search" class="sr-only">Search jobs</label>
|
|
17
|
+
<div class="relative rounded-md shadow-sm">
|
|
18
|
+
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
|
|
19
|
+
<div class="h-5 w-5 text-gray-400 i-hugeicons-search-01"></div>
|
|
20
|
+
</div>
|
|
21
|
+
<input x-model="searchQuery" type="search" name="search" id="search" placeholder="Search jobs..." class="block pl-10 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" />
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="sm:flex-shrink-0">
|
|
26
|
+
<select x-model="selectedQueue" class="pl-3 pr-8 py-1.5 h-9 w-full text-gray-900 sm:leading-6 sm:text-sm dark:text-neutral-100 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">
|
|
27
|
+
<option value="all">All Queue</option>
|
|
28
|
+
<option value="default">Default Queue</option>
|
|
29
|
+
<option value="high">High Queue</option>
|
|
30
|
+
<option value="low">Low Queue</option>
|
|
31
|
+
</select>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="sm:flex-shrink-0">
|
|
35
|
+
<select x-model="selectedStatus" class="pl-3 pr-8 py-1.5 h-9 w-full text-gray-900 sm:leading-6 sm:text-sm dark:text-neutral-100 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">
|
|
36
|
+
<option value="all">All</option>
|
|
37
|
+
<option value="queued">Queued</option>
|
|
38
|
+
<option value="processing">Processing</option>
|
|
39
|
+
<option value="completed">Completed</option>
|
|
40
|
+
<option value="failed">Failed</option>
|
|
41
|
+
</select>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="flow-root mt-8">
|
|
46
|
+
<div class="overflow-x-auto -mx-4 -my-2 sm:-mx-6 lg:-mx-8">
|
|
47
|
+
<div class="inline-block py-2 sm:px-6 lg:px-8 min-w-full align-middle">
|
|
48
|
+
<div class="overflow-hidden ring-1 ring-black ring-opacity-5 rounded-lg dark:ring-opacity-20 shadow">
|
|
49
|
+
<table class="min-w-full divide-gray-300 divide-y dark:divide-neutral-600">
|
|
50
|
+
<thead class="bg-gray-50 dark:bg-neutral-600">
|
|
51
|
+
<tr>
|
|
52
|
+
<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">Job</th>
|
|
53
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Queue</th>
|
|
54
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Status</th>
|
|
55
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Attempts</th>
|
|
56
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-neutral-100">Runtime</th>
|
|
57
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-right text-sm dark:text-neutral-100">Started</th>
|
|
58
|
+
<th scope="col" class="relative pl-3 pr-4 py-3.5 sm:pr-6"><span class="sr-only">Actions</span></th>
|
|
59
|
+
</tr>
|
|
60
|
+
</thead>
|
|
61
|
+
<tbody class="bg-white dark:bg-neutral-700 divide-gray-200 divide-y dark:divide-neutral-600">
|
|
62
|
+
<tr :show="isLoading" class="animate-pulse">
|
|
63
|
+
<td colspan="7" class="px-3 py-4 text-gray-500 text-sm dark:text-neutral-400">Loading jobs...</td>
|
|
64
|
+
</tr>
|
|
65
|
+
<tr :show="hasNoJobs" class="hover:bg-gray-50 dark:hover:bg-neutral-600/50">
|
|
66
|
+
<td colspan="7" class="px-3 py-4 text-center text-gray-500 text-sm dark:text-neutral-400">No jobs found matching your criteria</td>
|
|
67
|
+
</tr>
|
|
68
|
+
<template :for="job in paginatedJobs">
|
|
69
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-neutral-600/50">
|
|
70
|
+
<td class="pl-4 pr-3 py-4 sm:pl-6 text-sm whitespace-nowrap">
|
|
71
|
+
<div class="font-medium text-gray-900 dark:text-neutral-100">{{ job.name }}</div>
|
|
72
|
+
<div class="font-mono text-gray-500 text-xs dark:text-neutral-400">{{ job.id }}</div>
|
|
73
|
+
</td>
|
|
74
|
+
<td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ job.queue }}</td>
|
|
75
|
+
<td class="px-3 py-4 text-sm whitespace-nowrap">
|
|
76
|
+
<span class="{{ getJobStatusColor(job.status) }} inline-flex items-center px-2 py-1 font-medium text-xs ring-1 ring-inset rounded-md">{{ job.status }}</span>
|
|
77
|
+
</td>
|
|
78
|
+
<td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ job.attempts }}</td>
|
|
79
|
+
<td class="px-3 py-4 font-mono text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ job.runtime ? `${job.runtime.toFixed(1)}s` : '-' }}</td>
|
|
80
|
+
<td class="px-3 py-4 text-gray-500 text-right text-sm whitespace-nowrap dark:text-neutral-400">{{ job.started_at }}</td>
|
|
81
|
+
<td class="relative pl-3 pr-4 py-4 sm:pr-6 font-medium text-right text-sm whitespace-nowrap">
|
|
82
|
+
<div class="flex items-center justify-end space-x-3">
|
|
83
|
+
<StxLink to="/jobs/{{ job.id }}" class="text-blue-600 hover:text-blue-900 dark:hover:text-blue-400">View</StxLink>
|
|
84
|
+
<button :show="{{ job.status === 'failed' ? 'true' : 'false' }}" type="button" class="text-blue-600 hover:text-blue-900 dark:hover:text-blue-400" @click="handleRetry('{{ job.id }}')">Retry</button>
|
|
85
|
+
</div>
|
|
86
|
+
</td>
|
|
87
|
+
</tr>
|
|
88
|
+
</template>
|
|
89
|
+
</tbody>
|
|
90
|
+
</table>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="flex items-center justify-between mt-4">
|
|
97
|
+
<div class="flex items-center">
|
|
98
|
+
<p class="text-gray-700 text-sm dark:text-neutral-300">
|
|
99
|
+
Showing <span class="font-medium">{{ rangeStart() }}</span> to <span class="font-medium">{{ rangeEnd() }}</span> of <span class="font-medium">{{ totalFiltered() }}</span> results
|
|
100
|
+
</p>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="flex items-center space-x-2">
|
|
103
|
+
<button type="button" class="inline-flex relative focus:z-10 items-center px-3 py-2 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 disabled:opacity-50" :disabled="isFirstPage" @click="previousPage()">Previous</button>
|
|
104
|
+
<button type="button" class="inline-flex relative focus:z-10 items-center px-3 py-2 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 disabled:opacity-50" :disabled="isLastPage" @click="nextPage()">Next</button>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<script client>
|
|
111
|
+
interface JobHistoryEntry {
|
|
112
|
+
id: string
|
|
113
|
+
name: string
|
|
114
|
+
queue: string
|
|
115
|
+
status: 'queued' | 'processing' | 'failed' | 'completed'
|
|
116
|
+
attempts: number
|
|
117
|
+
runtime?: number
|
|
118
|
+
started_at?: string
|
|
119
|
+
finished_at?: string
|
|
120
|
+
error?: string
|
|
121
|
+
payload: unknown
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const jobs = state<JobHistoryEntry[]>([])
|
|
125
|
+
const isLoading = state(true)
|
|
126
|
+
const currentPage = state(1)
|
|
127
|
+
const perPage = state(25)
|
|
128
|
+
const selectedQueue = state('all')
|
|
129
|
+
const selectedStatus = state('all')
|
|
130
|
+
const searchQuery = state('')
|
|
131
|
+
|
|
132
|
+
const jobStatusColors: Record<string, string> = {
|
|
133
|
+
queued: 'text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-900/50 ring-blue-600/20',
|
|
134
|
+
processing: 'text-yellow-700 dark:text-yellow-300 bg-yellow-50 dark:bg-yellow-900/50 ring-yellow-600/20',
|
|
135
|
+
completed: 'text-green-700 dark:text-green-300 bg-green-50 dark:bg-green-900/50 ring-green-600/20',
|
|
136
|
+
failed: 'text-red-700 dark:text-red-300 bg-red-50 dark:bg-red-900/50 ring-red-600/20',
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getJobStatusColor(status: string): string {
|
|
140
|
+
return jobStatusColors[status] || 'text-gray-700 dark:text-neutral-300 bg-gray-50 dark:bg-neutral-900/50 ring-gray-600/20'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const filteredJobs = derived(() => {
|
|
144
|
+
const list = jobs()
|
|
145
|
+
const q = searchQuery().toLowerCase()
|
|
146
|
+
const sq = selectedQueue()
|
|
147
|
+
const ss = selectedStatus()
|
|
148
|
+
return list.filter((job) => {
|
|
149
|
+
if (sq !== 'all' && job.queue !== sq)
|
|
150
|
+
return false
|
|
151
|
+
if (ss !== 'all' && job.status !== ss)
|
|
152
|
+
return false
|
|
153
|
+
if (q && !job.name.toLowerCase().includes(q))
|
|
154
|
+
return false
|
|
155
|
+
return true
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const paginatedJobs = derived(() => {
|
|
160
|
+
const start = (currentPage() - 1) * perPage()
|
|
161
|
+
return filteredJobs().slice(start, start + perPage())
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const totalPages = derived(() => Math.max(1, Math.ceil(filteredJobs().length / perPage())))
|
|
165
|
+
const totalFiltered = derived(() => filteredJobs().length)
|
|
166
|
+
const rangeStart = derived(() => totalFiltered() === 0 ? 0 : (currentPage() - 1) * perPage() + 1)
|
|
167
|
+
const rangeEnd = derived(() => Math.min(currentPage() * perPage(), totalFiltered()))
|
|
168
|
+
const hasNoJobs = derived(() => !isLoading() && paginatedJobs().length === 0)
|
|
169
|
+
const isFirstPage = derived(() => currentPage() === 1)
|
|
170
|
+
const isLastPage = derived(() => currentPage() >= totalPages())
|
|
171
|
+
|
|
172
|
+
function previousPage(): void {
|
|
173
|
+
if (currentPage() > 1)
|
|
174
|
+
currentPage.update(p => p - 1)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function nextPage(): void {
|
|
178
|
+
if (currentPage() < totalPages())
|
|
179
|
+
currentPage.update(p => p + 1)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function handleRetry(jobId: string): Promise<void> {
|
|
183
|
+
try {
|
|
184
|
+
const baseUrl = `http://localhost:${(globalThis as { process?: { env?: { PORT_API?: string } } }).process?.env?.PORT_API || '3008'}`
|
|
185
|
+
const response = await fetch(`${baseUrl}/jobs/${jobId}/retry`, {
|
|
186
|
+
method: 'POST',
|
|
187
|
+
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
|
188
|
+
})
|
|
189
|
+
if (response.ok) {
|
|
190
|
+
jobs.set(jobs().map(j => j.id === jobId ? { ...j, status: 'queued', attempts: (j.attempts || 0) + 1 } : j))
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
// eslint-disable-next-line no-console
|
|
195
|
+
console.error('Failed to retry job:', e)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
onMount(async () => {
|
|
200
|
+
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
201
|
+
|
|
202
|
+
const jobNames = ['ProcessPayment', 'SendWelcomeEmail', 'GenerateReport', 'SyncInventory']
|
|
203
|
+
const queueNames = ['default', 'high', 'low']
|
|
204
|
+
const statusTypes: Array<JobHistoryEntry['status']> = ['queued', 'processing', 'completed', 'failed']
|
|
205
|
+
const randomItem = <T>(arr: readonly T[]): T => arr[Math.floor(Math.random() * arr.length)] as T
|
|
206
|
+
|
|
207
|
+
jobs.set(Array.from({ length: 100 }, (_, i) => ({
|
|
208
|
+
id: `${i + 1}`,
|
|
209
|
+
name: randomItem(jobNames),
|
|
210
|
+
queue: randomItem(queueNames),
|
|
211
|
+
status: randomItem(statusTypes),
|
|
212
|
+
attempts: Math.floor(Math.random() * 3) + 1,
|
|
213
|
+
runtime: Math.random() * 10,
|
|
214
|
+
started_at: new Date(Date.now() - Math.random() * 86400000).toISOString(),
|
|
215
|
+
finished_at: new Date(Date.now() - Math.random() * 3600000).toISOString(),
|
|
216
|
+
payload: { data: 'sample' },
|
|
217
|
+
})))
|
|
218
|
+
isLoading.set(false)
|
|
219
|
+
})
|
|
220
|
+
</script>
|
|
221
|
+
|
|
222
|
+
@endsection
|