wowowowowo
This commit is contained in:
296
docs/managed-registry.md
Normal file
296
docs/managed-registry.md
Normal file
@@ -0,0 +1,296 @@
|
||||
# Managed Registry Plan
|
||||
|
||||
Keystone should be self-hosted first. A fresh install should include a working build and image pipeline without requiring the user to bring an external Docker registry, S3 bucket, or separate build server.
|
||||
|
||||
## Product Principles
|
||||
|
||||
- The Keystone control node is the default build node.
|
||||
- Keystone provides a first-party managed Docker registry by default.
|
||||
- The managed registry stores images on local disk first.
|
||||
- The registry storage path must be configurable for mounted VPS volumes.
|
||||
- External registries, S3-backed storage, and dedicated build nodes are optional advanced features.
|
||||
- Multi-server deployments should work out of the box after Keystone is installed.
|
||||
- Registry credentials must not be persisted in operation scripts, logs, or UI-visible output.
|
||||
- Old build artifacts should be pruned automatically, retaining the latest 3 successful artifacts per environment by default.
|
||||
- Build and deploy should be separate phases, even when started by one user action.
|
||||
- Users should be able to connect an existing Ubuntu server as a Keystone node without using a cloud provider integration.
|
||||
|
||||
## Default Self-Hosted Shape
|
||||
|
||||
When Keystone is installed on a server, that server becomes the control node. The install process should prepare:
|
||||
|
||||
- Keystone application services.
|
||||
- Docker and Docker Compose.
|
||||
- A managed `registry:2` service.
|
||||
- Local registry storage.
|
||||
- Generated registry credentials.
|
||||
- A default build capability on the control node.
|
||||
|
||||
This is separate from server provisioning. Keystone needs two scripts/flows:
|
||||
|
||||
- `install-keystone.sh` installs Keystone itself on the control node.
|
||||
- The remote provisioning script prepares other servers so they can be managed by Keystone.
|
||||
|
||||
Remote provisioning should continue to install Docker, configure SSH access, prepare the `keystone` user, and link the server back to Keystone. It should not be responsible for installing the Keystone application itself.
|
||||
|
||||
Default settings:
|
||||
|
||||
```text
|
||||
Build node: Keystone control node
|
||||
Registry: registry:2 managed by Keystone
|
||||
Registry storage driver: local
|
||||
Registry storage path: /home/keystone/registry/data
|
||||
Image retention: latest 3 successful artifacts per environment
|
||||
Auth: generated htpasswd credentials managed by Keystone
|
||||
```
|
||||
|
||||
The install flow should allow overriding the storage path, for example:
|
||||
|
||||
```text
|
||||
/mnt/keystone-registry
|
||||
```
|
||||
|
||||
This lets users place registry image data on a mounted VPS volume while keeping Keystone's default behavior simple.
|
||||
|
||||
## Default Image Flow
|
||||
|
||||
```text
|
||||
Git repository
|
||||
-> Keystone control node builds Docker image
|
||||
-> Keystone pushes image to the managed registry
|
||||
-> Target servers pull image from the managed registry
|
||||
-> Target servers run containers
|
||||
```
|
||||
|
||||
The build node and registry are separate concepts:
|
||||
|
||||
- Build node: where `git clone`, `docker build`, and `docker push` run.
|
||||
- Registry: where built images are stored and later pulled from.
|
||||
|
||||
The control node is the default build node, but users should later be able to add a dedicated build node from Keystone settings.
|
||||
|
||||
The running Keystone server is the control node. This does not necessarily need to be represented as a normal deploy target server at first. A lightweight installation/control-node setting may be enough until Keystone needs HA control-plane support.
|
||||
|
||||
If Keystone later supports HA control planes, the control node concept should become more explicit so the app can distinguish between:
|
||||
|
||||
- The current web/queue/scheduler node.
|
||||
- The active registry host.
|
||||
- The default build node.
|
||||
- Runtime nodes used for deployed applications.
|
||||
|
||||
## Registry Exposure
|
||||
|
||||
The managed registry should be exposed over HTTPS where possible, ideally behind the control node's web proxy, for example:
|
||||
|
||||
```text
|
||||
registry.example.com
|
||||
```
|
||||
|
||||
Avoid defaulting to a plain `host:5000` registry if possible. Plain HTTP registries require Docker daemon insecure-registry configuration on every build and target server, which adds onboarding friction.
|
||||
|
||||
Target servers must be able to reach the registry URL before they can deploy images built by Keystone.
|
||||
|
||||
## Authentication
|
||||
|
||||
Use `registry:2` htpasswd authentication for the first version.
|
||||
|
||||
Keystone should:
|
||||
|
||||
- Generate registry credentials.
|
||||
- Write the registry htpasswd file during provisioning.
|
||||
- Store credentials encrypted.
|
||||
- Configure build and target servers for registry access.
|
||||
- Use `docker login --password-stdin` when login is needed.
|
||||
|
||||
Do not inline registry passwords into persisted operation scripts. Operation steps are stored and may be visible in the UI or logs.
|
||||
|
||||
Preferred approaches:
|
||||
|
||||
- Configure Docker auth on each server through a separate secure action.
|
||||
- Or write root-owned / user-owned credential files on the server and have deployment scripts read from those files.
|
||||
|
||||
Token auth can be considered later if Keystone needs per-repository or per-server scoped credentials. It should not be part of the first implementation.
|
||||
|
||||
## Build Planning
|
||||
|
||||
Build planning should assume a default managed registry exists after install.
|
||||
|
||||
For the default path:
|
||||
|
||||
- Build strategy: build on control node.
|
||||
- Registry: managed local registry.
|
||||
- Artifact reference: full managed registry image reference.
|
||||
|
||||
Multi-server deploys should no longer block because the user has not configured an external registry. They should only block if the managed registry is missing, unhealthy, or unreachable.
|
||||
|
||||
External registries should remain available as an advanced override.
|
||||
|
||||
Build strategy should not be exposed to users as low-level values such as `target_server`, `dedicated_builder`, or `external_registry`. The UI should expose intent instead:
|
||||
|
||||
- Default build node.
|
||||
- Specific build node.
|
||||
- External registry override.
|
||||
|
||||
Internally, build planning can still map those choices to implementation strategies.
|
||||
|
||||
## Build Execution
|
||||
|
||||
The default build execution should:
|
||||
|
||||
1. Select the configured build node, defaulting to the control node.
|
||||
2. Clone the application repository.
|
||||
3. Render the Keystone Dockerfile.
|
||||
4. Log in to the managed registry.
|
||||
5. Build the image.
|
||||
6. Tag the image using the managed registry reference.
|
||||
7. Push the image.
|
||||
8. Resolve and store the registry manifest digest.
|
||||
|
||||
Example flow:
|
||||
|
||||
```bash
|
||||
docker login registry.example.com --username keystone --password-stdin
|
||||
docker build --file Dockerfile.keystone --tag registry.example.com/application:aaaaaaaaaaaa .
|
||||
docker push registry.example.com/application:aaaaaaaaaaaa
|
||||
docker manifest inspect registry.example.com/application:aaaaaaaaaaaa
|
||||
```
|
||||
|
||||
The stored digest must be the registry manifest digest, not a local image ID. Digest-based pulls and registry manifest deletion depend on this being correct.
|
||||
|
||||
Build execution should create a build operation that can succeed or fail independently from deployment. A deployment can then depend on a successful build artifact.
|
||||
|
||||
## Deploy Execution
|
||||
|
||||
Target servers should pull immutable image references from the managed registry.
|
||||
|
||||
Deploy execution should:
|
||||
|
||||
1. Ensure the target server has registry auth configured.
|
||||
2. Pull the exact image digest.
|
||||
3. Render Compose with the full registry image reference.
|
||||
4. Start or update containers.
|
||||
|
||||
Example pull reference:
|
||||
|
||||
```text
|
||||
registry.example.com/application@sha256:...
|
||||
```
|
||||
|
||||
Compose should use the full registry reference, not only `sha256:...`.
|
||||
|
||||
Deploy execution should be a separate operation phase from build execution. The deploy phase should consume a completed build artifact and should not be responsible for building the artifact itself.
|
||||
|
||||
Operations should have explicit execution targets. Inferring the SSH target only from the operation target model becomes fragile once Keystone has build nodes, registry maintenance, and runtime deployment steps.
|
||||
|
||||
Each operation or operation step should be able to declare where it runs:
|
||||
|
||||
- Control node.
|
||||
- Build node.
|
||||
- Runtime server.
|
||||
- Specific server.
|
||||
|
||||
## Pruning And Retention
|
||||
|
||||
Default retention should keep the latest 3 successful build artifacts per environment.
|
||||
|
||||
Pruning should also retain:
|
||||
|
||||
- Any artifact currently referenced by a service's available image digest.
|
||||
- Any artifact currently referenced by a service's current image digest.
|
||||
- Any artifact needed for an active deployment operation.
|
||||
|
||||
Pruning should remove old registry manifests first, then run registry garbage collection to remove unreferenced blobs from local disk.
|
||||
|
||||
`registry:2` requires deletion to be enabled:
|
||||
|
||||
```text
|
||||
REGISTRY_STORAGE_DELETE_ENABLED=true
|
||||
```
|
||||
|
||||
Garbage collection is safest when the registry is not accepting writes. The first implementation should run cleanup during a controlled maintenance window, using a lock so pruning does not race with active builds or pushes.
|
||||
|
||||
Suggested cleanup flow:
|
||||
|
||||
1. Acquire a registry maintenance lock.
|
||||
2. Find prunable artifacts by environment retention rules.
|
||||
3. Delete old manifests through the registry API.
|
||||
4. Stop the registry or put it in a safe maintenance state.
|
||||
5. Run registry garbage collection.
|
||||
6. Restart the registry.
|
||||
7. Mark artifacts as pruned or delete their records.
|
||||
8. Release the lock.
|
||||
|
||||
## Future Extensions
|
||||
|
||||
These should be optional settings, not onboarding requirements:
|
||||
|
||||
- Dedicated build nodes.
|
||||
- S3-compatible registry storage.
|
||||
- External registries such as GHCR, Gitea, Docker Hub, or generic registries.
|
||||
- Separate push and pull credentials.
|
||||
- Credential rotation.
|
||||
- Per-server or per-repository scoped auth.
|
||||
- Configurable retention per application or environment.
|
||||
|
||||
The first version should optimize for a self-hosted user installing Keystone on a VPS and being able to deploy with minimal additional setup.
|
||||
|
||||
## Existing Server Provisioning
|
||||
|
||||
Keystone should support connecting an existing Ubuntu server as a managed node. This is important for users running VPSs, Proxmox VMs, homelab hardware, or manually provisioned servers.
|
||||
|
||||
The flow should be:
|
||||
|
||||
1. User creates a server record in Keystone as an existing server.
|
||||
2. Keystone shows a one-time provisioning command.
|
||||
3. User runs the command on the server as root or a sudo-capable user.
|
||||
4. The script installs Docker and required packages.
|
||||
5. The script creates/configures the `keystone` user.
|
||||
6. The script installs Keystone's management SSH key.
|
||||
7. The script calls back to Keystone with a one-time token.
|
||||
8. Keystone marks the server active.
|
||||
|
||||
This should sit alongside cloud-provider provisioning. Cloud providers can create the VM automatically, but the same remote preparation logic should be reused where possible.
|
||||
|
||||
Provisioning callbacks should not authenticate only by `server_id` or IP address. They should use a short-lived, single-use provisioning token tied to the server record.
|
||||
|
||||
Avoid passing sensitive values such as sudo passwords in URL query strings. Safer options include:
|
||||
|
||||
- Generate a short-lived provisioning token and pass only that in the URL.
|
||||
- Store sensitive bootstrap data server-side and let the provisioning script exchange the one-time token for the data it needs.
|
||||
- Prefer SSH key-based provider bootstrap where available instead of root password bootstrap.
|
||||
- If a password must be used, pass it over SSH stdin or an encrypted job payload, not through a script URL.
|
||||
|
||||
The remote provisioning script can still be downloaded from Keystone, but the URL should not contain long-lived secrets or reusable credentials.
|
||||
|
||||
### Sudo Password Handling
|
||||
|
||||
Keep the current Forge-like user model for now:
|
||||
|
||||
- Provisioned servers have a `keystone` user.
|
||||
- SSH login is key-only.
|
||||
- The generated sudo password is for the human user to SSH in and run elevated commands manually.
|
||||
- Keystone automation continues to use SSH key access and Docker/sudo-capable permissions as required.
|
||||
|
||||
This model is acceptable, but sudo password delivery should be hardened.
|
||||
|
||||
Laravel protections help with some leak paths:
|
||||
|
||||
- `ShouldBeEncrypted` protects queued job payloads.
|
||||
- Encrypted casts protect stored secrets.
|
||||
- Hidden model attributes avoid accidental serialization.
|
||||
- PHP `#[\SensitiveParameter]` can prevent secret values appearing in stack traces.
|
||||
|
||||
These protections do not cover query strings, shell process arguments, rendered scripts left on disk, reverse-proxy logs, or third-party request logging.
|
||||
|
||||
Minimal hardening plan:
|
||||
|
||||
1. Keep generating a sudo password for the provisioned `keystone` user.
|
||||
2. Keep flashing the sudo password to the user once after server creation.
|
||||
3. Add `#[\SensitiveParameter]` to job constructor parameters such as `rootPassword` and `sudoPassword`.
|
||||
4. Stop passing `sudo_password` in the provision script URL.
|
||||
5. Use a short-lived, single-use provisioning token in the URL instead.
|
||||
6. Store the sudo password encrypted server-side until the provisioning script is rendered or exchanged.
|
||||
7. Ensure the remote provisioning script deletes itself at the end of provisioning.
|
||||
8. Avoid writing the plaintext sudo password to logs or long-lived files.
|
||||
|
||||
The goal is to preserve the simple human-admin UX while removing avoidable secret exposure from URLs and leftover bootstrap artifacts.
|
||||
@@ -15,14 +15,14 @@ Conventions:
|
||||
|
||||
## 1. Global Navigation & Information Architecture
|
||||
|
||||
- **Partial — sidebar only exposes Dashboard + Servers.** `resources/js/components/AppSidebar.vue:19-37` builds a `mainNavItems` array with only `Dashboard` and `Servers`. There are no entries for `Applications`, `Operations`, `Onboarding`, or organisation `Settings`. The spec frames environments as the primary surface (§20 Phase 6: "Present environments as the primary application surface"). Add at minimum `Applications` and `Operations` items, plus a context switcher / link to onboarding while incomplete.
|
||||
- **Missing — no organisation switcher.** Multiple organisations are modeled (`Organisation::members()` on `app/Models/Organisation.php`), and the dashboard already supports multiple orgs (`resources/js/pages/Dashboard.vue:8-13`). After picking an org there is no way to switch without going back to `/dashboard`. Add a switcher in the sidebar header.
|
||||
- **Partial — active header navigation is still not environment-first.** The active `AppLayout` uses `resources/js/layouts/app/AppHeaderLayout.vue`, whose header navigation (`resources/js/components/AppHeader.vue`) exposes organisation, `Applications`, and `Servers` when an organisation is selected. There are still no entries for `Operations` or `Onboarding`, and environments are only reachable after choosing an application. The inactive sidebar layout (`resources/js/components/AppSidebar.vue:19-37`) is even narrower, exposing only `Dashboard` and `Servers`. The spec frames environments as the primary surface (§20 Phase 6: "Present environments as the primary application surface"). Add an environment-primary route/navigation surface, plus `Operations` and onboarding access while setup is incomplete.
|
||||
- **Partial — organisation switcher exists only in the active header chrome.** Multiple organisations are modeled (`Organisation::members()` on `app/Models/Organisation.php`), and the active header (`resources/js/components/AppSidebarHeader.vue`) includes organisation/application/environment dropdowns. The inactive sidebar layout has no equivalent, and onboarding/operations are still absent from the switcher flow. Keep the header switcher if `AppHeaderLayout` remains the canonical layout; otherwise add parity to the sidebar chrome.
|
||||
- **Missing — no Operations index.** Operations are the spec's audit/execution backbone (§3) but the UI only surfaces them inline on `environments/Show.vue` and `servers/Show.vue`. There is no organisation-wide operations feed for triage. Add an `operations.index` view with filters (kind, status, target).
|
||||
- **Missing — no global empty/help state.** A fresh org with no servers/apps has no "Get started" CTA in the sidebar; user must guess to visit `/onboarding`. Promote the onboarding link until all onboarding steps are complete.
|
||||
- **Missing — no global empty/help state.** A fresh org with no servers/apps has no "Get started" CTA in the primary app chrome; user must guess to visit `/onboarding`. Promote the onboarding link until all onboarding steps are complete.
|
||||
|
||||
## 2. Onboarding (Spec §19)
|
||||
|
||||
- **Partial — onboarding page exists but is unreachable from primary nav.** `resources/js/pages/onboarding/Show.vue` is only reachable via the URL `/organisations/{id}/onboarding`. There is no link from `Dashboard`, `AppSidebar`, or `organisations/Show.vue`. Surface a persistent banner or sidebar entry while `nextStep` is non-terminal.
|
||||
- **Partial — onboarding page exists but is unreachable from primary nav.** `resources/js/pages/onboarding/Show.vue` is only reachable via the URL `/organisations/{id}/onboarding`. There is no link from `Dashboard`, the active header navigation, `AppSidebar`, or `organisations/Show.vue`. Surface a persistent banner or primary-nav entry while `nextStep` is non-terminal.
|
||||
- **Partial — onboarding "Provider" step routes to organisation show.** `app/Http/Controllers/OnboardingController.php:25` sets the Provider step `href` to `organisations.show`, but the Server Providers list there (`resources/js/pages/organisations/Show.vue:202-220`) has no Add button. There is no `providers.create` route or page. Either add a `ProviderController@create` + Vue page or make the step open an inline dialog.
|
||||
- **Missing — registry/source/server-create steps don't enforce a single org-level "default" once configured.** Spec §19 says registry is required for multi-server. The UI never blocks deployment on this — see Deployment Flow gap below.
|
||||
- **Missing — onboarding doesn't reflect deploy-key install step (§5).** The spec lists "Deploy key installation and verification" as a discrete step; onboarding shows none. Add a step gated on `applications.deploy_key_installed_at`.
|
||||
@@ -39,7 +39,7 @@ Conventions:
|
||||
- **Partial — deploy-key card disappears once installed.** `resources/js/pages/applications/Show.vue:46-75` only shows the deploy-key card when `application.deploy_key_public && !application.deploy_key_installed_at`. After install there is no way to view or rotate the key. Show key + `deploy_key_fingerprint` and a "Rotate" action permanently in an Application Settings tab.
|
||||
- **Missing — no fingerprint display.** The model stores `deploy_key_fingerprint` (`app/Models/Application.php`), but the UI never renders it. Surface beside the public key for verification.
|
||||
- **Missing — no way to re-run `git ls-remote` verification after install.** Verify button is gated by the same conditional in `applications/Show.vue:46`. Move it to an always-available action; spec §5 implies verification can be re-run.
|
||||
- **Missing — application creation does not pick a source provider.** `resources/js/pages/applications/Create.vue` collects `repository_url` as a free string. Source providers exist (§5: Gitea/GitHub/generic Git) but the form never references them — users have no guidance for which provider corresponds to the URL, and `application.source_provider_id` is not captured.
|
||||
- **Missing — application creation does not pick a source provider.** `resources/js/pages/applications/Create.vue` collects `repository_url` as a free string. Source providers exist (§5: Gitea/GitHub/generic Git) but the form never references them — users have no guidance for which provider corresponds to the URL, and the application schema/UI currently has no source-provider association.
|
||||
- **Missing — repository type selector.** Spec lists `repository_type` (§2 Application). UI hardcodes `RepositoryType::GIT` (`app/Http/Controllers/ApplicationController.php:39`). Even if Git is the only v1 type, the form should display the resolved type.
|
||||
|
||||
## 5. Applications & Environments (Spec §2, §6, §17)
|
||||
@@ -107,7 +107,7 @@ Conventions:
|
||||
|
||||
## 9. Servers (Spec §4)
|
||||
|
||||
- **Broken — `<template>` block has dangling fallback.** `resources/js/pages/servers/Show.vue:217` reads `<template> Something else </template>` outside any `v-if`/`v-else-if` chain. This is a stray Vue `<template>` rendered literally for any status that isn't `active` or `provisioning` — clean up or convert to `<template v-else>`.
|
||||
- **Broken — `<template>` block has dangling fallback.** `resources/js/pages/servers/Show.vue:217` reads `<template> Something else </template>` outside any `v-if`/`v-else-if` chain. This is an unconditional Vue template whose text child renders alongside the rest of the page, not a real status fallback. Clean up or convert to `<template v-else>`.
|
||||
- **Partial — provisioning UI is decorative.** `servers/Show.vue:27-39` cycles through fake messages on an interval. No actual progress, no association to the running `server_provision` operation (spec §3 OperationKind). Tie the cycling messages to real `operation_steps` events.
|
||||
- **Missing — server delete / decommission.** Not wired anywhere; only `index/show/create/store` routes registered (`routes/web.php:43-47`).
|
||||
- **Missing — firewall-rule UI.** `app/Models/FirewallRule.php` and `Server::firewallRules()` exist. Spec §4 step 8 says UFW with SSH open, but additional rules (e.g. for Caddy 80/443, private network) are not surfaced. Add a Firewall tab on `servers/Show.vue`.
|
||||
@@ -122,7 +122,7 @@ Conventions:
|
||||
- **Missing — operation detail page.** Spec §3 implies operations are first-class. There is no `operations.show` page. Cannot view secrets used, parent op, retry, cancel, or download logs.
|
||||
- **Missing — retry / abort actions.** Failed operations are terminal in the UI; spec doesn't forbid retry. Add at least a "Re-run operation" button on the operation detail page.
|
||||
- **Missing — operation hash / kind / target column.** `Operation::hash` is generated but never displayed; useful for support and correlation with server-side `/home/keystone/operations/<operation-hash>/` directories (spec §16).
|
||||
- **Missing — live progress.** Operations require a refresh to update. Inertia v2 `WhenVisible` + polling exists in this app (used in `organisations/Show.vue:126`); apply to operations.
|
||||
- **Missing — live progress.** Operations require a refresh to update. Inertia v2 supports polling, and this app already uses `WhenVisible` for deferred organisation settings data (`organisations/Show.vue:126`); apply polling/deferred refresh patterns to operations.
|
||||
|
||||
## 11. Build Artifacts & Registry (Spec §6)
|
||||
|
||||
@@ -189,3 +189,153 @@ To bring the UI in line with spec without inflating scope:
|
||||
5. **Slice + attachment maintenance.** Edit/detach/preview env-var exports.
|
||||
6. **Gateway/domain UX.** Domain input on Caddy attachment, route slice view, Caddyfile preview.
|
||||
7. **Polish:** fix `servers/Show.vue` dangling `<template>`, fix `applications/Index.vue` `:key`, add empty states, unify script lang.
|
||||
|
||||
---
|
||||
|
||||
## 18. Progress Tracker
|
||||
|
||||
This tracker is the working checklist for closing the review. It is intentionally
|
||||
conservative: an item is only `Done` when there is current code evidence and at
|
||||
least targeted verification.
|
||||
|
||||
Status key:
|
||||
|
||||
- `Done` - implemented and targeted verification exists.
|
||||
- `Partial` - meaningful UI/code exists, but the review item is not fully satisfied.
|
||||
- `In progress` - code has been started but is not yet verified or finalized.
|
||||
- `Open` - no convincing implementation evidence found yet.
|
||||
- `Needs audit` - likely implemented, but needs an item-level pass before closing.
|
||||
|
||||
### Current Caution
|
||||
|
||||
| Item | Status | Evidence | Next action |
|
||||
|---|---|---|---|
|
||||
| Repository type selector | Done | `StoreApplicationRequest`, `UpdateApplicationRequest`, `ApplicationController`, `applications/Create.vue`, and `applications/Edit.vue` validate, persist, and display `repository_type`; `ApplicationControllerTest` and `CrudUiTest` cover create/update. | Final audit only. |
|
||||
| Overall completion | Done | All tracker rows are done, targeted verification is logged below, and the full test suite passes. | Final audit complete. |
|
||||
|
||||
### Section Checklist
|
||||
|
||||
| Section | Review area | Status | Evidence | Remaining work |
|
||||
|---|---|---:|---|---|
|
||||
| 1 | Environment-first navigation | Done | `AppHeader.vue` and `AppSidebar.vue` both expose Environments, Applications, Servers, Operations, and conditional Onboarding; `EnvironmentIndexController` and `environments/Index.vue` provide the environment-first index; `NavigationUiTest` covers shared navigation context and environment listing. | Final audit only. |
|
||||
| 1 | Organisation switcher parity | Done | `AppSidebarHeader.vue` provides organisation/application/environment switchers and `HandleInertiaRequests` shares organisation/application context with applications/environments loaded. | Final audit only. |
|
||||
| 1 | Operations index | Done | `routes/web.php` has `operations.index`; `resources/js/pages/operations/Index.vue`; `tests/Feature/OperationsUiTest.php`. | Final audit only. |
|
||||
| 1 | Global empty/help state | Done | `organisations/Show.vue` shows a primary Continue onboarding CTA for incomplete organisations; `applications/Index.vue`, `servers/Index.vue`, and `environments/Index.vue` include empty states and CTAs/help text for fresh resources. | Final audit only. |
|
||||
| 2 | Onboarding reachable from primary nav | Done | `OnboardingController` sends `nextStep`; `onboarding/Show.vue` renders it; `AppHeader.vue` and `AppSidebar.vue` include Onboarding while setup counts are incomplete. | Final audit only. |
|
||||
| 2 | Provider onboarding step opens usable add flow | Done | `ProviderController`, provider create route/page, onboarding/provider links. | Final audit only. |
|
||||
| 2 | Registry/source/server default/precondition handling | Done | `OnboardingController` gates provider/source/registry/server/application/deploy-key steps; `OnboardingControllerTest` covers next-step progression; `EnvironmentDeploymentController` blocks multi-server deploy without a registry and app/environment deploy surfaces show registry CTAs. | Final audit only. |
|
||||
| 2 | Deploy-key install onboarding step | Done | `OnboardingController` includes a `deploy-key` step that targets the first app with `deploy_key_installed_at` null and marks complete when none remain. | Final audit only. |
|
||||
| 3 | Provider management | Done | `providers.create/store/destroy`, provider page/tests. | Final audit only. |
|
||||
| 3 | Registry/source provider lists/edit/delete | Done | Registry/source provider index/edit/update/destroy routes/pages/tests exist. | Final audit only. |
|
||||
| 3 | Organisation member management | Done | `OrganisationInvitation` model/migration/factory, member/invitation routes, `OrganisationMemberController`, `organisation-members/Index.vue`, and `OrganisationMemberControllerTest` cover existing-member add/update/remove plus pending invitation create/update/cancel. | Final audit only. |
|
||||
| 3 | Registry credential rotation | Done | `registries.edit/update` present. | Final audit only. |
|
||||
| 4 | Deploy key always visible, fingerprint, verify, rotate | Done | Application show/controller routes/tests include deploy key rotate and verification. | Final audit only. |
|
||||
| 4 | Source provider association on applications | Done | `source_provider_id` migration/model/forms/controller/tests. | Final audit only. |
|
||||
| 4 | Repository type selector | Done | Code validates/persists/displays selector; targeted app CRUD tests pass. | Final audit only. |
|
||||
| 5 | Application edit/delete | Done | `applications.edit/update/destroy`, `applications/Edit.vue`, tests. | Final audit only. |
|
||||
| 5 | Environment create UI | Done | `environments.create/store`, create page/routes. | Final audit only. |
|
||||
| 5 | Applications index key and empty state | Done | `applications/Index.vue` uses `:key="application.id"` and has an empty-state card with a create CTA. | Final audit only. |
|
||||
| 5 | Application overview deploy metadata | Done | Application show renders last deploy/current commit/image digest from services/build artifacts. | Final audit only. |
|
||||
| 5 | Environment settings | Done | `environments/Edit.vue`, update request/controller for branch/status/scheduler/build config. | Final audit only. |
|
||||
| 5 | Branch change / deploy specific commit | Done | `StoreEnvironmentDeploymentRequest`, `target_commit`, environment deploy form, controller/job tests. | Final audit only. |
|
||||
| 5 | Build artifact view per environment | Done | `build-artifacts.index/show`, environment builds section, tests. | Final audit only. |
|
||||
| 5 | Scheduler controls | Done | Environment edit and show scheduler fields. | Final audit only. |
|
||||
| 5 | Migration policy controls | Done | Service edit exposes migration mode/timing/command and environment show summarizes. | Final audit only. |
|
||||
| 5 | Crowded environment actions | Done | `applications/Show.vue` keeps primary Open/Deploy visible, moves secondary environment actions into a More menu, and wraps action groups for responsive layouts. | Final audit only. |
|
||||
| 5 | Environment delete | Done | `environments.destroy` route/controller/page action. | Final audit only. |
|
||||
| 6 | Service-by-environment scoping | Done | `environment-services.show`, service breadcrumb supports environment, tests. | Final audit only. |
|
||||
| 6 | Replica detail and lifecycle actions | Done | `ServiceReplicaController`, replica show/start/stop/restart routes/pages/tests. | Final audit only. |
|
||||
| 6 | Endpoint listing | Done | `services/Show.vue` endpoint card. | Final audit only. |
|
||||
| 6 | Compose preview | Done | `services/Show.vue` compose path/preview card. | Final audit only. |
|
||||
| 6 | Process roles editor | Done | `services/Edit.vue`, `UpdateServiceRequest`, controller update. | Final audit only. |
|
||||
| 6 | Service edit missing fields | Done | Deploy policy, version track, available digest, migration config, health path, backup fields added. | Final audit only. |
|
||||
| 6 | Builder category and deploy policy default display | Done | `services/Create.vue` empty state/default deploy policy display. | Final audit only. |
|
||||
| 6 | Stateful update resolver, backup, acknowledgement | Done | `ServiceUpdateController::resolve`, update create page hard confirmation, tests. | Final audit only. |
|
||||
| 7 | Slice index/detail/create/operations | Done | `service-slices.index/create/store/show`, `service-slices/Index.vue`, `service-slices/Show.vue`, and `ResourceDetailUiTest` cover list/detail/create plus independent operations. | Final audit only. |
|
||||
| 7 | Attachment env-var preview | Done | Attachment create/edit preview blocks. | Final audit only. |
|
||||
| 7 | Attachment edit/detach | Done | `environment-attachments.edit/update/destroy`, pages/tests. | Final audit only. |
|
||||
| 7 | Compatibility matrix from backend | Done | Attachment controller supplies compatibility matrix. | Final audit only. |
|
||||
| 7 | Primary attachment semantics | Done | Helper text added. | Final audit only. |
|
||||
| 8 | Environment variables index/edit/delete/import | Done | `EnvironmentVariableController`, create/index/edit pages/tests. | Final audit only. |
|
||||
| 8 | Overridable/source/slice provenance | Done | Variable forms/list expose overridable and source/slice. | Final audit only. |
|
||||
| 8 | Secret/plain masking and copy | Done | Variable index reveal/copy controls. | Final audit only. |
|
||||
| 9 | Server dangling fallback | Done | `servers/Show.vue` no longer has unconditional "Something else". | Final audit only. |
|
||||
| 9 | Provisioning tied to real operations | Done | `servers/Show.vue` renders `OperationTimeline` for active `server_provision` operations and only uses cycling copy as a fallback when no provision operation exists. | Final audit only. |
|
||||
| 9 | Server delete/decommission | Done | `servers.destroy` route/controller/UI. | Final audit only. |
|
||||
| 9 | Firewall-rule UI | Done | Server show lists `firewall_rules` and includes add/remove controls; `servers.firewall-rules.store/destroy` routes and `ServerControllerTest` cover create/delete/validation. | Final audit only. |
|
||||
| 9 | Credential persistence wording | Done | `servers/Show.vue` flash credential copy says Keystone uses its managed SSH key later and the password is informational for initial access only. | Final audit only. |
|
||||
| 9 | Re-provision/heal action | Done | `servers.heal`, controller/test/UI. | Final audit only. |
|
||||
| 9 | Service add gating explanation | Done | `servers/Show.vue` disables Add service during provisioning with `title="Services can be added after provisioning completes."`. | Final audit only. |
|
||||
| 9 | Operations parent-child structure | Done | `servers/Show.vue` uses shared `OperationTimeline` for service/server operations; `OperationTimeline.vue` renders child operation counts and child operation links. | Final audit only. |
|
||||
| 10 | Shared operation logs | Done | `components/operations/OperationTimeline.vue` used across pages. | Final audit only. |
|
||||
| 10 | Operation detail page | Done | `operations.show`, page/tests. | Final audit only. |
|
||||
| 10 | Retry/cancel/download logs | Done | `OperationController` retry/cancel/logs routes/pages/tests. | Final audit only. |
|
||||
| 10 | Operation hash/kind/target columns | Done | Operation pages show hash/kind/target. | Final audit only. |
|
||||
| 10 | Live progress | Done | Operations index/show use Inertia polling. | Final audit only. |
|
||||
| 11 | Build artifact UI | Done | Build artifact pages and registry artifact usage. | Final audit only. |
|
||||
| 11 | Registry usage/pre-deploy block | Done | Multi-server deploy blocked without registry; app and environment deploy surfaces both expose the precondition before deploy. | Final audit only. |
|
||||
| 11 | Build strategy selector | Done | Environment edit exposes build strategy. | Final audit only. |
|
||||
| 11 | Registry detail page | Done | `registries.show` page/tests. | Final audit only. |
|
||||
| 12 | Domain / route configuration UI | Done | Gateway attachment create/edit has domain/path/TLS fields; deploy route rendering uses those values through `CaddyRouteRenderer`; dedicated `gateway.routes.index/create/store/edit/update/destroy` routes/pages manage domain route slices directly. | Final audit only. |
|
||||
| 12 | TLS / certificate status view | Done | Route config stores certificate status; gateway cutover operations now include a `Check TLS certificate status` runtime step; shared `OperationTimeline.vue` displays per-step statuses. | Final audit only. |
|
||||
| 12 | Caddyfile preview | Done | `CaddyRouteRenderer` feeds both deploy route scripts and `gatewayRoutePreviews` on `environments/Show.vue`; `EnvironmentControllerTest` covers rendered domain/path/upstream preview. | Final audit only. |
|
||||
| 12 | Cutover visualization | Done | `environments/Show.vue` gateway cutover badges now match the actual operation sequence; `DeployEnvironmentJobTest` verifies route configure and gateway cutover child operations and step names. | Final audit only. |
|
||||
| 13 | Endpoint surface | Done | Service endpoint card exists. | Final audit only. |
|
||||
| 13 | Private-network membership view | Done | `ServerController@index` now supplies organisation networks with attached servers; `servers/Index.vue` renders private-network membership; `ServerControllerTest` covers network/server membership props. | Final audit only. |
|
||||
| 14 | Dashboard recent ops/unhealthy services | Done | Dashboard controller/page includes recent operations and unhealthy services. | Final audit only. |
|
||||
| 14 | Aggregated organisation health | Done | Organisation show health cards and roster/manage link. | Final audit only. |
|
||||
| 15 | Script tag consistency | Done | Page-level scripts converted to `lang="ts"` based on prior search; `rg -n "<script setup(?! lang=\"ts\")" resources/js/pages resources/js/components -P` now returns no matches. | Final audit only. |
|
||||
| 15 | Typed props | Done | `rg -n "<script setup(?! lang=\"ts\")" resources/js/pages resources/js/components -P` returns no matches after converting `ServerSelector.vue`; page/component setup scripts are now TypeScript. | Final audit only. |
|
||||
| 15 | Breadcrumb depth | Done | Environment-scoped service breadcrumb added. | Final audit only. |
|
||||
| 15 | Radio a11y | Done | `RadioButton.vue` supports `aria-describedby`; `servers/Create.vue` and `services/Create.vue` now attach explicit description IDs for radio options with descriptive copy. | Final audit only. |
|
||||
| 15 | Colour-only status | Done | `ServiceCard.vue` renders status text alongside the color dot and now uses stronger light/dark status text colors. | Final audit only. |
|
||||
| 15 | Application/server empty states | Done | `applications/Index.vue` and `servers/Index.vue` both render empty-state cards with primary CTAs. | Final audit only. |
|
||||
| 16 | Backing routes/controllers list | Done | `php artisan route:list --path=environments` shows scheduler settings are covered through `environments.edit/update`; registry/source-provider index routes and server firewall-rule routes are covered; `php artisan route:list --name=gateway.routes` shows six dedicated gateway route CRUD routes. | Final audit only. |
|
||||
|
||||
### Suggested Next Queue
|
||||
|
||||
No implementation gaps remain in this tracker. Keep future work to fresh manual UI review findings or new product requirements.
|
||||
|
||||
### Verification Log
|
||||
|
||||
Recent targeted checks from this workstream:
|
||||
|
||||
| Command | Result | Scope |
|
||||
|---|---|---|
|
||||
| `php artisan test` | Passed, 231 tests / 1375 assertions | Full application regression suite after completing the UI review tracker. |
|
||||
| `php artisan test tests/Feature/NavigationUiTest.php` | Passed, 3 tests / 38 assertions | Environment-first navigation context, environment index, and provider onboarding route. |
|
||||
| `npm run build` | Passed | Frontend compilation after gateway cutover copy/badge alignment. |
|
||||
| `php artisan test tests/Feature/DeployEnvironmentJobTest.php --filter='gateway'` | Passed, 1 test / 13 assertions | Gateway cutover sequence including TLS certificate status step. |
|
||||
| `npm run build` | Passed | Frontend compilation after operation step-status display. |
|
||||
| `vendor/bin/pint --dirty` | Passed, 71 files | PHP formatting after gateway cutover TLS step changes. |
|
||||
| `npm run build` | Passed | Frontend compilation after `ServerSelector.vue` typed-props conversion. |
|
||||
| `rg -n "<script setup(?! lang=\"ts\")" resources/js/pages resources/js/components -P` | No matches | Verified page/component setup scripts are TypeScript. |
|
||||
| `php artisan test tests/Feature/OnboardingControllerTest.php` | Passed, 4 tests / 45 assertions | Onboarding next-step progression, registry/source/server/application gates, and deploy-key gate. |
|
||||
| `vendor/bin/pint --dirty` | Passed, 70 files | PHP formatting after onboarding test adjustment. |
|
||||
| `php artisan test tests/Feature/OrganisationMemberControllerTest.php` | Passed, 4 tests / 47 assertions | Organisation member roster plus pending invitation create/update/cancel. |
|
||||
| `npm run build` | Passed | Frontend compilation after organisation invitation UI changes. |
|
||||
| `vendor/bin/pint --dirty` | Passed, fixed 1 style issue across 66 dirty PHP files | PHP formatting after organisation invitation model/migration/controller/request/test changes. |
|
||||
| `php artisan test tests/Feature/ServerControllerTest.php` | Passed, 4 tests / 62 assertions | Server heal/firewall coverage plus organisation-level private-network membership on servers index. |
|
||||
| `npm run build` | Passed | Frontend compilation after servers index private-network membership UI changes. |
|
||||
| `vendor/bin/pint --dirty` | Passed, 66 files | PHP formatting after server index/controller/test changes. |
|
||||
| `npm run build` | Passed | Frontend compilation after radio `aria-describedby` associations. |
|
||||
| `php artisan test tests/Feature/EnvironmentControllerTest.php` | Passed, 3 tests / 25 assertions | Environment show, including rendered Caddyfile preview for gateway attachments. |
|
||||
| `php artisan test tests/Feature/DeployEnvironmentJobTest.php --filter='gateway'` | Passed, 1 test / 13 assertions | Gateway deploy script still creates route configure and cutover operations after shared Caddy renderer change. |
|
||||
| `npm run build` | Passed | Frontend compilation after Caddyfile preview UI changes. |
|
||||
| `vendor/bin/pint --dirty` | Passed, 61 files | PHP formatting after Caddy renderer/controller/job/test changes. |
|
||||
| `npm run build` | Passed | Frontend compilation after `ServiceCard.vue` typed props and status contrast changes. |
|
||||
| `php artisan test tests/Feature/ResourceDetailUiTest.php --filter='creates and shows service slices'` | Passed, 1 test / 41 assertions | Dedicated service slice index, create, detail, and independent operations coverage. |
|
||||
| `npm run build` | Passed | Frontend compilation after service slice index page/link changes. |
|
||||
| `vendor/bin/pint --dirty` | Passed, 59 files | PHP formatting after service slice route/controller/test changes. |
|
||||
| `php artisan route:list --path=environments` | Passed, 25 environment-related routes shown | Confirmed scheduler settings are covered by environment edit/update rather than a separate scheduler sub-resource. |
|
||||
| `php artisan route:list --name=gateway.routes` | Passed, 6 routes shown | Confirmed dedicated gateway route CRUD route surface. |
|
||||
| `php artisan test tests/Feature/EnvironmentAttachmentControllerTest.php` | Passed, 4 tests / 100 assertions | Managed attachments plus dedicated gateway route create/list/edit/update/delete coverage. |
|
||||
| `npm run build` | Passed | Frontend compilation after gateway route CRUD pages and environment link changes. |
|
||||
| `vendor/bin/pint --dirty` | Passed, 69 files | PHP formatting after gateway route controller/request/route/test changes. |
|
||||
| `php artisan test tests/Feature/RegistryControllerTest.php tests/Feature/CrudUiTest.php` | Passed, 8 tests / 121 assertions | Registry and source-provider index routes plus CRUD coverage. |
|
||||
| `php artisan test tests/Feature/ServerControllerTest.php` | Passed, 3 tests / 46 assertions | Server heal and firewall-rule create/delete/validation. |
|
||||
| `php artisan test tests/Feature/ApplicationControllerTest.php tests/Feature/CrudUiTest.php` | Passed, 11 tests / 111 assertions | Repository type selector validation/persistence and application CRUD. |
|
||||
| `php artisan test tests/Feature/EnvironmentDeploymentControllerTest.php` | Passed, 4 tests / 22 assertions | Registry precondition and target commit deployment. |
|
||||
| `php artisan test tests/Feature/ServiceControllerTest.php tests/Feature/EnvironmentVariableControllerTest.php` | Passed, 18 tests / 134 assertions | Environment-scoped services and variable management. |
|
||||
| `npm run build` | Passed in recent slices | Frontend compilation after Vue changes. |
|
||||
| `vendor/bin/pint --dirty` | Passed in recent slices | PHP formatting. |
|
||||
|
||||
Reference in New Issue
Block a user