feat: dynamic database storage size with PVC expansion

- Add dbStorageSize column to Application entity (default: 1Gi)
- Add dbStorageSize to CreateApplicationDto, frontend types
- Use dynamic storage size in K8s deployDatabase instead of hardcoded 5Gi
- Deploy page: storage size selector with +/- buttons (min 1GB, max 100GB)
- Auto-suggest storage based on DB dump file size (3x dump size, min 1GB)
- Show DB storage in Review step
- App detail: Database Storage section with expand button
- GET /applications/:id/db-storage — read current PVC size from K8s
- PATCH /applications/:id/db-storage — expand PVC (only increase, no shrink)
- PVC resize uses JSON patch on K8s API
This commit is contained in:
keyhan
2026-04-07 01:28:01 +03:30
parent e775b57421
commit ac489c88d8
8 changed files with 289 additions and 3 deletions
@@ -66,6 +66,8 @@ export default function AppDetailPage() {
memoryLimit: '',
replicas: 1,
});
const [dbStorageSize, setDbStorageSize] = useState('1');
const [dbStorageLoading, setDbStorageLoading] = useState(false);
const { data: app, isLoading } = useQuery<Application>({
queryKey: ['application', appId],
@@ -109,6 +111,36 @@ export default function AppDetailPage() {
queryFn: () => api.get('/clusters/pools/public').then((r) => r.data),
});
// Fetch DB storage size
const { data: dbStorageData } = useQuery<{ currentSize: string; savedSize: string }>({
queryKey: ['db-storage', appId],
queryFn: () => api.get(`/applications/${appId}/db-storage`).then((r) => r.data),
enabled: !!app && app.databaseType !== 'none',
});
// Sync dbStorageSize state when data loads
useEffect(() => {
if (dbStorageData?.currentSize) {
const sizeNum = parseInt(dbStorageData.currentSize.replace('Gi', ''), 10) || 1;
setDbStorageSize(String(sizeNum));
}
}, [dbStorageData]);
const resizeDbMutation = useMutation({
mutationFn: (size: string) => api.patch(`/applications/${appId}/db-storage`, { size }),
onSuccess: (res) => {
if (res.data.success) {
toast.success(res.data.message || 'Database storage expanded!');
queryClient.invalidateQueries({ queryKey: ['db-storage', appId] });
} else {
toast.error(res.data.message || 'Failed to expand storage');
}
},
onError: (err: any) => {
toast.error(err.response?.data?.message || 'Failed to resize database storage');
},
});
// Sync form when resource data loads
useEffect(() => {
if (resourceUsage?.configured) {
@@ -667,6 +699,71 @@ export default function AppDetailPage() {
</p>
</div>
{/* Database Storage Management */}
<div className="bg-gray-50 rounded-xl p-4 mb-4">
<h3 className="text-sm font-semibold text-gray-700 mb-3">Database Storage</h3>
<div className="flex items-center gap-4">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<span className="text-xs text-gray-500">Current Size:</span>
<span className="text-sm font-semibold text-gray-800">{dbStorageData?.currentSize || app.dbStorageSize || '1Gi'}</span>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center border border-gray-300 rounded-lg overflow-hidden">
<button
type="button"
onClick={() => {
const current = parseInt(dbStorageSize, 10);
const min = parseInt((dbStorageData?.currentSize || '1Gi').replace('Gi', ''), 10) || 1;
if (current > min + 1) setDbStorageSize(String(current - 1));
}}
className="px-3 py-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 font-bold text-sm transition-colors"
>
</button>
<input
type="number"
min={1}
max={100}
value={dbStorageSize}
onChange={(e) => {
const val = Math.max(1, Math.min(100, parseInt(e.target.value, 10) || 1));
setDbStorageSize(String(val));
}}
className="w-14 text-center py-1.5 border-x border-gray-300 text-sm font-semibold focus:outline-none"
/>
<button
type="button"
onClick={() => {
const current = parseInt(dbStorageSize, 10);
if (current < 100) setDbStorageSize(String(current + 1));
}}
className="px-3 py-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 font-bold text-sm transition-colors"
>
+
</button>
</div>
<span className="text-sm text-gray-600">GB</span>
<button
type="button"
onClick={() => {
const newSize = `${parseInt(dbStorageSize, 10)}Gi`;
resizeDbMutation.mutate(newSize);
}}
disabled={
resizeDbMutation.isPending ||
parseInt(dbStorageSize, 10) <= (parseInt((dbStorageData?.currentSize || '1Gi').replace('Gi', ''), 10) || 1)
}
className="btn-primary text-xs px-3 py-1.5 disabled:opacity-50"
>
{resizeDbMutation.isPending ? 'Expanding...' : 'Expand'}
</button>
</div>
<p className="text-xs text-gray-400 mt-1">فقط امکان افزایش حجم وجود دارد (کاهش ممکن نیست)</p>
</div>
</div>
</div>
{/* DB Dump Upload */}
<h3 className="text-sm font-semibold text-gray-700 mb-3">Restore Database Dump</h3>
<div