Skip to content

RTSP installer changes - #14

Merged
baasith6 merged 2 commits into
mainfrom
installer_rtsp
Jul 29, 2026
Merged

RTSP installer changes#14
baasith6 merged 2 commits into
mainfrom
installer_rtsp

Conversation

@Kirusthiya

Copy link
Copy Markdown
Collaborator

changes done

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@baasith6, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a50a8f40-a4ef-487d-b3ff-efb9de16ca98

📥 Commits

Reviewing files that changed from the base of the PR and between e36007d and 10c5ac8.

📒 Files selected for processing (30)
  • .env.example
  • backend/Contracts/Dtos.cs
  • backend/Controllers/CamerasController.cs
  • backend/Controllers/ConnectorsController.cs
  • backend/Domain/Enums.cs
  • backend/appsettings.json
  • connector/app/admin.py
  • connector/app/backend_client.py
  • connector/app/baked_config.py
  • connector/app/capture.py
  • connector/app/config.py
  • connector/app/main.py
  • connector/app/orchestrator.py
  • connector/app/paths.py
  • connector/app/runtime.py
  • connector/app/store.py
  • connector/app/tray.py
  • connector/installer/onevo-connector.iss
  • connector/onevo_launcher.py
  • connector/tests/test_installer_config.py
  • connector/tests/test_rtsp.py
  • connector/tests/test_runtime_safety.py
  • dashboard/src/app/core/api.service.ts
  • dashboard/src/app/pages/get-started/get-started.component.ts
  • dashboard/src/app/pages/setup/setup.component.ts
  • docker-compose.yml
  • installer-site/.gitignore
  • installer-site/.vercelignore
  • installer-site/index.html
  • installer-site/latest.json

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Connector v1.1.12: safe installer updates, live preview, and camera disable flows

✨ Enhancement 🐞 Bug fix ⚙️ Configuration changes 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Bump Windows connector installer to v1.1.12 and update download metadata.
• Add safer connector pairing/uninstall flows and camera disable endpoints.
• Improve local admin UI with live MJPEG preview, bulk source removal, and pause-as-stop behavior.
Diagram

graph TD
  D["Dashboard (Angular)"] -->|"disable cameras"| B["Backend API (.NET)"] -->|"camera list (excludes Disabled)"| C["Connector Service"] -->|"publish frames/status"| R["Runtime state"] -->|"MJPEG/snapshot"| A["Local Admin UI"]
  C -->|"heartbeat / uninstall"| B -->|"installer version"| D
  T["Windows Tray"] -->|"check latest.json"| M{{"Installer manifest"}} -->|"download .exe"| T -->|"run /UPDATE"| I["Inno Installer"] -->|"install/start service"| C
  C -->|"pause marker + settings"| P[("ProgramData")]
  A -->|"pause monitoring"| P

  subgraph Legend
    direction LR
    _ui["UI"] ~~~ _svc["Service"] ~~~ _db[("Local storage")] ~~~ _ext{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use a standard Windows updater framework (Squirrel/MSIX/Winget)
  • ➕ Battle-tested update semantics (atomic install, rollback, signing expectations)
  • ➕ Less custom update logic in tray app (hash/size validation, prompt gating, etc.)
  • ➖ Higher initial integration cost and packaging changes
  • ➖ May constrain installer UX and service/tray topology
2. Replace camera disable with soft-delete + archival model
  • ➕ Clearer semantics (removed vs disabled) and fewer status states
  • ➕ Can hide archived cameras by default while retaining history
  • ➖ Requires broader data-model changes (queries, reporting, migrations)
  • ➖ May complicate connector reconciliation if IDs are reused or restored
3. Serve live preview via separate local-only preview process
  • ➕ Decouples capture loop performance from HTTP streaming load
  • ➕ Easier to clamp bandwidth/CPU independently
  • ➖ More moving pieces (IPC, extra process supervision)
  • ➖ Harder deployment/troubleshooting on shop PCs

Recommendation: The PR’s approach is reasonable for the current architecture: keep updates driven by tray + manifest, persist pause across restarts, and introduce Disabled state to prevent connector from re-creating pipelines. Given the custom updater, ensure the manifest is always HTTPS (already enforced), consider requiring code-signing verification in addition to SHA-256, and confirm the admin host remains loopback by default (now explicit via CONNECTOR_ADMIN_HOST).

Files changed (31) +1315 / -151

Enhancement (16) +1075 / -92
Dtos.csAdd bulk disable cameras request DTO +1/-0

Add bulk disable cameras request DTO

• Introduces BulkDisableCamerasRequest to support disabling multiple cameras in one call.

backend/Contracts/Dtos.cs

CamerasController.csAdd disable and bulk-disable camera endpoints +26/-0

Add disable and bulk-disable camera endpoints

• Adds DELETE /api/cameras/{id} to set Status=Disabled and POST /api/cameras/bulk-disable to disable multiple cameras scoped by tenant access.

backend/Controllers/CamerasController.cs

Enums.csAdd Disabled camera status +2/-1

Add Disabled camera status

• Extends CameraStatus enum with Disabled to represent removed-from-monitoring cameras without deleting the record.

backend/Domain/Enums.cs

admin.pyLocal admin: management gating, source editing/removal, live MJPEG preview +389/-37

Local admin: management gating, source editing/removal, live MJPEG preview

• Adds source keys, credential-masked display, update-by-key and bulk delete endpoints, and live camera preview endpoints (MJPEG stream + status). Pause now persists a marker and stops the Windows service after responding; admin server binds to CONNECTOR_ADMIN_HOST (default loopback).

connector/app/admin.py

backend_client.pyAdd backend uninstall notification call +8/-0

Add backend uninstall notification call

• Adds notify_uninstall() to call POST /api/connectors/uninstall using connector auth headers.

connector/app/backend_client.py

capture.pyPause-aware capture loop, bounded processing size, and live preview publishing +100/-12

Pause-aware capture loop, bounded processing size, and live preview publishing

• Adds pause handling that releases/reopens sources, publishes per-camera runtime status, throttled JPEG preview publishing, and downscales frames for processing to limit RAM/CPU. Improves file-source pacing using source FPS and sets explicit Offline/Reconnecting statuses.

connector/app/capture.py

config.pyBump connector version and add processing_max_width +3/-1

Bump connector version and add processing_max_width

• Updates runtime version to 1.1.12 and introduces CONNECTOR_PROCESSING_MAX_WIDTH to control downscaling for processing.

connector/app/config.py

paths.pyPrefer CONNECTOR_PROGRAM_DATA override and add pause marker path +8/-1

Prefer CONNECTOR_PROGRAM_DATA override and add pause marker path

• Changes ProgramData root resolution order to honor CONNECTOR_PROGRAM_DATA first and adds pause_marker_path() for machine-wide pause persistence.

connector/app/paths.py

runtime.pyAdd per-camera runtime state and safe frame accessors +46/-0

Add per-camera runtime state and safe frame accessors

• Introduces camera_states with publish_frame/status helpers, adds thread-safe get_frame() and camera_statuses(), and ensures pausing clears frames and marks cameras Paused.

connector/app/runtime.py

store.pyAdd persistent boolean settings helpers +9/-0

Add persistent boolean settings helpers

• Adds get_bool_setting/set_bool_setting wrappers stored as creds for persisted operator settings like monitoring_paused.

connector/app/store.py

tray.pyTray: self-update via manifest, persistent pause integration, start monitoring action +233/-2

Tray: self-update via manifest, persistent pause integration, start monitoring action

• Adds periodic update checks against latest.json, secure download with size+SHA256 verification, and elevated installer launch (/UPDATE). Integrates pause marker to block auto-start/open-admin when paused and adds a Start monitoring action that clears pause and restores service auto-start.

connector/app/tray.py

onevo-connector.issInstaller update mode, paused-marker behavior, and safer service install +59/-20

Installer update mode, paused-marker behavior, and safer service install

• Bumps AppVersion to 1.1.12, adds /UPDATE mode to skip wizard pages, avoids uninstall/install service races on in-place updates, respects monitoring.paused by not starting service, and tightens validation so camera pages require at least one entry unless user chooses Skip earlier.

connector/installer/onevo-connector.iss

onevo_launcher.pyAdd uninstall notification and elevated start-monitoring dispatch +29/-0

Add uninstall notification and elevated start-monitoring dispatch

• Adds --notify-uninstall to best-effort call backend uninstall endpoint before local data removal, and --start-monitoring to resume monitoring via tray helper.

connector/onevo_launcher.py

api.service.tsAdd camera disable API wrappers +7/-0

Add camera disable API wrappers

• Adds deleteCamera() and bulkDisableCameras() client methods for the new backend endpoints.

dashboard/src/app/core/api.service.ts

get-started.component.tsRefine connector install checklist using registered vs online counts +16/-5

Refine connector install checklist using registered vs online counts

• Tracks installedConnectorCount and updates checklist copy/logic to differentiate installed-but-offline/uninstalled vs online connectors.

dashboard/src/app/pages/get-started/get-started.component.ts

setup.component.tsCamera edit UI, bulk removal, masked RTSP, and update availability hint +139/-13

Camera edit UI, bulk removal, masked RTSP, and update availability hint

• Adds checkbox selection + bulk remove (disable) flow, per-camera edit form, RTSP password masking, snapshot link using camera_id, hides Disabled cameras, and shows update-available messaging when connector versions lag the installer version.

dashboard/src/app/pages/setup/setup.component.ts

Bug fix (3) +122 / -15
ConnectorsController.csPrevent double-claim and support uninstall notification +38/-1

Prevent double-claim and support uninstall notification

• Rejects setup-code claims when an active connector recently heartbeated, adds /api/connectors/uninstall to mark a connector Offline/uninstalled, and excludes Disabled cameras from the connector camera payload.

backend/Controllers/ConnectorsController.cs

main.pyPersist monitoring pause and improve setup retry behavior +52/-6

Persist monitoring pause and improve setup retry behavior

• Persists monitoring_paused across restarts via LocalStore setting, passes backend client into admin for management readiness, and changes service-mode setup logic to wait for a setup code instead of retrying blindly.

connector/app/main.py

orchestrator.pyRestart pipelines on source changes and normalize file:// sources +32/-8

Restart pipelines on source changes and normalize file:// sources

• Tracks per-camera source fingerprints to restart pipelines when configuration changes, converts file:// URIs to native Windows paths, and consolidates pipeline teardown via _remove_pipeline().

connector/app/orchestrator.py

Refactor (1) +0 / -24
wizard.pyAvoid persisting creds during claim and remove duplicated provisioning code +0/-24

Avoid persisting creds during claim and remove duplicated provisioning code

• Removes direct LocalStore credential writes during wizard claim and deletes redundant camera creation logic in favor of provisioning helpers.

connector/app/wizard.py

Tests (3) +100 / -4
test_installer_config.pyAdjust mocks for wizard config persistence paths +13/-3

Adjust mocks for wizard config persistence paths

• Updates tests to patch save_wizard_config in its new call sites to keep installer provisioning tests stable.

connector/tests/test_installer_config.py

test_rtsp.pyUpdate config helper and add test for file:// source normalization +33/-0

Update config helper and add test for file:// source normalization

• Adds processing_max_width to minimal config and introduces a test asserting file:// MP4 URIs are opened as native Windows paths and looped.

connector/tests/test_rtsp.py

test_runtime_safety.pyAdd tests for persisted pause setting and tray update gating +54/-1

Add tests for persisted pause setting and tray update gating

• Adds LocalStore persistence test for monitoring_paused and tray tests verifying update prompting is remembered and update menu visibility depends on manifest version.

connector/tests/test_runtime_safety.py

Documentation (1) +4 / -4
index.htmlUpdate installer landing page to v1.1.12 and new download URL +4/-4

Update installer landing page to v1.1.12 and new download URL

• Updates displayed version/filename/size and changes download link to the new blob-hosted installer.

installer-site/index.html

Other (7) +14 / -12
.env.exampleBump connector installer version and hosted binary metadata +4/-4

Bump connector installer version and hosted binary metadata

• Updates sample env variables to reference connector installer v1.1.12, including URL, size, and SHA-256.

.env.example

appsettings.jsonUpdate connector installer version to 1.1.12 +1/-1

Update connector installer version to 1.1.12

• Bumps ConnectorInstaller.Version to match the new release.

backend/appsettings.json

baked_config.pyUpdate baked backend URL for local development +1/-1

Update baked backend URL for local development

• Switches BAKED_BACKEND_URL to localhost:8081 in generated config.

connector/app/baked_config.py

docker-compose.ymlDefault connector installer version to 1.1.12 +1/-1

Default connector installer version to 1.1.12

• Updates ConnectorInstaller__Version default environment wiring to 1.1.12.

docker-compose.yml

.gitignoreIgnore installer-site env files +1/-0

Ignore installer-site env files

• Adds .env* to avoid committing environment-specific secrets/config.

installer-site/.gitignore

.vercelignorePrevent uploading .exe files to Vercel build context +1/-0

Prevent uploading .exe files to Vercel build context

• Ignores *.exe to keep the deploy lightweight and avoid bundling binaries.

installer-site/.vercelignore

latest.jsonPublish v1.1.12 update manifest +5/-5

Publish v1.1.12 update manifest

• Updates latest.json with version 1.1.12, blob download URL, size, and SHA-256 for tray-based updates.

installer-site/latest.json

@baasith6
baasith6 merged commit f58d7d4 into main Jul 29, 2026
0 of 3 checks passed
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Baked backend URL localhost 🐞 Bug ≡ Correctness
Description
connector/app/baked_config.py now bakes http://localhost:8081, and connector/app/config.py prefers
this baked value when CONNECTOR_BACKEND_URL is unset, so real installs will attempt to
pair/heartbeat against the shop PC instead of the API. This breaks connector setup and ongoing
operation unless every deployment explicitly overrides the backend URL.
Code

connector/app/baked_config.py[2]

+BAKED_BACKEND_URL = "http://localhost:8081"
Relevance

⭐⭐⭐ High

Strong precedent: team fixed baked backend URL when it incorrectly pointed to localhost.

PR-#6

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR sets the baked backend URL to localhost, and the runtime config loader explicitly returns the
baked value when no environment override is present, making localhost the default backend for the
connector.

connector/app/baked_config.py[1-3]
connector/app/config.py[50-57]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The connector’s baked default backend URL was changed to `http://localhost:8081`, but `_default_backend_url()` uses this baked value whenever `CONNECTOR_BACKEND_URL` is not set. For production installers, this causes the connector to call the wrong backend by default.

## Issue Context
- `baked_config.py` is marked auto-generated and is used as the default backend URL.
- If the installer/build pipeline doesn’t overwrite this value during release builds, the shipped connector will fail to register/claim/heartbeat.

## Fix Focus Areas
- connector/app/baked_config.py[1-3]
- connector/app/config.py[50-57]

### Implementation guidance
- Restore the baked URL to the intended deployed API base URL (or ensure the build step always injects the correct value for the target environment).
- Add a build/CI guard (or a runtime startup check in release builds) to fail fast if `BAKED_BACKEND_URL` is `localhost` / `127.0.0.1` unless an explicit dev flag is set.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Disabled cameras can’t re-enable 🐞 Bug ≡ Correctness
Description
DELETE /api/cameras/{id} sets Status=Disabled, but camera provisioning/upsert does not reset Status
on sourceKey conflicts, so re-adding the same source later keeps the camera Disabled. Because
connector camera retrieval excludes Disabled cameras, the connector will never resume monitoring
that camera without a separate manual status fix.
Code

backend/Controllers/CamerasController.cs[R91-101]

+    [HttpDelete("{id:guid}")]
+    [Authorize(Roles = "Admin,Manager,Installer")]
+    public async Task<IActionResult> Disable(Guid id)
+    {
+        var cam = await _db.Cameras.FindAsync(id);
+        if (cam is null) return NotFound();
+        if (!TenantAccess.CanAccessStore(User, cam.StoreId)) return Forbid();
+        cam.Status = CameraStatus.Disabled;
+        await _db.SaveChangesAsync();
+        return Ok(new { ok = true, cameraId = cam.Id });
+    }
Relevance

⭐⭐⭐ High

Correctness bug in disable/provision lifecycle; team previously accepted camera/connector state
fixes.

PR-#4

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The disable endpoint sets the new Disabled status; connector camera fetch excludes Disabled; and the
provisioning upsert updates fields on conflict without updating Status, so a disabled camera remains
disabled even when reprovisioned via the same sourceKey.

backend/Controllers/CamerasController.cs[91-101]
backend/Controllers/ConnectorsController.cs[213-216]
backend/Services/CameraProvisioningService.cs[52-67]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A camera can be put into `CameraStatus.Disabled`, and the connector is then prevented from seeing it. However, when the same physical source is provisioned again (same `sourceKey`), the backend upsert path updates name/URLs but does not change `Status`, leaving the camera permanently disabled from the connector’s perspective.

## Issue Context
- Disabling a camera is implemented as a status change, not a delete.
- Connector camera fetch explicitly filters out `Disabled`.
- Provisioning uses `ON CONFLICT (ConnectorId, SourceKey) DO UPDATE` but does not update `Status`.

## Fix Focus Areas
- backend/Controllers/CamerasController.cs[91-115]
- backend/Services/CameraProvisioningService.cs[52-67]
- backend/Controllers/ConnectorsController.cs[213-216]

### Implementation guidance
Pick one consistent behavior:
1) **Re-enable on reprovision**: In `CameraProvisioningService.ProvisionConnectorCameraAsync`, update `Status` in the `DO UPDATE` clause *at least when the existing row is Disabled* (e.g., set to Pending/Active).
2) **Disable = detach**: In the disable endpoint, also clear `ConnectorId` and/or `SourceKey` so provisioning creates a fresh active row (only if that’s acceptable for historical continuity).
3) **Explicit enable**: Add an enable endpoint / UI flow that sets status back to Active/Pending and ensure connector can see it again.

Add a regression test for: disable camera → provision same `sourceKey` again → camera becomes visible to `/api/connectors/cameras`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Disabled cameras still counted 🐞 Bug ≡ Correctness
Description
SetupComponent now hides Disabled cameras client-side, but the backend camera list endpoint still
returns Disabled cameras and GetStartedComponent counts cameras using cams.length, so
onboarding/metrics can claim cameras are configured even when all are Disabled. This creates
inconsistent UI state across pages after using the new disable flow.
Code

dashboard/src/app/pages/setup/setup.component.ts[R542-543]

+    this.selectedCameraIds.clear();
+    this.api.listCameras(id).subscribe((c) => (this.cameras = c.filter(x => x.status !== 'Disabled')));
Relevance

⭐⭐ Medium

UI/metrics semantics unclear (disabled vs configured); no close precedent on counting/filtering
disabled cameras.

PR-#4

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The setup page now filters out Disabled cameras, but the backend list endpoint returns all cameras
regardless of status, and Get Started uses the raw list length to determine whether camera setup is
complete.

dashboard/src/app/pages/setup/setup.component.ts[539-545]
backend/Controllers/CamerasController.cs[25-35]
dashboard/src/app/pages/get-started/get-started.component.ts[218-236]
dashboard/src/app/pages/get-started/get-started.component.ts[332-336]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The dashboard now treats `Disabled` cameras inconsistently:
- Setup page filters them out.
- Other pages (e.g., Get Started) still count them because the backend list API includes them and the UI uses `cams.length`.
This makes workflows and counts disagree after cameras are disabled.

## Issue Context
- The PR introduces a new Disabled status and UI flows that hide disabled cameras in some places.
- The backend `/api/cameras` list does not exclude Disabled.

## Fix Focus Areas
- dashboard/src/app/pages/setup/setup.component.ts[539-545]
- backend/Controllers/CamerasController.cs[25-35]
- dashboard/src/app/pages/get-started/get-started.component.ts[218-236]
- dashboard/src/app/pages/get-started/get-started.component.ts[332-336]

### Implementation guidance
Define a single policy and apply it consistently:
- Option A (recommended): Make `GET /api/cameras` exclude `Status == Disabled` by default (and add an `includeDisabled=true` query param for admin/history views).
- Option B: Keep backend as-is, but update all UI counts and lists (Get Started, overview counts, etc.) to filter out Disabled before computing cameraCount/done flags.

Add an e2e/unit test ensuring that after disabling the only camera in a store, Get Started does not mark the camera setup step as done.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

@@ -1,3 +1,3 @@
# AUTO-GENERATED by installer/build.ps1 - do not edit by hand.
BAKED_BACKEND_URL = "http://20.193.69.220:8081"
BAKED_BACKEND_URL = "http://localhost:8081"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Baked backend url localhost 🐞 Bug ≡ Correctness

connector/app/baked_config.py now bakes http://localhost:8081, and connector/app/config.py prefers
this baked value when CONNECTOR_BACKEND_URL is unset, so real installs will attempt to
pair/heartbeat against the shop PC instead of the API. This breaks connector setup and ongoing
operation unless every deployment explicitly overrides the backend URL.
Agent Prompt
## Issue description
The connector’s baked default backend URL was changed to `http://localhost:8081`, but `_default_backend_url()` uses this baked value whenever `CONNECTOR_BACKEND_URL` is not set. For production installers, this causes the connector to call the wrong backend by default.

## Issue Context
- `baked_config.py` is marked auto-generated and is used as the default backend URL.
- If the installer/build pipeline doesn’t overwrite this value during release builds, the shipped connector will fail to register/claim/heartbeat.

## Fix Focus Areas
- connector/app/baked_config.py[1-3]
- connector/app/config.py[50-57]

### Implementation guidance
- Restore the baked URL to the intended deployed API base URL (or ensure the build step always injects the correct value for the target environment).
- Add a build/CI guard (or a runtime startup check in release builds) to fail fast if `BAKED_BACKEND_URL` is `localhost` / `127.0.0.1` unless an explicit dev flag is set.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +91 to +101
[HttpDelete("{id:guid}")]
[Authorize(Roles = "Admin,Manager,Installer")]
public async Task<IActionResult> Disable(Guid id)
{
var cam = await _db.Cameras.FindAsync(id);
if (cam is null) return NotFound();
if (!TenantAccess.CanAccessStore(User, cam.StoreId)) return Forbid();
cam.Status = CameraStatus.Disabled;
await _db.SaveChangesAsync();
return Ok(new { ok = true, cameraId = cam.Id });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Disabled cameras can’t re-enable 🐞 Bug ≡ Correctness

DELETE /api/cameras/{id} sets Status=Disabled, but camera provisioning/upsert does not reset Status
on sourceKey conflicts, so re-adding the same source later keeps the camera Disabled. Because
connector camera retrieval excludes Disabled cameras, the connector will never resume monitoring
that camera without a separate manual status fix.
Agent Prompt
## Issue description
A camera can be put into `CameraStatus.Disabled`, and the connector is then prevented from seeing it. However, when the same physical source is provisioned again (same `sourceKey`), the backend upsert path updates name/URLs but does not change `Status`, leaving the camera permanently disabled from the connector’s perspective.

## Issue Context
- Disabling a camera is implemented as a status change, not a delete.
- Connector camera fetch explicitly filters out `Disabled`.
- Provisioning uses `ON CONFLICT (ConnectorId, SourceKey) DO UPDATE` but does not update `Status`.

## Fix Focus Areas
- backend/Controllers/CamerasController.cs[91-115]
- backend/Services/CameraProvisioningService.cs[52-67]
- backend/Controllers/ConnectorsController.cs[213-216]

### Implementation guidance
Pick one consistent behavior:
1) **Re-enable on reprovision**: In `CameraProvisioningService.ProvisionConnectorCameraAsync`, update `Status` in the `DO UPDATE` clause *at least when the existing row is Disabled* (e.g., set to Pending/Active).
2) **Disable = detach**: In the disable endpoint, also clear `ConnectorId` and/or `SourceKey` so provisioning creates a fresh active row (only if that’s acceptable for historical continuity).
3) **Explicit enable**: Add an enable endpoint / UI flow that sets status back to Active/Pending and ensure connector can see it again.

Add a regression test for: disable camera → provision same `sourceKey` again → camera becomes visible to `/api/connectors/cameras`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +542 to +543
this.selectedCameraIds.clear();
this.api.listCameras(id).subscribe((c) => (this.cameras = c.filter(x => x.status !== 'Disabled')));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Disabled cameras still counted 🐞 Bug ≡ Correctness

SetupComponent now hides Disabled cameras client-side, but the backend camera list endpoint still
returns Disabled cameras and GetStartedComponent counts cameras using cams.length, so
onboarding/metrics can claim cameras are configured even when all are Disabled. This creates
inconsistent UI state across pages after using the new disable flow.
Agent Prompt
## Issue description
The dashboard now treats `Disabled` cameras inconsistently:
- Setup page filters them out.
- Other pages (e.g., Get Started) still count them because the backend list API includes them and the UI uses `cams.length`.
This makes workflows and counts disagree after cameras are disabled.

## Issue Context
- The PR introduces a new Disabled status and UI flows that hide disabled cameras in some places.
- The backend `/api/cameras` list does not exclude Disabled.

## Fix Focus Areas
- dashboard/src/app/pages/setup/setup.component.ts[539-545]
- backend/Controllers/CamerasController.cs[25-35]
- dashboard/src/app/pages/get-started/get-started.component.ts[218-236]
- dashboard/src/app/pages/get-started/get-started.component.ts[332-336]

### Implementation guidance
Define a single policy and apply it consistently:
- Option A (recommended): Make `GET /api/cameras` exclude `Status == Disabled` by default (and add an `includeDisabled=true` query param for admin/history views).
- Option B: Keep backend as-is, but update all UI counts and lists (Get Started, overview counts, etc.) to filter out Disabled before computing cameraCount/done flags.

Add an e2e/unit test ensuring that after disabling the only camera in a store, Get Started does not mark the camera setup step as done.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants