@velor/remote-mouse 6.5.0 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -312,6 +312,7 @@ Useful CLI commands:
312
312
  - `service restart` restarts the local daemon/service
313
313
  - `tasks` prints the task manager snapshot
314
314
  - `task-manager` is an alias of `tasks`
315
+ - `update-events` prints persisted update-manager events
315
316
  - `samsung-detect` detects Samsung TVs available on the network
316
317
  - `tokens` lists persisted entry tokens
317
318
  - `open-qr` opens the QR page on the server
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velor/remote-mouse",
3
- "version": "6.5.0",
3
+ "version": "6.6.0",
4
4
  "type": "module",
5
5
  "description": "Remote mouse and keyboard with mobile client",
6
6
  "homepage": "https://github.com/glennerichall/node-mouse#readme",
@@ -150,6 +150,28 @@
150
150
  </div>
151
151
  </section>
152
152
 
153
+ <section>
154
+ <h2>Update-manager events</h2>
155
+ <div class="table-wrap">
156
+ <table>
157
+ <thead>
158
+ <tr>
159
+ <th>ID</th>
160
+ <th>Type</th>
161
+ <th>At</th>
162
+ <th>Enabled</th>
163
+ <th>Key</th>
164
+ <th>Checked</th>
165
+ <th>Has update</th>
166
+ <th>Skipped</th>
167
+ <th>Error</th>
168
+ </tr>
169
+ </thead>
170
+ <tbody id="update-events-table"></tbody>
171
+ </table>
172
+ </div>
173
+ </section>
174
+
153
175
  <section>
154
176
  <h2 data-i18n="serverInfo.section.capabilities">Capabilities</h2>
155
177
  <pre id="capabilities"></pre>
@@ -192,6 +214,7 @@
192
214
  const tasksTable = document.getElementById('tasks-table');
193
215
  const tokensTable = document.getElementById('tokens-table');
194
216
  const restartsTable = document.getElementById('restarts-table');
217
+ const updateEventsTable = document.getElementById('update-events-table');
195
218
  const capabilitiesEl = document.getElementById('capabilities');
196
219
  const configEl = document.getElementById('config');
197
220
  const sysConfigEl = document.getElementById('sys-config');
@@ -202,10 +225,6 @@
202
225
  timeStyle: 'medium',
203
226
  });
204
227
  let logsAutoScroll = true;
205
- let configStream = null;
206
- let configSubscriptionId = '';
207
- let pendingConfigPayload = null;
208
- let applyingConfigPayload = false;
209
228
 
210
229
  function t(key, params) {
211
230
  return i18n.t(key, params);
@@ -349,6 +368,33 @@
349
368
  }
350
369
  }
351
370
 
371
+ function renderUpdateEvents(updateEvents) {
372
+ updateEventsTable.innerHTML = '';
373
+ if (!Array.isArray(updateEvents) || updateEvents.length === 0) {
374
+ const row = document.createElement('tr');
375
+ row.innerHTML = '<td colspan="9">No update-manager events.</td>';
376
+ updateEventsTable.appendChild(row);
377
+ return;
378
+ }
379
+
380
+ for (const event of updateEvents) {
381
+ const row = document.createElement('tr');
382
+ const result = event.lastResult || {};
383
+ row.innerHTML = `
384
+ <td>${event.id ?? ''}</td>
385
+ <td>${event.type || '-'}</td>
386
+ <td>${formatDateTime(event.eventAt)}</td>
387
+ <td>${event.enabled ? 'yes' : 'no'}</td>
388
+ <td>${event.lastKey || '-'}</td>
389
+ <td>${Object.hasOwn(result, 'checked') ? (result.checked ? 'yes' : 'no') : '-'}</td>
390
+ <td>${Object.hasOwn(result, 'hasUpdate') ? (result.hasUpdate ? 'yes' : 'no') : '-'}</td>
391
+ <td>${Object.hasOwn(result, 'skipped') ? (result.skipped ? 'yes' : 'no') : '-'}</td>
392
+ <td>${result.error || '-'}</td>
393
+ `;
394
+ updateEventsTable.appendChild(row);
395
+ }
396
+ }
397
+
352
398
  function renderLogs(logs) {
353
399
  const previousScrollTop = logsEl.scrollTop;
354
400
  const shouldAutoScroll = logsAutoScroll;
@@ -446,33 +492,6 @@
446
492
  sysConfigEl.textContent = JSON.stringify(snapshot || {}, null, 2);
447
493
  }
448
494
 
449
- function flushPendingConfigPayload() {
450
- if (applyingConfigPayload || !pendingConfigPayload) {
451
- return;
452
- }
453
-
454
- applyingConfigPayload = true;
455
- const payload = pendingConfigPayload;
456
- pendingConfigPayload = null;
457
-
458
- try {
459
- renderConfigSnapshot(payload.config);
460
- renderSystemConfigSnapshot(payload.sysConfig);
461
- } finally {
462
- applyingConfigPayload = false;
463
- if (pendingConfigPayload) {
464
- flushPendingConfigPayload();
465
- }
466
- }
467
- }
468
-
469
- function scheduleConfigPayload(payload) {
470
- pendingConfigPayload = payload;
471
- queueMicrotask(() => {
472
- flushPendingConfigPayload();
473
- });
474
- }
475
-
476
495
  async function refresh() {
477
496
  try {
478
497
  const response = await fetch('/api/admin/server-info/data', { cache: 'no-store' });
@@ -498,77 +517,26 @@
498
517
  renderTasks(data.tasks || []);
499
518
  renderTokens(data.tokens || []);
500
519
  renderRestarts(data.restarts || []);
520
+ renderUpdateEvents(data.updateEvents || []);
501
521
  renderCapabilities(data.system || null);
502
- scheduleConfigPayload({
503
- config: data.config,
504
- sysConfig: data.sysConfig,
505
- });
522
+ renderConfigSnapshot(data.config);
523
+ renderSystemConfigSnapshot(data.sysConfig);
506
524
  renderLogs(data.logs || []);
507
525
  } catch (error) {
508
526
  logsEl.textContent = t('serverInfo.loadError', {message: error?.message || error});
509
527
  }
510
528
  }
511
529
 
512
- async function subscribeToConfigEvents() {
513
- if (typeof window.EventSource !== 'function') {
514
- return;
515
- }
516
-
517
- const response = await fetch('/api/admin/subs/configs', {
518
- method: 'POST',
519
- headers: {
520
- 'Content-Type': 'application/json',
521
- },
522
- body: JSON.stringify({scope: 'config'}),
523
- });
524
- if (!response.ok) {
525
- throw new Error(t('serverInfo.sseCreateError'));
526
- }
527
-
528
- const payload = await response.json();
529
- configSubscriptionId = String(payload.id || '');
530
- if (!configSubscriptionId || !payload.eventsUrl) {
531
- throw new Error(t('serverInfo.sseInvalid'));
532
- }
533
-
534
- configStream?.close();
535
- configStream = new EventSource(payload.eventsUrl);
536
- configStream.addEventListener('config.changed', (event) => {
537
- try {
538
- scheduleConfigPayload(JSON.parse(event.data || '{}'));
539
- } catch (_error) {
540
- // Best effort.
541
- }
542
- });
543
- }
544
-
545
- function unsubscribeFromConfigEvents() {
546
- if (!configSubscriptionId) {
547
- return;
548
- }
549
-
550
- fetch(`/api/admin/subs/${encodeURIComponent(configSubscriptionId)}`, {
551
- method: 'DELETE',
552
- keepalive: true,
553
- }).catch(() => {});
554
- configSubscriptionId = '';
555
- }
556
-
557
530
  logsEl.addEventListener('scroll', () => {
558
531
  logsAutoScroll = isLogScrollAtBottom();
559
532
  }, { passive: true });
560
533
 
561
534
  refresh();
562
535
  setInterval(refresh, 2000);
563
- subscribeToConfigEvents().catch(() => {});
564
536
  i18n.onChange(() => {
565
537
  i18n.translateRoot(document);
566
538
  refresh();
567
539
  });
568
- window.addEventListener('beforeunload', () => {
569
- configStream?.close();
570
- unsubscribeFromConfigEvents();
571
- });
572
540
  </script>
573
541
  </body>
574
542
  </html>
@@ -44,6 +44,7 @@ export function createApplicationShutdown(services) {
44
44
  runShutdownStep('Erreur a l arret du task manager', () => taskManager.stop()),
45
45
  runShutdownStep('Erreur a l arret de l observateur de configuration', () => state.stopConfigObserver()),
46
46
  runShutdownStep('Erreur a l arret de l observateur de notifications', () => state.stopNotificationObserver()),
47
+ runShutdownStep('Erreur a l arret de l observateur des evenements update-manager', () => state.stopUpdateManagerLogObserver()),
47
48
  runShutdownStep('Erreur a l arret de l observateur de resolution ecran', () => state.stopDisplaySizeObserver()),
48
49
  runShutdownStep('Erreur a l arret de l observateur du QR overlay', () => state.stopQrOverlayRefreshObserver()),
49
50
  runShutdownStep('Erreur a l arret de l observateur du survol QR overlay', () => state.stopQrOverlayHoverObserver()),
@@ -8,6 +8,7 @@ import {startNotificationObserver} from '../init/observers/startNotificationObse
8
8
  import {startDisplaySizeObserver} from '../init/observers/startDisplaySizeObserver.js';
9
9
  import {startQrOverlayRefreshObserver} from '../init/observers/startQrOverlayRefreshObserver.js';
10
10
  import {startQrOverlayHoverObserver} from '../init/observers/startQrOverlayHoverObserver.js';
11
+ import {startUpdateManagerLogObserver} from '../init/observers/startUpdateManagerLogObserver.js';
11
12
  import {notifyIfRestarted} from '../remotes/admin/notifyIfRestarted.js';
12
13
  import {ensureApplicationLifecycleState} from './state.js';
13
14
  import {createLogger} from './logger.js';
@@ -41,6 +42,7 @@ export function createApplicationStart(services) {
41
42
 
42
43
  state.stopConfigObserver = startConfigObserver(services);
43
44
  state.stopNotificationObserver = startNotificationObserver(services);
45
+ state.stopUpdateManagerLogObserver = startUpdateManagerLogObserver(services);
44
46
  state.stopDisplaySizeObserver = startDisplaySizeObserver(services);
45
47
  state.stopQrOverlayRefreshObserver = startQrOverlayRefreshObserver(services);
46
48
  state.stopQrOverlayHoverObserver = startQrOverlayHoverObserver(services);
@@ -8,6 +8,7 @@ export function ensureApplicationLifecycleState(services) {
8
8
  shuttingDown: false,
9
9
  stopConfigObserver: () => {},
10
10
  stopNotificationObserver: () => {},
11
+ stopUpdateManagerLogObserver: () => {},
11
12
  stopDisplaySizeObserver: () => {},
12
13
  stopQrOverlayRefreshObserver: () => {},
13
14
  stopQrOverlayHoverObserver: () => {},
@@ -128,6 +128,7 @@ export function createServerInfoRouter(services) {
128
128
  const tasks = services.getTaskManager().getTasksSnapshot();
129
129
  const tokenEntries = services.getPersistence().entryTokenDao.loadEntryTokens();
130
130
  const restarts = services.getPersistence().restartLogDao.listRecentRestartRecords(20);
131
+ const updateEvents = services.getPersistence().updateEventLogDao.listRecentEvents(20);
131
132
  const currentToken = services.getTokenManager().getToken();
132
133
  const entryPathConfig = services.getSystemConfig().entryPath;
133
134
  const daemon = await services.getApplicationDaemonService().getInfo();
@@ -150,6 +151,7 @@ export function createServerInfoRouter(services) {
150
151
  daemon,
151
152
  system,
152
153
  restarts,
154
+ updateEvents,
153
155
  config,
154
156
  sysConfig,
155
157
  logs,
@@ -0,0 +1,9 @@
1
+ import {PUBSUB_SERVICE_UPDATE_MANAGER} from '../../services/pubsub/serviceEventConstants.js';
2
+
3
+ export function startUpdateManagerLogObserver(services) {
4
+ return services.getPubSub().subscribe((event) => {
5
+ services.getPersistence().updateEventLogDao.createEvent(event);
6
+ }, {
7
+ service: PUBSUB_SERVICE_UPDATE_MANAGER,
8
+ });
9
+ }
@@ -0,0 +1,93 @@
1
+ const UPDATE_EVENT_LOG_TABLE = 'update_manager_event_log';
2
+
3
+ function normalizeRecord(row) {
4
+ if (!row) {
5
+ return null;
6
+ }
7
+
8
+ let lastResult = null;
9
+ try {
10
+ lastResult = JSON.parse(String(row.last_result_json || 'null'));
11
+ } catch (_error) {
12
+ lastResult = null;
13
+ }
14
+
15
+ return {
16
+ id: Number(row.id),
17
+ eventAt: Number(row.event_at),
18
+ type: String(row.event_type || ''),
19
+ enabled: Boolean(row.enabled),
20
+ lastKey: String(row.last_key || ''),
21
+ lastInstallCommand: String(row.last_install_command || ''),
22
+ lastResult,
23
+ };
24
+ }
25
+
26
+ export function createUpdateEventLogDao({getDatabase}) {
27
+ let bootstrapped = false;
28
+
29
+ function bootstrapUpdateEventLogTable() {
30
+ if (bootstrapped) {
31
+ return;
32
+ }
33
+
34
+ const db = getDatabase();
35
+ db.exec(`
36
+ CREATE TABLE IF NOT EXISTS ${UPDATE_EVENT_LOG_TABLE} (
37
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38
+ event_at INTEGER NOT NULL,
39
+ event_type TEXT NOT NULL,
40
+ enabled INTEGER NOT NULL DEFAULT 0,
41
+ last_key TEXT NOT NULL DEFAULT '',
42
+ last_install_command TEXT NOT NULL DEFAULT '',
43
+ last_result_json TEXT NOT NULL DEFAULT 'null'
44
+ )
45
+ `);
46
+
47
+ bootstrapped = true;
48
+ }
49
+
50
+ function createEvent(event = {}) {
51
+ bootstrapUpdateEventLogTable();
52
+ const db = getDatabase();
53
+ const payload = event?.payload || {};
54
+ const eventAtMs = Math.floor(new Date(event.at || Date.now()).getTime());
55
+
56
+ const result = db.prepare(`
57
+ INSERT INTO ${UPDATE_EVENT_LOG_TABLE} (
58
+ event_at,
59
+ event_type,
60
+ enabled,
61
+ last_key,
62
+ last_install_command,
63
+ last_result_json
64
+ )
65
+ VALUES (?, ?, ?, ?, ?, ?)
66
+ `).run(
67
+ Number.isFinite(eventAtMs) ? eventAtMs : Date.now(),
68
+ String(event.type || 'event'),
69
+ payload?.enabled ? 1 : 0,
70
+ String(payload?.lastKey || ''),
71
+ String(payload?.lastInstallCommand || ''),
72
+ JSON.stringify(payload?.lastResult ?? null),
73
+ );
74
+
75
+ return Number(result.lastInsertRowid);
76
+ }
77
+
78
+ function listRecentEvents(limit = 20) {
79
+ bootstrapUpdateEventLogTable();
80
+ const db = getDatabase();
81
+ return db.prepare(`
82
+ SELECT *
83
+ FROM ${UPDATE_EVENT_LOG_TABLE}
84
+ ORDER BY event_at DESC, id DESC
85
+ LIMIT ?
86
+ `).all(Math.max(1, Number(limit) || 20)).map(normalizeRecord);
87
+ }
88
+
89
+ return {
90
+ createEvent,
91
+ listRecentEvents,
92
+ };
93
+ }
@@ -2,6 +2,7 @@ import {createConfigDao} from './createConfigDao.js';
2
2
  import {createEntryTokenDao} from './createEntryTokenDao.js';
3
3
  import {createDatabaseProvider} from './createDatabaseProvider.js';
4
4
  import {createRestartLogDao} from './createRestartLogDao.js';
5
+ import {createUpdateEventLogDao} from './createUpdateEventLogDao.js';
5
6
 
6
7
  export function createPersistence(services) {
7
8
  const getDatabase = createDatabaseProvider({
@@ -15,5 +16,6 @@ export function createPersistence(services) {
15
16
  }),
16
17
  entryTokenDao: createEntryTokenDao({getDatabase}),
17
18
  restartLogDao: createRestartLogDao({getDatabase}),
19
+ updateEventLogDao: createUpdateEventLogDao({getDatabase}),
18
20
  };
19
21
  }
@@ -75,6 +75,9 @@ export function parseCliArgs(args) {
75
75
  .command('task-manager', false, () => {}, () => {
76
76
  command = {name: 'task-manager', args: {}};
77
77
  })
78
+ .command('update-events', false, () => {}, () => {
79
+ command = {name: 'update-events', args: {}};
80
+ })
78
81
  .command('samsung-detect', false, () => {}, () => {
79
82
  command = {name: 'samsung-detect', args: {}};
80
83
  })
@@ -30,6 +30,7 @@ export async function runCliCmd(args) {
30
30
  console.log(' remote-mouse service restart Redemarre le daemon/service local');
31
31
  console.log(' remote-mouse tasks Affiche les informations du task manager');
32
32
  console.log(' remote-mouse task-manager Alias de tasks');
33
+ console.log(' remote-mouse update-events Affiche les evenements persistés du update-manager');
33
34
  console.log(' remote-mouse info --verbosity 2 Affiche les capacites serveur avec logs detailles');
34
35
  console.log(' remote-mouse system-info Alias de info');
35
36
  console.log(' remote-mouse tokens Liste les tokens en base');
@@ -14,6 +14,7 @@ export async function executeHelpCommand() {
14
14
  'remote-mouse service <install|disable|uninstall|restart>',
15
15
  'remote-mouse tasks',
16
16
  'remote-mouse task-manager',
17
+ 'remote-mouse update-events',
17
18
  'remote-mouse samsung-detect',
18
19
  'remote-mouse tokens',
19
20
  'remote-mouse open-qr',
@@ -0,0 +1,7 @@
1
+ export async function executeUpdateEventsCommand(services) {
2
+ return {
3
+ ok: true,
4
+ message: 'Evenements update-manager.',
5
+ data: services.getPersistence().updateEventLogDao.listRecentEvents(50),
6
+ };
7
+ }
@@ -7,6 +7,7 @@ import {executeServiceCommand} from './commands/serviceCommand.js';
7
7
  import {executeSystemConfigCommand} from './commands/systemConfigCommand.js';
8
8
  import {executeTasksCommand} from './commands/tasksCommand.js';
9
9
  import {executeTokensCommand} from './commands/tokensCommand.js';
10
+ import {executeUpdateEventsCommand} from './commands/updateEventsCommand.js';
10
11
  import {formatCliCommand} from '../cli/parseCliArgs.js';
11
12
 
12
13
  const commandHandlers = {
@@ -21,6 +22,7 @@ const commandHandlers = {
21
22
  service: executeServiceCommand,
22
23
  tasks: executeTasksCommand,
23
24
  'task-manager': executeTasksCommand,
25
+ 'update-events': executeUpdateEventsCommand,
24
26
  'samsung-detect': executeSamsungDetectCommand,
25
27
  tokens: executeTokensCommand,
26
28
  };