fix(ui): keep resource scaling form values while user is editing

Skip syncing form state from the 5s resources poll when the form is
dirty, and reset dirty only after apply or workload tab change.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
keyhan
2026-05-15 01:11:10 +03:30
parent 7c7e8ae254
commit c7981074d4
+22 -8
View File
@@ -70,6 +70,7 @@ export default function AppDetailPage() {
replicas: 1, replicas: 1,
}); });
const [scaleWorkload, setScaleWorkload] = useState<'app' | 'database' | 'redis' | 'rabbitmq'>('app'); const [scaleWorkload, setScaleWorkload] = useState<'app' | 'database' | 'redis' | 'rabbitmq'>('app');
const [resourceFormDirty, setResourceFormDirty] = useState(false);
const [showDbDiskExpand, setShowDbDiskExpand] = useState(false); const [showDbDiskExpand, setShowDbDiskExpand] = useState(false);
const [dbStorageSize, setDbStorageSize] = useState('1'); const [dbStorageSize, setDbStorageSize] = useState('1');
const [dbStorageLoading, setDbStorageLoading] = useState(false); const [dbStorageLoading, setDbStorageLoading] = useState(false);
@@ -467,8 +468,19 @@ export default function AppDetailPage() {
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}; };
// Sync form when resource data loads (selected workload) const patchResourceForm = (patch: Partial<typeof resourceForm>) => {
setResourceFormDirty(true);
setResourceForm((f) => ({ ...f, ...patch }));
};
// Sync form when workload changes or when live metrics refresh — but not while user is editing.
useEffect(() => { useEffect(() => {
setResourceFormDirty(false);
}, [scaleWorkload]);
useEffect(() => {
if (resourceFormDirty) return;
const workloads = resourceUsage?.workloads; const workloads = resourceUsage?.workloads;
const w = const w =
workloads?.find((x) => x.key === scaleWorkload) || workloads?.find((x) => x.key === scaleWorkload) ||
@@ -487,7 +499,7 @@ export default function AppDetailPage() {
replicas: w.configured.replicas ?? 1, replicas: w.configured.replicas ?? 1,
}); });
} }
}, [resourceUsage, scaleWorkload]); }, [resourceUsage, scaleWorkload, resourceFormDirty]);
// Auto-scroll logs to bottom // Auto-scroll logs to bottom
useEffect(() => { useEffect(() => {
@@ -560,6 +572,7 @@ export default function AppDetailPage() {
mutationFn: (data: { cpuRequest?: string; cpuLimit?: string; memoryRequest?: string; memoryLimit?: string; replicas?: number }) => mutationFn: (data: { cpuRequest?: string; cpuLimit?: string; memoryRequest?: string; memoryLimit?: string; replicas?: number }) =>
api.post(`/billing/applications/${appId}/upgrade`, data), api.post(`/billing/applications/${appId}/upgrade`, data),
onSuccess: (res) => { onSuccess: (res) => {
setResourceFormDirty(false);
invalidateAll(); invalidateAll();
queryClient.invalidateQueries({ queryKey: ['resources', appId] }); queryClient.invalidateQueries({ queryKey: ['resources', appId] });
queryClient.invalidateQueries({ queryKey: ['wallet'] }); queryClient.invalidateQueries({ queryKey: ['wallet'] });
@@ -585,6 +598,7 @@ export default function AppDetailPage() {
memoryLimit?: string; memoryLimit?: string;
}) => api.patch(`/applications/${appId}/resources`, data), }) => api.patch(`/applications/${appId}/resources`, data),
onSuccess: () => { onSuccess: () => {
setResourceFormDirty(false);
invalidateAll(); invalidateAll();
queryClient.invalidateQueries({ queryKey: ['resources', appId] }); queryClient.invalidateQueries({ queryKey: ['resources', appId] });
toast.success('Resources updated'); toast.success('Resources updated');
@@ -2107,7 +2121,7 @@ export default function AppDetailPage() {
<input <input
type="text" type="text"
value={resourceForm.cpuRequest} value={resourceForm.cpuRequest}
onChange={(e) => setResourceForm((f) => ({ ...f, cpuRequest: e.target.value }))} onChange={(e) => patchResourceForm({ cpuRequest: e.target.value })}
className="input-field text-sm" className="input-field text-sm"
placeholder="100m" placeholder="100m"
/> />
@@ -2117,7 +2131,7 @@ export default function AppDetailPage() {
<input <input
type="text" type="text"
value={resourceForm.cpuLimit} value={resourceForm.cpuLimit}
onChange={(e) => setResourceForm((f) => ({ ...f, cpuLimit: e.target.value }))} onChange={(e) => patchResourceForm({ cpuLimit: e.target.value })}
className="input-field text-sm" className="input-field text-sm"
placeholder="500m" placeholder="500m"
/> />
@@ -2127,7 +2141,7 @@ export default function AppDetailPage() {
<input <input
type="text" type="text"
value={resourceForm.memoryRequest} value={resourceForm.memoryRequest}
onChange={(e) => setResourceForm((f) => ({ ...f, memoryRequest: e.target.value }))} onChange={(e) => patchResourceForm({ memoryRequest: e.target.value })}
className="input-field text-sm" className="input-field text-sm"
placeholder="128Mi" placeholder="128Mi"
/> />
@@ -2137,7 +2151,7 @@ export default function AppDetailPage() {
<input <input
type="text" type="text"
value={resourceForm.memoryLimit} value={resourceForm.memoryLimit}
onChange={(e) => setResourceForm((f) => ({ ...f, memoryLimit: e.target.value }))} onChange={(e) => patchResourceForm({ memoryLimit: e.target.value })}
className="input-field text-sm" className="input-field text-sm"
placeholder="512Mi" placeholder="512Mi"
/> />
@@ -2148,7 +2162,7 @@ export default function AppDetailPage() {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button <button
type="button" type="button"
onClick={() => setResourceForm((f) => ({ ...f, replicas: Math.max(1, f.replicas - 1) }))} onClick={() => patchResourceForm({ replicas: Math.max(1, resourceForm.replicas - 1) })}
className="btn-icon w-9 h-9" className="btn-icon w-9 h-9"
> >
@@ -2156,7 +2170,7 @@ export default function AppDetailPage() {
<span className="text-lg font-bold text-gray-800 w-8 text-center">{resourceForm.replicas}</span> <span className="text-lg font-bold text-gray-800 w-8 text-center">{resourceForm.replicas}</span>
<button <button
type="button" type="button"
onClick={() => setResourceForm((f) => ({ ...f, replicas: Math.min(10, f.replicas + 1) }))} onClick={() => patchResourceForm({ replicas: Math.min(10, resourceForm.replicas + 1) })}
className="btn-icon w-9 h-9" className="btn-icon w-9 h-9"
> >
+ +