diff --git a/web/src/pages/IssuersPage.tsx b/web/src/pages/IssuersPage.tsx index 786aaf3..449e16c 100644 --- a/web/src/pages/IssuersPage.tsx +++ b/web/src/pages/IssuersPage.tsx @@ -1,6 +1,7 @@ import { useEffect, useState, useMemo } from 'react'; import { Link } from 'react-router-dom'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; +import { useTrackedMutation } from '../hooks/useTrackedMutation'; import { getIssuers, testIssuerConnection, deleteIssuer, createIssuer, updateIssuer } from '../api/client'; import PageHeader from '../components/PageHeader'; import DataTable from '../components/DataTable'; @@ -30,7 +31,6 @@ function issuerStatus(issuer: Issuer): string { } export default function IssuersPage() { - const queryClient = useQueryClient(); const [testResult, setTestResult] = useState<{ id: string; ok: boolean; msg: string } | null>(null); const [showCreateModal, setShowCreateModal] = useState(false); const [preselectedType, setPreselectedType] = useState(null); @@ -54,22 +54,27 @@ export default function IssuersPage() { queryFn: () => getIssuers(), }); - const testMutation = useMutation({ + // testIssuerConnection updates last_tested_at and test_status server-side; + // invalidating ['issuers'] refetches the list so the timestamp + status + // columns reflect the new probe state. The local setTestResult banner + // still surfaces the immediate pass/fail to the operator. + const testMutation = useTrackedMutation({ mutationFn: testIssuerConnection, + invalidates: [['issuers']], onSuccess: (_data, id) => setTestResult({ id, ok: true, msg: 'Connection successful' }), onError: (err: Error, id) => setTestResult({ id, ok: false, msg: err.message }), }); - const deleteMutation = useMutation({ + const deleteMutation = useTrackedMutation({ mutationFn: deleteIssuer, - onSuccess: () => queryClient.invalidateQueries({ queryKey: ['issuers'] }), + invalidates: [['issuers']], }); - const createMutation = useMutation({ + const createMutation = useTrackedMutation({ mutationFn: (data: { name: string; type: string; config: Record }) => createIssuer(data), + invalidates: [['issuers']], onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['issuers'] }); setShowCreateModal(false); setPreselectedType(null); }, @@ -80,10 +85,10 @@ export default function IssuersPage() { // docblock above. Sends `{ name, type, config }` to satisfy the backend // PUT contract (the handler decodes into a full domain.Issuer struct); // type + config are preserved by reading them from the editing target. - const updateMutation = useMutation({ + const updateMutation = useTrackedMutation({ mutationFn: ({ id, data }: { id: string; data: Partial }) => updateIssuer(id, data), + invalidates: [['issuers']], onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['issuers'] }); setEditingIssuer(null); }, }); diff --git a/web/src/pages/NetworkScanPage.tsx b/web/src/pages/NetworkScanPage.tsx index ebcdd8a..8419386 100644 --- a/web/src/pages/NetworkScanPage.tsx +++ b/web/src/pages/NetworkScanPage.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; +import { useTrackedMutation } from '../hooks/useTrackedMutation'; import { getNetworkScanTargets, createNetworkScanTarget, @@ -119,7 +120,6 @@ function CreateScanTargetModal({ onClose, onCreate }: { export default function NetworkScanPage() { const [showCreate, setShowCreate] = useState(false); - const queryClient = useQueryClient(); const { data, isLoading, error, refetch } = useQuery({ queryKey: ['network-scan-targets'], @@ -127,28 +127,31 @@ export default function NetworkScanPage() { refetchInterval: 30000, }); - const createMutation = useMutation({ + // Every network-scan-target mutation invalidates the same list query. + const scanTargetInvalidates = [['network-scan-targets']]; + + const createMutation = useTrackedMutation({ mutationFn: createNetworkScanTarget, + invalidates: scanTargetInvalidates, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['network-scan-targets'] }); setShowCreate(false); }, }); - const deleteMutation = useMutation({ + const deleteMutation = useTrackedMutation({ mutationFn: deleteNetworkScanTarget, - onSuccess: () => queryClient.invalidateQueries({ queryKey: ['network-scan-targets'] }), + invalidates: scanTargetInvalidates, }); - const toggleMutation = useMutation({ + const toggleMutation = useTrackedMutation({ mutationFn: ({ id, enabled }: { id: string; enabled: boolean }) => updateNetworkScanTarget(id, { enabled }), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ['network-scan-targets'] }), + invalidates: scanTargetInvalidates, }); - const scanMutation = useMutation({ + const scanMutation = useTrackedMutation({ mutationFn: triggerNetworkScan, - onSuccess: () => queryClient.invalidateQueries({ queryKey: ['network-scan-targets'] }), + invalidates: scanTargetInvalidates, }); const columns: Column[] = [