Updated commit message (now includes the test fix):

Fix camera button permissions & ffmpeg process leak (#550)

  Camera button on printer card was clickable without camera:view
  permission. ffmpeg processes (~240MB each) accumulated after closing
  camera streams because: (1) stop endpoint called terminate() without
  wait()/kill(), (2) HTTP disconnect detection only ran between frames
  so was blocked when the generator was stuck on stdout read, and
  (3) no mechanism caught processes orphaned by generator abandonment
  or app restarts.

  - Add camera:view permission check + tooltip to camera button
  - Fix stop endpoint: terminate() → wait(2s) → kill() → wait()
  - Add background disconnect monitor (polls every 2s, kills ffmpeg
    directly on disconnect)
  - Add periodic /proc scan (every 60s) that SIGKILLs any ffmpeg
    with rtsps://bblp: not in an active stream
  - Add noCamera i18n key to all 6 locales
  - Fix camera API test mocks for async wait() and pid attribute
This commit is contained in:
maziggy
2026-02-28 14:38:45 +01:00
parent 240eeb4f5d
commit 90b9523998
13 changed files with 238 additions and 5 deletions
+1
View File
@@ -305,6 +305,7 @@ export default {
noFiles: 'Sie haben keine Berechtigung, auf Druckerdateien zuzugreifen',
noAmsRfid: 'Sie haben keine Berechtigung, AMS-RFID erneut zu lesen',
noSmartPlugControl: 'Sie haben keine Berechtigung, Smart Plugs zu steuern',
noCamera: 'Sie haben keine Berechtigung, Kameras anzuzeigen',
},
// Add/Edit modal
modal: {
+1
View File
@@ -305,6 +305,7 @@ export default {
noFiles: 'You do not have permission to access printer files',
noAmsRfid: 'You do not have permission to re-read AMS RFID',
noSmartPlugControl: 'You do not have permission to control smart plugs',
noCamera: 'You do not have permission to view cameras',
},
// Add/Edit modal
modal: {
+1
View File
@@ -305,6 +305,7 @@ export default {
noFiles: 'Pas d\'autorisation pour les fichiers',
noAmsRfid: 'Pas d\'autorisation pour le RFID',
noSmartPlugControl: 'Pas d\'autorisation pour les prises',
noCamera: 'Pas d\'autorisation pour les caméras',
},
// Add/Edit modal
modal: {
+1
View File
@@ -296,6 +296,7 @@ export default {
noFiles: 'Non hai il permesso di accedere ai file stampante',
noAmsRfid: 'Non hai il permesso di rileggere AMS RFID',
noSmartPlugControl: 'Non hai il permesso di controllare smart plug',
noCamera: 'Non hai il permesso di visualizzare le telecamere',
},
// Add/Edit modal
modal: {
+1
View File
@@ -269,6 +269,7 @@ export default {
noControl: 'プリンターを制御する権限がありません',
noAmsRfid: 'AMS RFIDを再読み取りする権限がありません',
noSmartPlugControl: 'スマートプラグを制御する権限がありません',
noCamera: 'カメラを表示する権限がありません',
},
modal: {
selectModel: 'モデルを選択...',
+1
View File
@@ -305,6 +305,7 @@ export default {
noFiles: 'Você não tem permissão para acessar arquivos de impressora',
noAmsRfid: 'Você não tem permissão para reler RFID AMS',
noSmartPlugControl: 'Você não tem permissão para controlar tomadas inteligentes',
noCamera: 'Você não tem permissão para visualizar câmeras',
},
// Add/Edit modal
modal: {
+2 -2
View File
@@ -3610,8 +3610,8 @@ function PrinterCard({
window.open(`/camera/${printer.id}`, `camera-${printer.id}`, features);
}
}}
disabled={!status?.connected}
title={cameraViewMode === 'embedded' ? t('printers.openCameraOverlay') : t('printers.openCameraWindow')}
disabled={!status?.connected || !hasPermission('camera:view')}
title={!hasPermission('camera:view') ? t('printers.permission.noCamera') : (cameraViewMode === 'embedded' ? t('printers.openCameraOverlay') : t('printers.openCameraWindow'))}
>
<Video className="w-4 h-4" />
</Button>