feat: manual Proxmox re-sync button + make connection config env-only

Add a 'Re-sync now' button to the Proxmox auto-sync settings section that
triggers an immediate inventory import (POST /proxmox/sync-now) using the
server env config — the manual counterpart to scheduled auto-sync.

Also fix a dual-source-of-truth bug: Proxmox connection config (host, port,
token, verify_tls) is now env-only and never persisted to scan_config.json.
Previously save_overrides() dumped host/port/verify alongside scan settings,
so saving an unrelated setting wrote an empty proxmox_host that load_overrides
then clobbered PROXMOX_HOST with on every boot. Only the auto-sync activation
(sync_enabled + sync_interval) stays user-editable and persisted.

ha-relevant: no
This commit is contained in:
Pouzor
2026-07-07 21:20:13 +02:00
parent 4206d50c70
commit 9437a74147
9 changed files with 263 additions and 28 deletions
@@ -11,6 +11,7 @@ vi.mock('@/api/client', () => ({
proxmoxApi: {
getConfig: vi.fn(),
saveConfig: vi.fn(),
syncNow: vi.fn(),
},
}))
@@ -98,6 +99,51 @@ describe('SettingsModal', () => {
})
})
it('persists only sync fields (not connection config) on Save', async () => {
vi.mocked(proxmoxApi.getConfig).mockResolvedValue({
data: { host: 'pve', port: 8006, verify_tls: true, sync_enabled: true, sync_interval: 3600, token_configured: true },
} as never)
vi.mocked(proxmoxApi.saveConfig).mockResolvedValue({ data: {} } as never)
render(<SettingsModal open onClose={vi.fn()} />)
await screen.findByDisplayValue('60')
fireEvent.click(screen.getByRole('button', { name: 'Save' }))
await waitFor(() => {
expect(proxmoxApi.saveConfig).toHaveBeenCalledWith({ sync_enabled: true, sync_interval: 3600 })
})
})
it('triggers an immediate Proxmox sync from the Re-sync now button', async () => {
vi.mocked(proxmoxApi.getConfig).mockResolvedValue({
data: { host: 'pve', port: 8006, verify_tls: true, sync_enabled: false, sync_interval: 3600, token_configured: true },
} as never)
vi.mocked(proxmoxApi.syncNow).mockResolvedValue({ data: { status: 'running' } } as never)
render(<SettingsModal open onClose={vi.fn()} />)
const btn = await screen.findByRole('button', { name: 'Re-sync now' })
fireEvent.click(btn)
await waitFor(() => {
expect(proxmoxApi.syncNow).toHaveBeenCalledOnce()
expect(toast.success).toHaveBeenCalledWith('Proxmox sync started')
})
})
it('shows a PROXMOX_HOST hint instead of the button when host is unset', async () => {
vi.mocked(proxmoxApi.getConfig).mockResolvedValue({
data: { host: '', port: 8006, verify_tls: true, sync_enabled: false, sync_interval: 3600, token_configured: true },
} as never)
render(<SettingsModal open onClose={vi.fn()} />)
await screen.findByText('PROXMOX_HOST')
expect(screen.queryByRole('button', { name: 'Re-sync now' })).toBeNull()
})
it('hides Re-sync now when no Proxmox token is configured', async () => {
vi.mocked(proxmoxApi.getConfig).mockResolvedValue({
data: { host: 'pve', port: 8006, verify_tls: true, sync_enabled: false, sync_interval: 3600, token_configured: false },
} as never)
render(<SettingsModal open onClose={vi.fn()} />)
await screen.findByDisplayValue('60')
expect(screen.queryByRole('button', { name: 'Re-sync now' })).toBeNull()
})
it('calls onClose on Cancel', async () => {
const onClose = vi.fn()
render(<SettingsModal open onClose={onClose} />)