Open deploy progress immediately and improve delete row glass overlay.

Track in-progress deploys in the client store so the progress modal opens on click without waiting for the applications list refetch. Show deleting state as a blurred glass overlay on table rows and service cards instead of replacing row content.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
keyhan
2026-05-25 23:34:24 +03:30
parent dc9830383b
commit 1ade52825c
11 changed files with 266 additions and 59 deletions
@@ -12,26 +12,57 @@ import { filterApplications, filterManagedServices } from '@/lib/product-type';
import { BuildProgressModal, type BuildProgress } from '@/components/build-progress-modal';
import { DeploymentProgressBar } from '@/components/deployment-progress-bar';
function stubAppFromTrack(appId: string, appName: string): Application {
return {
id: appId,
name: appName,
runtime: 'nodejs',
databaseType: 'none',
cpuRequest: '',
cpuLimit: '',
memoryRequest: '',
memoryLimit: '',
replicas: 1,
port: 3000,
userId: '',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
deployments: [
{
id: `pending-${appId}`,
status: 'building',
imageTag: '',
applicationId: appId,
triggeredBy: 'user',
createdAt: new Date().toISOString(),
},
],
} as Application;
}
export function DeploymentProgressManager() {
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
const pathname = usePathname();
const { minimized, focusedAppId, minimize, expand } = useDeployProgressStore();
const { minimized, focusedAppId, tracked, minimize, expand, stopTracking } =
useDeployProgressStore();
const refetchWhileDeploying = (list: Application[] | undefined) =>
getAppsInProgress(list ?? []).length > 0 ? 3000 : false;
const pollAppsList = (list: Application[] | undefined) => {
const pending = useDeployProgressStore.getState().tracked.length;
return getAppsInProgress(list ?? []).length > 0 || pending > 0 ? 3000 : false;
};
const { data: appsList = [] } = useQuery<Application[]>({
queryKey: ['applications', 'application'],
queryFn: () => api.get('/applications', { params: { productType: 'application' } }).then((r) => r.data),
enabled: isAuthenticated,
refetchInterval: (query) => refetchWhileDeploying(query.state.data),
refetchInterval: (query) => pollAppsList(query.state.data),
});
const { data: servicesList = [] } = useQuery<Application[]>({
queryKey: ['applications', 'managed'],
queryFn: () => api.get('/applications', { params: { productType: 'managed' } }).then((r) => r.data),
enabled: isAuthenticated,
refetchInterval: (query) => refetchWhileDeploying(query.state.data),
refetchInterval: (query) => pollAppsList(query.state.data),
});
const allResources = useMemo(
@@ -39,7 +70,21 @@ export function DeploymentProgressManager() {
[appsList, servicesList],
);
const deployingApps = useMemo(() => getAppsInProgress(allResources), [allResources]);
const deployingFromList = useMemo(() => getAppsInProgress(allResources), [allResources]);
const deployingApps = useMemo(() => {
const byId = new Map<string, Application>();
for (const app of deployingFromList) {
byId.set(app.id, app);
}
for (const t of tracked) {
if (!byId.has(t.appId)) {
const fromList = allResources.find((a) => a.id === t.appId);
byId.set(t.appId, fromList ?? stubAppFromTrack(t.appId, t.appName));
}
}
return Array.from(byId.values());
}, [deployingFromList, tracked, allResources]);
const progressQueries = useQueries({
queries: deployingApps.map((app) => ({
@@ -50,6 +95,7 @@ export function DeploymentProgressManager() {
.then((r) => r.data.progress),
refetchInterval: 1500,
placeholderData: keepPreviousData,
enabled: isAuthenticated,
})),
});
@@ -66,7 +112,7 @@ export function DeploymentProgressManager() {
useEffect(() => {
if (deployingApps.length === 0) {
useDeployProgressStore.setState({ minimized: false, focusedAppId: null });
useDeployProgressStore.setState({ minimized: false, focusedAppId: null, tracked: [] });
}
}, [deployingApps.length]);
@@ -80,12 +126,25 @@ export function DeploymentProgressManager() {
useDeployProgressStore.setState({ focusedAppId: preferred });
}, [activeItems, routeAppId]);
useEffect(() => {
for (const { app, progress } of activeItems) {
if (!progress) continue;
if (progress.phase === 'done' || progress.phase === 'failed' || progress.phase === 'cancelled') {
const stillBuilding = deployingFromList.some((a) => a.id === app.id);
if (!stillBuilding) {
stopTracking(app.id);
}
}
}
}, [activeItems, deployingFromList, stopTracking]);
const focusedItem =
activeItems.find((item) => item.app.id === focusedAppId) ?? activeItems[0];
const focusedProgress =
focusedItem?.progress ??
({ phase: 'building', percent: 0, message: 'Loading progress…' } satisfies BuildProgress);
({ phase: 'building', percent: 0, message: 'Starting deployment…' } satisfies BuildProgress);
const showModal =
!minimized &&
!!focusedItem &&