From 529c447413dec84b394e94aee93e8bb6eb14a115 Mon Sep 17 00:00:00 2001 From: Kelly Date: Thu, 11 Dec 2025 09:41:58 -0700 Subject: [PATCH] refactor: Move worker scaling to Workers page with password confirmation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Worker scaling controls now on /workers page only (removed from /tasks) - Password confirmation required before scaling - Show only git SHA in header (removed version number) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- cannaiq/src/pages/TasksDashboard.tsx | 89 +------------------------- cannaiq/src/pages/WorkersDashboard.tsx | 43 ++++++++++--- 2 files changed, 36 insertions(+), 96 deletions(-) diff --git a/cannaiq/src/pages/TasksDashboard.tsx b/cannaiq/src/pages/TasksDashboard.tsx index 53d9acd4..2c8dbbbb 100644 --- a/cannaiq/src/pages/TasksDashboard.tsx +++ b/cannaiq/src/pages/TasksDashboard.tsx @@ -1,7 +1,6 @@ import { useState, useEffect } from 'react'; import { api } from '../lib/api'; import { Layout } from '../components/Layout'; -import { PasswordConfirmModal } from '../components/PasswordConfirmModal'; import { ListChecks, Clock, @@ -17,9 +16,6 @@ import { Users, Play, Square, - Plus, - Minus, - Server, } from 'lucide-react'; interface Task { @@ -144,16 +140,6 @@ export default function TasksDashboard() { const [poolPaused, setPoolPaused] = useState(false); const [poolLoading, setPoolLoading] = useState(false); - // K8s worker state - const [k8sAvailable, setK8sAvailable] = useState(false); - const [workerReplicas, setWorkerReplicas] = useState(0); - const [workerReady, setWorkerReady] = useState(0); - const [scalingWorkers, setScalingWorkers] = useState(false); - - // Password confirmation for scaling - const [showConfirmModal, setShowConfirmModal] = useState(false); - const [pendingScaleDelta, setPendingScaleDelta] = useState(0); - // Filters const [roleFilter, setRoleFilter] = useState(''); const [statusFilter, setStatusFilter] = useState(''); @@ -163,7 +149,7 @@ export default function TasksDashboard() { const fetchData = async () => { try { - const [tasksRes, countsRes, capacityRes, poolStatus, k8sRes] = await Promise.all([ + const [tasksRes, countsRes, capacityRes, poolStatus] = await Promise.all([ api.getTasks({ role: roleFilter || undefined, status: statusFilter || undefined, @@ -172,16 +158,12 @@ export default function TasksDashboard() { api.getTaskCounts(), api.getTaskCapacity(), api.getTaskPoolStatus(), - api.getK8sWorkers(), ]); setTasks(tasksRes.tasks || []); setCounts(countsRes); setCapacity(capacityRes.metrics || []); setPoolPaused(poolStatus.paused); - setK8sAvailable(k8sRes.available); - setWorkerReplicas(k8sRes.replicas); - setWorkerReady(k8sRes.readyReplicas); setError(null); } catch (err: any) { setError(err.message || 'Failed to load tasks'); @@ -190,35 +172,6 @@ export default function TasksDashboard() { } }; - // Request to scale workers - shows confirmation modal first - const requestScaleWorkers = (delta: number) => { - const newReplicas = Math.max(0, Math.min(50, workerReplicas + delta)); - if (newReplicas === workerReplicas) return; - - setPendingScaleDelta(delta); - setShowConfirmModal(true); - }; - - // Actually scale workers after password confirmation - const executeScaleWorkers = async () => { - const newReplicas = Math.max(0, Math.min(50, workerReplicas + pendingScaleDelta)); - - setScalingWorkers(true); - try { - const res = await api.scaleK8sWorkers(newReplicas); - if (res.success) { - setWorkerReplicas(res.replicas); - } else { - setError(res.error || 'Failed to scale workers'); - } - } catch (err: any) { - setError(err.message || 'Failed to scale workers'); - } finally { - setScalingWorkers(false); - setPendingScaleDelta(0); - } - }; - const togglePool = async () => { setPoolLoading(true); try { @@ -285,34 +238,8 @@ export default function TasksDashboard() {
- {/* Worker Scaling */} - {k8sAvailable && ( -
- - - - {workerReady}/{workerReplicas} - - -
- )} - {/* Pool Toggle */} -
- - {/* Password Confirmation Modal for Worker Scaling */} - { - setShowConfirmModal(false); - setPendingScaleDelta(0); - }} - onConfirm={executeScaleWorkers} - title="Confirm Worker Scaling" - description={`You are about to ${pendingScaleDelta > 0 ? 'add' : 'remove'} ${Math.abs(pendingScaleDelta)} workers (${workerReplicas} → ${Math.max(0, Math.min(50, workerReplicas + pendingScaleDelta))}). This action affects production infrastructure.`} - /> ); } diff --git a/cannaiq/src/pages/WorkersDashboard.tsx b/cannaiq/src/pages/WorkersDashboard.tsx index 4773894b..e9afacaa 100644 --- a/cannaiq/src/pages/WorkersDashboard.tsx +++ b/cannaiq/src/pages/WorkersDashboard.tsx @@ -1,5 +1,6 @@ import { useState, useEffect, useCallback } from 'react'; import { Layout } from '../components/Layout'; +import { PasswordConfirmModal } from '../components/PasswordConfirmModal'; import { api } from '../lib/api'; import { Users, @@ -232,6 +233,10 @@ export function WorkersDashboard() { const [scaling, setScaling] = useState(false); const [targetReplicas, setTargetReplicas] = useState(null); + // Password confirmation for scaling + const [showConfirmModal, setShowConfirmModal] = useState(false); + const [pendingReplicas, setPendingReplicas] = useState(null); + // Pagination const [page, setPage] = useState(0); const workersPerPage = 15; @@ -254,14 +259,21 @@ export function WorkersDashboard() { } }, [targetReplicas]); - // Scale workers (added 2024-12-10) - const handleScale = useCallback(async (replicas: number) => { + // Request scale - shows confirmation modal first + const requestScale = useCallback((replicas: number) => { if (replicas < 0 || replicas > 20) return; + setPendingReplicas(replicas); + setShowConfirmModal(true); + }, []); + + // Execute scale after password confirmation + const executeScale = useCallback(async () => { + if (pendingReplicas === null) return; setScaling(true); try { - const res = await api.post('/api/workers/k8s/scale', { replicas }); + const res = await api.post('/api/workers/k8s/scale', { replicas: pendingReplicas }); if (res.data.success) { - setTargetReplicas(replicas); + setTargetReplicas(pendingReplicas); // Refresh after a short delay to see the change setTimeout(fetchK8sReplicas, 1000); } @@ -270,8 +282,9 @@ export function WorkersDashboard() { setK8sError(err.response?.data?.error || 'Failed to scale'); } finally { setScaling(false); + setPendingReplicas(null); } - }, [fetchK8sReplicas]); + }, [fetchK8sReplicas, pendingReplicas]); const fetchData = useCallback(async () => { try { @@ -389,7 +402,7 @@ export function WorkersDashboard() {
+ + {/* Password Confirmation Modal for Scaling */} + { + setShowConfirmModal(false); + setPendingReplicas(null); + }} + onConfirm={executeScale} + title="Confirm Worker Scaling" + description={`You are about to scale workers to ${pendingReplicas} replicas${k8sReplicas ? ` (currently ${k8sReplicas.desired})` : ''}. This action affects production infrastructure.`} + /> ); }