Varaverk: monitor grid overhaul + Containers & VMs card

Monitor layout (8-col grid):
- Row 1: System | Power | CPU | Memory | Network
- Row 2: Scripts | Partner & Fallback (span 3) | Containers & VMs (span 4)
- Row 3: GPU (span 2) | Transcode (span 2) | Streams (span 4)
- Row 4: Parity (cols 1-2) | Pools (cols 3-4) | Array (cols 5-8)

New Containers & VMs card:
- Reads FolderView3 folders from docker.json; expand/collapse per folder
- VMs listed first (virsh); containers show start/stop/webui/edit actions
- 2-column balanced layout; collapses to 1 column below 900px
- Docker WebUI URLs resolved from dockerMan template XMLs

New files: include/vms.php, include/docker_folders.php, api/docker_action.php, api/snapshot.php
Responsive: explicit grid-column placements reset at 1024px breakpoint
This commit is contained in:
Gmer4Lfe
2026-05-26 18:59:19 -04:00
parent 6078af0dbb
commit 072c8b720d
12 changed files with 689 additions and 83 deletions
@@ -0,0 +1,21 @@
<?php
header('Content-Type: application/json');
$action = trim($_POST['action'] ?? '');
$name = trim($_POST['name'] ?? '');
if (!$name || !in_array($action, ['start', 'stop'], true)) {
echo json_encode(['ok' => false, 'error' => 'Invalid request']);
exit;
}
// Confirm container exists
$check = trim(shell_exec('docker ps -a --filter ' . escapeshellarg('name=^' . $name . '$') . " --format '{{.Names}}' 2>/dev/null") ?? '');
if ($check !== $name) {
echo json_encode(['ok' => false, 'error' => 'Container not found']);
exit;
}
exec(($action === 'start' ? 'docker start' : 'docker stop') . ' ' . escapeshellarg($name) . ' 2>&1', $out, $rc);
echo json_encode(['ok' => $rc === 0, 'output' => implode("\n", $out)]);
@@ -1,6 +1,8 @@
<?php
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/include/monitor.php';
require_once dirname(__DIR__) . '/include/vms.php';
require_once dirname(__DIR__) . '/include/docker_folders.php';
echo json_encode([
'system' => vv_system_info(),
@@ -20,7 +22,9 @@ echo json_encode([
'parity' => vv_parity_status(),
'storage' => vv_storage_pools(),
'array_disks' => vv_array_disks(),
'scripts' => vv_scripts_status(),
'thresholds' => vv_disk_thresholds(),
'ts' => time(),
'scripts' => vv_scripts_status(),
'thresholds' => vv_disk_thresholds(),
'vms' => vv_get_vms(),
'docker_folders' => vv_get_docker_folders(),
'ts' => time(),
]);
@@ -2,6 +2,28 @@
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/include/scheduler.php';
// Batch save — all entries in one load/write/rebuild cycle
if (!empty($_POST['batch'])) {
$entries = json_decode($_POST['batch'], true) ?: [];
$clean = [];
foreach ($entries as $e) {
$id = trim($e['id'] ?? '');
$cron = trim($e['cron'] ?? '');
if (!$id) continue;
if ($cron && !in_array($cron, ['@array_start', '@array_stop'], true)
&& !preg_match('/^(\S+\s+){4}\S+$/', $cron)) $cron = '';
$clean[] = [
'id' => $id,
'enabled' => ($e['enabled'] ?? '0') === '1',
'cron' => $cron,
'log_enabled' => ($e['log_enabled'] ?? '0') === '1',
];
}
$ok = vv_schedule_update_batch($clean);
echo json_encode(['ok' => $ok, 'error' => $ok ? null : 'Failed to write schedule']);
exit;
}
$id = trim($_POST['id'] ?? '');
$enabled = (bool)($_POST['enabled'] ?? false);
$cron = trim($_POST['cron'] ?? '');
@@ -0,0 +1,72 @@
<?php
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/include/monitor.php';
require_once dirname(__DIR__) . '/include/media.php';
// CPU% — delta from own state file so it doesn't conflict with monitor.php
$cpuPct = 0;
$cpuLine = '';
foreach (file('/proc/stat') ?: [] as $line) {
if (strncmp($line, 'cpu ', 4) === 0) { $cpuLine = $line; break; }
}
if (preg_match('/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/', $cpuLine, $m)) {
$c = [(int)$m[1],(int)$m[2],(int)$m[3],(int)$m[4],(int)$m[5],(int)$m[6],(int)$m[7]];
$sf = '/tmp/vv_snap_cpu.json';
$p = file_exists($sf) ? (json_decode(file_get_contents($sf), true) ?: null) : null;
file_put_contents($sf, json_encode($c));
if ($p && is_array($p)) {
$dt = array_sum($c) - array_sum($p);
$di = ($c[3] + $c[4]) - ($p[3] + $p[4]);
$cpuPct = $dt > 0 ? max(0, min(100, (int)round((1 - $di / $dt) * 100))) : 0;
}
}
// RAM%
$mem = [];
foreach (file('/proc/meminfo') ?: [] as $line) {
if (preg_match('/^(MemTotal|MemAvailable):\s+(\d+)/', $line, $m)) $mem[$m[1]] = (int)$m[2];
}
$ramTotalMb = (int)(($mem['MemTotal'] ?? 0) / 1024);
$ramUsedMb = (int)((($mem['MemTotal'] ?? 0) - ($mem['MemAvailable'] ?? 0)) / 1024);
$ramPct = $ramTotalMb > 0 ? (int)round($ramUsedMb / $ramTotalMb * 100) : 0;
// Fallback state (fast file read, no exec)
$fallbackState = 'UNKNOWN';
foreach (@file('/tmp/fallback_state.db') ?: [] as $line) {
if (preg_match('/^state=(.+)/', trim($line), $m)) { $fallbackState = trim($m[1]); break; }
}
// Partner
$partner = vv_partner_state();
$peers = array_values(array_filter($partner['hosts'], fn($h) => !$h['is_me']));
// Media sessions — cached 30s so the HTTP calls don't hold up every snapshot poll
$streamCount = 0;
$transcodeCount = 0;
$mediaCacheFile = '/tmp/vv_snap_media.json';
$cacheMaxAge = 30;
$cacheValid = file_exists($mediaCacheFile) && (time() - filemtime($mediaCacheFile)) < $cacheMaxAge;
if ($cacheValid) {
$cached = json_decode(file_get_contents($mediaCacheFile), true) ?: [];
} else {
$media = vv_media_sessions();
$cached = [
'stream_count' => count($media['sessions']),
'transcode_count' => count(array_filter($media['sessions'], fn($s) => !empty($s['is_tc']))),
];
file_put_contents($mediaCacheFile, json_encode($cached));
}
$streamCount = (int)($cached['stream_count'] ?? 0);
$transcodeCount = (int)($cached['transcode_count'] ?? 0);
echo json_encode([
'cpu_pct' => $cpuPct,
'ram_pct' => $ramPct,
'ram_used_mb' => $ramUsedMb,
'ram_total_mb' => $ramTotalMb,
'fallback' => $fallbackState,
'partner_enabled' => $partner['enabled'],
'peers' => $peers,
'stream_count' => $streamCount,
'transcode_count' => $transcodeCount,
]);