feat: split scan config and app settings into separate endpoints
- New GET/POST /api/v1/settings for status check interval - Scan /api/v1/scan/config now handles ranges only - Frontend: settingsApi client, SettingsPanel uses settingsApi - ScanConfigModal no longer reads/writes interval - 4 new backend tests for settings endpoint
This commit is contained in:
@@ -15,16 +15,12 @@ interface ScanConfigModalProps {
|
||||
|
||||
export function ScanConfigModal({ open, onClose, onScanNow }: ScanConfigModalProps) {
|
||||
const [ranges, setRanges] = useState<string[]>([''])
|
||||
const [interval, setInterval] = useState(60)
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
scanApi.getConfig()
|
||||
.then((res) => {
|
||||
setRanges(res.data.ranges.length > 0 ? res.data.ranges : [''])
|
||||
setInterval(res.data.interval_seconds)
|
||||
})
|
||||
.then((res) => setRanges(res.data.ranges.length > 0 ? res.data.ranges : ['']))
|
||||
.catch(() => {/* use defaults */})
|
||||
}, [open])
|
||||
|
||||
@@ -33,7 +29,7 @@ export function ScanConfigModal({ open, onClose, onScanNow }: ScanConfigModalPro
|
||||
if (cleaned.length === 0) { toast.error('Add at least one IP range'); return }
|
||||
setSaving(true)
|
||||
try {
|
||||
await scanApi.saveConfig({ ranges: cleaned, interval_seconds: interval })
|
||||
await scanApi.saveConfig({ ranges: cleaned })
|
||||
toast.success('Scan config saved')
|
||||
onClose()
|
||||
} catch {
|
||||
|
||||
@@ -13,7 +13,7 @@ vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.f
|
||||
import { scanApi } from '@/api/client'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
const defaultConfig = { data: { ranges: ['192.168.1.0/24'], interval_seconds: 60 } }
|
||||
const defaultConfig = { data: { ranges: ['192.168.1.0/24'] } }
|
||||
|
||||
describe('ScanConfigModal', () => {
|
||||
beforeEach(() => {
|
||||
@@ -38,13 +38,13 @@ describe('ScanConfigModal', () => {
|
||||
expect(input).toBeDefined()
|
||||
})
|
||||
|
||||
it('preserves interval from API when saving ranges', async () => {
|
||||
vi.mocked(scanApi.getConfig).mockResolvedValue({ data: { ranges: ['10.0.0.0/8'], interval_seconds: 120 } } as never)
|
||||
it('saves only ranges (interval managed by settings endpoint)', async () => {
|
||||
vi.mocked(scanApi.getConfig).mockResolvedValue({ data: { ranges: ['10.0.0.0/8'] } } as never)
|
||||
render(<ScanConfigModal open onClose={vi.fn()} onScanNow={vi.fn()} />)
|
||||
await screen.findByDisplayValue('10.0.0.0/8')
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save' }))
|
||||
await waitFor(() => {
|
||||
expect(scanApi.saveConfig).toHaveBeenCalledWith({ ranges: ['10.0.0.0/8'], interval_seconds: 120 })
|
||||
expect(scanApi.saveConfig).toHaveBeenCalledWith({ ranges: ['10.0.0.0/8'] })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -65,7 +65,7 @@ describe('ScanConfigModal', () => {
|
||||
})
|
||||
|
||||
it('can remove a range when more than one exist', async () => {
|
||||
vi.mocked(scanApi.getConfig).mockResolvedValue({ data: { ranges: ['192.168.1.0/24', '10.0.0.0/8'], interval_seconds: 60 } } as never)
|
||||
vi.mocked(scanApi.getConfig).mockResolvedValue({ data: { ranges: ['192.168.1.0/24', '10.0.0.0/8'], } } as never)
|
||||
render(<ScanConfigModal open onClose={vi.fn()} onScanNow={vi.fn()} />)
|
||||
await screen.findByDisplayValue('192.168.1.0/24')
|
||||
// Both trash buttons should be enabled
|
||||
@@ -74,7 +74,7 @@ describe('ScanConfigModal', () => {
|
||||
})
|
||||
|
||||
it('shows error toast and does not save when all ranges are empty', async () => {
|
||||
vi.mocked(scanApi.getConfig).mockResolvedValue({ data: { ranges: [''], interval_seconds: 60 } } as never)
|
||||
vi.mocked(scanApi.getConfig).mockResolvedValue({ data: { ranges: [''], } } as never)
|
||||
render(<ScanConfigModal open onClose={vi.fn()} onScanNow={vi.fn()} />)
|
||||
await waitFor(() => expect(scanApi.getConfig).toHaveBeenCalled())
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save' }))
|
||||
@@ -90,7 +90,7 @@ describe('ScanConfigModal', () => {
|
||||
await screen.findByDisplayValue('192.168.1.0/24')
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save' }))
|
||||
await waitFor(() => {
|
||||
expect(scanApi.saveConfig).toHaveBeenCalledWith({ ranges: ['192.168.1.0/24'], interval_seconds: 60 })
|
||||
expect(scanApi.saveConfig).toHaveBeenCalledWith({ ranges: ['192.168.1.0/24'] })
|
||||
expect(toast.success).toHaveBeenCalledWith('Scan config saved')
|
||||
expect(onClose).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Plus, Save, ScanLine, ChevronLeft, ChevronRight, LayoutDashboard, Clock
|
||||
import { Logo } from '@/components/ui/Logo'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useCanvasStore } from '@/stores/canvasStore'
|
||||
import { scanApi } from '@/api/client'
|
||||
import { scanApi, settingsApi } from '@/api/client'
|
||||
import { toast } from 'sonner'
|
||||
import { PendingDeviceModal, type PendingDevice } from '@/components/modals/PendingDeviceModal'
|
||||
|
||||
@@ -429,22 +429,18 @@ function ScanHistoryPanel() {
|
||||
|
||||
function SettingsPanel() {
|
||||
const [interval, setIntervalValue] = useState(60)
|
||||
const [ranges, setRangesValue] = useState<string[]>([])
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
scanApi.getConfig()
|
||||
.then((res) => {
|
||||
setIntervalValue(res.data.interval_seconds)
|
||||
setRangesValue(res.data.ranges)
|
||||
})
|
||||
.catch(() => {/* use current defaults */})
|
||||
settingsApi.get()
|
||||
.then((res) => setIntervalValue(res.data.interval_seconds))
|
||||
.catch(() => {/* use default */})
|
||||
}, [])
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true)
|
||||
try {
|
||||
await scanApi.saveConfig({ ranges, interval_seconds: interval })
|
||||
await settingsApi.save({ interval_seconds: interval })
|
||||
toast.success('Settings saved')
|
||||
} catch {
|
||||
toast.error('Failed to save settings')
|
||||
|
||||
Reference in New Issue
Block a user