Add Feature Software Inventory (#7597)
* Feature Software Added new Feature Software with Uninstall function * Fix else if Fix else if * Feature Software Show Uninstall Button to Admins only! * Software Feature Change rights for software commands to DeviceDetails. * Add Software Feature Add possibility to view / uninstall Windows Apps and Store Apps. You need ‘Details’ rights to view the software. You need admin rights in MeshCentral to uninstall the application. * Change InstalledApps from PS to RegistryQuery
esgheorghe committed
May 16, 2026 at 18:43 UTC
51d62c1e5b1c1a25c42d02e73c10fc576f166f84
6 files changed
+1503
-81
agents/meshcore.js
+459
-7
@@ -1283,6 +1283,191 @@ function handleServerCommand(data) {
1283
break;
1284
case 'msg': {
1285
switch (data.type) {
1286
+ // FÜGE DIESEN NEUEN BLOCK HIER EIN
1287
+ case 'software': {
1288
+ // Hilfsfunktion: Antwortet als 'software'-Typ
1289
+ var sendSoftwareResponse = function(responseData) {
1290
+ mesh.SendCommand({
1291
+ action: 'msg',
1292
+ type: 'software',
1293
+ value: (typeof responseData === 'string') ? responseData : JSON.stringify(responseData),
1294
+ sessionid: data.sessionid
1295
+ });
1296
+ };
1297
+
1298
+ if (data.value == 'installedapps') {
1299
+ try {
1300
+ if (require('win-info').installedApps) {
1301
+ require('win-info').installedApps().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1302
+ } else { sendSoftwareResponse({ error: "Not supported" }); }
1303
+ } catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1304
+ }
1305
+ else if (data.value == 'installedstoreapps') {
1306
+ try {
1307
+ if (require('win-info').installedStoreApps) {
1308
+ require('win-info').installedStoreApps().then(sendSoftwareResponse).catch(function(e) { sendSoftwareResponse({ error: e.toString() }); });
1309
+ }
1310
+ } catch (e) { sendSoftwareResponse({ error: e.toString() }); }
1311
+ }
1312
+ // --- UNINSTALL APP (Win32) ---
1313
+ else if (typeof data.value === 'string' && data.value.indexOf('uninstallapp ') === 0) {
1314
+ var base64Cmd = data.value.substring(13).trim(); // "uninstallapp " abschneiden
1315
+ var uninstallCmd = '';
1316
+
1317
+ try {
1318
+ var b = Buffer.from(base64Cmd, 'base64');
1319
+ var decoded = b.toString();
1320
+ if (decoded && decoded.length > 0 && decoded.indexOf(':') >= 0) { uninstallCmd = decoded; } else { uninstallCmd = base64Cmd; }
1321
+ } catch (e) { uninstallCmd = base64Cmd; }
1322
+
1323
+ if (!uninstallCmd || uninstallCmd.trim() === '' || uninstallCmd.trim() === '\\') {
1324
+ sendSoftwareResponse({ success: false, error: 'No valid uninstall command available' });
1325
+ } else {
1326
+ var logFile = 'C:\\ProgramData\\MeshAgent_Uninstall.log';
1327
+ var child_process = require('child_process');
1328
+ var cmdPath = process.env['windir'] + '\\system32\\cmd.exe';
1329
+
1330
+ var writeLog = function(message, callback) {
1331
+ try {
1332
+ var timestamp = new Date().toISOString();
1333
+ var logLine = timestamp + ' | ' + message;
1334
+ logLine = logLine.replace(/"/g, '""').replace(/&/g, '^&').replace(/</g, '^<').replace(/>/g, '^>').replace(/\|/g, '^|');
1335
+ var logChild = child_process.execFile(cmdPath, ['cmd', '/c', 'echo ' + logLine + ' >> "' + logFile + '"'], { timeout: 5000 });
1336
+ logChild.on('exit', function() { if (callback) callback(); });
1337
+ } catch (e) { if (callback) callback(); }
1338
+ };
1339
+
1340
+ try {
1341
+ writeLog('UNINSTALL START - Command: ' + uninstallCmd.replace(/\\/g, '/'));
1342
+ var originalCmd = uninstallCmd;
1343
+
1344
+ if (uninstallCmd.toLowerCase().indexOf('msiexec') >= 0) {
1345
+ uninstallCmd = uninstallCmd.replace(/\/q[nbrf]?/gi, '');
1346
+ if (uninstallCmd.indexOf('/QN') < 0) { uninstallCmd = uninstallCmd + ' /QN /norestart'; }
1347
+ } else {
1348
+ if (uninstallCmd.toLowerCase().indexOf('/s') < 0) { uninstallCmd = uninstallCmd + ' /S /silent /SILENT /VERYSILENT /quiet /norestart'; }
1349
+ }
1350
+ uninstallCmd = uninstallCmd.replace(/\s+/g, ' ').trim();
1351
+
1352
+ var child = child_process.execFile(cmdPath, ['cmd', '/c', uninstallCmd], { timeout: 300000 });
1353
+ child.stdout.str = ''; child.stderr.str = '';
1354
+ child._cmd = uninstallCmd;
1355
+
1356
+ child.stdout.on('data', function(c) { this.str += c.toString(); });
1357
+ child.stderr.on('data', function(c) { this.str += c.toString(); });
1358
+
1359
+ child.on('exit', function(code) {
1360
+ var success = (code === 0 || code === null || code === 3010);
1361
+ var status = success ? 'SUCCESS' : 'FAILED';
1362
+ writeLog('UNINSTALL ' + status + ' - ExitCode: ' + code);
1363
+
1364
+ var result = { success: success, exitCode: code, command: child._cmd };
1365
+ if (child.stdout.str) result.stdout = child.stdout.str.trim().substring(0, 500);
1366
+ if (child.stderr.str) result.stderr = child.stderr.str.trim().substring(0, 500);
1367
+ sendSoftwareResponse(result);
1368
+ });
1369
+ sendSoftwareResponse({ status: 'Silent uninstall started', command: uninstallCmd });
1370
+ } catch (ex) {
1371
+ writeLog('UNINSTALL ERROR - ' + ex.toString());
1372
+ sendSoftwareResponse({ error: ex.toString() });
1373
+ }
1374
+ }
1375
+ }
1376
+ // --- UNINSTALL STORE APP (Verbesserte Version) ---
1377
+ else if (typeof data.value === 'string' && data.value.indexOf('uninstallstoreapp ') === 0) {
1378
+ // Namen extrahieren und Anführungszeichen entfernen, falls vorhanden
1379
+ var rawName = data.value.substring(18).trim();
1380
+ var packageName = rawName.replace(/"/g, "").replace(/'/g, "");
1381
+
1382
+ var logFile = 'C:\\ProgramData\\MeshAgent_StoreUninstall.log';
1383
+
1384
+ try {
1385
+ var psPath = (process.env['SystemRoot'] || 'C:\\Windows') + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
1386
+ var child_process = require('child_process');
1387
+
1388
+ // Marker, um das Ende der Ausgabe zu erkennen
1389
+ var completionMarker = '###DONE###' + Date.now();
1390
+
1391
+ var child = child_process.execFile(psPath, ['powershell', '-NoProfile', '-NoLogo', '-ExecutionPolicy', 'Bypass', '-Command', '-'], {});
1392
+ child.stdout.str = ''; child.stderr.str = '';
1393
+ child._completed = false;
1394
+
1395
+ child.stdout.on('data', function(c) {
1396
+ this.str += c.toString();
1397
+ // Wir warten auf den Marker
1398
+ if (!child._completed && this.str.indexOf(completionMarker) >= 0) {
1399
+ child._completed = true;
1400
+ // Ergebnis extrahieren (alles vor dem Marker)
1401
+ var output = this.str.split(completionMarker)[0].trim();
1402
+ sendSoftwareResponse({ status: 'Finished', output: output });
1403
+ }
1404
+ });
1405
+ child.stderr.on('data', function(c) { this.str += c.toString(); });
1406
+
1407
+ // Das ausführliche PowerShell Skript (wie früher)
1408
+ var script = [
1409
+ "$LogFile = '" + logFile + "'",
1410
+ "$TargetName = '" + packageName + "'",
1411
+ "function Write-Log { param([string]$Msg); $Line = \"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $Msg\"; Add-Content -Path $LogFile -Value $Line -ErrorAction SilentlyContinue; Write-Output $Msg }",
1412
+ "",
1413
+ "Write-Log \"STORE UNINSTALL START: $TargetName\"",
1414
+ "Write-Log \"Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)\"",
1415
+ "",
1416
+ "$foundCount = 0",
1417
+ "$pkgs = Get-AppxPackage -AllUsers -Name \"*$TargetName*\"",
1418
+ "if ($pkgs) {",
1419
+ " if ($pkgs -isnot [array]) { $pkgs = @($pkgs) }",
1420
+ " Write-Log \"Found (AllUsers): $($pkgs.Count) packages\"",
1421
+ " foreach ($p in $pkgs) {",
1422
+ " Write-Log \"Removing: $($p.PackageFullName)\"",
1423
+ " try {",
1424
+ " Remove-AppxPackage -Package $p.PackageFullName -AllUsers -ErrorAction Stop",
1425
+ " Write-Log \"SUCCESS: Removed with -AllUsers\"",
1426
+ " $foundCount++",
1427
+ " } catch {",
1428
+ " Write-Log \"WARN: AllUsers failed, trying current user only. Error: $($_.Exception.Message)\"",
1429
+ " try {",
1430
+ " Remove-AppxPackage -Package $p.PackageFullName -ErrorAction Stop",
1431
+ " Write-Log \"SUCCESS: Removed from CurrentUser\"",
1432
+ " $foundCount++",
1433
+ " } catch {",
1434
+ " Write-Log \"ERROR: Failed to remove $($p.PackageFullName). Error: $($_.Exception.Message)\"",
1435
+ " }",
1436
+ " }",
1437
+ " }",
1438
+ "} else { Write-Log \"Found (AllUsers): 0 packages\" }",
1439
+ "",
1440
+ "$prov = Get-AppxProvisionedPackage -Online | Where-Object { $_.PackageName -like \"*$TargetName*\" }",
1441
+ "if ($prov) {",
1442
+ " if ($prov -isnot [array]) { $prov = @($prov) }",
1443
+ " Write-Log \"Found provisioned: $($prov.Count) packages\"",
1444
+ " foreach ($pr in $prov) {",
1445
+ " Write-Log \"Deprovisioning: $($pr.DisplayName)\"",
1446
+ " try {",
1447
+ " Remove-AppxProvisionedPackage -Online -PackageName $pr.PackageName -ErrorAction Stop | Out-Null",
1448
+ " Write-Log \"SUCCESS: Deprovisioned\"",
1449
+ " } catch {",
1450
+ " Write-Log \"ERROR: Failed to deprovision. Error: $($_.Exception.Message)\"",
1451
+ " }",
1452
+ " }",
1453
+ "} else { Write-Log \"Found provisioned: 0 packages\" }",
1454
+ "",
1455
+ "'" + completionMarker + "'",
1456
+ "exit"
1457
+ ].join("\r\n");
1458
+
1459
+ child.stdin.write(script + "\r\n");
1460
+
1461
+ // Timeout Safety
1462
+ setTimeout(function() { if (!child._completed) { child.kill(); sendSoftwareResponse({ error: 'Timeout' }); } }, 60000);
1463
+
1464
+ sendSoftwareResponse({ status: 'Store app removal started', package: packageName });
1465
+ } catch (ex) {
1466
+ sendSoftwareResponse({ error: ex.toString() });
1467
+ }
1468
+ }
1469
+ break;
1470
+ }
1471
case 'console': { // Process a console command
1472
if ((typeof data.rights != 'number') || ((data.rights & 8) == 0) || ((data.rights & 16) == 0)) break; // Check console rights (Remote Control and Console)
1473
if (data.value && data.sessionid) {
@@ -5803,20 +5988,287 @@ function processConsoleCommand(cmd, args, rights, sessionid) {
5988
break;
5989
}
5990
case 'installedapps': {
5806
- if(process.platform == 'win32'){
5807
- require('win-info').installedApps().then(function (apps){ sendConsoleText(JSON.stringify(apps,null,1)); });
5991
+ if (process.platform == 'win32') {
5992
+ try {
5993
+ // Wir nutzen 'sessionid' aus dem Funktionskopf von processConsoleCommand
5994
+ require('win-info').installedApps().then(function (apps) {
5995
+ sendConsoleText(JSON.stringify(apps, null, 1), sessionid);
5996
+ }).catch(function(e) {
5997
+ sendConsoleText("Error: " + e, sessionid);
5998
+ });
5999
+ } catch (ex) {
6000
+ sendConsoleText("Module win-info error: " + ex, sessionid);
6001
+ }
6002
}
6003
break;
6004
}
6005
case 'installedstoreapps': {
5812
- if(process.platform == 'win32'){
5813
- var apps = require('win-info').installedStoreApps();
5814
- if (apps[0]) {
5815
- sendConsoleText(JSON.stringify(apps,null,1));
5816
- };
6006
+ if (process.platform == 'win32') {
6007
+ try {
6008
+ // Hier muss der Name exakt so geschrieben sein wie in module.exports der win-info.js
6009
+ require('win-info').installedStoreApps().then(function (apps) {
6010
+ if (apps && apps.length > 0) {
6011
+ sendConsoleText(JSON.stringify(apps, null, 1), sessionid);
6012
+ } else {
6013
+ sendConsoleText("No Store Apps found or PowerShell error.", sessionid);
6014
+ }
6015
+ }).catch(function(e) {
6016
+ sendConsoleText("Promise Error: " + e, sessionid);
6017
+ });
6018
+ } catch (ex) {
6019
+ sendConsoleText("Module win-info error: " + ex, sessionid);
6020
+ }
6021
}
6022
break;
6023
}
6024
+ case 'uninstallapp':
6025
+ if (args['_'].length < 1) {
6026
+ response = "Usage: uninstallapp <base64-encoded-command>";
6027
+ } else {
6028
+ var base64Cmd = args['_'][0];
6029
+ var uninstallCmd = '';
6030
+
6031
+ // Base64 dekodieren
6032
+ try {
6033
+ var b = Buffer.from(base64Cmd, 'base64');
6034
+ var decoded = b.toString();
6035
+
6036
+ if (decoded && decoded.length > 0 && decoded.indexOf(':') >= 0) {
6037
+ uninstallCmd = decoded;
6038
+ } else {
6039
+ uninstallCmd = base64Cmd;
6040
+ }
6041
+ } catch (e) {
6042
+ uninstallCmd = base64Cmd;
6043
+ }
6044
+
6045
+ // Prüfen ob Befehl leer oder ungültig ist
6046
+ if (!uninstallCmd || uninstallCmd.trim() === '' || uninstallCmd.trim() === '\\') {
6047
+ response = JSON.stringify({ success: false, error: 'No valid uninstall command available' });
6048
+ break;
6049
+ }
6050
+
6051
+ var logFile = 'C:\\ProgramData\\MeshAgent_Uninstall.log';
6052
+ var child_process = require('child_process');
6053
+ var cmdPath = process.env['windir'] + '\\system32\\cmd.exe';
6054
+
6055
+ // Log-Funktion mit CMD echo
6056
+ function writeLog(message, callback) {
6057
+ try {
6058
+ var timestamp = new Date().toISOString();
6059
+ var logLine = timestamp + ' | ' + message;
6060
+ // Sonderzeichen escapen
6061
+ logLine = logLine.replace(/"/g, '""').replace(/&/g, '^&').replace(/</g, '^<').replace(/>/g, '^>').replace(/\|/g, '^|');
6062
+
6063
+ var logChild = child_process.execFile(cmdPath, ['cmd', '/c', 'echo ' + logLine + ' >> "' + logFile + '"'], { timeout: 5000 });
6064
+ logChild.on('exit', function() {
6065
+ if (callback) callback();
6066
+ });
6067
+ } catch (e) {
6068
+ if (callback) callback();
6069
+ }
6070
+ }
6071
+
6072
+ try {
6073
+ // Logging - Start
6074
+ writeLog('UNINSTALL START - Command: ' + uninstallCmd.replace(/\\/g, '/'));
6075
+
6076
+ var originalCmd = uninstallCmd;
6077
+
6078
+ // Silent-Parameter hinzufügen
6079
+ if (uninstallCmd.toLowerCase().indexOf('msiexec') >= 0) {
6080
+ uninstallCmd = uninstallCmd.replace(/\/q[nbrf]?/gi, '');
6081
+ if (uninstallCmd.indexOf('/QN') < 0) {
6082
+ uninstallCmd = uninstallCmd + ' /QN /norestart';
6083
+ }
6084
+ } else {
6085
+ if (uninstallCmd.toLowerCase().indexOf('/s') < 0) {
6086
+ uninstallCmd = uninstallCmd + ' /S /silent /SILENT /VERYSILENT /quiet /norestart';
6087
+ }
6088
+ }
6089
+
6090
+ uninstallCmd = uninstallCmd.replace(/\s+/g, ' ').trim();
6091
+
6092
+ writeLog('UNINSTALL EXEC - Modified: ' + uninstallCmd.replace(/\\/g, '/'));
6093
+
6094
+ var child = child_process.execFile(cmdPath, ['cmd', '/c', uninstallCmd], { timeout: 300000 });
6095
+
6096
+ child.stdout.str = '';
6097
+ child.stderr.str = '';
6098
+ child._sessionid = sessionid;
6099
+ child._cmd = uninstallCmd;
6100
+ child._originalCmd = originalCmd;
6101
+ child._writeLog = writeLog;
6102
+
6103
+ child.stdout.on('data', function(c) { this.str += c.toString(); });
6104
+ child.stderr.on('data', function(c) { this.str += c.toString(); });
6105
+
6106
+ child.on('exit', function(code) {
6107
+ var success = (code === 0 || code === null || code === 3010);
6108
+ var status = success ? 'SUCCESS' : 'FAILED';
6109
+
6110
+ child._writeLog('UNINSTALL ' + status + ' - ExitCode: ' + code);
6111
+
6112
+ var result = {
6113
+ success: success,
6114
+ exitCode: code,
6115
+ command: child._cmd
6116
+ };
6117
+
6118
+ if (child.stdout.str) result.stdout = child.stdout.str.trim().substring(0, 500);
6119
+ if (child.stderr.str) result.stderr = child.stderr.str.trim().substring(0, 500);
6120
+
6121
+ sendConsoleText(JSON.stringify(result), child._sessionid);
6122
+ });
6123
+
6124
+ response = JSON.stringify({ status: 'Silent uninstall started', command: uninstallCmd });
6125
+ } catch (ex) {
6126
+ writeLog('UNINSTALL ERROR - ' + ex.toString());
6127
+ response = JSON.stringify({ error: ex.toString() });
6128
+ }
6129
+ }
6130
+ break;
6131
+
6132
+ case 'uninstallstoreapp':
6133
+ if (args['_'].length < 1) {
6134
+ response = "Usage: uninstallstoreapp <PackageName>";
6135
+ } else {
6136
+ var packageName = args['_'][0];
6137
+ var logFile = 'C:\\ProgramData\\MeshAgent_StoreUninstall.log';
6138
+
6139
+ try {
6140
+ var psPath = (process.env['SystemRoot'] || 'C:\\Windows') + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
6141
+ var child_process = require('child_process');
6142
+ var safePackageName = packageName.replace(/'/g, "''");
6143
+ var completionMarker = '###DONE###' + Date.now();
6144
+
6145
+ var child = child_process.execFile(psPath, ['powershell', '-NoProfile', '-NoLogo', '-Command', '-'], {});
6146
+
6147
+ child.stdout.str = '';
6148
+ child.stderr.str = '';
6149
+ child._sessionid = sessionid;
6150
+ child._packageName = packageName;
6151
+ child._marker = completionMarker;
6152
+ child._completed = false;
6153
+ child._logFile = logFile;
6154
+
6155
+ child.stdout.on('data', function(c) {
6156
+ this.str += c.toString();
6157
+
6158
+ if (!child._completed && this.str.indexOf(child._marker) >= 0) {
6159
+ child._completed = true;
6160
+ var result = this.str.split(child._marker)[0].trim();
6161
+
6162
+ if (result === 'NO_MATCH' || result === '') {
6163
+ sendConsoleText(JSON.stringify({
6164
+ success: false,
6165
+ error: 'No matching packages found',
6166
+ package: child._packageName
6167
+ }), child._sessionid);
6168
+ } else if (result.indexOf('REMOVED') >= 0 || result.indexOf('DEPROVISIONED') >= 0) {
6169
+ var items = result.split('|').filter(function(x) { return x.length > 0; });
6170
+ sendConsoleText(JSON.stringify({
6171
+ success: true,
6172
+ message: 'Store app removed',
6173
+ results: items
6174
+ }), child._sessionid);
6175
+ } else if (result.indexOf('FAILED') >= 0) {
6176
+ sendConsoleText(JSON.stringify({
6177
+ success: false,
6178
+ error: 'Removal failed',
6179
+ details: result.split('|')
6180
+ }), child._sessionid);
6181
+ } else {
6182
+ sendConsoleText(JSON.stringify({ output: result }), child._sessionid);
6183
+ }
6184
+ }
6185
+ });
6186
+
6187
+ child.stderr.on('data', function(c) { this.str += c.toString(); });
6188
+
6189
+ // PowerShell-Skript mit Logging
6190
+ var script = [
6191
+ "$LogFile = '" + logFile + "'",
6192
+ "function Write-Log { param([string]$Message); $LogLine = \"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $Message\"; Add-Content -Path $LogFile -Value $LogLine -ErrorAction SilentlyContinue }",
6193
+ "",
6194
+ "Write-Log 'STORE UNINSTALL START: " + safePackageName + "'",
6195
+ "Write-Log \"Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)\"",
6196
+ "",
6197
+ "$ErrorActionPreference = 'SilentlyContinue'",
6198
+ "$results = @()",
6199
+ "",
6200
+ "# Packages suchen",
6201
+ "$pkgs = Get-AppxPackage -AllUsers -Name '*" + safePackageName + "*'",
6202
+ "Write-Log \"Found (AllUsers): $($pkgs.Count) packages\"",
6203
+ "",
6204
+ "if (-not $pkgs) {",
6205
+ " $pkgs = Get-AppxPackage -Name '*" + safePackageName + "*'",
6206
+ " Write-Log \"Found (CurrentUser): $($pkgs.Count) packages\"",
6207
+ "}",
6208
+ "",
6209
+ "foreach ($p in $pkgs) {",
6210
+ " Write-Log \"Removing: $($p.PackageFullName)\"",
6211
+ " try {",
6212
+ " Remove-AppxPackage -Package $p.PackageFullName -AllUsers -ErrorAction Stop",
6213
+ " Write-Log 'SUCCESS: Removed with -AllUsers'",
6214
+ " $results += 'REMOVED_ALLUSERS:' + $p.PackageFullName",
6215
+ " } catch {",
6216
+ " Write-Log \"FAILED -AllUsers: $($_.Exception.Message)\"",
6217
+ " try {",
6218
+ " Remove-AppxPackage -Package $p.PackageFullName -ErrorAction Stop",
6219
+ " Write-Log 'SUCCESS: Removed'",
6220
+ " $results += 'REMOVED:' + $p.PackageFullName",
6221
+ " } catch {",
6222
+ " Write-Log \"FAILED: $($_.Exception.Message)\"",
6223
+ " $results += 'FAILED:' + $p.PackageFullName + ':' + $_.Exception.Message",
6224
+ " }",
6225
+ " }",
6226
+ "}",
6227
+ "",
6228
+ "# Provisioned Packages entfernen",
6229
+ "$prov = Get-AppxProvisionedPackage -Online 2>$null | Where-Object { $_.PackageName -like '*" + safePackageName + "*' }",
6230
+ "Write-Log \"Found provisioned: $($prov.Count) packages\"",
6231
+ "",
6232
+ "foreach ($pr in $prov) {",
6233
+ " Write-Log \"Deprovisioning: $($pr.DisplayName)\"",
6234
+ " try {",
6235
+ " Remove-AppxProvisionedPackage -Online -PackageName $pr.PackageName -ErrorAction Stop | Out-Null",
6236
+ " Write-Log 'SUCCESS: Deprovisioned'",
6237
+ " $results += 'DEPROVISIONED:' + $pr.DisplayName",
6238
+ " } catch {",
6239
+ " Write-Log \"FAILED: $($_.Exception.Message)\"",
6240
+ " $results += 'DEPROV_FAILED:' + $pr.DisplayName",
6241
+ " }",
6242
+ "}",
6243
+ "",
6244
+ "if ($results.Count -eq 0) {",
6245
+ " Write-Log 'NO_MATCH'",
6246
+ " 'NO_MATCH'",
6247
+ "} else {",
6248
+ " $output = $results -join '|'",
6249
+ " Write-Log \"RESULT: $output\"",
6250
+ " $output",
6251
+ "}",
6252
+ "'" + completionMarker + "'",
6253
+ "exit"
6254
+ ].join("\r\n");
6255
+
6256
+ child.stdin.write(script + "\r\n");
6257
+
6258
+ setTimeout(function() {
6259
+ if (!child._completed) {
6260
+ sendConsoleText(JSON.stringify({ error: 'Timeout after 2 minutes' }), child._sessionid);
6261
+ }
6262
+ }, 120000);
6263
+
6264
+ response = JSON.stringify({ status: 'Store app removal started', package: packageName });
6265
+ } catch (ex) {
6266
+ var logErr = new Date().toISOString() + ' - STORE UNINSTALL ERROR: ' + ex.toString() + '\r\n';
6267
+ try { require('fs').appendFileSync(logFile, logErr); } catch (e) { }
6268
+ response = JSON.stringify({ error: ex.toString() });
6269
+ }
6270
+ break;
6271
+ }
6272
case 'qfe': {
6273
if(process.platform == 'win32'){
6274
var qfe = require('win-info').qfe();
agents/modules_meshcore/win-info.js
+124
-63
@@ -16,6 +16,9 @@ limitations under the License.
16
17
var promise = require('promise');
18
19
+// We use the environment variable directly or the standard Windows path
20
+var psPath = (process.env['SystemRoot'] ? process.env['SystemRoot'] : 'C:\\Windows') + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
21
+
22
function qfe()
23
{
24
try {
@@ -179,78 +182,136 @@ function pendingReboot()
182
return (ret);
183
}
184
182
-function installedApps()
183
-{
184
- var promise = require('promise');
185
- var ret = new promise(function (a, r) { this._resolve = a; this._reject = r; });
185
+function installedApps() {
186
+ var registry = require('win-registry');
187
+ var HKEY = registry.HKEY;
188
+ var results = [];
189
187
- var code = "\
188
- var reg = require('win-registry');\
189
- var result = [];\
190
- var val, tmp;\
191
- var items = reg.QueryKey(reg.HKEY.LocalMachine, 'SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall');\
192
- for (var key in items.subkeys)\
193
- {\
194
- val = {};\
195
- try\
196
- {\
197
- val.name = reg.QueryKey(reg.HKEY.LocalMachine, 'SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\' + items.subkeys[key], 'DisplayName');\
198
- }\
199
- catch(e)\
200
- {\
201
- continue;\
202
- }\
203
- try\
204
- {\
205
- val.version = reg.QueryKey(reg.HKEY.LocalMachine, 'SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\' + items.subkeys[key], 'DisplayVersion');\
206
- if (val.version == '') { delete val.version; }\
207
- }\
208
- catch(e)\
209
- {\
210
- }\
211
- try\
212
- {\
213
- val.location = reg.QueryKey(reg.HKEY.LocalMachine, 'SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\' + items.subkeys[key], 'InstallLocation');\
214
- if (val.location == '') { delete val.location; }\
215
- }\
216
- catch(e)\
217
- {\
218
- }\
219
- try\
220
- {\
221
- val.installdate = reg.QueryKey(reg.HKEY.LocalMachine, 'SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\' + items.subkeys[key], 'InstallDate');\
222
- if (val.installdate == '') { delete val.installdate; }\
223
- }\
224
- catch(e)\
225
- {\
226
- }\
227
- result.push(val);\
228
- }\
229
- console.log(JSON.stringify(result,'', 1));process.exit();";
190
+ var registryPaths = [
191
+ 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
192
+ 'SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
193
+ ];
194
231
- ret.child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop().split('.exe')[0], '-exec "' + code + '"']);
232
- ret.child.promise = ret;
233
- ret.child.stdout.str = ''; ret.child.stdout.on('data', function (c) { this.str += c.toString(); });
234
- ret.child.on('exit', function (c) { this.promise._resolve(JSON.parse(this.stdout.str.trim())); });
235
- return (ret);
195
+ try {
196
+ for (var i in registryPaths) {
197
+ var path = registryPaths[i];
198
+
199
+ // Wir nutzen direkt die Registry-Komponente für die Unterschlüssel
200
+ var keyInfo = registry.QueryKey(HKEY.LocalMachine, path);
201
+
202
+ // Falls QueryKey fehlschlägt oder keine Subkeys hat, überspringen
203
+ if (!keyInfo || !keyInfo.subkeys) continue;
204
+
205
+ for (var j = 0; j < keyInfo.subkeys.length; j++) {
206
+ var subPath = path + '\\' + keyInfo.subkeys[j];
207
+
208
+ // Wir nutzen deine im Modul definierte regQuery Funktion!
209
+ // Diese ist sicher, da sie try-catch bereits enthält.
210
+ var name = regQuery(HKEY.LocalMachine, subPath, 'DisplayName');
211
+
212
+ if (name && name != '') {
213
+ results.push({
214
+ name: name,
215
+ version: regQuery(HKEY.LocalMachine, subPath, 'DisplayVersion') || '',
216
+ publisher: regQuery(HKEY.LocalMachine, subPath, 'Publisher') || '',
217
+ uninstall: regQuery(HKEY.LocalMachine, subPath, 'QuietUninstallString') ||
218
+ regQuery(HKEY.LocalMachine, subPath, 'UninstallString') || ''
219
+ });
220
+ }
221
+ }
222
+ }
223
+ } catch (e) {
224
+ // Stille Fehlerbehandlung für maximale Stabilität im Agenten
225
+ }
226
+
227
+ // Das Promise-Mirror Objekt für MeshCentral Kompatibilität
228
+ return {
229
+ data: results,
230
+ then: function(cb) { if (typeof cb === 'function') cb(this.data); return this; },
231
+ catch: function(cb) { return this; },
232
+ finally: function(cb) { if (typeof cb === 'function') cb(); return this; },
233
+ on: function(ev, cb) { if (ev === 'exit' && typeof cb === 'function') cb(0); return this; }
234
+ };
235
}
236
238
-function installedStoreApps(){
237
+function installedStoreApps() {
238
+ var ret = new promise(function (a, r) { this._resolve = a; this._reject = r; });
239
+
240
+ // Basierend auf deiner funktionierenden Version + Scope-Erkennung
241
+ var psCommand = [
242
+ "$ErrorActionPreference = 'SilentlyContinue'",
243
+ "$allUsersApps = @(Get-AppxPackage -AllUsers)",
244
+ "$allUsersPkgNames = @($allUsersApps | Select-Object -ExpandProperty PackageFullName)",
245
+ "$userOnlyApps = @(Get-AppxPackage | Where-Object { $allUsersPkgNames -notcontains $_.PackageFullName })",
246
+ "$provPkgs = @(Get-AppxProvisionedPackage -Online | Select-Object -ExpandProperty DisplayName)",
247
+ "$results = @()",
248
+ "foreach ($app in $allUsersApps) {",
249
+ " if ($app.Name -and $app.Name -notlike 'Microsoft.Windows.*' -and $app.Name -notlike 'windows.*' -and $app.Name -notlike '*_neutral_*') {",
250
+ " $scope = 'System'",
251
+ " if ($provPkgs -contains $app.Name) { $scope = 'System+Prov' }",
252
+ " $results += [PSCustomObject]@{ Name=$app.Name; Version=[string]$app.Version; PackageFullName=$app.PackageFullName; Publisher=$app.Publisher; Scope=$scope }",
253
+ " }",
254
+ "}",
255
+ "foreach ($app in $userOnlyApps) {",
256
+ " if ($app.Name -and $app.Name -notlike 'Microsoft.Windows.*' -and $app.Name -notlike 'windows.*' -and $app.Name -notlike '*_neutral_*') {",
257
+ " $scope = 'User'",
258
+ " if ($provPkgs -contains $app.Name) { $scope = 'User+Prov' }",
259
+ " $results += [PSCustomObject]@{ Name=$app.Name; Version=[string]$app.Version; PackageFullName=$app.PackageFullName; Publisher=$app.Publisher; Scope=$scope }",
260
+ " }",
261
+ "}",
262
+ "$results | Sort-Object Name -Unique | ConvertTo-Json -Compress"
263
+ ].join("; ");
264
+
265
try {
240
- var tokens = require('win-wmi-fixed').query('ROOT\\CIMV2', 'SELECT * FROM Win32_InstalledStoreProgram');
241
- if (tokens[0]){
242
- for (var index = 0; index < tokens.length; index++) {
243
- for (var key in tokens[index]) {
244
- if (key.startsWith('__')) delete tokens[index][key];
266
+ var psPath = (process.env['SystemRoot'] || 'C:\\Windows') + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
267
+
268
+ ret.child = require('child_process').execFile(
269
+ psPath,
270
+ ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', psCommand],
271
+ { timeout: 60000, maxBuffer: 10 * 1024 * 1024 }
272
+ );
273
+
274
+ ret.child.promise = ret;
275
+ ret.child.stdout.str = '';
276
+ ret.child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
277
+
278
+ ret.child.on('exit', function (code) {
279
+ try {
280
+ var output = this.stdout.str.trim();
281
+
282
+ if (output === '' || output === 'null' || output === '[]') {
283
+ this.promise._resolve([]);
284
+ return;
285
}
286
+
287
+ var data = JSON.parse(output);
288
+ if (!Array.isArray(data)) { data = [data]; }
289
+
290
+ var apps = data.map(function(app) {
291
+ return {
292
+ name: app.Name || '',
293
+ version: app.Version || '',
294
+ publisher: app.Publisher || '',
295
+ packageFullName: app.PackageFullName || '',
296
+ scope: app.Scope || '',
297
+ uninstall: 'Remove-AppxPackage -Package "' + (app.PackageFullName || '') + '" -AllUsers'
298
+ };
299
+ });
300
+
301
+ this.promise._resolve(apps);
302
+ } catch (e) {
303
+ this.promise._resolve([]);
304
}
247
- return (tokens);
248
- } else {
249
- return ([]);
250
- };
305
+ });
306
+
307
+ ret.child.on('error', function (err) {
308
+ this.promise._resolve([]);
309
+ });
310
} catch (ex) {
252
- return ([]);
311
+ ret._resolve([]);
312
}
313
+
314
+ return (ret);
315
}
316
317
function defender(){
meshuser.js
+42
-3
@@ -1055,9 +1055,48 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
1055
// If a response is needed, set a callback function
1056
var func = null;
1057
if (command.responseid != null) { func = function (r) { try { ws.send(JSON.stringify({ action: 'msg', result: r ? 'OK' : 'Unable to route', tag: command.tag, responseid: command.responseid })); } catch (ex) { } } }
1058
-
1059
- // Route this command to a target node
1060
- routeCommandToNode(command, requiredRights, requiredNonRights, func, routingOptions);
1058
+
1059
+ // BEGIN SOFTWARE
1060
+ var isSoftwareCmd = (command.type === 'console') &&
1061
+ (command.value === 'installedapps' ||
1062
+ command.value === 'installedstoreapps' ||
1063
+ (typeof command.value === 'string' &&
1064
+ (command.value.indexOf('uninstallapp ') === 0 ||
1065
+ command.value.indexOf('uninstallstoreapp ') === 0)));
1066
+
1067
+ if (isSoftwareCmd) {
1068
+ // Wir prüfen auf MESHRIGHT_DEVICEDETAILS (0x100000)
1069
+ parent.GetNodeWithRights(domain, user, command.nodeid, function (node, rights, visible) {
1070
+ var mesh = parent.meshes[node.meshid];
1071
+ if ((node != null) && (mesh != null) && ((rights & MESHRIGHT_DEVICEDETAILS) != 0)) {
1072
+ var agent = parent.wsagents[command.nodeid];
1073
+ if (agent != null) {
1074
+ // Wir bauen das Command um
1075
+ // WICHTIG: type='software' und sessionid muss gesetzt sein
1076
+ var newCommand = {
1077
+ action: 'msg',
1078
+ type: 'software', // Neuer Typ!
1079
+ value: command.value,
1080
+ sessionid: ws.sessionId, // Essenziell für den Rückweg
1081
+ rights: rights,
1082
+ username: user.name,
1083
+ remoteaddr: req.clientIp
1084
+ };
1085
+
1086
+ try { agent.send(JSON.stringify(newCommand)); } catch (ex) { }
1087
+ if (func) { func(true); }
1088
+ } else {
1089
+ if (func) { func(false); } // Agent offline
1090
+ }
1091
+ } else {
1092
+ if (func) { func(false); } // Keine Rechte
1093
+ }
1094
+ });
1095
+ } else {
1096
+ // Standard Routing für Console etc.
1097
+ routeCommandToNode(command, requiredRights, requiredNonRights, func, routingOptions);
1098
+ }
1099
+ // END SOFTWARE
1100
break;
1101
}
1102
case 'events':
translate/translate.json
+39
@@ -22450,6 +22450,45 @@
22450
"default3.handlebars->container->column_l->p17->p17title->3"
22451
]
22452
},
22453
+ {
22454
+ "bs": "Softver",
22455
+ "ca": "Programari",
22456
+ "cs": "Software",
22457
+ "da": "Software",
22458
+ "de": "Software",
22459
+ "en": "Software",
22460
+ "es": "Software",
22461
+ "fi": "Ohjelmisto",
22462
+ "fr": "Logiciel",
22463
+ "hi": "सॉफ्टवेयर",
22464
+ "hu": "Szoftver",
22465
+ "it": "Software",
22466
+ "ja": "ソフトウェア",
22467
+ "ko": "소프트웨어",
22468
+ "nl": "Software",
22469
+ "pl": "Oprogramowanie",
22470
+ "pt": "Software",
22471
+ "pt-br": "Software",
22472
+ "ru": "Программное обеспечение",
22473
+ "sv": "Programvara",
22474
+ "tr": "Yazılım",
22475
+ "uk": "Програмне забезпечення",
22476
+ "zh-chs": "软件",
22477
+ "zh-cht": "軟體",
22478
+ "xloc": [
22479
+ "default-mobile.handlebars->53->564",
22480
+ "default.handlebars->118->1133",
22481
+ "default.handlebars->118->1158",
22482
+ "default.handlebars->118->2437",
22483
+ "default.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevInfoSW",
22484
+ "default.handlebars->contextMenu->cxdetails",
22485
+ "default3.handlebars->118->1127",
22486
+ "default3.handlebars->118->1152",
22487
+ "default3.handlebars->118->2434",
22488
+ "default3.handlebars->container->topbar->1->1->MainSubMenuSpan->MainSubMenu->1->0->MainDevInfoSW",
22489
+ "default3.handlebars->contextMenu->cxdetails"
22490
+ ]
22491
+ },
22492
{
22493
"bs": "Uređaj",
22494
"ca": "Dispositiu",
views/default.handlebars
+419
-3
@@ -237,6 +237,7 @@
237
<td tabindex=0 id=MainDevFiles class="topbar_td style3x" onmouseup=go(13,event) onkeypress="if (event.key == 'Enter') go(13)">Files</td>
238
<td tabindex=0 id=MainDevEvents class="topbar_td style3x" onmouseup=go(16,event) onkeypress="if (event.key == 'Enter') go(16)">Events</td>
239
<td tabindex=0 id=MainDevInfo class="topbar_td style3x" onmouseup=go(17,event) onkeypress="if (event.key == 'Enter') go(17)">Details</td>
240
+ <td tabindex=0 id=MainDevApps class="topbar_td style3x" onmouseup=go(18,event) onkeypress="if (event.key == 'Enter') go(18)">Software</td>
241
<td tabindex=0 id=MainDevAmt class="topbar_td style3x" onmouseup=go(14,event) onkeypress="if (event.key == 'Enter') go(14)">Intel®AMT</td>
242
<td tabindex=0 id=MainDevConsole class="topbar_td style3x" onmouseup=go(15,event) onkeypress="if (event.key == 'Enter') go(15)">Console</td>
243
<td tabindex=0 id=MainDevPlugins class="topbar_td style3x" onmouseup=go(19,event) onkeypress="if (event.key == 'Enter') go(19)">Plugins</td>
@@ -1058,6 +1059,33 @@
1059
<div id=p17info2></div>
1060
</div>
1061
</div>
1062
+ <div id=p18 style="display:none">
1063
+ <div id="p18title">
1064
+ <div id="p18BackButton" style="float:left">
1065
+ <div class="backButton" tabindex=0 onclick=goBack() title="Back"
1066
+ onkeypress="if (event.key == 'Enter') goBack()">
1067
+ <div class="backButtonEx"></div>
1068
+ </div>
1069
+ </div>
1070
+ <h1>Software - <span id=p18deviceName></span></h1>
1071
+ </div>
1072
+ <div id="p18toolbar" style="padding:10px;background-color:#f5f5f5;border-bottom:1px solid #ddd">
1073
+ <input type="button" id="p18RefreshBtn" class="btn btn-primary btn-sm" value="Refresh" onclick="loadInstalledApps()" />
1074
+ <input type="text" id="p18SearchInput" class="form-control-sm" placeholder="Search software..."
1075
+ style="margin-left:10px;width:250px" onkeyup="filterInstalledApps()" />
1076
+ <label style="margin-left:15px">
1077
+ <input type="checkbox" id="p18ShowStore" onchange="toggleStoreApps()" /> Show Store Apps
1078
+ </label>
1079
+ <span id="p18Status" style="margin-left:20px;color:gray"></span>
1080
+ </div>
1081
+ <div id="p18info" style="overflow-y:auto;padding:10px">
1082
+ <div id="p18html">
1083
+ <div style="text-align:center;padding:50px;color:gray">
1084
+ <i>Click "Refresh" to load installed software...</i>
1085
+ </div>
1086
+ </div>
1087
+ </div>
1088
+ </div>
1089
<div id=p19 style="display:none">
1090
<div id="p19title">
1091
<div id="p19BackButton" style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
@@ -1625,6 +1653,13 @@
1653
var p12TermConsoleMsgTimer = null;
1654
var p13FilesConsoleMsgTimer = null;
1655
1656
+ //
1657
+ // INSTALLED SOFTWARE
1658
+ //
1659
+ var installedAppsData = null; // Gespeicherte App-Daten
1660
+ var installedAppsLoading = false; // Ladestatus
1661
+ var showStoreApps = false; // Store Apps anzeigen?
1662
+
1663
/*
1664
// Check browser dark mode preference
1665
var prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
@@ -1995,6 +2030,7 @@
2030
QV('p15title', !(hide & 8));
2031
QV('p16title', !(hide & 8));
2032
QV('p17title', !(hide & 8));
2033
+ QV('p18title', !(hide & 8));
2034
QV('p20title', !(hide & 8));
2035
QV('p21title', !(hide & 8));
2036
QV('p30title', !(hide & 8));
@@ -2066,6 +2102,8 @@
2102
QS('p10info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2103
QS('p17info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2104
QS('p17info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2105
+ QS('p18info')['height'] = 'calc(100vh - ' + (120 + xh + xh2) + 'px)';
2106
+ QS('p18info')['max-height'] = 'calc(100vh - ' + (120 + xh + xh2) + 'px)';
2107
QS('p20main')['height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
2108
QS('p20main')['max-height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
2109
QS('p21main')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
@@ -2095,6 +2133,7 @@
2133
QV('p15BackButton', false);
2134
QV('p16BackButton', false);
2135
QV('p17BackButton', false);
2136
+ QV('p18BackButton', false);
2137
}
2138
p1updateInfo();
2139
}
@@ -2747,6 +2786,7 @@
2786
if (node != null) { node.lastconnect = message.lastconnects[lcnodeid] }
2787
}
2788
mainUpdate(4);
2789
+ break;
2790
}
2791
case 'msg': {
2792
// Check if this is a message from a node
@@ -2761,7 +2801,22 @@
2801
if (typeof message.memory.percentConsumed == 'number') { memory = message.memory.percentConsumed; }
2802
deviceDetailsStatsData.push([now, cpu, memory]);
2803
deviceDetailsStatsDraw(message);
2764
- } else if (message.type === 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
2804
+ } else if (message.type === 'software') {
2805
+ var softwareData = message.value;
2806
+
2807
+ if (typeof softwareData === 'string' && softwareData.trim().charAt(0) === '[' && softwareData.indexOf('"name"') >= 0) {
2808
+ if (typeof handleInstalledAppsResponse === 'function') {
2809
+ handleInstalledAppsResponse(softwareData);
2810
+ }
2811
+ }
2812
+
2813
+ else if (typeof softwareData === 'string' && (softwareData.indexOf('"success"') >= 0 || softwareData.indexOf('"status"') >= 0 || softwareData.indexOf('"error"') >= 0)) {
2814
+ if (typeof handleUninstallResponse === 'function') {
2815
+ handleUninstallResponse(softwareData);
2816
+ }
2817
+ }
2818
+
2819
+ } else if (message.type === 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
2820
else if (message.type === 'notify') { // This is a notification message.
2821
var n = getstore('notifications', 0);
2822
if (((n & 8) == 0) && (message.amtMessage != null)) { break; } // Intel AMT desktop & terminal messages should be ignored.
@@ -3118,7 +3173,7 @@
3173
if (clipb != '') { clipb += ' '; }
3174
clipb += p;
3175
} else {
3121
- x += '<td><strike style=color:#BBB>' + p.substring(0, 4) + ' ' + p.substring(4); + '</strike>';
3176
+ x += '<td><strike style=color:#BBB>' + p.substring(0, 4) + ' ' + p.substring(4) + '</strike>';
3177
}
3178
}
3179
} else {
@@ -8092,6 +8147,8 @@
8147
QV('MainDevTerminal', (((node.agent == null) && (node.intelamt != null)) || ((node.agent) && (node.agent.caps == null)) || ((node.agent) && ((node.agent.caps & 2) != 0)) || (node.intelamt && (node.intelamt.state == 2))) && (meshrights & 8) && terminalAccess);
8148
QV('MainDevFiles', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 4) != 0) && (meshrights & 8) && fileAccess);
8149
QV('MainDevInfo', (node.mtype < 3) && ((meshrights & 1048576) != 0));
8150
+ //QV('MainDevApps', (rights & 1) != 0);
8151
+ QV('MainDevApps',(node.mtype < 3) && ((meshrights & 1048576) != 0));
8152
QV('MainDevAmt', (node.intelamt != null) && ((node.intelamt.state == 2) || (node.conn & 2)) && (meshrights & 8) && amtAccess);
8153
QV('MainDevConsole', (consoleRights && ((node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 8) != 0))) && (meshrights & 8));
8154
QV('MainDevPlugins', false);
@@ -12837,6 +12894,357 @@
12894
setDialogMode(2, decodeURIComponent(drive) + ': ' + "BitLocker Information", 1, null, x, '');
12895
}
12896
12897
+ // ============================================
12898
+ // INSTALLED SOFTWARE (Panel 18)
12899
+ // ============================================
12900
+ //
12901
+
12902
+ var installedAppsData = { desktop: [], store: [] }; // Getrennte Speicherung
12903
+ var installedAppsLoading = false;
12904
+ var installedAppsLoadingType = 0; // 0=idle, 1=desktop, 2=store
12905
+ var showStoreApps = false;
12906
+
12907
+ function loadInstalledApps() {
12908
+ if (currentNode == null) return;
12909
+ if (installedAppsLoading) return;
12910
+
12911
+ installedAppsLoading = true;
12912
+ installedAppsLoadingType = 1; // Desktop Apps laden
12913
+ installedAppsData = { desktop: [], store: [] }; // Reset
12914
+
12915
+ QH('p18Status', '<span style="color:blue">Loading desktop apps...</span>');
12916
+ QH('p18html', '<div style="text-align:center;padding:50px;color:gray">Loading installed software...</div>');
12917
+
12918
+ // Prüfen ob Agent online
12919
+ if ((currentNode.conn & 1) == 0) {
12920
+ QH('p18html', '<div style="text-align:center;padding:50px;color:#cc0000"><b>Device is offline.</b></div>');
12921
+ QH('p18Status', '<span style="color:red">Offline</span>');
12922
+ installedAppsLoading = false;
12923
+ installedAppsLoadingType = 0;
12924
+ return;
12925
+ }
12926
+
12927
+ // Desktop Apps laden
12928
+ meshserver.send({
12929
+ action: 'msg',
12930
+ type: 'software',
12931
+ nodeid: currentNode._id,
12932
+ value: 'installedapps'
12933
+ });
12934
+ }
12935
+
12936
+ function handleInstalledAppsResponse(data) {
12937
+ try {
12938
+ var apps = JSON.parse(data);
12939
+ if (!Array.isArray(apps)) { apps = []; }
12940
+
12941
+ if (installedAppsLoadingType === 1) {
12942
+ // Desktop Apps erhalten
12943
+ installedAppsData.desktop = apps;
12944
+ installedAppsLoading = false;
12945
+ installedAppsLoadingType = 0;
12946
+
12947
+ // Wenn Store Apps Checkbox aktiv, diese auch laden
12948
+ if (showStoreApps && currentNode != null) {
12949
+ loadStoreApps();
12950
+ } else {
12951
+ displayInstalledApps();
12952
+ }
12953
+ } else if (installedAppsLoadingType === 2) {
12954
+ // Store Apps erhalten
12955
+ installedAppsData.store = apps;
12956
+ installedAppsLoading = false;
12957
+ installedAppsLoadingType = 0;
12958
+ displayInstalledApps();
12959
+ } else {
12960
+ // Unbekannter Typ - als Desktop behandeln
12961
+ installedAppsData.desktop = apps;
12962
+ installedAppsLoading = false;
12963
+ installedAppsLoadingType = 0;
12964
+ displayInstalledApps();
12965
+ }
12966
+ } catch (e) {
12967
+ H('p18Status', '<span style="color:red">Error parsing data: ' + e.message + '</span>');
12968
+ installedAppsLoading = false;
12969
+ installedAppsLoadingType = 0;
12970
+ }
12971
+ }
12972
+
12973
+ function loadStoreApps() {
12974
+ if (currentNode == null) return;
12975
+ if (installedAppsLoading) return;
12976
+
12977
+ installedAppsLoading = true;
12978
+ installedAppsLoadingType = 2; // Store Apps laden
12979
+
12980
+ QH('p18Status', '<span style="color:blue">Loading store apps...</span>');
12981
+
12982
+ meshserver.send({
12983
+ action: 'msg',
12984
+ type: 'software',
12985
+ nodeid: currentNode._id,
12986
+ value: 'installedstoreapps'
12987
+ });
12988
+ }
12989
+
12990
+ function toggleStoreApps() {
12991
+ showStoreApps = Q('p18ShowStore').checked;
12992
+
12993
+ if (showStoreApps && installedAppsData.store.length === 0 && currentNode != null) {
12994
+ // Store Apps müssen noch geladen werden
12995
+ loadStoreApps();
12996
+ } else {
12997
+ // Einfach neu anzeigen
12998
+ displayInstalledApps();
12999
+ }
13000
+ }
13001
+
13002
+ function displayInstalledApps() {
13003
+ var searchTerm = '';
13004
+ if (Q('p18SearchInput')) { searchTerm = Q('p18SearchInput').value.toLowerCase(); }
13005
+
13006
+ var desktopApps = installedAppsData.desktop || [];
13007
+ var storeApps = installedAppsData.store || [];
13008
+
13009
+ // Filtern nach Suchbegriff
13010
+ if (searchTerm) {
13011
+ desktopApps = desktopApps.filter(function(app) {
13012
+ return (app.name && app.name.toLowerCase().indexOf(searchTerm) >= 0) ||
13013
+ (app.publisher && app.publisher.toLowerCase().indexOf(searchTerm) >= 0);
13014
+ });
13015
+ storeApps = storeApps.filter(function(app) {
13016
+ return (app.name && app.name.toLowerCase().indexOf(searchTerm) >= 0) ||
13017
+ (app.publisher && app.publisher.toLowerCase().indexOf(searchTerm) >= 0);
13018
+ });
13019
+ }
13020
+
13021
+ // Sortieren nach Name
13022
+ desktopApps.sort(function(a, b) {
13023
+ return (a.name || '').toLowerCase().localeCompare((b.name || '').toLowerCase());
13024
+ });
13025
+ storeApps.sort(function(a, b) {
13026
+ return (a.name || '').toLowerCase().localeCompare((b.name || '').toLowerCase());
13027
+ });
13028
+
13029
+ var x = '';
13030
+
13031
+ // ===== DESKTOP APPS TABELLE (immer anzeigen) =====
13032
+ x += '<div style="margin-bottom:20px">';
13033
+ x += '<h4 style="border-bottom:2px solid #007bff;padding-bottom:5px;margin-top:0">';
13034
+ x += '<span style="color:#007bff">💻</span> Desktop Applications (' + desktopApps.length + ')';
13035
+ x += '</h4>';
13036
+ x += '<table style="width:100%;border-collapse:collapse;font-size:13px">';
13037
+ x += '<thead><tr style="background-color:#e9ecef">';
13038
+ x += '<th style="text-align:left;padding:8px;border:1px solid #ddd">Name</th>';
13039
+ x += '<th style="text-align:left;padding:8px;border:1px solid #ddd;width:120px">Version</th>';
13040
+ x += '<th style="text-align:left;padding:8px;border:1px solid #ddd;width:200px">Publisher</th>';
13041
+ x += '<th style="text-align:center;padding:8px;border:1px solid #ddd;width:180px">Actions</th>';
13042
+ x += '</tr></thead><tbody>';
13043
+
13044
+ if (desktopApps.length === 0) {
13045
+ x += '<tr><td colspan="4" style="text-align:center;color:gray;padding:20px">';
13046
+ x += searchTerm ? 'No applications matching "' + EscapeHtml(searchTerm) + '"' : 'No desktop applications found';
13047
+ x += '</td></tr>';
13048
+ } else {
13049
+ for (var i = 0; i < desktopApps.length; i++) {
13050
+ var app = desktopApps[i];
13051
+ var uninstallCmd = app.uninstall ? encodeURIComponent(app.uninstall) : '';
13052
+ var hasUninstall = (uninstallCmd !== '');
13053
+ var rowColor = (i % 2 == 0) ? '#ffffff' : '#f8f9fa';
13054
+
13055
+ x += '<tr style="background-color:' + rowColor + '">';
13056
+ x += '<td style="padding:6px 8px;border:1px solid #eee" title="' + EscapeHtml(app.name || '') + '">' + EscapeHtml(app.name || '-') + '</td>';
13057
+ x += '<td style="padding:6px 8px;border:1px solid #eee">' + EscapeHtml(app.version || '-') + '</td>';
13058
+ x += '<td style="padding:6px 8px;border:1px solid #eee">' + EscapeHtml(app.publisher || '-') + '</td>';
13059
+ x += '<td style="text-align:center;padding:6px 8px;border:1px solid #eee">';
13060
+
13061
+ var userRights = GetNodeRights(currentNode);
13062
+
13063
+ if (hasUninstall && (userRights == 0xFFFFFFFF)) {
13064
+ x += '<input type="button" value="Uninstall" style="padding:2px 8px;cursor:pointer" onclick="uninstallDesktopApp(\'' + encodeURIComponent(app.uninstall) + '\')" />';
13065
+ } else {
13066
+ x += '<span style="color:#999">-</span>';
13067
+ }
13068
+
13069
+
13070
+ x += '</td>';
13071
+ x += '</tr>';
13072
+ }
13073
+ }
13074
+
13075
+ x += '</tbody></table>';
13076
+ x += '</div>';
13077
+
13078
+ // ===== STORE APPS TABELLE (nur wenn Checkbox aktiv) =====
13079
+ if (showStoreApps) {
13080
+ x += '<div style="margin-bottom:20px">';
13081
+ x += '<h4 style="border-bottom:2px solid #28a745;padding-bottom:5px">';
13082
+ x += '<span style="color:#28a745">🏫</span> Microsoft Store Apps (' + storeApps.length + ')';
13083
+ x += '</h4>';
13084
+ x += '<table style="width:100%;border-collapse:collapse;font-size:13px">';
13085
+ x += '<thead><tr style="background-color:#d4edda">';
13086
+ x += '<th style="text-align:left;padding:8px;border:1px solid #c3e6cb">Name</th>';
13087
+ x += '<th style="text-align:left;padding:8px;border:1px solid #c3e6cb;width:120px">Version</th>';
13088
+ x += '<th style="text-align:left;padding:8px;border:1px solid #c3e6cb;width:250px">Package</th>';
13089
+ x += '<th style="text-align:center;padding:8px;border:1px solid #c3e6cb;width:100px">Scope</th>';
13090
+ x += '<th style="text-align:center;padding:8px;border:1px solid #c3e6cb;width:80px">Actions</th>';
13091
+ x += '</tr></thead><tbody>';
13092
+
13093
+ if (storeApps.length === 0) {
13094
+ x += '<tr><td colspan="5" style="text-align:center;color:gray;padding:20px">';
13095
+ x += searchTerm ? 'No store apps matching "' + EscapeHtml(searchTerm) + '"' : 'No store apps found';
13096
+ x += '</td></tr>';
13097
+ } else {
13098
+ for (var i = 0; i < storeApps.length; i++) {
13099
+ var app = storeApps[i];
13100
+ var uninstallCmd = app.uninstall ? encodeURIComponent(app.uninstall) : '';
13101
+ var hasUninstall = (uninstallCmd !== '');
13102
+ var rowColor = (i % 2 == 0) ? '#ffffff' : '#f0fff0';
13103
+
13104
+ // Scope-Badge erstellen
13105
+ var scopeBadge = '';
13106
+ if (app.scope) {
13107
+ if (app.scope.indexOf('User') >= 0) {
13108
+ scopeBadge = '<span style="background:#ffc107;color:#000;padding:2px 6px;border-radius:3px;font-size:10px" title="Installed for current user only">User</span>';
13109
+ } else if (app.scope.indexOf('System') >= 0) {
13110
+ scopeBadge = '<span style="background:#17a2b8;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px" title="Installed for all users">System</span>';
13111
+ }
13112
+ if (app.scope.indexOf('Prov') >= 0) {
13113
+ scopeBadge += ' <span style="background:#6c757d;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px" title="Provisioned for new users">Prov</span>';
13114
+ }
13115
+ } else {
13116
+ scopeBadge = '<span style="color:#999">-</span>';
13117
+ }
13118
+
13119
+ x += '<tr style="background-color:' + rowColor + '">';
13120
+ x += '<td style="padding:6px 8px;border:1px solid #c3e6cb">' + EscapeHtml(app.name || '-') + '</td>';
13121
+ x += '<td style="padding:6px 8px;border:1px solid #c3e6cb">' + EscapeHtml(app.version || '-') + '</td>';
13122
+ x += '<td style="padding:6px 8px;border:1px solid #c3e6cb" title="' + EscapeHtml(app.packageFullName || '') + '">' + EscapeHtml(app.publisher || '-') + '</td>';
13123
+ x += '<td style="text-align:center;padding:6px 8px;border:1px solid #c3e6cb">' + scopeBadge + '</td>';
13124
+ x += '<td style="text-align:center;padding:6px 8px;border:1px solid #c3e6cb">';
13125
+
13126
+ var userRights = GetNodeRights(currentNode);
13127
+
13128
+ if (hasUninstall && (userRights == 0xFFFFFFFF)) {
13129
+ x += '<input type="button" value="Remove" style="padding:2px 8px;cursor:pointer" onclick="uninstallStoreApp(\'' + encodeURIComponent(app.name) + '\')" />';
13130
+ } else {
13131
+ x += '<span style="color:#999">-</span>';
13132
+ }
13133
+
13134
+ x += '</td>';
13135
+ x += '</tr>';
13136
+ }
13137
+ }
13138
+
13139
+ x += '</tbody></table>';
13140
+ x += '</div>';
13141
+ }
13142
+
13143
+ QH('p18html', x);
13144
+
13145
+ // Status aktualisieren
13146
+ var totalDesktop = (installedAppsData.desktop || []).length;
13147
+ var totalStore = (installedAppsData.store || []).length;
13148
+ var shownDesktop = desktopApps.length;
13149
+ var shownStore = showStoreApps ? storeApps.length : 0;
13150
+
13151
+ var statusText = 'Desktop: ' + shownDesktop + '/' + totalDesktop;
13152
+ if (showStoreApps) {
13153
+ statusText += ' | Store: ' + shownStore + '/' + totalStore;
13154
+ }
13155
+ QH('p18Status', statusText);
13156
+ }
13157
+
13158
+ function filterInstalledApps() {
13159
+ if (filterInstalledApps.timer) clearTimeout(filterInstalledApps.timer);
13160
+ filterInstalledApps.timer = setTimeout(function() {
13161
+ displayInstalledApps();
13162
+ }, 300);
13163
+ }
13164
+
13165
+ function uninstallDesktopApp(encodedCmd) {
13166
+ if (xxdialogMode) return;
13167
+ if (currentNode == null) return;
13168
+
13169
+ var cmd = decodeURIComponent(encodedCmd);
13170
+
13171
+ // Prüfen ob Befehl leer ist
13172
+ if (!cmd || cmd.trim() === '') {
13173
+ QH('p18Status', '<span style="color:red">Error: No uninstall command available</span>');
13174
+ return;
13175
+ }
13176
+
13177
+ var dialogContent = '<div style="max-width:500px">';
13178
+ dialogContent += '<p><b>Uninstall this software?</b></p>';
13179
+ dialogContent += '<div style="background-color:#f5f5f5;padding:10px;border-radius:5px;word-break:break-all;font-family:monospace;font-size:11px;max-height:80px;overflow-y:auto">' + EscapeHtml(cmd) + '</div>';
13180
+ dialogContent += '<p style="margin-top:10px"><i>Silent uninstall will be performed.</i></p>';
13181
+ dialogContent += '</div>';
13182
+
13183
+ setDialogMode(2, "Uninstall Software", 3, function() {
13184
+ QH('p18Status', '<span style="color:blue">Uninstalling...</span>');
13185
+
13186
+ // Base64 kodieren um Sonderzeichen-Probleme zu vermeiden
13187
+ var base64Cmd = btoa(unescape(encodeURIComponent(cmd)));
13188
+
13189
+ meshserver.send({
13190
+ action: 'msg',
13191
+ type: 'software',
13192
+ nodeid: currentNode._id,
13193
+ value: 'uninstallapp ' + base64Cmd
13194
+ });
13195
+
13196
+ setTimeout(function() {
13197
+ QH('p18Status', 'Uninstall started. Click Refresh to update.');
13198
+ }, 2000);
13199
+ }, dialogContent);
13200
+ }
13201
+
13202
+
13203
+
13204
+ function uninstallStoreApp(encodedCmd) {
13205
+ if (xxdialogMode) return;
13206
+ if (currentNode == null) return;
13207
+
13208
+ var cmd = decodeURIComponent(encodedCmd);
13209
+
13210
+ var dialogContent = '<div style="max-width:500px">';
13211
+ dialogContent += '<p><b>Remove this Store app?</b></p>';
13212
+ dialogContent += '<div style="background-color:#f5f5f5;padding:10px;border-radius:5px;word-break:break-all;font-family:monospace;font-size:11px">' + EscapeHtml(cmd) + '</div>';
13213
+ dialogContent += '</div>';
13214
+
13215
+ setDialogMode(2, "Remove Store App", 3, function() {
13216
+ QH('p18Status', '<span style="color:blue">Removing...</span>');
13217
+
13218
+ var escapedCmd = cmd.replace(/"/g, '\\"');
13219
+
13220
+ meshserver.send({
13221
+ action: 'msg',
13222
+ type: 'software',
13223
+ nodeid: currentNode._id,
13224
+ value: 'uninstallstoreapp "' + escapedCmd + '"'
13225
+ });
13226
+
13227
+ setTimeout(function() {
13228
+ QH('p18Status', 'Remove command sent. Click Refresh to update.');
13229
+ }, 2000);
13230
+ }, dialogContent);
13231
+ }
13232
+
13233
+ function handleUninstallResponse(data) {
13234
+ try {
13235
+ var result = JSON.parse(data);
13236
+ if (result.success) {
13237
+ QH('p18Status', '<span style="color:green">✓ Success!</span> Click Refresh to update.');
13238
+ } else if (result.error) {
13239
+ QH('p18Status', '<span style="color:red">✗ Error: ' + EscapeHtml(result.error) + '</span>');
13240
+ }
13241
+ } catch(e) {}
13242
+ }
13243
+
13244
+ // ============================================
13245
+ // END INSTALLED SOFTWARE
13246
+ // ============================================
13247
+
13248
//
13249
// CONSOLE
13250
//
@@ -19136,7 +19544,7 @@
19544
QV('ServerSubMenuSpan', x == 6 || x == 115 || x == 40 || x == 41 || x == 42 || x == 43);
19545
QV('UsersSubMenuSpan', x == 4 || x == 50 || x == 52);
19546
QV('EventsSubMenuSpan', (x == 3) || (x == 60));
19139
- var panels = { 3: 'EventsLive', 4: 'UsersGeneral', 10: 'MainDev', 11: 'MainDevDesktop', 12: 'MainDevTerminal', 13: 'MainDevFiles', 14: 'MainDevAmt', 15: 'MainDevConsole', 16: 'MainDevEvents', 17: 'MainDevInfo', 19: 'MainDevPlugins', 20: 'MeshGeneral', 21: 'MeshSummary', 30: 'UserGeneral', 31: 'UserEvents', 6: 'ServerGeneral', 40: 'ServerStats', 41: 'ServerTrace', 42: 'ServerPlugins', 50: 'UsersGroups', 52: 'UsersRecordings', 60: 'EventsReport', 115: 'ServerConsole' };
19547
+ var panels = { 3: 'EventsLive', 4: 'UsersGeneral', 10: 'MainDev', 11: 'MainDevDesktop', 12: 'MainDevTerminal', 13: 'MainDevFiles', 14: 'MainDevAmt', 15: 'MainDevConsole', 16: 'MainDevEvents', 17: 'MainDevInfo', 18: 'MainDevApps', 19: 'MainDevPlugins', 20: 'MeshGeneral', 21: 'MeshSummary', 30: 'UserGeneral', 31: 'UserEvents', 6: 'ServerGeneral', 40: 'ServerStats', 41: 'ServerTrace', 42: 'ServerPlugins', 50: 'UsersGroups', 52: 'UsersRecordings', 60: 'EventsReport', 115: 'ServerConsole' };
19548
for (var i in panels) {
19549
QC(panels[i]).remove('style3x');
19550
QC(panels[i]).remove('style3sel');
@@ -19182,6 +19590,14 @@
19590
19591
// Some panels must not have scroll bars
19592
QS('column_l')['overflow'] = ([11,12].indexOf(x)>= 0)?'hidden':null;
19593
+
19594
+ // Software Panel - Apps laden wenn Panel 18 aktiv wird
19595
+ if (x == 18) {
19596
+ if (currentNode == null) { go(1); return; }
19597
+ QH('p18deviceName', EscapeHtml(currentNode.name));
19598
+ // Optional: Apps automatisch laden
19599
+ loadInstalledApps();
19600
+ }
19601
}
19602
19603
//
views/default3.handlebars
+420
-5
@@ -304,6 +304,8 @@
304
onkeypress="if (event.key == 'Enter') go(16)">Events</td>
305
<td tabindex=0 id=MainDevInfo class="topbar_td style3x" onmouseup=go(17,event)
306
onkeypress="if (event.key == 'Enter') go(17)">Details</td>
307
+ <td tabindex=0 id=MainDevApps class="topbar_td style3x" onmouseup=go(18,event)
308
+ onkeypress="if (event.key == 'Enter') go(18)">Software</td>
309
<td tabindex=0 id=MainDevAmt class="topbar_td style3x" onmouseup=go(14,event)
310
onkeypress="if (event.key == 'Enter') go(14)">Intel®AMT</td>
311
<td tabindex=0 id=MainDevConsole class="topbar_td style3x" onmouseup=go(15,event)
@@ -1445,6 +1447,33 @@
1447
<div id=p17info2></div>
1448
</div>
1449
</div>
1450
+ <div id=p18 style="display:none">
1451
+ <div id="p18title">
1452
+ <div id="p18BackButton" style="float:left">
1453
+ <div class="backButton" tabindex=0 onclick=goBack() title="Back"
1454
+ onkeypress="if (event.key == 'Enter') goBack()">
1455
+ <div class="backButtonEx"></div>
1456
+ </div>
1457
+ </div>
1458
+ <h1>Software - <span id=p18deviceName></span></h1>
1459
+ </div>
1460
+ <div id="p18toolbar" style="padding:10px;background-color:#f5f5f5;border-bottom:1px solid #ddd">
1461
+ <input type="button" id="p18RefreshBtn" class="btn btn-primary btn-sm" value="Refresh" onclick="loadInstalledApps()" />
1462
+ <input type="text" id="p18SearchInput" class="form-control-sm" placeholder="Search software..."
1463
+ style="margin-left:10px;width:250px" onkeyup="filterInstalledApps()" />
1464
+ <label style="margin-left:15px">
1465
+ <input type="checkbox" id="p18ShowStore" onchange="toggleStoreApps()" /> Show Store Apps
1466
+ </label>
1467
+ <span id="p18Status" style="margin-left:20px;color:gray"></span>
1468
+ </div>
1469
+ <div id="p18info" style="overflow-y:auto;padding:10px">
1470
+ <div id="p18html">
1471
+ <div style="text-align:center;padding:50px;color:gray">
1472
+ <i>Click "Refresh" to load installed software...</i>
1473
+ </div>
1474
+ </div>
1475
+ </div>
1476
+ </div>
1477
<div id=p19 style="display:none">
1478
<div id="p19title" class="d-flex align-items-center">
1479
<div id="p19BackButton" class="pe-2">
@@ -2141,6 +2170,13 @@
2170
var p12TermConsoleMsgTimer = null;
2171
var p13FilesConsoleMsgTimer = null;
2172
2173
+ //
2174
+ // INSTALLED SOFTWARE
2175
+ //
2176
+ var installedAppsData = null; // Gespeicherte App-Daten
2177
+ var installedAppsLoading = false; // Ladestatus
2178
+ var showStoreApps = false; // Store Apps anzeigen?
2179
+
2180
/*
2181
// Check browser dark mode preference
2182
var prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
@@ -2514,6 +2550,7 @@
2550
QC('p15title').add('d-none');
2551
QC('p16title').add('d-none');
2552
QC('p17title').add('d-none');
2553
+ QC('p18title').add('d-none');
2554
QC('p20title').add('d-none');
2555
QC('p21title').add('d-none');
2556
QC('p30title').add('d-none');
@@ -2587,6 +2624,8 @@
2624
QS('p10info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2625
QS('p17info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2626
QS('p17info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
2627
+ QS('p18info')['height'] = 'calc(100vh - ' + (120 + xh + xh2) + 'px)';
2628
+ QS('p18info')['max-height'] = 'calc(100vh - ' + (120 + xh + xh2) + 'px)';
2629
QS('p20main')['height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
2630
QS('p20main')['max-height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
2631
QS('p21main')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
@@ -2616,6 +2655,7 @@
2655
QV('p15BackButton', false);
2656
QV('p16BackButton', false);
2657
QV('p17BackButton', false);
2658
+ QV('p18BackButton', false);
2659
}
2660
p1updateInfo();
2661
}
@@ -3419,6 +3459,7 @@
3459
if (node != null) { node.lastconnect = message.lastconnects[lcnodeid] }
3460
}
3461
mainUpdate(4);
3462
+ break;
3463
}
3464
case 'msg': {
3465
// Check if this is a message from a node
@@ -3433,7 +3474,22 @@
3474
if (typeof message.memory.percentConsumed == 'number') { memory = message.memory.percentConsumed; }
3475
deviceDetailsStatsData.push([now, cpu, memory]);
3476
deviceDetailsStatsDraw(message);
3436
- } else if (message.type === 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
3477
+ } else if (message.type === 'software') {
3478
+ var softwareData = message.value;
3479
+
3480
+ if (typeof softwareData === 'string' && softwareData.trim().charAt(0) === '[' && softwareData.indexOf('"name"') >= 0) {
3481
+ if (typeof handleInstalledAppsResponse === 'function') {
3482
+ handleInstalledAppsResponse(softwareData);
3483
+ }
3484
+ }
3485
+
3486
+ else if (typeof softwareData === 'string' && (softwareData.indexOf('"success"') >= 0 || softwareData.indexOf('"status"') >= 0 || softwareData.indexOf('"error"') >= 0)) {
3487
+ if (typeof handleUninstallResponse === 'function') {
3488
+ handleUninstallResponse(softwareData);
3489
+ }
3490
+ }
3491
+
3492
+ } else if (message.type === 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
3493
else if (message.type === 'notify') { // This is a notification message.
3494
var n = getstore('notifications', 0);
3495
if (((n & 8) == 0) && (message.amtMessage != null)) { break; } // Intel AMT desktop & terminal messages should be ignored.
@@ -8987,6 +9043,8 @@
9043
QV('MainDevTerminal', (((node.agent == null) && (node.intelamt != null)) || ((node.agent) && (node.agent.caps == null)) || ((node.agent) && ((node.agent.caps & 2) != 0)) || (node.intelamt && (node.intelamt.state == 2))) && (meshrights & 8) && terminalAccess);
9044
QV('MainDevFiles', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 4) != 0) && (meshrights & 8) && fileAccess);
9045
QV('MainDevInfo', (node.mtype < 3) && ((meshrights & 1048576) != 0));
9046
+ //QV('MainDevApps', (rights & 1) != 0);
9047
+ QV('MainDevApps',(node.mtype < 3) && ((meshrights & 1048576) != 0));
9048
QV('MainDevAmt', (node.intelamt != null) && ((node.intelamt.state == 2) || (node.conn & 2)) && (meshrights & 8) && amtAccess);
9049
QV('MainDevConsole', (consoleRights && ((node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 8) != 0))) && (meshrights & 8));
9050
QV('MainDevPlugins', false);
@@ -13884,6 +13942,357 @@
13942
showModal('xxAddAgentModal', 'idx_dlgOkButton');
13943
}
13944
13945
+ // ============================================
13946
+ // INSTALLED SOFTWARE (Panel 18)
13947
+ // ============================================
13948
+ //
13949
+
13950
+ var installedAppsData = { desktop: [], store: [] }; // Getrennte Speicherung
13951
+ var installedAppsLoading = false;
13952
+ var installedAppsLoadingType = 0; // 0=idle, 1=desktop, 2=store
13953
+ var showStoreApps = false;
13954
+
13955
+ function loadInstalledApps() {
13956
+ if (currentNode == null) return;
13957
+ if (installedAppsLoading) return;
13958
+
13959
+ installedAppsLoading = true;
13960
+ installedAppsLoadingType = 1; // Desktop Apps laden
13961
+ installedAppsData = { desktop: [], store: [] }; // Reset
13962
+
13963
+ QH('p18Status', '<span style="color:blue">Loading desktop apps...</span>');
13964
+ QH('p18html', '<div style="text-align:center;padding:50px;color:gray">Loading installed software...</div>');
13965
+
13966
+ // Prüfen ob Agent online
13967
+ if ((currentNode.conn & 1) == 0) {
13968
+ QH('p18html', '<div style="text-align:center;padding:50px;color:#cc0000"><b>Device is offline.</b></div>');
13969
+ QH('p18Status', '<span style="color:red">Offline</span>');
13970
+ installedAppsLoading = false;
13971
+ installedAppsLoadingType = 0;
13972
+ return;
13973
+ }
13974
+
13975
+ // Desktop Apps laden
13976
+ meshserver.send({
13977
+ action: 'msg',
13978
+ type: 'software',
13979
+ nodeid: currentNode._id,
13980
+ value: 'installedapps'
13981
+ });
13982
+ }
13983
+
13984
+ function handleInstalledAppsResponse(data) {
13985
+ try {
13986
+ var apps = JSON.parse(data);
13987
+ if (!Array.isArray(apps)) { apps = []; }
13988
+
13989
+ if (installedAppsLoadingType === 1) {
13990
+ // Desktop Apps erhalten
13991
+ installedAppsData.desktop = apps;
13992
+ installedAppsLoading = false;
13993
+ installedAppsLoadingType = 0;
13994
+
13995
+ // Wenn Store Apps Checkbox aktiv, diese auch laden
13996
+ if (showStoreApps && currentNode != null) {
13997
+ loadStoreApps();
13998
+ } else {
13999
+ displayInstalledApps();
14000
+ }
14001
+ } else if (installedAppsLoadingType === 2) {
14002
+ // Store Apps erhalten
14003
+ installedAppsData.store = apps;
14004
+ installedAppsLoading = false;
14005
+ installedAppsLoadingType = 0;
14006
+ displayInstalledApps();
14007
+ } else {
14008
+ // Unbekannter Typ - als Desktop behandeln
14009
+ installedAppsData.desktop = apps;
14010
+ installedAppsLoading = false;
14011
+ installedAppsLoadingType = 0;
14012
+ displayInstalledApps();
14013
+ }
14014
+ } catch (e) {
14015
+ H('p18Status', '<span style="color:red">Error parsing data: ' + e.message + '</span>');
14016
+ installedAppsLoading = false;
14017
+ installedAppsLoadingType = 0;
14018
+ }
14019
+ }
14020
+
14021
+ function loadStoreApps() {
14022
+ if (currentNode == null) return;
14023
+ if (installedAppsLoading) return;
14024
+
14025
+ installedAppsLoading = true;
14026
+ installedAppsLoadingType = 2; // Store Apps laden
14027
+
14028
+ QH('p18Status', '<span style="color:blue">Loading store apps...</span>');
14029
+
14030
+ meshserver.send({
14031
+ action: 'msg',
14032
+ type: 'console',
14033
+ nodeid: currentNode._id,
14034
+ value: 'installedstoreapps'
14035
+ });
14036
+ }
14037
+
14038
+ function toggleStoreApps() {
14039
+ showStoreApps = Q('p18ShowStore').checked;
14040
+
14041
+ if (showStoreApps && installedAppsData.store.length === 0 && currentNode != null) {
14042
+ // Store Apps müssen noch geladen werden
14043
+ loadStoreApps();
14044
+ } else {
14045
+ // Einfach neu anzeigen
14046
+ displayInstalledApps();
14047
+ }
14048
+ }
14049
+
14050
+ function displayInstalledApps() {
14051
+ var searchTerm = '';
14052
+ if (Q('p18SearchInput')) { searchTerm = Q('p18SearchInput').value.toLowerCase(); }
14053
+
14054
+ var desktopApps = installedAppsData.desktop || [];
14055
+ var storeApps = installedAppsData.store || [];
14056
+
14057
+ // Filtern nach Suchbegriff
14058
+ if (searchTerm) {
14059
+ desktopApps = desktopApps.filter(function(app) {
14060
+ return (app.name && app.name.toLowerCase().indexOf(searchTerm) >= 0) ||
14061
+ (app.publisher && app.publisher.toLowerCase().indexOf(searchTerm) >= 0);
14062
+ });
14063
+ storeApps = storeApps.filter(function(app) {
14064
+ return (app.name && app.name.toLowerCase().indexOf(searchTerm) >= 0) ||
14065
+ (app.publisher && app.publisher.toLowerCase().indexOf(searchTerm) >= 0);
14066
+ });
14067
+ }
14068
+
14069
+ // Sortieren nach Name
14070
+ desktopApps.sort(function(a, b) {
14071
+ return (a.name || '').toLowerCase().localeCompare((b.name || '').toLowerCase());
14072
+ });
14073
+ storeApps.sort(function(a, b) {
14074
+ return (a.name || '').toLowerCase().localeCompare((b.name || '').toLowerCase());
14075
+ });
14076
+
14077
+ var x = '';
14078
+
14079
+ // ===== DESKTOP APPS TABELLE (immer anzeigen) =====
14080
+ x += '<div style="margin-bottom:20px">';
14081
+ x += '<h4 style="border-bottom:2px solid #007bff;padding-bottom:5px;margin-top:0">';
14082
+ x += '<span style="color:#007bff">💻</span> Desktop Applications (' + desktopApps.length + ')';
14083
+ x += '</h4>';
14084
+ x += '<table style="width:100%;border-collapse:collapse;font-size:13px">';
14085
+ x += '<thead><tr style="background-color:#e9ecef">';
14086
+ x += '<th style="text-align:left;padding:8px;border:1px solid #ddd">Name</th>';
14087
+ x += '<th style="text-align:left;padding:8px;border:1px solid #ddd;width:120px">Version</th>';
14088
+ x += '<th style="text-align:left;padding:8px;border:1px solid #ddd;width:200px">Publisher</th>';
14089
+ x += '<th style="text-align:center;padding:8px;border:1px solid #ddd;width:180px">Actions</th>';
14090
+ x += '</tr></thead><tbody>';
14091
+
14092
+ if (desktopApps.length === 0) {
14093
+ x += '<tr><td colspan="4" style="text-align:center;color:gray;padding:20px">';
14094
+ x += searchTerm ? 'No applications matching "' + EscapeHtml(searchTerm) + '"' : 'No desktop applications found';
14095
+ x += '</td></tr>';
14096
+ } else {
14097
+ for (var i = 0; i < desktopApps.length; i++) {
14098
+ var app = desktopApps[i];
14099
+ var uninstallCmd = app.uninstall ? encodeURIComponent(app.uninstall) : '';
14100
+ var hasUninstall = (uninstallCmd !== '');
14101
+ var rowColor = (i % 2 == 0) ? '#ffffff' : '#f8f9fa';
14102
+
14103
+ x += '<tr style="background-color:' + rowColor + '">';
14104
+ x += '<td style="padding:6px 8px;border:1px solid #eee" title="' + EscapeHtml(app.name || '') + '">' + EscapeHtml(app.name || '-') + '</td>';
14105
+ x += '<td style="padding:6px 8px;border:1px solid #eee">' + EscapeHtml(app.version || '-') + '</td>';
14106
+ x += '<td style="padding:6px 8px;border:1px solid #eee">' + EscapeHtml(app.publisher || '-') + '</td>';
14107
+ x += '<td style="text-align:center;padding:6px 8px;border:1px solid #eee">';
14108
+
14109
+ var userRights = GetNodeRights(currentNode);
14110
+
14111
+ if (hasUninstall && (userRights == 0xFFFFFFFF)) {
14112
+ x += '<input type="button" value="Uninstall" style="padding:2px 8px;cursor:pointer" onclick="uninstallDesktopApp(\'' + encodeURIComponent(app.uninstall) + '\')" />';
14113
+ } else {
14114
+ x += '<span style="color:#999">-</span>';
14115
+ }
14116
+
14117
+ x += '</td>';
14118
+ x += '</tr>';
14119
+ }
14120
+ }
14121
+
14122
+ x += '</tbody></table>';
14123
+ x += '</div>';
14124
+
14125
+ // ===== STORE APPS TABELLE (nur wenn Checkbox aktiv) =====
14126
+ if (showStoreApps) {
14127
+ x += '<div style="margin-bottom:20px">';
14128
+ x += '<h4 style="border-bottom:2px solid #28a745;padding-bottom:5px">';
14129
+ x += '<span style="color:#28a745">🏫</span> Microsoft Store Apps (' + storeApps.length + ')';
14130
+ x += '</h4>';
14131
+ x += '<table style="width:100%;border-collapse:collapse;font-size:13px">';
14132
+ x += '<thead><tr style="background-color:#d4edda">';
14133
+ x += '<th style="text-align:left;padding:8px;border:1px solid #c3e6cb">Name</th>';
14134
+ x += '<th style="text-align:left;padding:8px;border:1px solid #c3e6cb;width:120px">Version</th>';
14135
+ x += '<th style="text-align:left;padding:8px;border:1px solid #c3e6cb;width:250px">Package</th>';
14136
+ x += '<th style="text-align:center;padding:8px;border:1px solid #c3e6cb;width:100px">Scope</th>';
14137
+ x += '<th style="text-align:center;padding:8px;border:1px solid #c3e6cb;width:80px">Actions</th>';
14138
+ x += '</tr></thead><tbody>';
14139
+
14140
+ if (storeApps.length === 0) {
14141
+ x += '<tr><td colspan="5" style="text-align:center;color:gray;padding:20px">';
14142
+ x += searchTerm ? 'No store apps matching "' + EscapeHtml(searchTerm) + '"' : 'No store apps found';
14143
+ x += '</td></tr>';
14144
+ } else {
14145
+ for (var i = 0; i < storeApps.length; i++) {
14146
+ var app = storeApps[i];
14147
+ var uninstallCmd = app.uninstall ? encodeURIComponent(app.uninstall) : '';
14148
+ var hasUninstall = (uninstallCmd !== '');
14149
+ var rowColor = (i % 2 == 0) ? '#ffffff' : '#f0fff0';
14150
+
14151
+ // Scope-Badge erstellen
14152
+ var scopeBadge = '';
14153
+ if (app.scope) {
14154
+ if (app.scope.indexOf('User') >= 0) {
14155
+ scopeBadge = '<span style="background:#ffc107;color:#000;padding:2px 6px;border-radius:3px;font-size:10px" title="Installed for current user only">User</span>';
14156
+ } else if (app.scope.indexOf('System') >= 0) {
14157
+ scopeBadge = '<span style="background:#17a2b8;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px" title="Installed for all users">System</span>';
14158
+ }
14159
+ if (app.scope.indexOf('Prov') >= 0) {
14160
+ scopeBadge += ' <span style="background:#6c757d;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px" title="Provisioned for new users">Prov</span>';
14161
+ }
14162
+ } else {
14163
+ scopeBadge = '<span style="color:#999">-</span>';
14164
+ }
14165
+
14166
+ x += '<tr style="background-color:' + rowColor + '">';
14167
+ x += '<td style="padding:6px 8px;border:1px solid #c3e6cb">' + EscapeHtml(app.name || '-') + '</td>';
14168
+ x += '<td style="padding:6px 8px;border:1px solid #c3e6cb">' + EscapeHtml(app.version || '-') + '</td>';
14169
+ x += '<td style="padding:6px 8px;border:1px solid #c3e6cb" title="' + EscapeHtml(app.packageFullName || '') + '">' + EscapeHtml(app.publisher || '-') + '</td>';
14170
+ x += '<td style="text-align:center;padding:6px 8px;border:1px solid #c3e6cb">' + scopeBadge + '</td>';
14171
+ x += '<td style="text-align:center;padding:6px 8px;border:1px solid #c3e6cb">';
14172
+
14173
+ var userRights = GetNodeRights(currentNode);
14174
+
14175
+ if (hasUninstall && (userRights == 0xFFFFFFFF)) {
14176
+ x += '<input type="button" value="Remove" style="padding:2px 8px;cursor:pointer" onclick="uninstallStoreApp(\'' + encodeURIComponent(app.name) + '\')" />';
14177
+ } else {
14178
+ x += '<span style="color:#999">-</span>';
14179
+ }
14180
+
14181
+ x += '</td>';
14182
+ x += '</tr>';
14183
+ }
14184
+ }
14185
+
14186
+ x += '</tbody></table>';
14187
+ x += '</div>';
14188
+ }
14189
+
14190
+
14191
+ QH('p18html', x);
14192
+
14193
+ // Status aktualisieren
14194
+ var totalDesktop = (installedAppsData.desktop || []).length;
14195
+ var totalStore = (installedAppsData.store || []).length;
14196
+ var shownDesktop = desktopApps.length;
14197
+ var shownStore = showStoreApps ? storeApps.length : 0;
14198
+
14199
+ var statusText = 'Desktop: ' + shownDesktop + '/' + totalDesktop;
14200
+ if (showStoreApps) {
14201
+ statusText += ' | Store: ' + shownStore + '/' + totalStore;
14202
+ }
14203
+ QH('p18Status', statusText);
14204
+ }
14205
+
14206
+ function filterInstalledApps() {
14207
+ if (filterInstalledApps.timer) clearTimeout(filterInstalledApps.timer);
14208
+ filterInstalledApps.timer = setTimeout(function() {
14209
+ displayInstalledApps();
14210
+ }, 300);
14211
+ }
14212
+
14213
+ function uninstallDesktopApp(encodedCmd) {
14214
+ if (xxdialogMode) return;
14215
+ if (currentNode == null) return;
14216
+
14217
+ var cmd = decodeURIComponent(encodedCmd);
14218
+
14219
+ // Prüfen ob Befehl leer ist
14220
+ if (!cmd || cmd.trim() === '') {
14221
+ QH('p18Status', '<span style="color:red">Error: No uninstall command available</span>');
14222
+ return;
14223
+ }
14224
+
14225
+ var dialogContent = '<div style="max-width:500px">';
14226
+ dialogContent += '<p><b>Uninstall this software?</b></p>';
14227
+ dialogContent += '<div style="background-color:#f5f5f5;padding:10px;border-radius:5px;word-break:break-all;font-family:monospace;font-size:11px;max-height:80px;overflow-y:auto">' + EscapeHtml(cmd) + '</div>';
14228
+ dialogContent += '<p style="margin-top:10px"><i>Silent uninstall will be performed.</i></p>';
14229
+ dialogContent += '</div>';
14230
+
14231
+ setDialogMode(2, "Uninstall Software", 3, function() {
14232
+ QH('p18Status', '<span style="color:blue">Uninstalling...</span>');
14233
+
14234
+ // Base64 kodieren um Sonderzeichen-Probleme zu vermeiden
14235
+ var base64Cmd = btoa(unescape(encodeURIComponent(cmd)));
14236
+
14237
+ meshserver.send({
14238
+ action: 'msg',
14239
+ type: 'software',
14240
+ nodeid: currentNode._id,
14241
+ value: 'uninstallapp ' + base64Cmd
14242
+ });
14243
+
14244
+ setTimeout(function() {
14245
+ QH('p18Status', 'Uninstall started. Click Refresh to update.');
14246
+ }, 2000);
14247
+ }, dialogContent);
14248
+ }
14249
+
14250
+
14251
+ function uninstallStoreApp(encodedCmd) {
14252
+ if (xxdialogMode) return;
14253
+ if (currentNode == null) return;
14254
+
14255
+ var cmd = decodeURIComponent(encodedCmd);
14256
+
14257
+ var dialogContent = '<div style="max-width:500px">';
14258
+ dialogContent += '<p><b>Remove this Store app?</b></p>';
14259
+ dialogContent += '<div style="background-color:#f5f5f5;padding:10px;border-radius:5px;word-break:break-all;font-family:monospace;font-size:11px">' + EscapeHtml(cmd) + '</div>';
14260
+ dialogContent += '</div>';
14261
+
14262
+ setDialogMode(2, "Remove Store App", 3, function() {
14263
+ QH('p18Status', '<span style="color:blue">Removing...</span>');
14264
+
14265
+ var escapedCmd = cmd.replace(/"/g, '\\"');
14266
+
14267
+ meshserver.send({
14268
+ action: 'msg',
14269
+ type: 'software',
14270
+ nodeid: currentNode._id,
14271
+ value: 'uninstallstoreapp "' + escapedCmd + '"'
14272
+ });
14273
+
14274
+ setTimeout(function() {
14275
+ QH('p18Status', 'Remove command sent. Click Refresh to update.');
14276
+ }, 2000);
14277
+ }, dialogContent);
14278
+ }
14279
+
14280
+
14281
+ function handleUninstallResponse(data) {
14282
+ try {
14283
+ var result = JSON.parse(data);
14284
+ if (result.success) {
14285
+ QH('p18Status', '<span style="color:green">✓ Success!</span> Click Refresh to update.');
14286
+ } else if (result.error) {
14287
+ QH('p18Status', '<span style="color:red">✗ Error: ' + EscapeHtml(result.error) + '</span>');
14288
+ }
14289
+ } catch(e) {}
14290
+ }
14291
+
14292
+ // ============================================
14293
+ // END INSTALLED SOFTWARE
14294
+ // ============================================
14295
+
14296
//
14297
// CONSOLE
14298
//
@@ -17386,10 +17795,8 @@
17795
// Delete accounts, ask for confirmation
17796
var x = "Confirm delete selected account(s)?" + '<br /><br />';
17797
x += '<label><input id=d3check type=checkbox class="form-check-input me-2" onchange=p3usersGroupActionFunctionDelCheck() />' + "Confirm" + '</label>';
17389
- setModalContent('xxAddAgent', "Delete Accounts", x);
17390
- showModal('xxAddAgentModal', 'idx_dlgOkButton', p3groupActionFunctionDelExec);
17798
+ //setDialogMode(2, "Delete Accounts", 3, p3groupActionFunctionDelExec, x);
17799
QE('idx_dlgOkButton', false);
17392
- return false;
17800
} else if (op == 4) {
17801
// Validate emails
17802
for (var i in userids) {
@@ -20846,7 +21253,7 @@
21253
QV('ServerSubMenuSpan', x == 6 || x == 115 || x == 40 || x == 41 || x == 42 || x == 43);
21254
QV('UsersSubMenuSpan', x == 4 || x == 50 || x == 52);
21255
QV('EventsSubMenuSpan', (x == 3) || (x == 60));
20849
- var panels = { 3: 'EventsLive', 4: 'UsersGeneral', 10: 'MainDev', 11: 'MainDevDesktop', 12: 'MainDevTerminal', 13: 'MainDevFiles', 14: 'MainDevAmt', 15: 'MainDevConsole', 16: 'MainDevEvents', 17: 'MainDevInfo', 19: 'MainDevPlugins', 20: 'MeshGeneral', 21: 'MeshSummary', 30: 'UserGeneral', 31: 'UserEvents', 6: 'ServerGeneral', 40: 'ServerStats', 41: 'ServerTrace', 42: 'ServerPlugins', 50: 'UsersGroups', 52: 'UsersRecordings', 60: 'EventsReport', 115: 'ServerConsole' };
21256
+ var panels = { 3: 'EventsLive', 4: 'UsersGeneral', 10: 'MainDev', 11: 'MainDevDesktop', 12: 'MainDevTerminal', 13: 'MainDevFiles', 14: 'MainDevAmt', 15: 'MainDevConsole', 16: 'MainDevEvents', 17: 'MainDevInfo', 18: 'MainDevApps', 19: 'MainDevPlugins', 20: 'MeshGeneral', 21: 'MeshSummary', 30: 'UserGeneral', 31: 'UserEvents', 6: 'ServerGeneral', 40: 'ServerStats', 41: 'ServerTrace', 42: 'ServerPlugins', 50: 'UsersGroups', 52: 'UsersRecordings', 60: 'EventsReport', 115: 'ServerConsole' };
21257
for (var i in panels) {
21258
QC(panels[i]).remove('style3x');
21259
QC(panels[i]).remove('style3sel');
@@ -20892,6 +21299,14 @@
21299
21300
// Some panels must not have scroll bars
21301
QS('column_l')['overflow'] = ([11, 12].indexOf(x) >= 0) ? 'hidden' : null;
21302
+
21303
+ // Software Panel - Apps laden wenn Panel 18 aktiv wird
21304
+ if (x == 18) {
21305
+ if (currentNode == null) { go(1); return; }
21306
+ QH('p18deviceName', EscapeHtml(currentNode.name));
21307
+ // Optional: Apps automatisch laden
21308
+ loadInstalledApps();
21309
+ }
21310
}
21311
21312
//