feat: per-service status checks with offline colouring
Adds optional live status checking per service (not just per node), requested as a follow-up to issue #196. Backend: - New check_service / check_services: HTTP(S) GET for web services, TCP connect otherwise; UDP and port-less non-web services stay 'unknown'. - New scheduler job 'service_checks', independent interval (default 300s), added/removed live via set_service_checks_enabled. - Settings gain service_check_enabled + service_check_interval (>=30s), persisted to scan_config.json. New WS message type 'service_status'. Frontend: - Live per-service status overlay in canvasStore (not persisted, so it never round-trips through canvas save), fed by the WS message. - DetailPanel + canvas node service rows: offline service turns red (#f85149), otherwise keeps its category colour. - SettingsModal: toggle + interval input (default 300s / 5 min). Off by default — no behaviour change until enabled. ha-relevant: yes
This commit is contained in:
@@ -17,8 +17,8 @@ import { useCanvasStore } from '@/stores/canvasStore'
|
||||
describe('SettingsModal', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(settingsApi.get).mockResolvedValue({ data: { interval_seconds: 60 } } as never)
|
||||
vi.mocked(settingsApi.save).mockResolvedValue({ data: { interval_seconds: 60 } } as never)
|
||||
vi.mocked(settingsApi.get).mockResolvedValue({ data: { interval_seconds: 60, service_check_enabled: false, service_check_interval: 300 } } as never)
|
||||
vi.mocked(settingsApi.save).mockResolvedValue({ data: { interval_seconds: 60, service_check_enabled: false, service_check_interval: 300 } } as never)
|
||||
vi.mocked(toast.success).mockReset()
|
||||
vi.mocked(toast.error).mockReset()
|
||||
})
|
||||
@@ -47,7 +47,7 @@ describe('SettingsModal', () => {
|
||||
fireEvent.change(input, { target: { value: '180' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save' }))
|
||||
await waitFor(() => {
|
||||
expect(settingsApi.save).toHaveBeenCalledWith({ interval_seconds: 180 })
|
||||
expect(settingsApi.save).toHaveBeenCalledWith({ interval_seconds: 180, service_check_enabled: false, service_check_interval: 300 })
|
||||
expect(toast.success).toHaveBeenCalledWith('Settings saved')
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
@@ -76,6 +76,20 @@ describe('SettingsModal', () => {
|
||||
expect(localStorage.getItem('homelable.hideIp')).toBe('true')
|
||||
})
|
||||
|
||||
it('loads and toggles the per-service check setting, saving its interval', async () => {
|
||||
vi.mocked(settingsApi.get).mockResolvedValue({ data: { interval_seconds: 60, service_check_enabled: true, service_check_interval: 600 } } as never)
|
||||
render(<SettingsModal open onClose={vi.fn()} />)
|
||||
const toggle = await screen.findByLabelText('Toggle per-service status checks') as HTMLInputElement
|
||||
expect(toggle.checked).toBe(true)
|
||||
expect(await screen.findByDisplayValue('600')).toBeDefined()
|
||||
|
||||
fireEvent.click(toggle) // disable
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save' }))
|
||||
await waitFor(() => {
|
||||
expect(settingsApi.save).toHaveBeenCalledWith({ interval_seconds: 60, service_check_enabled: false, service_check_interval: 600 })
|
||||
})
|
||||
})
|
||||
|
||||
it('calls onClose on Cancel', async () => {
|
||||
const onClose = vi.fn()
|
||||
render(<SettingsModal open onClose={onClose} />)
|
||||
|
||||
Reference in New Issue
Block a user