Skip to content

Commit 879fbea

Browse files
committed
feat: Add new awesome feature
1 parent eab4f65 commit 879fbea

File tree

5 files changed

+343
-117
lines changed

5 files changed

+343
-117
lines changed

build/screenshot.png

1.19 MB
Loading

index.html

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,23 @@
2222
.header .tab.active { color: var(--text-main); border-bottom-color: var(--accent-color); }
2323

2424
.main-container { display: flex; flex-grow: 1; overflow: hidden; }
25-
.view-container { flex-grow: 1; padding: 10px; }
26-
#grid-container { width: 100%; height: 100%; display: grid; grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr); gap: 10px; }
27-
.grid-cell { position: relative; background-color: var(--bg-dark); border-radius: 4px; display: flex; flex-direction: column; justify-content: center; align-items: center; color: var(--text-secondary); border: 1px solid var(--border-color); cursor: default; text-align: center; transition: background-color 0.2s, border-color 0.2s; }
25+
.view-container { flex-grow: 1; padding: 10px; overflow-y: auto; }
26+
#grid-container { width: 100%; display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
27+
.grid-cell {
28+
position: relative;
29+
background-color: var(--bg-dark);
30+
border-radius: 4px;
31+
display: flex;
32+
flex-direction: column;
33+
justify-content: center;
34+
align-items: center;
35+
color: var(--text-secondary);
36+
border: 1px solid var(--border-color);
37+
cursor: default;
38+
text-align: center;
39+
transition: background-color 0.2s, border-color 0.2s;
40+
aspect-ratio: 16 / 9;
41+
}
2842
.grid-cell.drag-over { border: 2px dashed var(--accent-color); background-color: var(--bg-light); }
2943
.grid-cell canvas { width: 100%; height: 100%; object-fit: contain; border-radius: 4px; }
3044
.grid-cell.active { border: 2px solid var(--accent-color); }
@@ -39,9 +53,19 @@
3953
.sidebar { width: var(--sidebar-width); background-color: var(--bg-dark); border-left: 1px solid var(--border-color); display: flex; flex-direction: column; }
4054
.sidebar-header { padding: 10px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; }
4155
.sidebar-header h3 { margin: 0; font-size: 1em; }
56+
.sidebar-header .header-buttons { display: flex; gap: 5px; }
4257
.sidebar-header .icon-button { background: none; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; border-radius: 50%; display: flex; align-items: center; }
4358
.sidebar-header .icon-button:hover { color: var(--text-main); background-color: var(--bg-light); }
44-
#camera-list { list-style: none; padding: 10px; margin: 0; flex-grow: 1; overflow-y: auto; }
59+
60+
#camera-list-container { overflow-y: auto; flex-grow: 1; }
61+
.group-container { margin-bottom: 10px; padding: 0 5px; }
62+
.group-header { display: flex; align-items: center; cursor: pointer; padding: 5px; border-radius: 4px; }
63+
.group-header:hover { background-color: var(--bg-light); }
64+
.group-header .toggle-icon { font-size: 20px; transition: transform 0.2s; user-select: none; }
65+
.group-header .toggle-icon.collapsed { transform: rotate(-90deg); }
66+
.group-header .group-name { font-weight: bold; margin-left: 5px; }
67+
.group-cameras { padding-left: 10px; overflow: hidden; max-height: 1000px; transition: max-height 0.3s ease-in-out, padding 0.3s ease-in-out; }
68+
.group-cameras.collapsed { max-height: 0; padding-top: 0; padding-bottom: 0; }
4569
.camera-item { padding: 8px; border-radius: 4px; margin-bottom: 5px; cursor: grab; display: flex; align-items: center; gap: 8px; }
4670
.camera-item:hover { background-color: var(--bg-light); }
4771
.status-icon { width: 10px; height: 10px; border-radius: 50%; background-color: #6c757d; flex-shrink: 0; }
@@ -92,16 +116,20 @@
92116
<div class="sidebar">
93117
<div class="sidebar-header">
94118
<h3>Устройства</h3>
95-
<button id="add-camera-sidebar-btn" class="icon-button" title="Добавить новую камеру"><i class="material-icons">add_circle_outline</i></button>
119+
<div class="header-buttons">
120+
<button id="add-group-btn" class="icon-button" title="Создать новую группу"><i class="material-icons">create_new_folder</i></button>
121+
<button id="add-camera-sidebar-btn" class="icon-button" title="Добавить новую камеру"><i class="material-icons">add_circle_outline</i></button>
122+
</div>
96123
</div>
97-
<ul id="camera-list"></ul>
124+
<div id="camera-list-container"></div>
98125
</div>
99126
</div>
100127
<div class="bottom-toolbar">
101128
<div class="status-info" id="status-info">CPU: -- | RAM: --</div>
102129
<div class="toolbar-group" id="layout-controls"></div>
103130
</div>
104131

132+
<!-- +++ МОДАЛЬНЫЕ ОКНА ПЕРЕМЕЩЕНЫ СЮДА, В КОНЕЦ BODY +++ -->
105133
<div id="add-camera-modal" class="modal-backdrop hidden">
106134
<div class="modal-content">
107135
<span id="add-modal-close-btn" class="modal-close-btn">×</span>
@@ -158,14 +186,7 @@ <h3>System</h3>
158186
<span>HTTPS порт</span><input type="number" id="system.httpsPort" min="1" max="65535">
159187
<span class="full-width">Путь к SSL сертификату</span><input type="text" id="system.httpsCertificate" class="full-width">
160188
<span class="full-width">Путь к SSL ключу</span><input type="text" id="system.httpsCertificateKey" class="full-width">
161-
<span>Уровень логов</span>
162-
<select id="system.logLevel">
163-
<option value="verbose">Verbose</option>
164-
<option value="debug">Debug</option>
165-
<option value="info">Info</option>
166-
<option value="warning">Warning</option>
167-
<option value="error">Error</option>
168-
</select>
189+
<span>Уровень логов</span><select id="system.logLevel"><option value="verbose">Verbose</option><option value="debug">Debug</option><option value="info">Info</option><option value="warning">Warning</option><option value="error">Error</option></select>
169190
<span>Отключить авторизацию</span><input type="checkbox" id="system.unsafe">
170191
<span>Макс. размер буфера (KB)</span><input type="number" id="system.buffer">
171192
<span>Включить плагины</span><input type="checkbox" id="system.plugins">
@@ -177,15 +198,11 @@ <h3>ISP (Процессор изображений)</h3>
177198
<span>Сжатие динам. диапазона</span><input type="number" id="isp.drc">
178199
<span class="full-width">Конфиг. файл сенсора</span><input type="text" id="isp.sensorConfig" class="full-width">
179200
<span class="full-width">Профиль кач-ва изобр.</span><input type="text" id="isp.iqProfile" class="full-width">
180-
<span>Подавление мерцания</span>
181-
<select id="isp.antiFlicker"><option value="disabled">Disabled</option><option value="50">50 Hz</option><option value="60">60 Hz</option></select>
182-
<span>Медленный затвор</span>
183-
<select id="isp.slowShutter"><option value="disabled">Disabled</option><option value="low">Low</option><option value="medium">Medium</option><option value="high">High</option></select>
184-
<span>Режим RAW</span>
185-
<select id="isp.rawMode"><option value="none">None</option><option value="slow">Slow</option><option value="fast">Fast</option></select>
201+
<span>Подавление мерцания</span><select id="isp.antiFlicker"><option value="disabled">Disabled</option><option value="50">50 Hz</option><option value="60">60 Hz</option></select>
202+
<span>Медленный затвор</span><select id="isp.slowShutter"><option value="disabled">Disabled</option><option value="low">Low</option><option value="medium">Medium</option><option value="high">High</option></select>
203+
<span>Режим RAW</span><select id="isp.rawMode"><option value="none">None</option><option value="slow">Slow</option><option value="fast">Fast</option></select>
186204
<span>Блоки памяти для кодера</span><input type="number" id="isp.blkCnt">
187-
<span>Режим памяти</span>
188-
<select id="isp.memMode"><option value="normal">Normal</option><option value="reduction">Reduction</option></select>
205+
<span>Режим памяти</span><select id="isp.memMode"><option value="normal">Normal</option><option value="reduction">Reduction</option></select>
189206
<span>Цифр. стаб. изображения</span><input type="checkbox" id="isp.dis">
190207
<span>Зеркало (ISP)</span><input type="checkbox" id="isp.mirror">
191208
<span>Переворот (ISP)</span><input type="checkbox" id="isp.flip">
@@ -246,7 +263,7 @@ <h3>JPEG поток</h3>
246263
</div>
247264
</div>
248265
<div id="tab-osd" class="tab-content">
249-
<h3>OSD (On-Screen Display)</h3>
266+
<h3>OSD</h3>
250267
<div class="form-grid">
251268
<span>Включить OSD</span><input type="checkbox" id="osd.enabled">
252269
<span class="full-width">Шаблон</span><input type="text" id="osd.template" class="full-width">
@@ -261,7 +278,7 @@ <h3>OSD (On-Screen Display)</h3>
261278
<h3>Аудио</h3>
262279
<div class="form-grid">
263280
<span>Включено</span><input type="checkbox" id="audio.enabled">
264-
<span>Кодек</span><select id="audio.codec"><option value="opus">Opus</option><option value="aac">AAC</option><option value="pcm">PCM</option><option value="alaw">G.711A (alaw)</option><option value="ulaw">G.711U (ulaw)</option></select>
281+
<span>Кодек</span><select id="audio.codec"><option value="opus">Opus</option><option value="aac">AAC</option><option value="pcm">PCM</option><option value="alaw">G.711A</option><option value="ulaw">G.711U</option></select>
265282
<span>Частота</span><select id="audio.srate"><option value="8000">8000 Hz</option><option value="16000">16000 Hz</option><option value="32000">32000 Hz</option><option value="48000">48000 Hz</option></select>
266283
<span>Громкость входа</span><input type="number" id="audio.volume" min="0" max="100">
267284
<span>Двойной микрофон</span><input type="checkbox" id="audio.dual">
@@ -272,7 +289,7 @@ <h3>Аудио</h3>
272289
</div>
273290
</div>
274291
<div id="tab-rtsp" class="tab-content">
275-
<h3>RTSP сервер</h3>
292+
<h3>RTSP</h3>
276293
<div class="form-grid">
277294
<span>Включен</span><input type="checkbox" id="rtsp.enabled">
278295
<span>Порт</span><input type="number" id="rtsp.port">
@@ -353,6 +370,21 @@ <h3>NETIP</h3>
353370
<div id="settings-toast" class="toast-notification"></div>
354371
</div>
355372
</div>
373+
374+
<div id="add-group-modal" class="modal-backdrop hidden">
375+
<div class="modal-content">
376+
<span id="add-group-modal-close-btn" class="modal-close-btn">×</span>
377+
<h2 id="add-group-modal-title">Создать новую группу</h2>
378+
<div class="form-grid simple">
379+
<b>Название:</b> <input type="text" id="new-group-name" placeholder="Моя новая группа">
380+
<b></b>
381+
<div class="form-buttons">
382+
<button id="save-group-btn">Сохранить</button>
383+
<button id="cancel-group-btn" style="background-color: #6c757d;">Отмена</button>
384+
</div>
385+
</div>
386+
</div>
387+
</div>
356388

357389
<script src="jsmpeg.min.js"></script>
358390
<script src="./renderer.js"></script>

main.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,26 @@ const dgram = require('dgram');
1212
const crypto = require('crypto');
1313
const ffmpeg = require('@ffmpeg-installer/ffmpeg');
1414

15-
// --- ИСПРАВЛЕНИЕ ЗДЕСЬ ---
15+
// Исправление пути к ffmpeg для упакованного приложения
1616
const ffmpegPath = ffmpeg.path.replace('app.asar', 'app.asar.unpacked');
17-
// -------------------------
1817

1918
let mainWindow = null;
2019
const streamManager = {};
2120
const usedPorts = new Set();
2221
const BASE_PORT = 9001;
2322

2423
function getDataPath() {
24+
// В режиме разработки используем стандартный путь, чтобы не засорять папку проекта
2525
if (!app.isPackaged) {
2626
return app.getPath('userData');
2727
}
28+
// В упакованном приложении проверяем наличие файла-маркера
2829
const portableMarkerPath = path.join(path.dirname(app.getPath('exe')), 'portable.txt');
2930
if (fs.existsSync(portableMarkerPath)) {
31+
// Портативный режим: используем папку с exe
3032
return path.dirname(app.getPath('exe'));
3133
} else {
34+
// Стандартный режим: используем AppData и т.д.
3235
return app.getPath('userData');
3336
}
3437
}
@@ -159,7 +162,7 @@ ipcMain.handle('start-video-stream', async (event, { credentials, streamId }) =>
159162
'-f', 'mpegts',
160163
'-codec:v', 'mpeg1video',
161164
'-q:v', '4',
162-
'-s', streamId === 1 ? '640x360' : '1280x720',
165+
'-s', streamId === 0 ? '1280x720' : '640x360',
163166
'-r', '25',
164167
'-codec:a', 'mp2',
165168
'-b:a', '128k',
@@ -170,6 +173,10 @@ ipcMain.handle('start-video-stream', async (event, { credentials, streamId }) =>
170173
];
171174
const ffmpegProcess = spawn(ffmpegPath, ffmpegArgs, { detached: false, windowsHide: true });
172175

176+
ffmpegProcess.on('error', (err) => {
177+
console.error(`[FFMPEG] Failed to start subprocess: ${err.message}`);
178+
});
179+
173180
ffmpegProcess.stdout.on('data', (data) => {
174181
wss.clients.forEach((client) => {
175182
if (client.readyState === WebSocket.OPEN) client.send(data);
@@ -245,13 +252,15 @@ ipcMain.handle('save-configuration', async (event, config) => {
245252
ipcMain.handle('load-configuration', async () => {
246253
const defaultConfig = {
247254
cameras: [],
255+
groups: [],
248256
layout: { cols: 2, rows: 2 },
249257
gridState: [null, null, null, null]
250258
};
251259
try {
252260
await fsPromises.access(configPath);
253261
const data = await fsPromises.readFile(configPath, 'utf-8');
254-
return JSON.parse(data);
262+
let config = JSON.parse(data);
263+
return { ...defaultConfig, ...config };
255264
} catch (e) {
256265
try {
257266
await fsPromises.access(oldCamerasPath);
@@ -261,7 +270,7 @@ ipcMain.handle('load-configuration', async () => {
261270
const newConfig = { ...defaultConfig, cameras: oldCameras };
262271
await fsPromises.writeFile(configPath, JSON.stringify(newConfig, null, 2));
263272
await fsPromises.rename(oldCamerasPath, `${oldCamerasPath}.bak`);
264-
console.log('Migration successful: cameras.json has been migrated to config.json');
273+
console.log('Migration successful');
265274
return newConfig;
266275
} catch (migrationError) {
267276
console.log('No existing config found, returning default.');
@@ -282,7 +291,6 @@ ipcMain.handle('get-system-stats', () => {
282291
const idle = totalIdle / cpus.length;
283292
const total = totalTick / cpus.length;
284293
const usage = 100 * (1 - idle / total);
285-
286294
return {
287295
cpu: usage.toFixed(0),
288296
ram: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(0),

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openipc-dashboard",
3-
"version": "2.2.1",
3+
"version": "2.2.2",
44
"description": "App for managing OpenIPC cameras",
55
"main": "main.js",
66
"scripts": {

0 commit comments

Comments
 (0)