@stacksjs/ts-cloud 0.7.55 → 0.7.57
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/dist/alerts/alerts.test.d.ts +1 -0
- package/dist/alerts/delivery.d.ts +45 -0
- package/dist/alerts/evaluator.d.ts +12 -0
- package/dist/alerts/health.d.ts +34 -0
- package/dist/alerts/index.d.ts +6 -0
- package/dist/alerts/model.d.ts +170 -0
- package/dist/alerts/store.d.ts +90 -0
- package/dist/alerts/telemetry.d.ts +3 -0
- package/dist/api/client.d.ts +197 -0
- package/dist/api/handler.d.ts +18 -0
- package/dist/api/handler.test.d.ts +1 -0
- package/dist/api/index.d.ts +6 -0
- package/dist/api/openapi.d.ts +2 -0
- package/dist/api/service.d.ts +28 -0
- package/dist/api/types.d.ts +119 -0
- package/dist/auth/encryption.d.ts +3 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/mailer.d.ts +9 -0
- package/dist/auth/oidc.d.ts +36 -0
- package/dist/auth/oidc.test.d.ts +1 -0
- package/dist/auth/store.d.ts +121 -0
- package/dist/auth/store.test.d.ts +1 -0
- package/dist/auth/totp.d.ts +11 -0
- package/dist/auth/totp.test.d.ts +1 -0
- package/dist/auth/types.d.ts +138 -0
- package/dist/automation/index.d.ts +2 -0
- package/dist/automation/store.d.ts +46 -0
- package/dist/automation/store.test.d.ts +1 -0
- package/dist/automation/types.d.ts +66 -0
- package/dist/aws/acm.d.ts +0 -4
- package/dist/aws/application-autoscaling.d.ts +0 -5
- package/dist/aws/backup.d.ts +42 -0
- package/dist/aws/bedrock.d.ts +0 -5
- package/dist/aws/client.d.ts +3 -6
- package/dist/aws/cloudformation.d.ts +0 -4
- package/dist/aws/cloudfront-media.test.d.ts +1 -0
- package/dist/aws/cloudfront-origin.test.d.ts +1 -0
- package/dist/aws/cloudfront.d.ts +87 -3
- package/dist/aws/cloudwatch-logs.d.ts +2 -4
- package/dist/aws/cloudwatch.d.ts +1 -1
- package/dist/aws/comprehend.d.ts +0 -5
- package/dist/aws/connect.d.ts +0 -4
- package/dist/aws/cost-explorer-cache.d.ts +1 -15
- package/dist/aws/cost-explorer.d.ts +18 -9
- package/dist/aws/cost-explorer.test.d.ts +1 -0
- package/dist/aws/dynamodb.d.ts +0 -4
- package/dist/aws/ec2.d.ts +0 -4
- package/dist/aws/ecr.d.ts +0 -4
- package/dist/aws/ecs.d.ts +17 -7
- package/dist/aws/efs.d.ts +0 -4
- package/dist/aws/elasticache.d.ts +0 -4
- package/dist/aws/elbv2.d.ts +0 -7
- package/dist/aws/email.d.ts +0 -10
- package/dist/aws/eventbridge.d.ts +0 -4
- package/dist/aws/iam.d.ts +1 -5
- package/dist/aws/imap-server.d.ts +0 -7
- package/dist/aws/index.d.ts +4 -3
- package/dist/aws/index.js +31 -31
- package/dist/aws/kendra.d.ts +0 -5
- package/dist/aws/lambda.d.ts +3 -5
- package/dist/aws/opensearch.d.ts +0 -4
- package/dist/aws/personalize.d.ts +0 -5
- package/dist/aws/polly.d.ts +0 -5
- package/dist/aws/rds.d.ts +59 -4
- package/dist/aws/rekognition.d.ts +0 -5
- package/dist/aws/route53-domains.d.ts +0 -4
- package/dist/aws/route53.d.ts +0 -4
- package/dist/aws/s3.d.ts +10 -6
- package/dist/aws/scheduler.d.ts +0 -4
- package/dist/aws/secrets-manager.d.ts +0 -4
- package/dist/aws/setup-sms.d.ts +0 -14
- package/dist/aws/sms.d.ts +0 -10
- package/dist/aws/smtp-server.d.ts +0 -11
- package/dist/aws/sns.d.ts +0 -4
- package/dist/aws/sqs.d.ts +0 -4
- package/dist/aws/ssm.d.ts +0 -4
- package/dist/aws/sts.d.ts +0 -4
- package/dist/aws/support.d.ts +0 -4
- package/dist/aws/textract.d.ts +0 -5
- package/dist/aws/transcribe.d.ts +0 -4
- package/dist/aws/translate.d.ts +0 -5
- package/dist/aws/voice.d.ts +0 -11
- package/dist/aws/xray.d.ts +1 -0
- package/dist/backups/aws-database-source.d.ts +19 -0
- package/dist/backups/aws-database-source.test.d.ts +1 -0
- package/dist/backups/aws-infrastructure-source.d.ts +43 -0
- package/dist/backups/aws-infrastructure-source.test.d.ts +1 -0
- package/dist/backups/control-plane-source.d.ts +16 -0
- package/dist/backups/control-plane-source.test.d.ts +1 -0
- package/dist/backups/docker-volume-source.d.ts +31 -0
- package/dist/backups/docker-volume-source.test.d.ts +1 -0
- package/dist/backups/filesystem-source.d.ts +34 -0
- package/dist/backups/filesystem-source.test.d.ts +1 -0
- package/dist/backups/index.d.ts +10 -0
- package/dist/backups/logical-database-source.d.ts +16 -0
- package/dist/backups/logical-database-source.test.d.ts +1 -0
- package/dist/backups/model.d.ts +121 -0
- package/dist/backups/s3-destination.d.ts +85 -0
- package/dist/backups/s3-destination.test.d.ts +1 -0
- package/dist/backups/service.d.ts +80 -0
- package/dist/backups/service.test.d.ts +1 -0
- package/dist/backups/store.d.ts +30 -0
- package/dist/backups/store.test.d.ts +1 -0
- package/dist/bin/cli.js +3360 -1802
- package/dist/{chunk-tjjgajbh.js → chunk-01d86gt1.js} +301 -9
- package/dist/{chunk-73vcqg6z.js → chunk-0z43x8re.js} +7 -7
- package/dist/{chunk-tnztxpcb.js → chunk-32e7ya18.js} +61 -49
- package/dist/{chunk-qpj3edwz.js → chunk-4cjrg98a.js} +3 -7
- package/dist/{chunk-hsk6fe6x.js → chunk-50jpda9q.js} +2 -2
- package/dist/{chunk-93hjhs78.js → chunk-703nkybg.js} +1 -1
- package/dist/{chunk-d5vqwj8s.js → chunk-9m6b4m74.js} +755 -720
- package/dist/chunk-e05phx5f.js +10 -0
- package/dist/{chunk-f29w04w5.js → chunk-exbcsab2.js} +10 -4
- package/dist/{chunk-eq08r166.js → chunk-f55nkpvb.js} +2 -2
- package/dist/{chunk-3bfv1z4v.js → chunk-fv1bj3yt.js} +63 -56
- package/dist/chunk-fw3q88wk.js +48641 -0
- package/dist/{chunk-3knnr7wh.js → chunk-he9a874b.js} +170 -2
- package/dist/{chunk-zn0nxxa8.js → chunk-hpv68b4a.js} +1070 -1070
- package/dist/chunk-k114qwg9.js +12 -0
- package/dist/{chunk-r16e02bc.js → chunk-vdk53yyq.js} +3900 -4101
- package/dist/{chunk-jp2a8yad.js → chunk-vvkd46k1.js} +15 -13
- package/dist/{chunk-vd87cpvn.js → chunk-wj3s95p9.js} +62 -46
- package/dist/{chunk-mj1tmhte.js → chunk-x9s0cqfx.js} +1 -1
- package/dist/{chunk-3rsfns7x.js → chunk-ybcz6sxc.js} +2048 -387
- package/dist/{chunk-arsh1g5h.js → chunk-zqtpg06c.js} +14 -11
- package/dist/compose/index.d.ts +6 -0
- package/dist/compose/parser.d.ts +13 -0
- package/dist/compose/parser.test.d.ts +1 -0
- package/dist/compose/runtime.d.ts +10 -0
- package/dist/compose/service.d.ts +18 -0
- package/dist/compose/store.d.ts +59 -0
- package/dist/compose/store.test.d.ts +1 -0
- package/dist/compose/templates.d.ts +17 -0
- package/dist/compose/types.d.ts +164 -0
- package/dist/configuration/backends.d.ts +66 -0
- package/dist/configuration/backends.test.d.ts +1 -0
- package/dist/configuration/dotenv.d.ts +14 -0
- package/dist/configuration/dotenv.test.d.ts +1 -0
- package/dist/configuration/index.d.ts +6 -0
- package/dist/configuration/migrate.d.ts +10 -0
- package/dist/configuration/migrate.test.d.ts +1 -0
- package/dist/configuration/model.d.ts +50 -0
- package/dist/configuration/service.d.ts +144 -0
- package/dist/configuration/service.test.d.ts +1 -0
- package/dist/configuration/store.d.ts +33 -0
- package/dist/configuration/store.test.d.ts +1 -0
- package/dist/control-plane/authorization.d.ts +24 -0
- package/dist/control-plane/authorization.test.d.ts +1 -0
- package/dist/control-plane/index.d.ts +8 -0
- package/dist/control-plane/migrations.d.ts +7 -0
- package/dist/control-plane/search.d.ts +25 -0
- package/dist/control-plane/store.d.ts +117 -0
- package/dist/control-plane/store.test.d.ts +1 -0
- package/dist/control-plane/types.d.ts +381 -0
- package/dist/cost/reporting.d.ts +24 -0
- package/dist/cost/reporting.test.d.ts +1 -0
- package/dist/cost/resource-inventory.d.ts +32 -0
- package/dist/cost/resource-inventory.test.d.ts +1 -0
- package/dist/cost/resource-optimization.d.ts +59 -0
- package/dist/cost/resource-optimization.test.d.ts +1 -0
- package/dist/data-services/adapters.d.ts +39 -0
- package/dist/data-services/aws-transports.d.ts +29 -0
- package/dist/data-services/aws-transports.test.d.ts +1 -0
- package/dist/data-services/capabilities.d.ts +2 -0
- package/dist/data-services/data-services.test.d.ts +1 -0
- package/dist/data-services/docker-transport.d.ts +55 -0
- package/dist/data-services/docker-transport.test.d.ts +1 -0
- package/dist/data-services/index.d.ts +8 -0
- package/dist/data-services/model.d.ts +79 -0
- package/dist/data-services/secrets.d.ts +13 -0
- package/dist/data-services/secrets.test.d.ts +1 -0
- package/dist/data-services/service.d.ts +51 -0
- package/dist/data-services/store.d.ts +17 -0
- package/dist/deploy/container-image.d.ts +28 -0
- package/dist/deploy/container-image.test.d.ts +1 -0
- package/dist/deploy/dashboard-audit.test.d.ts +1 -0
- package/dist/deploy/dashboard-auth.d.ts +4 -23
- package/dist/deploy/dashboard-backups.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-configuration.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-control-plane.d.ts +37 -0
- package/dist/deploy/dashboard-control-plane.test.d.ts +1 -0
- package/dist/deploy/dashboard-data-services.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-environment-scope.test.d.ts +1 -0
- package/dist/deploy/dashboard-guard.d.ts +12 -0
- package/dist/deploy/dashboard-guard.test.d.ts +1 -0
- package/dist/deploy/dashboard-identities.d.ts +15 -0
- package/dist/deploy/dashboard-identities.test.d.ts +1 -0
- package/dist/deploy/dashboard-jobs.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-login-page.d.ts +5 -5
- package/dist/deploy/dashboard-login-page.test.d.ts +1 -0
- package/dist/deploy/dashboard-oidc.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-onboarding.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-policy.d.ts +4 -2
- package/dist/deploy/dashboard-queue-worker.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-release.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-route-manifest.d.ts +14 -0
- package/dist/deploy/dashboard-route-manifest.test.d.ts +1 -0
- package/dist/deploy/dashboard-security.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-session.d.ts +3 -14
- package/dist/deploy/dashboard-source.integration.test.d.ts +1 -0
- package/dist/deploy/dashboard-users.d.ts +3 -0
- package/dist/deploy/fullstack-container.d.ts +87 -0
- package/dist/deploy/fullstack-container.test.d.ts +1 -0
- package/dist/deploy/index.d.ts +7 -2
- package/dist/deploy/index.js +65 -25
- package/dist/deploy/local-dashboard-server.d.ts +30 -0
- package/dist/deploy/quick-deploy.d.ts +1 -1
- package/dist/deploy/serverless-operations.d.ts +1 -0
- package/dist/deploy/site-target.d.ts +16 -0
- package/dist/deploy/static-api-origin.d.ts +107 -0
- package/dist/deploy/static-api-origin.test.d.ts +1 -0
- package/dist/deploy/static-site-helper.d.ts +0 -16
- package/dist/deploy/telemetry-access.d.ts +5 -0
- package/dist/deploy/telemetry-access.test.d.ts +1 -0
- package/dist/deploy/telemetry-collection.d.ts +44 -0
- package/dist/deploy/telemetry-collection.test.d.ts +1 -0
- package/dist/dns/index.js +3 -3
- package/dist/dns/route53-adapter.d.ts +1 -1
- package/dist/drivers/hetzner/cloud-init.d.ts +1 -1
- package/dist/drivers/index.d.ts +2 -2
- package/dist/drivers/index.js +6 -6
- package/dist/drivers/shared/notifications.d.ts +1 -1
- package/dist/drivers/shared/rpx-gateway.d.ts +1 -0
- package/dist/fleet/fleet.test.d.ts +1 -0
- package/dist/fleet/index.d.ts +144 -0
- package/dist/fleet/queue.d.ts +3 -0
- package/dist/fleet/ssh-driver.d.ts +34 -0
- package/dist/index.d.ts +28 -4
- package/dist/index.js +1132 -424
- package/dist/jobs/adapters.d.ts +23 -0
- package/dist/jobs/index.d.ts +7 -0
- package/dist/jobs/jobs.test.d.ts +1 -0
- package/dist/jobs/model.d.ts +100 -0
- package/dist/jobs/provider-adapters.d.ts +49 -0
- package/dist/jobs/reconcile.d.ts +19 -0
- package/dist/jobs/schedule.d.ts +9 -0
- package/dist/jobs/service.d.ts +30 -0
- package/dist/jobs/store.d.ts +48 -0
- package/dist/maintenance/drivers.d.ts +117 -0
- package/dist/maintenance/index.d.ts +6 -0
- package/dist/maintenance/maintenance.test.d.ts +1 -0
- package/dist/maintenance/manifest.d.ts +15 -0
- package/dist/maintenance/queue.d.ts +9 -0
- package/dist/maintenance/service.d.ts +31 -0
- package/dist/maintenance/store.d.ts +84 -0
- package/dist/maintenance/types.d.ts +192 -0
- package/dist/media.d.ts +62 -0
- package/dist/media.test.d.ts +1 -0
- package/dist/object-storage/index.d.ts +0 -24
- package/dist/onboarding/archive.d.ts +12 -0
- package/dist/onboarding/artifact-store.d.ts +22 -0
- package/dist/onboarding/detection.d.ts +8 -0
- package/dist/onboarding/index.d.ts +9 -0
- package/dist/onboarding/migrations.d.ts +3 -0
- package/dist/onboarding/onboarding.test.d.ts +1 -0
- package/dist/onboarding/plan.d.ts +3 -0
- package/dist/onboarding/registry.d.ts +41 -0
- package/dist/onboarding/service.d.ts +16 -0
- package/dist/onboarding/store.d.ts +30 -0
- package/dist/onboarding/types.d.ts +204 -0
- package/dist/placement/builds.d.ts +20 -0
- package/dist/placement/drivers.d.ts +54 -0
- package/dist/placement/index.d.ts +6 -0
- package/dist/placement/placement.test.d.ts +1 -0
- package/dist/placement/queue.d.ts +4 -0
- package/dist/placement/service.d.ts +34 -0
- package/dist/placement/store.d.ts +49 -0
- package/dist/placement/types.d.ts +109 -0
- package/dist/preview/index.d.ts +3 -0
- package/dist/preview/service.d.ts +45 -0
- package/dist/preview/service.test.d.ts +1 -0
- package/dist/preview/store.d.ts +47 -0
- package/dist/preview/store.test.d.ts +1 -0
- package/dist/preview/types.d.ts +99 -0
- package/dist/push/apns.d.ts +0 -26
- package/dist/push/fcm.d.ts +0 -23
- package/dist/push/index.d.ts +2 -2
- package/dist/push/index.js +1 -1
- package/dist/queue/deployment-handlers.d.ts +27 -0
- package/dist/queue/deployment-handlers.test.d.ts +1 -0
- package/dist/queue/index.d.ts +4 -0
- package/dist/queue/queue.d.ts +57 -0
- package/dist/queue/queue.test.d.ts +1 -0
- package/dist/queue/types.d.ts +93 -0
- package/dist/queue/worker.d.ts +32 -0
- package/dist/queue/worker.test.d.ts +1 -0
- package/dist/regions/drivers.d.ts +139 -0
- package/dist/regions/index.d.ts +5 -0
- package/dist/regions/queue.d.ts +4 -0
- package/dist/regions/regions.test.d.ts +1 -0
- package/dist/regions/service.d.ts +23 -0
- package/dist/regions/store.d.ts +41 -0
- package/dist/regions/types.d.ts +132 -0
- package/dist/release/index.d.ts +6 -0
- package/dist/release/queue.d.ts +15 -0
- package/dist/release/queue.test.d.ts +1 -0
- package/dist/release/runtime.d.ts +35 -0
- package/dist/release/runtime.test.d.ts +1 -0
- package/dist/release/service.d.ts +28 -0
- package/dist/release/store.d.ts +88 -0
- package/dist/release/store.test.d.ts +1 -0
- package/dist/release/strategy.d.ts +8 -0
- package/dist/release/types.d.ts +103 -0
- package/dist/runtime/adapters/aws.d.ts +5 -0
- package/dist/runtime/adapters/docker.d.ts +53 -0
- package/dist/runtime/adapters/systemd.d.ts +26 -0
- package/dist/runtime/index.d.ts +9 -0
- package/dist/runtime/inventory.d.ts +6 -0
- package/dist/runtime/model.d.ts +130 -0
- package/dist/runtime/normalize.d.ts +5 -0
- package/dist/runtime/operations.d.ts +113 -0
- package/dist/runtime/service.d.ts +32 -0
- package/dist/runtime/streams.d.ts +49 -0
- package/dist/security/artifacts.d.ts +105 -0
- package/dist/security/artifacts.test.d.ts +1 -0
- package/dist/security/container-release.d.ts +35 -0
- package/dist/security/container-release.test.d.ts +1 -0
- package/dist/security/index.d.ts +8 -0
- package/dist/security/integration.d.ts +41 -0
- package/dist/security/integration.test.d.ts +1 -0
- package/dist/security/posture-store.d.ts +80 -0
- package/dist/security/posture-store.test.d.ts +1 -0
- package/dist/security/pre-deploy-scanner.d.ts +0 -4
- package/dist/security/scanners.d.ts +81 -0
- package/dist/security/scanners.test.d.ts +1 -0
- package/dist/security/types.d.ts +150 -0
- package/dist/source/git-workspace.d.ts +26 -0
- package/dist/source/git-workspace.test.d.ts +1 -0
- package/dist/source/index.d.ts +6 -0
- package/dist/source/providers.d.ts +199 -0
- package/dist/source/providers.test.d.ts +1 -0
- package/dist/source/service.d.ts +18 -0
- package/dist/source/store.d.ts +149 -0
- package/dist/source/store.test.d.ts +1 -0
- package/dist/source/types.d.ts +191 -0
- package/dist/source/webhooks.d.ts +33 -0
- package/dist/source/webhooks.test.d.ts +1 -0
- package/dist/ssl/acme-client.d.ts +0 -6
- package/dist/storage/compose.d.ts +4 -0
- package/dist/storage/compose.test.d.ts +1 -0
- package/dist/storage/drivers.d.ts +107 -0
- package/dist/storage/drivers.test.d.ts +1 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/model.d.ts +112 -0
- package/dist/storage/service.d.ts +95 -0
- package/dist/storage/storage.test.d.ts +1 -0
- package/dist/storage/store.d.ts +40 -0
- package/dist/telemetry/correlation.test.d.ts +1 -0
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/model.d.ts +145 -0
- package/dist/telemetry/policy.d.ts +8 -0
- package/dist/telemetry/policy.test.d.ts +1 -0
- package/dist/telemetry/redaction.d.ts +9 -0
- package/dist/telemetry/store.d.ts +34 -0
- package/dist/telemetry/store.test.d.ts +1 -0
- package/dist/ui/404.html +1506 -17
- package/dist/ui/accept-invitation.html +1163 -0
- package/dist/ui/{server/backups.html → access-denied.html} +344 -55
- package/dist/ui/account/automation.html +1669 -0
- package/dist/ui/account/security.html +1646 -0
- package/dist/ui/applications/compose.html +1590 -0
- package/dist/ui/applications/new.html +1868 -0
- package/dist/ui/data/backups.html +1757 -0
- package/dist/ui/data/services.html +1723 -0
- package/dist/ui/data/volumes.html +1639 -0
- package/dist/ui/index.html +392 -29
- package/dist/ui/integrations.html +1865 -0
- package/dist/ui/operations/alerts.html +1610 -0
- package/dist/ui/operations/configuration.html +1746 -0
- package/dist/ui/operations/jobs.html +1709 -0
- package/dist/ui/operations/maintenance.html +1540 -0
- package/dist/ui/operations/observability.html +1742 -0
- package/dist/ui/{serverless/scheduler.html → operations/previews.html} +447 -46
- package/dist/ui/operations/queue.html +1615 -0
- package/dist/ui/{server/workers.html → operations/regions.html} +413 -49
- package/dist/ui/operations/releases.html +1596 -0
- package/dist/ui/operations/workloads.html +1775 -0
- package/dist/ui/security.html +1767 -0
- package/dist/ui/server/actions.html +446 -57
- package/dist/ui/server/activity.html +346 -1
- package/dist/ui/server/capacity.html +1542 -0
- package/dist/ui/server/database.html +443 -36
- package/dist/ui/server/deployments.html +439 -53
- package/dist/ui/server/diagnostics.html +346 -1
- package/dist/ui/server/firewall.html +476 -50
- package/dist/ui/server/fleet.html +1577 -0
- package/dist/ui/server/logs.html +389 -25
- package/dist/ui/server/metrics.html +346 -1
- package/dist/ui/server/security.html +346 -1
- package/dist/ui/server/services.html +421 -46
- package/dist/ui/server/sites.html +476 -95
- package/dist/ui/server/ssh-keys.html +476 -46
- package/dist/ui/server/team.html +700 -77
- package/dist/ui/server/terminal.html +444 -54
- package/dist/ui/serverless/alarms.html +430 -42
- package/dist/ui/serverless/assets.html +421 -46
- package/dist/ui/serverless/cost.html +346 -1
- package/dist/ui/serverless/data.html +422 -47
- package/dist/ui/serverless/deployments.html +421 -46
- package/dist/ui/serverless/firewall.html +346 -1
- package/dist/ui/serverless/functions.html +423 -48
- package/dist/ui/serverless/logs.html +389 -25
- package/dist/ui/serverless/metrics.html +346 -1
- package/dist/ui/serverless/queues.html +448 -62
- package/dist/ui/serverless/secrets.html +448 -42
- package/dist/ui/serverless/traces.html +350 -5
- package/dist/ui/serverless.html +434 -58
- package/dist/ui-src/pages/404.stx +18 -0
- package/dist/ui-src/pages/accept-invitation.stx +50 -0
- package/dist/ui-src/pages/access-denied.stx +18 -0
- package/dist/ui-src/pages/account/automation.stx +187 -0
- package/dist/ui-src/pages/account/security.stx +249 -0
- package/dist/ui-src/pages/applications/compose.stx +38 -0
- package/dist/ui-src/pages/applications/new.stx +76 -0
- package/dist/ui-src/pages/data/backups.stx +214 -0
- package/dist/ui-src/pages/data/services.stx +257 -0
- package/dist/ui-src/pages/data/volumes.stx +40 -0
- package/dist/ui-src/pages/index.stx +24 -10
- package/dist/ui-src/pages/integrations.stx +186 -0
- package/dist/ui-src/pages/operations/alerts.stx +40 -0
- package/dist/ui-src/pages/operations/configuration.stx +164 -0
- package/dist/ui-src/pages/operations/jobs.stx +238 -0
- package/dist/ui-src/pages/operations/maintenance.stx +15 -0
- package/dist/ui-src/pages/operations/observability.stx +76 -0
- package/dist/ui-src/pages/operations/previews.stx +62 -0
- package/dist/ui-src/pages/operations/queue.stx +158 -0
- package/dist/ui-src/pages/operations/regions.stx +12 -0
- package/dist/ui-src/pages/operations/releases.stx +39 -0
- package/dist/ui-src/pages/operations/workloads.stx +151 -0
- package/dist/ui-src/pages/partials/head.stx +148 -15
- package/dist/ui-src/pages/partials/nav.stx +491 -74
- package/dist/ui-src/pages/partials/op-confirm.stx +6 -6
- package/dist/ui-src/pages/security.stx +264 -0
- package/dist/ui-src/pages/server/actions.stx +17 -26
- package/dist/ui-src/pages/server/capacity.stx +16 -0
- package/dist/ui-src/pages/server/database.stx +31 -16
- package/dist/ui-src/pages/server/deployments.stx +28 -30
- package/dist/ui-src/pages/server/firewall.stx +25 -30
- package/dist/ui-src/pages/server/fleet.stx +10 -0
- package/dist/ui-src/pages/server/logs.stx +11 -9
- package/dist/ui-src/pages/server/metrics.stx +8 -16
- package/dist/ui-src/pages/server/services.stx +19 -28
- package/dist/ui-src/pages/server/sites.stx +75 -72
- package/dist/ui-src/pages/server/ssh-keys.stx +24 -30
- package/dist/ui-src/pages/server/team.stx +286 -149
- package/dist/ui-src/pages/server/terminal.stx +6 -28
- package/dist/ui-src/pages/serverless/alarms.stx +17 -31
- package/dist/ui-src/pages/serverless/assets.stx +13 -22
- package/dist/ui-src/pages/serverless/cost.stx +15 -3
- package/dist/ui-src/pages/serverless/data.stx +12 -25
- package/dist/ui-src/pages/serverless/deployments.stx +14 -23
- package/dist/ui-src/pages/serverless/firewall.stx +5 -1
- package/dist/ui-src/pages/serverless/functions.stx +20 -26
- package/dist/ui-src/pages/serverless/logs.stx +11 -10
- package/dist/ui-src/pages/serverless/metrics.stx +18 -17
- package/dist/ui-src/pages/serverless/queues.stx +34 -42
- package/dist/ui-src/pages/serverless/secrets.stx +31 -34
- package/dist/ui-src/pages/serverless/traces.stx +2 -6
- package/dist/ui-src/pages/serverless.stx +40 -74
- package/package.json +11 -11
- package/dist/chunk-b17vf1g1.js +0 -13787
- package/dist/chunk-b82pbxyp.js +0 -1572
- package/dist/chunk-pqfzdg68.js +0 -12
- package/dist/chunk-z58fqnjm.js +0 -10
- package/dist/ui-src/pages/server/backups.stx +0 -91
- package/dist/ui-src/pages/server/workers.stx +0 -99
- package/dist/ui-src/pages/serverless/scheduler.stx +0 -89
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
import { usePolling } from '../../functions/usePolling.ts'
|
|
3
|
+
|
|
4
|
+
const services = state([])
|
|
5
|
+
const operations = state([])
|
|
6
|
+
const search = state('')
|
|
7
|
+
const engineFilter = state('all')
|
|
8
|
+
const providerFilter = state('all')
|
|
9
|
+
const statusFilter = state('all')
|
|
10
|
+
const selected = state(null)
|
|
11
|
+
const plan = state(null)
|
|
12
|
+
const guidance = state([])
|
|
13
|
+
const oneTime = state(null)
|
|
14
|
+
const notice = state('')
|
|
15
|
+
const actionError = state('')
|
|
16
|
+
const busy = state(false)
|
|
17
|
+
const createOpen = state(false)
|
|
18
|
+
const operationAction = state('observe')
|
|
19
|
+
|
|
20
|
+
const actionLabels = {
|
|
21
|
+
observe: 'Refresh status', backup: 'Create backup', restore: 'Restore backup',
|
|
22
|
+
restart: 'Restart', resize: 'Resize', version: 'Upgrade version',
|
|
23
|
+
rotate: 'Rotate credential', expose: 'Change exposure', delete: 'Retire service',
|
|
24
|
+
logs: 'Read logs', slow_queries: 'Read slow queries', users: 'Manage users',
|
|
25
|
+
databases: 'Manage databases',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function api(url, options) {
|
|
29
|
+
const response = await fetch(url, options)
|
|
30
|
+
const body = await response.json()
|
|
31
|
+
if (!response.ok || body.ok === false) {
|
|
32
|
+
const error = new Error(body.error || 'Request failed.')
|
|
33
|
+
error.stepUpRequired = body.stepUpRequired
|
|
34
|
+
throw error
|
|
35
|
+
}
|
|
36
|
+
return body
|
|
37
|
+
}
|
|
38
|
+
async function load() {
|
|
39
|
+
const [data, queue] = await Promise.all([
|
|
40
|
+
api('/api/data-services'),
|
|
41
|
+
api('/api/queue?limit=250').catch(() => ({ operations: [] })),
|
|
42
|
+
])
|
|
43
|
+
services.set(data.services || [])
|
|
44
|
+
operations.set((queue.operations || []).filter(item => item.kind === 'data_service.action'))
|
|
45
|
+
if (selected()) selected.set(data.services?.find(item => item.id === selected().id) || null)
|
|
46
|
+
}
|
|
47
|
+
const { pollingState, pollingError, lastUpdatedAt, refresh } = usePolling(load, 15000)
|
|
48
|
+
function tone(value) { return ['available', 'succeeded', 'in_sync'].includes(value) ? 'ok' : ['failed', 'degraded', 'timed_out'].includes(value) ? 'bad' : 'warn' }
|
|
49
|
+
function when(value) { return value ? new Date(value).toLocaleString() : '—' }
|
|
50
|
+
function filtered() {
|
|
51
|
+
const query = search().trim().toLowerCase()
|
|
52
|
+
return services().filter(item =>
|
|
53
|
+
(!query || `${item.name} ${item.engine} ${item.provider} ${item.placement}`.toLowerCase().includes(query)) &&
|
|
54
|
+
(engineFilter() === 'all' || item.engine === engineFilter()) &&
|
|
55
|
+
(providerFilter() === 'all' || item.provider === providerFilter()) &&
|
|
56
|
+
(statusFilter() === 'all' || item.status === statusFilter()))
|
|
57
|
+
}
|
|
58
|
+
function formBody(form) { return Object.fromEntries(new FormData(form)) }
|
|
59
|
+
async function createService(event) {
|
|
60
|
+
event.preventDefault()
|
|
61
|
+
busy.set(true); actionError.set(''); oneTime.set(null)
|
|
62
|
+
try {
|
|
63
|
+
const value = formBody(event.currentTarget)
|
|
64
|
+
const desiredState = {}
|
|
65
|
+
if (value.subnetGroup) desiredState.subnetGroup = value.subnetGroup
|
|
66
|
+
if (value.securityGroupIds) desiredState.securityGroupIds = String(value.securityGroupIds).split(',').map(item => item.trim()).filter(Boolean)
|
|
67
|
+
if (value.database) desiredState.database = value.database
|
|
68
|
+
if (value.minCapacity) desiredState.minCapacity = Number(value.minCapacity)
|
|
69
|
+
if (value.maxCapacity) desiredState.maxCapacity = Number(value.maxCapacity)
|
|
70
|
+
const result = await api('/api/data-services', {
|
|
71
|
+
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({
|
|
72
|
+
name: value.name, engine: value.engine, provider: value.provider,
|
|
73
|
+
placement: value.placement || value.name, plan: value.plan,
|
|
74
|
+
engineVersion: value.engineVersion || undefined,
|
|
75
|
+
storageGb: value.storageGb ? Number(value.storageGb) : undefined,
|
|
76
|
+
highAvailability: value.highAvailability === 'on',
|
|
77
|
+
publicExposure: value.publicExposure === 'on',
|
|
78
|
+
allowedCidrs: value.allowedCidrs ? String(value.allowedCidrs).split(',').map(item => item.trim()).filter(Boolean) : [],
|
|
79
|
+
adopt: value.adopt === 'on', username: value.username || undefined, desiredState,
|
|
80
|
+
}),
|
|
81
|
+
})
|
|
82
|
+
oneTime.set(result.oneTimeCredential || null)
|
|
83
|
+
notice.set(result.readOnly ? `${result.service.name} adopted in read-only mode.` : `${result.service.name} queued for provisioning.`)
|
|
84
|
+
event.currentTarget.reset(); createOpen.set(false); await refresh(); selected.set(result.service)
|
|
85
|
+
}
|
|
86
|
+
catch (error) { actionError.set(error.message) }
|
|
87
|
+
finally { busy.set(false) }
|
|
88
|
+
}
|
|
89
|
+
async function inspect(item) {
|
|
90
|
+
selected.set(item); plan.set(null); guidance.set([])
|
|
91
|
+
try { guidance.set((await api(`/api/data-services/connections?id=${encodeURIComponent(item.id)}`)).guidance || []) }
|
|
92
|
+
catch (error) { actionError.set(error.message) }
|
|
93
|
+
}
|
|
94
|
+
async function previewAction(item, action, changes = {}) {
|
|
95
|
+
try {
|
|
96
|
+
const result = await api('/api/data-services/preview', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: item.id, action, changes }) })
|
|
97
|
+
plan.set(result.plan); selected.set(item); return result.plan
|
|
98
|
+
}
|
|
99
|
+
catch (error) { actionError.set(error.message) }
|
|
100
|
+
}
|
|
101
|
+
async function runAction(item, action, requested = {}) {
|
|
102
|
+
const changes = { ...requested }
|
|
103
|
+
const proposed = await previewAction(item, action, changes)
|
|
104
|
+
if (!proposed || !proposed.capability.supported) return
|
|
105
|
+
if (action === 'delete' && !changes.retention) {
|
|
106
|
+
const retention = window.prompt('Deletion retention: type final_backup or retain', 'final_backup') || ''
|
|
107
|
+
if (!['final_backup', 'retain'].includes(retention)) return
|
|
108
|
+
changes.retention = retention
|
|
109
|
+
}
|
|
110
|
+
if (proposed.preflight.typedConfirmation) {
|
|
111
|
+
changes.confirm = window.prompt(`Type ${proposed.preflight.typedConfirmation} to confirm ${action}.`) || ''
|
|
112
|
+
if (changes.confirm !== proposed.preflight.typedConfirmation) return
|
|
113
|
+
}
|
|
114
|
+
busy.set(true)
|
|
115
|
+
try {
|
|
116
|
+
const result = await api('/api/data-services/action', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: item.id, action, execute: true, changes }) })
|
|
117
|
+
notice.set(`${action} queued as ${result.operationId}.`); actionError.set(''); await refresh()
|
|
118
|
+
}
|
|
119
|
+
catch (error) { actionError.set(error.stepUpRequired ? `${error.message} Use Sign-in & security to verify again.` : error.message) }
|
|
120
|
+
finally { busy.set(false) }
|
|
121
|
+
}
|
|
122
|
+
async function runConfiguredAction(event) {
|
|
123
|
+
event.preventDefault()
|
|
124
|
+
const item = selected()
|
|
125
|
+
if (!item) return
|
|
126
|
+
const action = operationAction()
|
|
127
|
+
const value = formBody(event.currentTarget)
|
|
128
|
+
const changes = {}
|
|
129
|
+
for (const key of ['backupId', 'targetId', 'plan', 'engineVersion', 'storageGb', 'memoryMb', 'cpus', 'minCapacity', 'maxCapacity', 'lines', 'operation', 'database', 'retention']) {
|
|
130
|
+
if (value[key] !== undefined && value[key] !== '') changes[key] = ['storageGb', 'memoryMb', 'cpus', 'minCapacity', 'maxCapacity', 'lines'].includes(key) ? Number(value[key]) : value[key]
|
|
131
|
+
}
|
|
132
|
+
if (action === 'version') {
|
|
133
|
+
changes.compatibilityReviewed = value.compatibilityReviewed === 'on'
|
|
134
|
+
if (value.versionBackupId) changes.backupId = value.versionBackupId
|
|
135
|
+
}
|
|
136
|
+
if (action === 'expose') {
|
|
137
|
+
changes.publicExposure = value.publicExposure === 'on'
|
|
138
|
+
changes.allowedCidrs = value.allowedCidrs ? String(value.allowedCidrs).split(',').map(item => item.trim()).filter(Boolean) : []
|
|
139
|
+
}
|
|
140
|
+
if (action === 'databases' && changes.operation === 'delete') {
|
|
141
|
+
changes.confirm = window.prompt(`Type ${changes.database} to delete this logical database.`) || ''
|
|
142
|
+
if (changes.confirm !== changes.database) return
|
|
143
|
+
}
|
|
144
|
+
await runAction(item, action, changes)
|
|
145
|
+
}
|
|
146
|
+
async function chooseAction(action) {
|
|
147
|
+
operationAction.set(action)
|
|
148
|
+
if (selected()) await previewAction(selected(), action)
|
|
149
|
+
}
|
|
150
|
+
async function reveal(item) {
|
|
151
|
+
try {
|
|
152
|
+
const result = await api('/api/data-services/reveal', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: item.id }) })
|
|
153
|
+
oneTime.set(result.credential); notice.set('Credential revealed. Copy it now; it is never shown in lists or logs.')
|
|
154
|
+
}
|
|
155
|
+
catch (error) { actionError.set(error.stepUpRequired ? `${error.message} Verify your session, then retry.` : error.message) }
|
|
156
|
+
}
|
|
157
|
+
function serviceOperations(item) { return operations().filter(operation => operation.input?.serviceId === item.id) }
|
|
158
|
+
function observationEntries(item) { return Object.entries(item?.observedState || {}).filter(([key]) => key !== 'error') }
|
|
159
|
+
async function copy(value, label) {
|
|
160
|
+
await navigator.clipboard.writeText(String(value || ''))
|
|
161
|
+
notice.set(`${label} copied.`)
|
|
162
|
+
}
|
|
163
|
+
</script>
|
|
164
|
+
<!DOCTYPE html>
|
|
165
|
+
<html lang="en" data-theme="dark">
|
|
166
|
+
<head>@include('../partials/head', { title: 'ts-cloud · Data services' })</head>
|
|
167
|
+
<body>
|
|
168
|
+
@include('../partials/nav', { active: 'data-services', env: 'production' })
|
|
169
|
+
<main class="data-wrap wrap" id="main-content">
|
|
170
|
+
<div class="crumbs">Infrastructure <span class="sep">/</span> Data services</div>
|
|
171
|
+
<header class="page">
|
|
172
|
+
<div><h1>Data services</h1><p>Provision, adopt, connect, observe, back up, rotate, upgrade, resize, and retire databases and caches from one lifecycle.</p></div>
|
|
173
|
+
<div class="header-actions"><span class="pill" :class="pollingState()==='live'?'ok':'warn'">{{ pollingState() }}</span><small>{{ lastUpdatedAt()?.toLocaleTimeString() || 'connecting' }}</small><button class="btn ghost sm" @click="refresh()">Refresh</button><button class="btn sm" @click="createOpen.set(!createOpen())">{{ createOpen()?'Close':'New service' }}</button></div>
|
|
174
|
+
</header>
|
|
175
|
+
<p class="notice" role="status" @show="!!notice()">{{ notice() }}</p>
|
|
176
|
+
<p class="bad notice" role="alert" @show="!!actionError() || !!pollingError()">{{ actionError() || pollingError() }}</p>
|
|
177
|
+
|
|
178
|
+
<section class="summary">
|
|
179
|
+
<article class="panel"><b>Services</b><strong>{{ services().length }}</strong><small>current environment</small></article>
|
|
180
|
+
<article class="panel"><b>Available</b><strong>{{ services().filter(item=>item.status==='available').length }}</strong><small>healthy and connectable</small></article>
|
|
181
|
+
<article class="panel"><b>Needs attention</b><strong>{{ services().filter(item=>['failed','degraded'].includes(item.status)).length }}</strong><small>failed or degraded</small></article>
|
|
182
|
+
<article class="panel"><b>Dependency redeploys</b><strong>{{ services().reduce((sum,item)=>sum+(item.dependencies||[]).filter(dep=>dep.requiresRedeploy).length,0) }}</strong><small>after credential rotation</small></article>
|
|
183
|
+
</section>
|
|
184
|
+
|
|
185
|
+
<form class="create-grid panel" @show="createOpen()" @submit="createService($event)">
|
|
186
|
+
<div class="wide"><h2>Create or adopt a service</h2><p class="note">Private networking, encryption, deletion protection, and seven-day backup retention are the managed defaults. Previewing never mutates production.</p></div>
|
|
187
|
+
<label>Name<input name="name" required pattern="[a-z0-9][a-z0-9-]{1,62}" placeholder="orders-db"></label>
|
|
188
|
+
<label>Engine<select name="engine" required><option value="postgres">PostgreSQL</option><option value="mysql">MySQL</option><option value="mariadb">MariaDB</option><option value="redis">Redis</option><option value="mongodb">MongoDB</option><option value="libsql">libSQL</option></select></label>
|
|
189
|
+
<label>Provider<select name="provider" required><option value="aws_rds">AWS RDS</option><option value="aws_aurora">AWS Aurora</option><option value="aws_elasticache">AWS ElastiCache</option><option value="server">Server</option><option value="container">Container</option><option value="external">External / adopted</option></select></label>
|
|
190
|
+
<label>Provider ID or placement<input name="placement" placeholder="orders-production"></label>
|
|
191
|
+
<label>Plan<input name="plan" value="db.t4g.micro" placeholder="db.t4g.micro"></label>
|
|
192
|
+
<label>Engine version<input name="engineVersion" placeholder="16.3"></label>
|
|
193
|
+
<label>Storage GB<input name="storageGb" type="number" min="1" value="20"></label>
|
|
194
|
+
<label>Username<input name="username" value="app" autocomplete="off"></label>
|
|
195
|
+
<label>Database<input name="database" placeholder="app"></label>
|
|
196
|
+
<label>Subnet group<input name="subnetGroup" placeholder="private-data"></label>
|
|
197
|
+
<label class="wide">Security group IDs<input name="securityGroupIds" placeholder="sg-database"></label>
|
|
198
|
+
<label class="wide">Allowed public CIDRs<input name="allowedCidrs" placeholder="203.0.113.8/32"><span>Required only for public access. Broad 0.0.0.0/0 is always refused.</span></label>
|
|
199
|
+
<label class="check"><input name="highAvailability" type="checkbox"> High availability</label>
|
|
200
|
+
<label class="check"><input name="publicExposure" type="checkbox"> Public endpoint</label>
|
|
201
|
+
<label class="check wide"><input name="adopt" type="checkbox"> Adopt existing provider ID read-only (no credential or mutation)</label>
|
|
202
|
+
<button class="btn wide" type="submit" :disabled="busy()">{{ busy()?'Creating…':'Review and create' }}</button>
|
|
203
|
+
</form>
|
|
204
|
+
|
|
205
|
+
<section class="filters panel"><input type="search" aria-label="Search data services" placeholder="Search name, engine, provider, placement" @input="search.set($event.currentTarget.value)"><select aria-label="Filter engine" @change="engineFilter.set($event.currentTarget.value)"><option value="all">All engines</option><option value="postgres">PostgreSQL</option><option value="mysql">MySQL</option><option value="mariadb">MariaDB</option><option value="redis">Redis</option><option value="mongodb">MongoDB</option><option value="libsql">libSQL</option></select><select aria-label="Filter provider" @change="providerFilter.set($event.currentTarget.value)"><option value="all">All providers</option><option value="aws_rds">RDS</option><option value="aws_aurora">Aurora</option><option value="aws_elasticache">ElastiCache</option><option value="server">Server</option><option value="container">Container</option><option value="external">External</option></select><select aria-label="Filter status" @change="statusFilter.set($event.currentTarget.value)"><option value="all">All states</option><option value="available">Available</option><option value="planning">Planning</option><option value="failed">Failed</option><option value="adopted">Adopted</option><option value="retained">Retained</option></select></section>
|
|
206
|
+
|
|
207
|
+
<div class="workspace" :class="{hasDetail:!!selected()}">
|
|
208
|
+
<section class="data-cards">
|
|
209
|
+
<template :for="item in filtered()"><article class="panel service-card" :class="{selected:selected()?.id===item.id}">
|
|
210
|
+
<div class="service-head"><div><span class="pill" :class="tone(item.status)">{{ item.status }}</span><span class="pill">{{ item.engine }}</span><span class="pill">{{ item.provider }}</span><h2>{{ item.name }}</h2></div><button class="btn ghost sm" @click="inspect(item)">Details</button></div>
|
|
211
|
+
<div class="service-grid"><div><small>Placement</small><b class="mono">{{ item.placement }}</b><span>{{ item.plan }} · {{ item.engineVersion || 'provider default' }}</span></div><div><small>Endpoint</small><b class="mono">{{ item.observedState?.endpoint || 'pending' }}</b><span>{{ item.publicExposure?'public allowlist':'private network' }}</span></div><div><small>Credential</small><b>{{ item.credential?.configured ? item.credential.username : 'not managed' }}</b><span>{{ item.credential?.rotatedAt ? 'rotated '+when(item.credential.rotatedAt) : item.credential?.configured ? 'version '+item.credential.version : 'reference only' }}</span></div><div><small>Protection</small><b>{{ item.highAvailability?'high availability':'single placement' }}</b><span>{{ item.origin }} · {{ item.managementEnabled?'managed':'read-only' }}</span></div></div>
|
|
212
|
+
<div class="actions"><button class="btn ghost sm" @click="runAction(item,'observe')">Refresh status</button><button class="btn ghost sm" @click="runAction(item,'backup')" :disabled="!item.capabilities.actions.backup.supported">Back up</button><button class="btn ghost sm" @click="runAction(item,'restart')">Restart</button><button class="btn ghost sm" @click="runAction(item,'rotate')" :disabled="!item.capabilities.actions.rotate.supported">Rotate credential</button><button class="btn danger sm" @click="runAction(item,'delete')">Retire</button></div>
|
|
213
|
+
<p class="bad note" @show="!!item.observedState?.error">{{ item.observedState.error }}</p>
|
|
214
|
+
</article></template>
|
|
215
|
+
<div class="empty panel" @show="!filtered().length"><strong>No matching data services</strong><span>Create a managed service or adopt an existing provider resource read-only.</span></div>
|
|
216
|
+
</section>
|
|
217
|
+
|
|
218
|
+
<aside class="detail panel" @show="!!selected()">
|
|
219
|
+
<div class="detail-head"><div><small>Selected service</small><h2>{{ selected()?.name }}</h2></div><button class="btn ghost sm" aria-label="Close details" @click="selected.set(null)">Close</button></div>
|
|
220
|
+
<div class="capability"><h3>Lifecycle operations</h3><p class="note">Every operation is capability-checked and previewed before a durable production job is created.</p><div class="operation-picker"><template :for="entry in Object.entries(selected()?.capabilities?.actions || {}).filter(([action])=>action!=='create'&&action!=='connect')"><button class="btn ghost sm" :class="{active:operationAction()===entry[0]}" :disabled="!entry[1].supported" :title="entry[1].explanation" @click="chooseAction(entry[0])">{{ actionLabels[entry[0]] || entry[0] }}</button></template></div>
|
|
221
|
+
<form class="operation-form" @submit="runConfiguredAction($event)">
|
|
222
|
+
<label @show="operationAction()==='restore'">Backup ID<input name="backupId" placeholder="orders-snapshot"></label>
|
|
223
|
+
<label @show="operationAction()==='restore'">New target ID<input name="targetId" placeholder="orders-restored"></label>
|
|
224
|
+
<label @show="operationAction()==='resize'">Provider plan<input name="plan" :placeholder="selected()?.plan"></label>
|
|
225
|
+
<label @show="operationAction()==='resize'">Storage GB<input name="storageGb" type="number" min="1"></label>
|
|
226
|
+
<label @show="operationAction()==='resize'">Runtime memory MB<input name="memoryMb" type="number" min="16"></label>
|
|
227
|
+
<label @show="operationAction()==='resize'">Runtime CPUs<input name="cpus" type="number" min="0.1" step="0.1"></label>
|
|
228
|
+
<label @show="operationAction()==='version'">Target engine version<input name="engineVersion" placeholder="17.0"></label>
|
|
229
|
+
<label class="check" @show="operationAction()==='version'"><input name="compatibilityReviewed" type="checkbox"> Compatibility reviewed</label>
|
|
230
|
+
<label @show="operationAction()==='version'">Pre-upgrade backup ID<input name="versionBackupId" placeholder="pre-upgrade-snapshot"></label>
|
|
231
|
+
<label @show="['logs','slow_queries'].includes(operationAction())">Maximum lines<input name="lines" type="number" min="1" max="2000" value="500"></label>
|
|
232
|
+
<label @show="['users','databases'].includes(operationAction())">Operation<select name="operation"><option value="list">List</option><option value="create" @show="operationAction()==='databases'">Create</option><option value="delete" @show="operationAction()==='databases'">Delete</option></select></label>
|
|
233
|
+
<label @show="operationAction()==='databases'">Database<input name="database" placeholder="analytics"></label>
|
|
234
|
+
<label @show="operationAction()==='delete'">Retention<select name="retention"><option value="final_backup">Final backup, then delete</option><option value="retain">Retain resource unmanaged</option></select></label>
|
|
235
|
+
<label class="check" @show="operationAction()==='expose'"><input name="publicExposure" type="checkbox"> Public endpoint</label>
|
|
236
|
+
<label @show="operationAction()==='expose'">Allowed CIDRs<input name="allowedCidrs" placeholder="203.0.113.8/32"></label>
|
|
237
|
+
<button class="btn sm" type="submit" :disabled="busy() || !selected()?.capabilities?.actions?.[operationAction()]?.supported">{{ busy()?'Queueing…':actionLabels[operationAction()] }}</button>
|
|
238
|
+
</form>
|
|
239
|
+
<div class="action-preview"><p class="note" @show="!plan()">Choose an action to inspect support, downtime, confirmation, backup, and compatibility gates.</p><div @show="!!plan()"><span class="pill" :class="plan()?.capability?.supported?'ok':'bad'">{{ plan()?.capability?.supported?'supported':'unsupported' }}</span><span class="pill">downtime {{ plan()?.capability?.downtime }}</span><p>{{ plan()?.capability?.explanation }}</p><ul><li @show="plan()?.preflight?.backupRequired">Backup required</li><li @show="plan()?.preflight?.compatibilityRequired">Compatibility review required</li><li @show="plan()?.preflight?.retentionChoiceRequired">Retention choice required</li><template :for="warning in plan()?.warnings || []"><li>{{ warning }}</li></template></ul></div></div>
|
|
240
|
+
</div>
|
|
241
|
+
<div><h3>Health, metrics, and provider state</h3><div class="observation-grid"><template :for="entry in observationEntries(selected())"><div><small>{{ entry[0] }}</small><code>{{ typeof entry[1] === 'object' ? JSON.stringify(entry[1], null, 2) : entry[1] }}</code></div></template></div><p class="note" @show="!observationEntries(selected()).length">Observed state is pending. Run Refresh status after provisioning.</p></div>
|
|
242
|
+
<div><h3>Connections</h3><template :for="item in guidance()"><div class="connection"><span class="pill">{{ item.type }}</span><code>{{ item.command }}</code><small>{{ item.secretRef || 'no managed credential' }}</small><button class="btn ghost sm" @click="copy(item.command,'Connection command')">Copy command</button></div></template><p class="note" @show="!guidance().length">Endpoint discovery is pending. Refresh status after provisioning.</p></div>
|
|
243
|
+
<div><h3>Credentials</h3><p class="note">Secret values are encrypted and omitted from lists, plans, logs, and operation payloads.</p><button class="btn ghost sm" @show="selected()?.credential?.configured" @click="reveal(selected())">Reveal with recent authentication</button></div>
|
|
244
|
+
<div><h3>Recent operations</h3><template :for="operation in serviceOperations(selected())"><div class="operation"><span class="pill" :class="tone(operation.state)">{{ operation.state }}</span><b>{{ operation.input?.action }}</b><small>{{ when(operation.createdAt) }} · attempt {{ operation.attempt }}</small></div></template><p class="note" @show="!serviceOperations(selected()).length">No durable operations yet.</p></div>
|
|
245
|
+
<div><h3>Dependency impact</h3><template :for="dependency in selected()?.dependencies || []"><div class="operation"><b class="mono">{{ dependency.resourceId }}</b><span class="pill" :class="dependency.requiresRedeploy?'warn':'ok'">{{ dependency.requiresRedeploy?'redeploy after rotation':'dynamic reference' }}</span></div></template><p class="note" @show="!(selected()?.dependencies || []).length">No application dependencies registered.</p></div>
|
|
246
|
+
</aside>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<section class="credential panel" @show="!!oneTime()" role="alert">
|
|
250
|
+
<div><h2>Credential — copy now</h2><p>This value is deliberately absent from resource lists, logs, and operation history.</p></div><div class="secret"><span>{{ oneTime()?.username }}</span><code>{{ oneTime()?.password }}</code></div><button class="btn ghost sm" @click="navigator.clipboard.writeText(oneTime()?.password || '')">Copy password</button><button class="btn ghost sm" @click="oneTime.set(null)">Hide</button>
|
|
251
|
+
</section>
|
|
252
|
+
</main>
|
|
253
|
+
<style>
|
|
254
|
+
.data-wrap{max-width:1500px}.header-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.summary{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:14px}.summary article{display:grid;gap:5px}.summary strong{font-size:28px}.summary small,.service-grid small,.detail small{color:var(--txt3)}.create-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:14px}.create-grid label{display:grid;gap:5px;color:var(--txt3);font-size:11px}.create-grid input,.create-grid select,.filters input,.filters select,.operation-form input,.operation-form select{width:100%;background:var(--bg2);border:1px solid var(--line);border-radius:8px;color:var(--txt);padding:9px}.create-grid label span{font-size:10px}.create-grid .check,.operation-form .check{display:flex;align-items:center;gap:7px}.create-grid .check input,.operation-form .check input{width:auto}.wide{grid-column:1/-1}.filters{display:grid;grid-template-columns:minmax(260px,1fr) repeat(3,170px);gap:8px;margin-bottom:12px}.workspace{display:grid;grid-template-columns:1fr;gap:12px;align-items:start}.workspace.hasDetail{grid-template-columns:minmax(0,1.6fr) minmax(360px,.8fr)}.data-cards{display:grid;gap:10px}.data-cards>.empty{max-width:none;width:100%}.service-card.selected{border-color:var(--accent)}.service-head,.detail-head{display:flex;justify-content:space-between;gap:10px;align-items:center}.service-head>div{display:flex;gap:7px;align-items:center;flex-wrap:wrap}.service-head h2{font-size:17px;margin:0 0 0 3px}.service-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin:14px 0}.service-grid>div{display:grid;gap:4px;min-width:0}.service-grid b,.service-grid span{overflow-wrap:anywhere}.service-grid span{color:var(--txt3);font-size:11px}.actions,.operation-picker{display:flex;gap:6px;flex-wrap:wrap}.operation-picker{margin:10px 0}.operation-picker .active{border-color:var(--accent);color:var(--accent)}.operation-form{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:10px 0}.operation-form label{display:grid;gap:4px;color:var(--txt3);font-size:11px}.operation-form button{grid-column:1/-1}.action-preview{background:var(--bg2);border-radius:8px;padding:10px}.observation-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:7px;margin-top:9px}.observation-grid>div{display:grid;gap:4px;background:var(--bg2);border-radius:7px;padding:8px;min-width:0}.observation-grid code{white-space:pre-wrap;overflow-wrap:anywhere;font-size:10px}.detail{position:sticky;top:10px;display:grid;gap:18px}.detail h2,.detail h3{margin:0}.capability,.detail>div+div{border-top:1px solid var(--line);padding-top:14px}.capability ul{padding-left:18px;color:var(--txt2)}.connection{display:grid;gap:6px;padding:9px 0;border-bottom:1px solid var(--line)}.connection code{white-space:normal;overflow-wrap:anywhere;font-size:10px}.connection button{justify-self:start}.operation{display:flex;gap:7px;align-items:center;flex-wrap:wrap;padding:7px 0}.operation small{margin-left:auto}.credential{position:fixed;z-index:30;right:18px;bottom:18px;width:min(620px,calc(100vw - 36px));display:grid;grid-template-columns:1fr auto;gap:10px;box-shadow:0 16px 60px #0009;border-color:var(--accent)}.credential h2,.credential p{margin:0}.secret{grid-column:1/-1;display:grid;gap:5px;background:var(--bg2);padding:10px;border-radius:8px}.secret code{overflow-wrap:anywhere}.mono,code{font-family:var(--mono)}@media(max-width:1100px){.workspace.hasDetail{grid-template-columns:1fr}.detail{position:static}.create-grid{grid-template-columns:repeat(2,1fr)}.service-grid{grid-template-columns:repeat(2,1fr)}}@media(max-width:760px){.summary{grid-template-columns:repeat(2,1fr)}.filters{grid-template-columns:1fr}.service-grid,.create-grid,.operation-form,.observation-grid{grid-template-columns:1fr}.create-grid>*{grid-column:1!important}}@media(max-width:460px){.summary{grid-template-columns:1fr}.credential{grid-template-columns:1fr}}
|
|
255
|
+
</style>
|
|
256
|
+
</body>
|
|
257
|
+
</html>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
import { usePolling } from '../../functions/usePolling.ts'
|
|
3
|
+
const volumes=state([]),resources=state([]),operations=state([]),recoveryPoints=state([]),providers=state([]),selected=state(null),search=state(''),notice=state(''),error=state(''),busy=state(false),createOpen=state(false),mountOpen=state(false)
|
|
4
|
+
async function api(url,options){const response=await fetch(url,options),body=await response.json();if(!response.ok||body.ok===false)throw new Error(body.error||'Request failed.');return body}
|
|
5
|
+
async function load(){const data=await api('/api/volumes');volumes.set(data.volumes||[]);resources.set(data.resources||[]);operations.set(data.operations||[]);recoveryPoints.set(data.recoveryPoints||[]);providers.set(data.providers||[]);if(selected())selected.set((data.volumes||[]).find(item=>item.id===selected().id)||null)}
|
|
6
|
+
const {pollingState,pollingError,lastUpdatedAt,refresh}=usePolling(load,15000)
|
|
7
|
+
function body(form){const value=Object.fromEntries(new FormData(form));for(const key of ['capacityBytes','uid','gid'])if(value[key])value[key]=Number(value[key]);for(const key of ['encrypted','readOnly'])value[key]=value[key]==='on';return value}
|
|
8
|
+
async function mutate(url,value,method='POST'){busy.set(true);error.set('');try{const result=await api(url,{method,headers:{'content-type':'application/json'},body:JSON.stringify(value)});notice.set(result.operation?`Queued ${result.operation.kind} as ${result.operation.id}.`:'Saved.');await refresh();return result}catch(cause){error.set(cause.message)}finally{busy.set(false)}}
|
|
9
|
+
async function create(event){event.preventDefault();const value=body(event.currentTarget),driver=String(value.driver||'docker|docker').split('|');value.provider=driver[0];value.type=driver[1];delete value.driver;const result=await mutate('/api/volumes',value);if(result){event.currentTarget.reset();createOpen.set(false);selected.set(result.volume)}}
|
|
10
|
+
async function attach(event){event.preventDefault();const value=body(event.currentTarget);value.volumeId=selected().id;const result=await mutate('/api/volumes/attach',value);if(result){event.currentTarget.reset();mountOpen.set(false)}}
|
|
11
|
+
async function detach(item){if(!confirm(`Drain and unmount ${item.targetPath}, then detach it?`))return;await mutate('/api/volumes/detach',{volumeId:selected().id,attachmentId:item.id,drained:true,unmounted:true})}
|
|
12
|
+
async function resize(){const bytes=Number(prompt('New capacity in bytes (volumes cannot shrink)',String(selected().capacityBytes||'')));if(bytes)await mutate('/api/volumes/resize',{volumeId:selected().id,capacityBytes:bytes,drained:selected().capabilities?.resize?.online===false})}
|
|
13
|
+
async function snapshot(){await mutate('/api/volumes/snapshot',{volumeId:selected().id})}
|
|
14
|
+
async function restore(item){const name=prompt('Name for the restored replacement volume',`${selected().name}-restored`);if(name)await mutate('/api/volumes/restore',{snapshotId:item.id,name})}
|
|
15
|
+
async function remove(){const item=selected(),confirmation=prompt(`Type ${item.name} to permanently delete this detached volume.`)||'';if(confirmation!==item.name)return;const override=!item.lastBackupAt&&confirm('No recent verified backup is linked. Explicitly delete without a backup?');await mutate('/api/volumes',{volumeId:item.id,confirmation,backupOverride:override,backupOverrideConfirmation:override?`delete without backup ${item.name}`:undefined},'DELETE')}
|
|
16
|
+
async function discover(){await mutate('/api/volumes/discover',{})}
|
|
17
|
+
async function adopt(item){const name=prompt('Adopt this provider volume as',item.name.replace(/^orphan-/,'').slice(0,63));if(name)await mutate('/api/volumes/adopt',{volumeId:item.id,name})}
|
|
18
|
+
function filtered(){const q=search().trim().toLowerCase();return volumes().filter(item=>!q||`${item.name} ${item.type} ${item.provider} ${item.status}`.toLowerCase().includes(q))}
|
|
19
|
+
function tone(status){return ['available','attached'].includes(status)?'ok':['error'].includes(status)?'bad':'warn'}
|
|
20
|
+
function bytes(value){if(value==null)return'unknown';const units=['B','KB','MB','GB','TB'];let size=Number(value),unit=0;while(size>=1024&&unit<units.length-1){size/=1024;unit++}return`${size.toFixed(unit?1:0)} ${units[unit]}`}
|
|
21
|
+
function operationFor(item){return operations().find(value=>value.operation?.input?.volumeId===item.id&&['queued','running'].includes(value.operation?.state))}
|
|
22
|
+
</script>
|
|
23
|
+
<!DOCTYPE html><html lang="en" data-theme="dark"><head>@include('../partials/head', { title: 'ts-cloud · Persistent volumes' })</head><body>
|
|
24
|
+
@include('../partials/nav', { active: 'volumes', env: 'production' })
|
|
25
|
+
<main class="volume-wrap wrap" id="main-content">
|
|
26
|
+
<div class="crumbs">Infrastructure <span class="sep">/</span> Persistent volumes</div>
|
|
27
|
+
<header class="page"><div><h1>Persistent volumes</h1><p>Storage with stable identity, explicit consumers, recovery status, capability-aware operations, and safe retirement.</p></div><div class="header-actions"><span class="pill" :class="pollingState()==='live'?'ok':'warn'">{{ pollingState() }}</span><small>{{ lastUpdatedAt()?.toLocaleTimeString()||'connecting' }}</small><button class="btn ghost sm" @click="discover()">Discover orphans</button><button class="btn sm" @click="createOpen.set(!createOpen())">{{ createOpen()?'Close':'New volume' }}</button></div></header>
|
|
28
|
+
<p class="notice" role="status" @show="!!notice()">{{ notice() }}</p><p class="bad notice" role="alert" @show="!!error()||!!pollingError()">{{ error()||pollingError() }}</p>
|
|
29
|
+
<section class="summary"><article class="panel"><b>Volumes</b><strong>{{ volumes().length }}</strong><small>release-independent</small></article><article class="panel"><b>Attached</b><strong>{{ volumes().filter(item=>item.attachments.some(m=>m.observedState==='attached')).length }}</strong><small>active consumers</small></article><article class="panel"><b>Protected</b><strong>{{ volumes().filter(item=>item.backupState==='protected').length }}</strong><small>recent verified backup</small></article><article class="panel"><b>Orphans</b><strong>{{ volumes().filter(item=>item.orphaned).length }}</strong><small>adopt, attach, snapshot, or delete</small></article></section>
|
|
30
|
+
<form class="create-volume panel" @show="createOpen()" @submit="create($event)"><div class="wide"><h2>Create persistent volume</h2><p class="note">The identity remains stable through releases. Provider limits are shown before unsupported operations can be submitted.</p></div><label>Name<input name="name" pattern="[a-z0-9][a-z0-9_.-]{1,62}" required placeholder="uploads"></label><label>Driver<select name="driver" required><template :for="driver in providers()"><option :value="driver.provider+'|'+driver.type">{{ driver.provider }} · {{ driver.type }}</option></template></select></label><label>Capacity bytes<input name="capacityBytes" type="number" min="1" placeholder="optional"></label><label>Filesystem<input name="filesystem" placeholder="provider default"></label><label class="check"><input name="encrypted" type="checkbox"> Encrypted where supported</label><button class="btn" type="submit" :disabled="busy()">Queue create</button></form>
|
|
31
|
+
<section class="filters panel"><input type="search" aria-label="Search volumes" placeholder="Search volume, driver, or state" @input="search.set($event.currentTarget.value)"><span>{{ filtered().length }} shown</span></section>
|
|
32
|
+
<div class="volume-workspace" :class="{detail:!!selected()}"><section class="volume-cards"><template :for="item in filtered()"><article class="panel volume-card" :class="{selected:selected()?.id===item.id}"><div class="volume-head"><div><span class="pill" :class="tone(item.status)">{{ item.status }}</span><span class="pill">{{ item.provider }} · {{ item.type }}</span><h2>{{ item.name }}</h2></div><button class="btn ghost sm" @click="selected.set(item)">Details</button></div><div class="volume-facts"><div><small>Capacity / usage</small><b>{{ bytes(item.usedBytes) }} / {{ bytes(item.capacityBytes) }}</b><span>{{ item.usagePercent==null?'usage unavailable':item.usagePercent+'%' }}</span></div><div><small>Consumers</small><b>{{ item.attachments.filter(m=>m.observedState==='attached').length }}</b><span>{{ item.attachments.map(m=>m.targetPath).join(', ')||'detached' }}</span></div><div><small>Recovery</small><b>{{ item.backupState }}</b><span>{{ item.snapshots.length }} snapshots · {{ item.lastBackupAt||'never backed up' }}</span></div></div><p class="notice warn" @show="!!operationFor(item)">{{ operationFor(item)?.operation?.kind }} · {{ operationFor(item)?.operation?.state }}</p><div class="actions" @show="item.orphaned"><button class="btn sm" @click="adopt(item)">Adopt safely</button></div></article></template><div class="empty panel" @show="!filtered().length"><strong>No persistent volumes</strong><span>Create one or discover provider resources without deleting anything automatically.</span></div></section>
|
|
33
|
+
<aside class="panel volume-detail" @show="!!selected()"><div class="detail-head"><div><small>Selected volume</small><h2>{{ selected()?.name }}</h2></div><button class="btn ghost sm" @click="selected.set(null)">Close</button></div><div class="capabilities"><h3>Capabilities</h3><template :for="entry in Object.entries(selected()?.capabilities||{})"><p><b>{{ entry[0] }}</b><span :class="entry[1].supported?'ok-text':'muted'">{{ entry[1].supported?'supported':entry[1].reason }}</span></p></template></div><div class="detail-actions"><button class="btn sm" @click="mountOpen.set(!mountOpen())" :disabled="!selected()?.capabilities?.attach?.supported">Attach</button><button class="btn ghost sm" @click="resize()" :disabled="!selected()?.capabilities?.resize?.supported">Resize</button><button class="btn ghost sm" @click="snapshot()" :disabled="!selected()?.capabilities?.snapshot?.supported">Snapshot</button><a class="btn ghost sm" href="/data/backups">Backups</a><button class="btn danger sm" @click="remove()" :disabled="selected()?.attachments.some(item=>item.observedState==='attached')">Delete</button></div>
|
|
34
|
+
<form class="mount-form" @show="mountOpen()" @submit="attach($event)"><h3>Mount editor</h3><label>Workload<select name="resourceId" required><template :for="resource in resources()"><option :value="resource.id">{{ resource.name }} · {{ resource.kind }}</option></template></select></label><label>Target path<input name="targetPath" required placeholder="/app/uploads"></label><label>UID<input name="uid" type="number" min="0"></label><label>GID<input name="gid" type="number" min="0"></label><label>Mode<input name="mode" pattern="0?[0-7]{3}" placeholder="0750"></label><label>Propagation<select name="propagation"><option>private</option><option>rprivate</option><option>shared</option><option>rshared</option></select></label><label class="check"><input name="readOnly" type="checkbox"> Read only</label><button class="btn" type="submit">Validate and attach</button></form>
|
|
35
|
+
<section><h3>Consumers</h3><template :for="mount in selected()?.attachments||[]"><article class="mount-row"><div><b>{{ resources().find(item=>item.id===mount.resourceId)?.name||mount.resourceId }}</b><code>{{ mount.targetPath }}</code><small>{{ mount.readOnly?'read only':'read/write' }} · {{ mount.observedState }}</small></div><button class="btn danger sm" @click="detach(mount)" :disabled="mount.observedState!=='attached'">Drain & detach</button></article></template><p class="muted" @show="!selected()?.attachments?.length">No workloads consume this volume.</p></section>
|
|
36
|
+
<section><h3>Recovery points</h3><template :for="snapshot in selected()?.snapshots||[]"><p><b>{{ snapshot.status }}</b> {{ snapshot.name }} <button class="btn ghost sm" @click="restore(snapshot)" :disabled="!selected()?.capabilities?.restore?.supported">Restore replacement</button></p></template><template :for="point in recoveryPoints().filter(item=>item.resourceId===selected()?.resourceId)"><p><b>{{ point.verificationState }}</b> {{ point.pointInTime }}</p></template><p class="muted" @show="!selected()?.snapshots?.length">Link a backup policy for verified recovery and restore drills.</p></section>
|
|
37
|
+
</aside></div>
|
|
38
|
+
</main><style>
|
|
39
|
+
.volume-wrap{padding-bottom:5rem}.header-actions,.actions,.detail-actions{display:flex;gap:.55rem;align-items:center;flex-wrap:wrap}.summary{display:grid;grid-template-columns:repeat(4,1fr);gap:1rem;margin:1rem 0}.summary article{display:grid;gap:.3rem}.summary strong{font-size:1.8rem}.create-volume,.mount-form{display:grid;grid-template-columns:repeat(2,1fr);gap:1rem;margin:1rem 0}.wide{grid-column:1/-1}.create-volume label,.mount-form label{display:grid;gap:.35rem}.filters{display:flex;gap:1rem;align-items:center;margin:1rem 0}.filters input{flex:1}.volume-workspace{display:grid}.volume-workspace.detail{grid-template-columns:minmax(0,1fr) minmax(320px,430px);gap:1rem}.volume-cards{display:grid;gap:1rem}.volume-card{display:grid;gap:1rem}.volume-head,.detail-head{display:flex;justify-content:space-between;gap:1rem}.volume-head h2{margin:.5rem 0 0}.volume-facts{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}.volume-facts div{display:grid;gap:.25rem}.volume-detail{position:sticky;top:1rem;align-self:start;display:grid;gap:1.25rem}.capabilities p,.mount-row{display:flex;justify-content:space-between;gap:1rem;border-bottom:1px solid var(--border);padding:.55rem 0}.mount-row div{display:grid;gap:.25rem}.ok-text{color:var(--success)}.muted{color:var(--muted)}@media(max-width:900px){.summary{grid-template-columns:repeat(2,1fr)}.volume-workspace.detail{grid-template-columns:1fr}.volume-detail{position:static}.volume-facts{grid-template-columns:1fr}}@media(max-width:560px){.summary,.create-volume,.mount-form{grid-template-columns:1fr}.wide{grid-column:auto}}
|
|
40
|
+
</style></body></html>
|
|
@@ -100,6 +100,8 @@ const resourceChart = renderResourceBars([
|
|
|
100
100
|
const initialData = __d ?? null
|
|
101
101
|
</script>
|
|
102
102
|
<script client>
|
|
103
|
+
import { usePolling } from '../functions/usePolling.ts'
|
|
104
|
+
|
|
103
105
|
// Live at-a-glance metrics: seeded from build-time data (stx server→client
|
|
104
106
|
// bridge) so the cards render real numbers on first paint, then polled from
|
|
105
107
|
// /api/dashboard-data - bind cards, health pill, indicator, and service
|
|
@@ -126,11 +128,14 @@ const cpuSub = derived(() => { const m = (data() && data().systemMetrics) || {};
|
|
|
126
128
|
const memSub = derived(() => { const m = (data() && data().systemMetrics) || {}; return liveData() ? ' · ' + gbVal(m.memUsedMb) + '/' + gbVal(m.memTotalMb) + ' GB' : 'probe unavailable' })
|
|
127
129
|
const overallC = derived(() => { if (!liveData()) return 'warn'; const r = { ok: 0, warn: 1, bad: 2 }; return [pTone(loadP()), pTone(memP()), pTone(diskP())].sort((a, b) => r[b] - r[a])[0] })
|
|
128
130
|
const overallWordC = derived(() => liveData() ? ({ ok: 'healthy', warn: 'elevated', bad: 'critical' }[overallC()]) : 'probe unavailable')
|
|
129
|
-
const indicator = derived(() => liveData() ? 'live' : 'snapshot')
|
|
130
131
|
const servicesC = derived(() => (data() && data().services) || [])
|
|
131
132
|
function svcTone(s) { return (s === 'running' || s === 'active') ? 'ok' : ((s === 'failed' || s === 'stopped') ? 'bad' : 'warn') }
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
const { pollingState, pollingError } = usePolling(async () => {
|
|
134
|
+
const response = await fetch('/api/dashboard-data')
|
|
135
|
+
if (!response.ok) throw new Error('Dashboard refresh failed with HTTP ' + response.status + '.')
|
|
136
|
+
data.set(await response.json())
|
|
137
|
+
}, 10000)
|
|
138
|
+
const indicator = derived(() => pollingState() === 'stale' ? 'stale' : liveData() ? 'live' : 'snapshot')
|
|
134
139
|
</script>
|
|
135
140
|
<!DOCTYPE html>
|
|
136
141
|
<html lang="en" data-theme="dark">
|
|
@@ -153,7 +158,7 @@ onMount(() => { refresh(); const id = setInterval(refresh, 10000); return () =>
|
|
|
153
158
|
<span class="pill" :class="overallC()">{{ overallWordC() }}</span>
|
|
154
159
|
</div>
|
|
155
160
|
</div>
|
|
156
|
-
<div class="header-actions"><span class="env" title="Auto-refreshing every 10s">{{ indicator() }}</span><a class="btn ghost" href="/server/actions">Operations</a></div>
|
|
161
|
+
<div class="header-actions"><span class="env" :class="{ bad: pollingState() === 'stale' }" :title="pollingState() === 'stale' ? pollingError() : 'Auto-refreshing every 10s'">{{ indicator() }}</span><a class="btn ghost" href="/server/actions">Operations</a></div>
|
|
157
162
|
</header>
|
|
158
163
|
|
|
159
164
|
<div class="grid cards">
|
|
@@ -172,7 +177,7 @@ onMount(() => { refresh(); const id = setInterval(refresh, 10000); return () =>
|
|
|
172
177
|
<div class="stat">{{ pDisp(diskP()) }}<small></small></div>
|
|
173
178
|
<div :class="barCss(diskP())"><i :style="barStyle(diskP())"></i></div>
|
|
174
179
|
</a>
|
|
175
|
-
<a class="panel" href="/
|
|
180
|
+
<a class="panel" href="/data/backups">
|
|
176
181
|
<h3>Backups</h3>
|
|
177
182
|
<div class="stat">{{ backup.size }}</div>
|
|
178
183
|
<p style="color: var(--txt2); font-size: 13px; margin-top: 8px;">Last {{ backup.last }}</p>
|
|
@@ -215,6 +220,7 @@ onMount(() => { refresh(); const id = setInterval(refresh, 10000); return () =>
|
|
|
215
220
|
<div class="item">{{ svc.name }} <span class="pill" :class="svcTone(svc.status)">{{ svc.status }}</span></div>
|
|
216
221
|
</template>
|
|
217
222
|
</div>
|
|
223
|
+
<div class="compact empty" @show="servicesC().length === 0"><strong>No managed services</strong><span>Configured services will appear after the next successful server probe.</span></div>
|
|
218
224
|
</div>
|
|
219
225
|
</div>
|
|
220
226
|
|
|
@@ -240,9 +246,10 @@ onMount(() => { refresh(); const id = setInterval(refresh, 10000); return () =>
|
|
|
240
246
|
<div class="section">
|
|
241
247
|
<h2>Sites <a class="more" href="/server/sites">View all →</a></h2>
|
|
242
248
|
<div class="panel">
|
|
249
|
+
@if (sites.length)
|
|
243
250
|
<table>
|
|
244
251
|
<thead>
|
|
245
|
-
<tr><th>Site</th><th>Route</th><th>Kind</th><th>Runtime</th><th>TLS</th><th>Deploy</th><th>Status</th></tr>
|
|
252
|
+
<tr><th scope="col">Site</th><th scope="col">Route</th><th scope="col">Kind</th><th scope="col">Runtime</th><th scope="col">TLS</th><th scope="col">Deploy</th><th scope="col">Status</th></tr>
|
|
246
253
|
</thead>
|
|
247
254
|
<tbody>
|
|
248
255
|
@foreach (sites as site)
|
|
@@ -264,6 +271,9 @@ onMount(() => { refresh(); const id = setInterval(refresh, 10000); return () =>
|
|
|
264
271
|
@endforeach
|
|
265
272
|
</tbody>
|
|
266
273
|
</table>
|
|
274
|
+
@else
|
|
275
|
+
<div class="empty"><strong>No sites configured</strong><span>Add a site to expose an application or static build from this server.</span></div>
|
|
276
|
+
@endif
|
|
267
277
|
</div>
|
|
268
278
|
</div>
|
|
269
279
|
|
|
@@ -289,13 +299,17 @@ onMount(() => { refresh(); const id = setInterval(refresh, 10000); return () =>
|
|
|
289
299
|
<div>
|
|
290
300
|
<h2>Queues & Scheduler <a class="more" href="/server/workers">Details →</a></h2>
|
|
291
301
|
<div class="panel">
|
|
292
|
-
@
|
|
293
|
-
|
|
294
|
-
|
|
302
|
+
@if (workers.length)
|
|
303
|
+
@foreach (workers as w)
|
|
304
|
+
<div class="kv"><span>queue:{{ w.name }} ×{{ w.processes }}</span> <span class="pill {{ statusTone(w.status) }}">{{ w.status }}</span></div>
|
|
305
|
+
@endforeach
|
|
306
|
+
@else
|
|
307
|
+
<div class="compact empty"><strong>No queue workers</strong><span>Add worker definitions to a site to process background jobs.</span></div>
|
|
308
|
+
@endif
|
|
295
309
|
<div class="kv"><span>scheduler</span> @if (scheduler.enabled)<span class="ok pill">on · {{ scheduler.lastRun }}</span>@else<span class="pill warn">off</span>@endif</div>
|
|
296
310
|
</div>
|
|
297
311
|
<div style="height: 16px"></div>
|
|
298
|
-
<h2>Backups <a class="more" href="/
|
|
312
|
+
<h2>Backups <a class="more" href="/data/backups">Details →</a></h2>
|
|
299
313
|
<div class="panel">
|
|
300
314
|
<div class="kv"><span>destination</span> <span class="mono">{{ backup.destination }}</span></div>
|
|
301
315
|
<div class="kv"><span>retention</span> <span>{{ backup.retention }} copies</span></div>
|