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,99 @@
<?php
function vv_local_ip(): string {
static $ip = null;
if ($ip !== null) return $ip;
$ip = trim(shell_exec("ip route get 8.8.8.8 2>/dev/null | awk '/src/{for(i=1;i<=NF;i++)if(\$i==\"src\")print \$(i+1)}'") ?? '');
if (!$ip) $ip = gethostbyname(gethostname());
return $ip;
}
function vv_container_webui(string $name, array $portMap): string {
$template = '/boot/config/plugins/dockerMan/templates-user/my-' . $name . '.xml';
if (!file_exists($template)) return '';
$xml = @file_get_contents($template) ?: '';
if (!preg_match('/<WebUI>(.*?)<\/WebUI>/s', $xml, $m)) return '';
$url = trim($m[1]);
if (!$url) return '';
$url = str_replace('[IP]', vv_local_ip(), $url);
// [PORT:XXXX] → mapped host port
$url = preg_replace_callback('/\[PORT:(\d+)\]/', function($pm) use ($name, $portMap) {
return $portMap[$name][$pm[1]] ?? $pm[1];
}, $url);
return $url;
}
function vv_get_docker_folders(): array {
$folderFile = '/boot/config/plugins/folder.view3/docker.json';
$folderData = file_exists($folderFile)
? (json_decode(@file_get_contents($folderFile), true) ?: [])
: [];
// One docker ps call: names, status, port mappings
$raw = shell_exec("docker ps -a --format '{{.Names}}\t{{.Status}}\t{{.Ports}}' 2>/dev/null") ?? '';
$statusMap = [];
$portMap = [];
foreach (explode("\n", trim($raw)) as $line) {
$parts = explode("\t", $line, 3);
if (count($parts) < 2) continue;
[$cname, $status, $ports] = array_pad($parts, 3, '');
$cname = trim($cname);
if ($cname === '') continue;
$statusMap[$cname] = trim($status);
foreach (explode(',', $ports) as $entry) {
if (preg_match('/(\d+)->(\d+)\/tcp/', trim($entry), $pm)) {
$portMap[$cname][$pm[2]] = $pm[1]; // containerPort => hostPort
}
}
}
$folderContainerNames = [];
$folders = [];
foreach ($folderData as $id => $f) {
$containers = [];
foreach ($f['containers'] ?? [] as $cname) {
$folderContainerNames[] = $cname;
$status = $statusMap[$cname] ?? '';
$running = str_starts_with($status, 'Up');
$containers[] = [
'name' => $cname,
'running' => $running,
'status' => $status,
'webui' => vv_container_webui($cname, $portMap),
];
}
usort($containers, fn($a, $b) => $b['running'] <=> $a['running'] ?: strcmp($a['name'], $b['name']));
$folders[] = [
'id' => $id,
'name' => $f['name'] ?? 'Unnamed',
'icon' => $f['icon'] ?? '',
'containers' => $containers,
];
}
usort($folders, fn($a, $b) => strcmp($a['name'], $b['name']));
$ungrouped = [];
foreach ($statusMap as $cname => $status) {
if (in_array($cname, $folderContainerNames, true)) continue;
$running = str_starts_with($status, 'Up');
$ungrouped[] = [
'name' => $cname,
'running' => $running,
'status' => $status,
'webui' => vv_container_webui($cname, $portMap),
];
}
usort($ungrouped, fn($a, $b) => strcmp($a['name'], $b['name']));
return [
'available' => true,
'folders' => $folders,
'ungrouped' => $ungrouped,
];
}
@@ -33,6 +33,23 @@ function vv_schedule_update(string $id, bool $enabled, string $cron, bool $log_e
return vv_cron_rebuild($schedule);
}
function vv_schedule_update_batch(array $entries): bool {
$schedule = vv_schedule_load();
foreach ($entries as $e) {
$id = trim($e['id'] ?? '');
if (!$id) continue;
$schedule[$id] = [
'id' => $id,
'enabled' => (bool)($e['enabled'] ?? false),
'cron' => trim($e['cron'] ?? ''),
'log_enabled' => (bool)($e['log_enabled'] ?? false),
'updated' => date('c'),
];
}
if (!vv_schedule_save($schedule)) return false;
return vv_cron_rebuild($schedule);
}
function vv_job_flags(string $id): string {
$schedule = vv_schedule_load();
return !empty($schedule[$id]['log_enabled']) ? '--log' : '';
@@ -0,0 +1,46 @@
<?php
function vv_get_vms(): array {
if (!file_exists('/usr/bin/virsh')) return ['available' => false, 'vms' => []];
exec('virsh list --all --name 2>/dev/null', $names, $rc);
if ($rc !== 0) return ['available' => false, 'vms' => []];
$vms = [];
foreach ($names as $raw) {
$name = trim($raw);
if ($name === '') continue;
$state = trim(shell_exec('virsh domstate ' . escapeshellarg($name) . ' 2>/dev/null') ?? 'unknown');
$vcpus = null;
$memMb = null;
if ($state === 'running') {
$info = shell_exec('virsh dominfo ' . escapeshellarg($name) . ' 2>/dev/null') ?? '';
if (preg_match('/CPU\(s\)\s*:\s*(\d+)/i', $info, $m)) $vcpus = (int)$m[1];
if (preg_match('/Used memory\s*:\s*(\d+)/i', $info, $m)) $memMb = (int)round((int)$m[1] / 1024);
}
// OS detection from libvirt XML
$os = 'linux';
$xmlPath = '/etc/libvirt/qemu/' . $name . '.xml';
if (file_exists($xmlPath)) {
$xml = @file_get_contents($xmlPath) ?: '';
if (stripos($xml, 'windows') !== false || stripos($xml, 'win10') !== false || stripos($xml, 'win11') !== false) $os = 'windows';
elseif (stripos($xml, 'darwin') !== false || stripos($xml, 'macos') !== false) $os = 'macos';
}
$nl = strtolower($name);
if (str_contains($nl, 'win')) $os = 'windows';
elseif (str_contains($nl, 'mac') || str_contains($nl, 'osx')) $os = 'macos';
elseif (str_contains($nl, 'bsd') || str_contains($nl, 'freebsd')) $os = 'bsd';
$vms[] = [
'name' => $name,
'state' => $state,
'os' => $os,
'vcpus' => $vcpus,
'mem_mb' => $memMb,
];
}
return ['available' => true, 'vms' => $vms];
}