1- // js/camera-list.js
1+ // js/camera-list.js (Полная версия с изменениями для системы пользователей)
22
33( function ( window ) {
44 window . AppModules = window . AppModules || { } ;
55
66 AppModules . createCameraList = function ( App ) {
7- const cameraListContainer = document . getElementById ( 'camera-list-container' ) ;
8- const openRecordingsBtn = document . getElementById ( 'open-recordings-btn' ) ;
7+ const stateManager = App . stateManager ;
8+ const cameraListContainer = document . getElementById ( 'camera-list-container' ) ;
9+ const openRecordingsBtn = document . getElementById ( 'open-recordings-btn' ) ;
910
10- async function pollCameraStatuses ( ) {
11- for ( const camera of App . cameras ) {
12- const statusIcon = document . getElementById ( `status-icon-${ camera . id } ` ) ;
13- if ( statusIcon ) {
14- const pulse = await window . api . getCameraPulse ( camera ) ;
15- statusIcon . classList . toggle ( 'online' , pulse . success ) ;
11+ async function pollCameraStatuses ( ) {
12+ for ( const camera of stateManager . state . cameras ) {
13+ const statusIcon = document . getElementById ( `status-icon-${ camera . id } ` ) ;
14+ if ( statusIcon ) {
15+ try {
16+ const pulse = await window . api . getCameraPulse ( camera ) ;
17+ statusIcon . classList . toggle ( 'online' , pulse . success ) ;
18+ } catch ( e ) {
19+ statusIcon . classList . remove ( 'online' ) ;
20+ }
21+ }
1622 }
1723 }
18- }
1924
20- async function deleteCamera ( cameraId ) {
21- if ( confirm ( App . t ( 'confirm_delete_camera' ) ) ) {
22- if ( App . recordingStates [ cameraId ] ) {
23- await window . api . stopRecording ( cameraId ) ;
25+ async function deleteCamera ( cameraId ) {
26+ if ( confirm ( App . i18n . t ( 'confirm_delete_camera' ) ) ) {
27+ if ( stateManager . state . recordingStates [ cameraId ] ) {
28+ await window . api . stopRecording ( cameraId ) ;
29+ }
30+ stateManager . deleteCamera ( cameraId ) ;
2431 }
25- App . gridManager . removeStreamsForCamera ( cameraId ) ;
26- App . cameras = App . cameras . filter ( c => c . id !== cameraId ) ;
27- await App . saveConfiguration ( ) ;
28- render ( ) ;
2932 }
30- }
3133
32- function render ( ) {
33- cameraListContainer . innerHTML = '' ;
34-
35- const createGroupHTML = ( group , camerasInGroup ) => {
36- const groupContainer = document . createElement ( 'div' ) ;
37- groupContainer . className = 'group-container' ;
38-
39- const groupHeader = document . createElement ( 'div' ) ;
40- groupHeader . className = 'group-header' ;
41- groupHeader . innerHTML = `<i class="material-icons toggle-icon">arrow_drop_down</i><span class="group-name">${ group . name } </span>` ;
42-
43- const groupCamerasList = document . createElement ( 'div' ) ;
44- groupCamerasList . className = 'group-cameras' ;
45-
46- camerasInGroup . forEach ( camera => {
47- const cameraItem = document . createElement ( 'div' ) ;
48- cameraItem . className = 'camera-item' ;
49- cameraItem . dataset . cameraId = camera . id ;
50- cameraItem . draggable = true ;
51- cameraItem . innerHTML = `<i class="status-icon" id="status-icon-${ camera . id } "></i><span>${ camera . name } </span><div class="rec-indicator"></div>` ;
52- if ( App . recordingStates [ camera . id ] ) {
53- cameraItem . classList . add ( 'recording' ) ;
54- }
55- cameraItem . addEventListener ( 'dragstart' , ( e ) => { e . dataTransfer . setData ( 'text/plain' , camera . id ) ; } ) ;
56- groupCamerasList . appendChild ( cameraItem ) ;
57- } ) ;
58-
59- groupContainer . appendChild ( groupHeader ) ;
60- groupContainer . appendChild ( groupCamerasList ) ;
61-
62- groupHeader . addEventListener ( 'click' , ( ) => {
63- groupHeader . querySelector ( '.toggle-icon' ) . classList . toggle ( 'collapsed' ) ;
64- groupCamerasList . classList . toggle ( 'collapsed' ) ;
65- } ) ;
66-
67- if ( group . id !== null ) {
68- groupHeader . addEventListener ( 'dragover' , ( e ) => { e . preventDefault ( ) ; groupHeader . style . backgroundColor = 'var(--accent-color)' ; } ) ;
69- groupHeader . addEventListener ( 'dragleave' , ( e ) => { groupHeader . style . backgroundColor = '' ; } ) ;
70- groupHeader . addEventListener ( 'drop' , ( e ) => {
71- e . preventDefault ( ) ;
72- groupHeader . style . backgroundColor = '' ;
73- const cameraId = parseInt ( e . dataTransfer . getData ( 'text/plain' ) , 10 ) ;
74- const camera = App . cameras . find ( c => c . id === cameraId ) ;
75- if ( camera && camera . groupId !== group . id ) {
76- camera . groupId = group . id ;
77- App . saveConfiguration ( ) ;
78- render ( ) ;
34+ function render ( ) {
35+ cameraListContainer . innerHTML = '' ;
36+ const { cameras, groups, recordingStates } = stateManager . state ;
37+
38+ const createGroupHTML = ( group , camerasInGroup ) => {
39+ const groupContainer = document . createElement ( 'div' ) ;
40+ groupContainer . className = 'group-container' ;
41+
42+ const groupHeader = document . createElement ( 'div' ) ;
43+ groupHeader . className = 'group-header' ;
44+ groupHeader . innerHTML = `<i class="material-icons toggle-icon">arrow_drop_down</i><span class="group-name">${ group . name } </span>` ;
45+
46+ const groupCamerasList = document . createElement ( 'div' ) ;
47+ groupCamerasList . className = 'group-cameras' ;
48+
49+ camerasInGroup . forEach ( camera => {
50+ const cameraItem = document . createElement ( 'div' ) ;
51+ cameraItem . className = 'camera-item' ;
52+ cameraItem . dataset . cameraId = camera . id ;
53+ // VVV ИЗМЕНЕНИЕ: Перетаскивание доступно только администратору VVV
54+ cameraItem . draggable = App . stateManager . state . currentUser ?. role === 'admin' ;
55+ // ^^^ КОНЕЦ ИЗМЕНЕНИЯ ^^^
56+ cameraItem . innerHTML = `<i class="status-icon" id="status-icon-${ camera . id } "></i><span>${ camera . name } </span><div class="rec-indicator"></div>` ;
57+ if ( recordingStates [ camera . id ] ) {
58+ cameraItem . classList . add ( 'recording' ) ;
7959 }
60+ cameraItem . addEventListener ( 'dragstart' , ( e ) => { e . dataTransfer . setData ( 'text/plain' , camera . id . toString ( ) ) ; } ) ;
61+ groupCamerasList . appendChild ( cameraItem ) ;
8062 } ) ;
81- }
82-
83- return groupContainer ;
84- } ;
63+
64+ groupContainer . appendChild ( groupHeader ) ;
65+ groupContainer . appendChild ( groupCamerasList ) ;
66+
67+ groupHeader . addEventListener ( 'click' , ( ) => {
68+ groupHeader . querySelector ( '.toggle-icon' ) . classList . toggle ( 'collapsed' ) ;
69+ groupCamerasList . classList . toggle ( 'collapsed' ) ;
70+ } ) ;
71+
72+ if ( group . id !== null ) {
73+ groupHeader . addEventListener ( 'dragover' , ( e ) => { e . preventDefault ( ) ; groupHeader . style . backgroundColor = 'var(--accent-color)' ; } ) ;
74+ groupHeader . addEventListener ( 'dragleave' , ( e ) => { groupHeader . style . backgroundColor = '' ; } ) ;
75+ groupHeader . addEventListener ( 'drop' , ( e ) => {
76+ e . preventDefault ( ) ;
77+ groupHeader . style . backgroundColor = '' ;
78+ const cameraId = parseInt ( e . dataTransfer . getData ( 'text/plain' ) , 10 ) ;
79+ const camera = cameras . find ( c => c . id === cameraId ) ;
80+ if ( camera && camera . groupId !== group . id ) {
81+ stateManager . updateCamera ( { ...camera , groupId : group . id } ) ;
82+ }
83+ } ) ;
84+ }
85+
86+ return groupContainer ;
87+ } ;
8588
86- App . groups . forEach ( group => {
87- const camerasInGroup = App . cameras . filter ( c => c . groupId === group . id ) ;
88- cameraListContainer . appendChild ( createGroupHTML ( group , camerasInGroup ) ) ;
89- } ) ;
89+ groups . forEach ( group => {
90+ const camerasInGroup = cameras . filter ( c => c . groupId === group . id ) ;
91+ cameraListContainer . appendChild ( createGroupHTML ( group , camerasInGroup ) ) ;
92+ } ) ;
9093
91- const ungroupedCameras = App . cameras . filter ( c => ! c . groupId ) ;
92- if ( ungroupedCameras . length > 0 ) {
93- const ungroupedPseudoGroup = { id : null , name : App . t ( 'ungrouped_cameras' ) } ;
94- cameraListContainer . appendChild ( createGroupHTML ( ungroupedPseudoGroup , ungroupedCameras ) ) ;
95- }
94+ const ungroupedCameras = cameras . filter ( c => ! c . groupId ) ;
95+ if ( ungroupedCameras . length > 0 ) {
96+ const ungroupedPseudoGroup = { id : null , name : App . i18n . t ( 'ungrouped_cameras' ) } ;
97+ cameraListContainer . appendChild ( createGroupHTML ( ungroupedPseudoGroup , ungroupedCameras ) ) ;
98+ }
9699
97- if ( cameraListContainer . innerHTML === '' ) {
98- cameraListContainer . innerHTML = `<p style="padding: 10px; color: var(--text-secondary);">${ App . t ( 'no_cameras_or_groups' ) } </p>` ;
99- }
100+ if ( cameraListContainer . innerHTML === '' ) {
101+ cameraListContainer . innerHTML = `<p style="padding: 10px; color: var(--text-secondary);">${ App . i18n . t ( 'no_cameras_or_groups' ) } </p>` ;
102+ }
100103
101- pollCameraStatuses ( ) ;
102- }
103-
104- function updateRecordingState ( cameraId , isRecording ) {
105- const cameraItem = cameraListContainer . querySelector ( `.camera-item[data-camera-id='${ cameraId } ']` ) ;
106- if ( cameraItem ) {
107- cameraItem . classList . toggle ( 'recording' , isRecording ) ;
104+ pollCameraStatuses ( ) ;
108105 }
109- }
110106
111- function init ( ) {
112- openRecordingsBtn . addEventListener ( 'click' , ( ) => window . api . openRecordingsFolder ( ) ) ;
113-
114- cameraListContainer . addEventListener ( 'contextmenu' , ( e ) => {
115- const cameraItem = e . target . closest ( '.camera-item' ) ;
116- if ( cameraItem ) {
117- e . preventDefault ( ) ;
118- const cameraId = parseInt ( cameraItem . dataset . cameraId , 10 ) ;
119- const labels = {
120- open_in_browser : `🌐 ${ App . t ( 'context_open_in_browser' ) } ` ,
121- files : `🗂️ ${ App . t ( 'context_file_manager' ) } ` ,
122- ssh : `💻 ${ App . t ( 'context_ssh' ) } ` ,
123- settings : `⚙️ ${ App . t ( 'context_settings' ) } ` ,
124- edit : `✏️ ${ App . t ( 'context_edit' ) } ` ,
125- delete : `🗑️ ${ App . t ( 'context_delete' ) } `
126- } ;
127- window . api . showCameraContextMenu ( { cameraId, labels } ) ;
128- }
129- } ) ;
107+ function init ( ) {
108+ openRecordingsBtn . addEventListener ( 'click' , ( ) => window . api . openRecordingsFolder ( ) ) ;
109+
110+ cameraListContainer . addEventListener ( 'contextmenu' , ( e ) => {
111+ // VVV ИЗМЕНЕНИЕ: Блокируем контекстное меню для всех, кроме админа VVV
112+ if ( App . stateManager . state . currentUser ?. role !== 'admin' ) {
113+ e . preventDefault ( ) ;
114+ return ;
115+ }
116+ // ^^^ КОНЕЦ ИЗМЕНЕНИЯ ^^^
117+
118+ const cameraItem = e . target . closest ( '.camera-item' ) ;
119+ if ( cameraItem ) {
120+ e . preventDefault ( ) ;
121+ const cameraId = parseInt ( cameraItem . dataset . cameraId , 10 ) ;
122+ const labels = {
123+ open_in_browser : `🌐 ${ App . i18n . t ( 'context_open_in_browser' ) } ` ,
124+ files : `🗂️ ${ App . i18n . t ( 'context_file_manager' ) } ` ,
125+ ssh : `💻 ${ App . i18n . t ( 'context_ssh' ) } ` ,
126+ archive : `🗄️ ${ App . i18n . t ( 'archive_title' ) } ` ,
127+ settings : `⚙️ ${ App . i18n . t ( 'context_settings' ) } ` ,
128+ edit : `✏️ ${ App . i18n . t ( 'context_edit' ) } ` ,
129+ delete : `🗑️ ${ App . i18n . t ( 'context_delete' ) } `
130+ } ;
131+ window . api . showCameraContextMenu ( { cameraId, labels } ) ;
132+ }
133+ } ) ;
130134
131- window . api . onContextMenuCommand ( ( { command, cameraId } ) => {
132- const camera = App . cameras . find ( c => c . id === cameraId ) ;
133- if ( ! camera ) return ;
135+ window . api . onContextMenuCommand ( ( { command, cameraId } ) => {
136+ const camera = stateManager . state . cameras . find ( c => c . id === cameraId ) ;
137+ if ( ! camera ) return ;
134138
135- // Создаем чистый объект для передачи через IPC
136- const cameraData = {
137- id : camera . id ,
138- name : camera . name ,
139- ip : camera . ip ,
140- port : camera . port ,
141- username : camera . username ,
142- password : camera . password ,
143- streamPath0 : camera . streamPath0 ,
144- streamPath1 : camera . streamPath1
145- } ;
139+ const cameraDataForIPC = {
140+ id : camera . id ,
141+ name : camera . name ,
142+ ip : camera . ip ,
143+ port : camera . port ,
144+ username : camera . username ,
145+ password : camera . password ,
146+ streamPath0 : camera . streamPath0 ,
147+ streamPath1 : camera . streamPath1 ,
148+ groupId : camera . groupId
149+ } ;
146150
147- switch ( command ) {
148- case 'open_in_browser' :
149- window . api . openInBrowser ( camera . ip ) ;
150- break ;
151- case 'files' : window . api . openFileManager ( cameraData ) ; break ;
152- case 'ssh' : window . api . openSshTerminal ( cameraData ) ; break ;
153- case 'settings' : App . modalHandler . openSettingsModal ( cameraId ) ; break ;
154- case 'edit' : App . modalHandler . openAddModal ( cameraData ) ; break ;
155- case 'delete' : deleteCamera ( cameraId ) ; break ;
156- }
157- } ) ;
158- }
151+ switch ( command ) {
152+ case 'open_in_browser' :
153+ window . api . openInBrowser ( cameraDataForIPC . ip ) ;
154+ break ;
155+ case 'files' : window . api . openFileManager ( cameraDataForIPC ) ; break ;
156+ case 'ssh' : window . api . openSshTerminal ( cameraDataForIPC ) ; break ;
157+ case 'archive' : App . archiveManager . openArchiveForCamera ( camera ) ; break ;
158+ case 'settings' : App . modalHandler . openSettingsModal ( cameraId ) ; break ;
159+ case 'edit' : App . modalHandler . openAddModal ( cameraDataForIPC ) ; break ;
160+ case 'delete' : deleteCamera ( cameraId ) ; break ;
161+ }
162+ } ) ;
163+ }
159164
160- return {
161- init,
162- render,
163- pollCameraStatuses,
164- updateRecordingState
165+ return {
166+ init,
167+ render,
168+ pollCameraStatuses
169+ }
165170 }
166- }
167171} ) ( window ) ;
0 commit comments