diff --git a/ui/src/pages/deployment-list-page.tsx b/ui/src/pages/deployment-list-page.tsx index 2da2e7f9..460146d7 100644 --- a/ui/src/pages/deployment-list-page.tsx +++ b/ui/src/pages/deployment-list-page.tsx @@ -1,16 +1,86 @@ -import { useCallback, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { createColumnHelper } from '@tanstack/react-table' import { Deployment } from 'kubernetes-types/apps/v1' import { useTranslation } from 'react-i18next' import { Link, useNavigate } from 'react-router-dom' +import { toast } from 'sonner' +import { scaleDeployment } from '@/lib/api' import { getDeploymentStatus } from '@/lib/k8s' -import { formatDate } from '@/lib/utils' +import { formatDate, translateError } from '@/lib/utils' import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' import { DeploymentStatusIcon } from '@/components/deployment-status-icon' import { DeploymentCreateDialog } from '@/components/editors/deployment-create-dialog' import { ResourceTable } from '@/components/resource-table' +function DeploymentScaleCell({ deployment }: { deployment: Deployment }) { + const { t } = useTranslation() + const [replicas, setReplicas] = useState( + deployment.spec?.replicas ?? deployment.status?.replicas ?? 0 + ) + const [isScaling, setIsScaling] = useState(false) + + useEffect(() => { + setReplicas(deployment.spec?.replicas ?? deployment.status?.replicas ?? 0) + }, [deployment.spec?.replicas, deployment.status?.replicas]) + + const namespace = deployment.metadata?.namespace + const name = deployment.metadata?.name + const canScale = Boolean(namespace && name) + + const handleScale = useCallback( + async (nextReplicas: number) => { + if (!canScale || nextReplicas < 0) return + setIsScaling(true) + try { + await scaleDeployment(namespace!, name!, nextReplicas) + setReplicas(nextReplicas) + toast.success( + t('detail.status.scaledTo', { + resource: 'Deployment', + replicas: nextReplicas, + }) + ) + } catch (error) { + console.error('Failed to scale deployment:', error) + toast.error(translateError(error, t)) + } finally { + setIsScaling(false) + } + }, + [canScale, namespace, name, t] + ) + + if (!canScale) { + return - + } + + return ( +
+ + {replicas} + +
+ ) +} + export function DeploymentListPage() { const { t } = useTranslation() const navigate = useNavigate() @@ -36,7 +106,7 @@ export function DeploymentListPage() { ), }), - columnHelper.accessor((row) => row.status, { + columnHelper.accessor((row) => row.status?.readyReplicas ?? 0, { id: 'ready', header: t('deployments.ready'), cell: ({ row }) => { @@ -50,6 +120,12 @@ export function DeploymentListPage() { ) }, }), + columnHelper.display({ + id: 'scale', + header: t('detail.buttons.scale'), + cell: ({ row }) => , + enableSorting: false, + }), columnHelper.accessor('status.conditions', { header: t('common.status'), cell: ({ row }) => { diff --git a/ui/src/pages/statefulset-list-page.tsx b/ui/src/pages/statefulset-list-page.tsx index b57a4891..6087e0e5 100644 --- a/ui/src/pages/statefulset-list-page.tsx +++ b/ui/src/pages/statefulset-list-page.tsx @@ -31,7 +31,7 @@ export function StatefulSetListPage() { ), }), - columnHelper.accessor((row) => row.status, { + columnHelper.accessor((row) => row.status?.readyReplicas ?? 0, { id: 'ready', header: t('deployments.ready'), cell: ({ row }) => {