diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63ae8c7e..b04f034e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,7 +107,7 @@ jobs: with: { path: zm-web } - uses: actions/checkout@v7 with: - repository: SteveGilvarry/zm-api + repository: ZoneMinder/zm-api path: zm_api - uses: actions/setup-node@v7 with: diff --git a/e2e/seed/seed.sql b/e2e/seed/seed.sql index f38bfac3..4d7919cc 100644 --- a/e2e/seed/seed.sql +++ b/e2e/seed/seed.sql @@ -92,14 +92,40 @@ VALUES (9001, '/var/cache/zoneminder/events-e2e', 'e2e-events', 'local', NULL, NULL, 'Medium', 9001, 1, 1); -- --------------------------------------------------------------------------- --- PTZ control profile: a copy of the stock Ffmpeg/Pelco-D row (positional --- VALUES, same column order as zm_create.sql.in) with our own id and name. --- Positional, so every column ZoneMinder adds to Controls has to be added --- here too, and every column it drops removed: Type went in 1.39.39 (ZoneMinder --- 1043273, 2026-10-10), the four audio columns at the end are the newest. +-- PTZ control profile: a copy of the stock Pelco-D row (db/controls.sql) +-- with our own id and name. +-- Columns are named, so a column ZoneMinder adds (with a default) or drops +-- elsewhere in Controls does not break the load. -- --------------------------------------------------------------------------- -INSERT INTO Controls VALUES - (9001,'e2e-PTZ Dome (Pelco-D)','PelcoD',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0,0,NULL,NULL,0); +INSERT INTO Controls + (Id, Name, Protocol, CanWake, CanSleep, CanReset, CanReboot, CanZoom, + CanAutoZoom, CanZoomAbs, CanZoomRel, CanZoomCon, MinZoomRange, + MaxZoomRange, MinZoomStep, MaxZoomStep, HasZoomSpeed, MinZoomSpeed, + MaxZoomSpeed, CanFocus, CanAutoFocus, CanFocusAbs, CanFocusRel, + CanFocusCon, MinFocusRange, MaxFocusRange, MinFocusStep, MaxFocusStep, + HasFocusSpeed, MinFocusSpeed, MaxFocusSpeed, CanIris, CanAutoIris, + CanIrisAbs, CanIrisRel, CanIrisCon, MinIrisRange, MaxIrisRange, + MinIrisStep, MaxIrisStep, HasIrisSpeed, MinIrisSpeed, MaxIrisSpeed, + CanGain, CanAutoGain, CanGainAbs, CanGainRel, CanGainCon, MinGainRange, + MaxGainRange, MinGainStep, MaxGainStep, HasGainSpeed, MinGainSpeed, + MaxGainSpeed, CanWhite, CanAutoWhite, CanWhiteAbs, CanWhiteRel, + CanWhiteCon, MinWhiteRange, MaxWhiteRange, MinWhiteStep, MaxWhiteStep, + HasWhiteSpeed, MinWhiteSpeed, MaxWhiteSpeed, CanLight, CanIndicatorLight, + HasPresets, NumPresets, HasHomePreset, CanSetPresets, CanMove, + CanMoveDiag, CanMoveMap, CanMoveAbs, CanMoveRel, CanMoveCon, CanPan, + MinPanRange, MaxPanRange, MinPanStep, MaxPanStep, HasPanSpeed, + MinPanSpeed, MaxPanSpeed, HasTurboPan, TurboPanSpeed, CanTilt, + MinTiltRange, MaxTiltRange, MinTiltStep, MaxTiltStep, HasTiltSpeed, + MinTiltSpeed, MaxTiltSpeed, HasTurboTilt, TurboTiltSpeed, CanAutoScan, + NumScanPaths, CanAudioPlay, MinAudioFile, MaxAudioFile, CanAudioVolume) +VALUES + (9001, 'e2e-PTZ Dome (Pelco-D)', 'PelcoD', 1, 1, 0, 0, 1, 1, 0, 0, 1, NULL, + NULL, NULL, NULL, 1, 0, 3, 1, 1, 0, 0, 1, NULL, NULL, NULL, NULL, 0, + NULL, NULL, 1, 1, 0, 1, 0, NULL, NULL, NULL, NULL, 0, NULL, NULL, 0, 0, + 0, 0, 0, NULL, NULL, NULL, NULL, 0, NULL, NULL, 1, 1, 0, 1, 0, NULL, + NULL, NULL, NULL, 0, NULL, NULL, 0, 0, 1, 20, 1, 1, 1, 1, 0, 0, 0, 1, 1, + NULL, NULL, NULL, NULL, 1, 0, 63, 1, 254, 1, NULL, NULL, NULL, NULL, 1, + 0, 63, 1, 254, 0, 0, 0, NULL, NULL, 0); -- --------------------------------------------------------------------------- -- Monitors. Four Ffmpeg cameras; 9002 is ROTATE_90 and 9003 is ROTATE_270 for diff --git a/src/api/controls.ts b/src/api/controls.ts index 431bcf33..6bb9d921 100644 --- a/src/api/controls.ts +++ b/src/api/controls.ts @@ -13,7 +13,8 @@ import type { PaginatedResponse } from '@/types'; export interface Control { id: number; name: string; - type: string; + /** Null from ZoneMinder 1.39.39 on, which dropped `Controls.Type`. */ + type: string | null; protocol?: string | null; // Movement capabilities diff --git a/src/skins/modern/pages/settings.ptzControls.test.tsx b/src/skins/modern/pages/settings.ptzControls.test.tsx index 6fc52457..fbdf321b 100644 --- a/src/skins/modern/pages/settings.ptzControls.test.tsx +++ b/src/skins/modern/pages/settings.ptzControls.test.tsx @@ -126,6 +126,20 @@ describe('PTZ controls page — editor and guards', () => { expect(names()).toEqual(['Bravo']); }); + it('searches profiles that have no type, as ZoneMinder 1.39.39+ returns them', async () => { + seedControls([ + { id: 1, name: 'Pelco-D', type: null, protocol: 'PelcoD' }, + { id: 2, name: 'Axis', type: null, protocol: 'AxisV2' }, + ]); + const user = userEvent.setup(); + renderPage(); + await waitFor(() => expect(screen.getByText('Pelco-D')).toBeInTheDocument()); + await user.type(screen.getByRole('searchbox', { name: /search control profiles/i }), 'pelco'); + expect(screen.getByRole('row', { name: /Pelco-D/ })).toBeInTheDocument(); + expect(screen.queryByRole('row', { name: /Axis/ })).not.toBeInTheDocument(); + expect(screen.queryByText('Cannot reach the server.')).not.toBeInTheDocument(); + }); + it('Add profile opens the tabbed editor at ?id=new and POSTs every field', async () => { seedControls([]); let body: Record | null = null;