diff --git a/frontend/src/app/dashboard/apps/[id]/page.tsx b/frontend/src/app/dashboard/apps/[id]/page.tsx index 271acdd..8b1b975 100644 --- a/frontend/src/app/dashboard/apps/[id]/page.tsx +++ b/frontend/src/app/dashboard/apps/[id]/page.tsx @@ -70,6 +70,7 @@ export default function AppDetailPage() { replicas: 1, }); const [scaleWorkload, setScaleWorkload] = useState<'app' | 'database' | 'redis' | 'rabbitmq'>('app'); + const [resourceFormDirty, setResourceFormDirty] = useState(false); const [showDbDiskExpand, setShowDbDiskExpand] = useState(false); const [dbStorageSize, setDbStorageSize] = useState('1'); const [dbStorageLoading, setDbStorageLoading] = useState(false); @@ -467,8 +468,19 @@ export default function AppDetailPage() { return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }; - // Sync form when resource data loads (selected workload) + const patchResourceForm = (patch: Partial) => { + setResourceFormDirty(true); + setResourceForm((f) => ({ ...f, ...patch })); + }; + + // Sync form when workload changes or when live metrics refresh — but not while user is editing. useEffect(() => { + setResourceFormDirty(false); + }, [scaleWorkload]); + + useEffect(() => { + if (resourceFormDirty) return; + const workloads = resourceUsage?.workloads; const w = workloads?.find((x) => x.key === scaleWorkload) || @@ -487,7 +499,7 @@ export default function AppDetailPage() { replicas: w.configured.replicas ?? 1, }); } - }, [resourceUsage, scaleWorkload]); + }, [resourceUsage, scaleWorkload, resourceFormDirty]); // Auto-scroll logs to bottom useEffect(() => { @@ -560,6 +572,7 @@ export default function AppDetailPage() { mutationFn: (data: { cpuRequest?: string; cpuLimit?: string; memoryRequest?: string; memoryLimit?: string; replicas?: number }) => api.post(`/billing/applications/${appId}/upgrade`, data), onSuccess: (res) => { + setResourceFormDirty(false); invalidateAll(); queryClient.invalidateQueries({ queryKey: ['resources', appId] }); queryClient.invalidateQueries({ queryKey: ['wallet'] }); @@ -585,6 +598,7 @@ export default function AppDetailPage() { memoryLimit?: string; }) => api.patch(`/applications/${appId}/resources`, data), onSuccess: () => { + setResourceFormDirty(false); invalidateAll(); queryClient.invalidateQueries({ queryKey: ['resources', appId] }); toast.success('Resources updated'); @@ -2107,7 +2121,7 @@ export default function AppDetailPage() { setResourceForm((f) => ({ ...f, cpuRequest: e.target.value }))} + onChange={(e) => patchResourceForm({ cpuRequest: e.target.value })} className="input-field text-sm" placeholder="100m" /> @@ -2117,7 +2131,7 @@ export default function AppDetailPage() { setResourceForm((f) => ({ ...f, cpuLimit: e.target.value }))} + onChange={(e) => patchResourceForm({ cpuLimit: e.target.value })} className="input-field text-sm" placeholder="500m" /> @@ -2127,7 +2141,7 @@ export default function AppDetailPage() { setResourceForm((f) => ({ ...f, memoryRequest: e.target.value }))} + onChange={(e) => patchResourceForm({ memoryRequest: e.target.value })} className="input-field text-sm" placeholder="128Mi" /> @@ -2137,7 +2151,7 @@ export default function AppDetailPage() { setResourceForm((f) => ({ ...f, memoryLimit: e.target.value }))} + onChange={(e) => patchResourceForm({ memoryLimit: e.target.value })} className="input-field text-sm" placeholder="512Mi" /> @@ -2148,7 +2162,7 @@ export default function AppDetailPage() {