diff --git a/ROADMAP.md b/ROADMAP.md index fa361d7..d80d494 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -225,8 +225,11 @@ pieces exist; this phase closes the gaps that block real usage. prefix, quoted values), inline validation (required / invalid / duplicate keys). Per-environment values are already covered by the schema: each environment owns its service instances, and variables are per-service. -- [ ] **Deploy lifecycle polish**: build → deploy status propagation to canvas - node, failed-deploy surfacing with log tail link, rollback confirmation. +- [x] **Deploy lifecycle polish**: backend already writes deploy progress to + `services.status`; the canvas now polls faster while any service is in a + transitional state so build → deploy propagates to nodes, deployment + history rows link straight into that deployment's log tail, and rollback + asks for confirmation before firing. - [ ] **Node/port collision handling**: two services claiming the same host port must produce a clear error, not a silent failure. - [ ] Auth hardening: e2e verify bootstrap gate (first user registers, second diff --git a/app/frontend/src/features/service/pages/ServiceDetailPage.tsx b/app/frontend/src/features/service/pages/ServiceDetailPage.tsx index 32322ac..4428d56 100644 --- a/app/frontend/src/features/service/pages/ServiceDetailPage.tsx +++ b/app/frontend/src/features/service/pages/ServiceDetailPage.tsx @@ -96,6 +96,7 @@ export function ServiceDetailPage() { const [varDrafts, setVarDrafts] = useState(null); const [bulkOpen, setBulkOpen] = useState(false); const [bulkText, setBulkText] = useState(''); + const [logDeploymentId, setLogDeploymentId] = useState(null); const projectQuery = useQuery({ queryKey: ['project', projectId], @@ -148,11 +149,14 @@ export function ServiceDetailPage() { return deployments[0] ?? null; }, [deploymentsQuery.data, isDemoMode]); + const logsDeployment = + (deploymentsQuery.data ?? []).find((d) => d.id === logDeploymentId) ?? latestDeployment; + const deploymentLogsQuery = useQuery({ - queryKey: ['deployment-logs', latestDeployment?.id], - queryFn: () => getDeploymentLogs(latestDeployment!.id, { type: 'all' }), + queryKey: ['deployment-logs', logsDeployment?.id], + queryFn: () => getDeploymentLogs(logsDeployment!.id, { type: 'all' }), enabled: - Boolean(latestDeployment?.id) && + Boolean(logsDeployment?.id) && !isDemoMode && activeSection === 'logs', }); @@ -775,7 +779,7 @@ export function ServiceDetailPage() { + )} + + -

{latestDeployment.id}

+

{logsDeployment.id}

                   {deploymentLogsQuery.isLoading
                     ? 'Loading...'
@@ -1109,13 +1125,28 @@ export function ServiceDetailPage() {
                               {deployment.createdAt ? formatRelative(deployment.createdAt) : '—'}
                             
                             
-                              
+                              
+ + +
)) diff --git a/app/frontend/src/features/workspace/pages/ProjectWorkspacePage.tsx b/app/frontend/src/features/workspace/pages/ProjectWorkspacePage.tsx index 59595ea..960e2eb 100644 --- a/app/frontend/src/features/workspace/pages/ProjectWorkspacePage.tsx +++ b/app/frontend/src/features/workspace/pages/ProjectWorkspacePage.tsx @@ -491,6 +491,10 @@ export function ProjectWorkspacePage() { queryKey: ['project-services', projectId], queryFn: () => listServicesByProject(projectId), enabled: Boolean(projectId) && !isDemoMode, + refetchInterval: (query) => + query.state.data?.some((s) => ['building', 'deploying', 'pending', 'rolling_back'].includes(s.status)) + ? 3000 + : 15000, }); const createServiceMutation = useMutation({