fallback tier 1-4 // vv_get_docker_folders() folder grouping in the legacy shape, each container carrying 'tier' // // CONFIGURATION // Inherits everything from docker.php — see that file's CONFIGURATION block. // ═══════════════════════════════════════════════════════════════════════════════════════════════ require_once __DIR__ . '/config.php'; require_once __DIR__ . '/docker.php'; 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>/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); // Scheme allowlist, applied here so a bad value never reaches the page rather than being // filtered at each sink. A WebUI entry is http or https in every real template; anything else // is either broken or a javascript: URL aimed at whoever clicks it. These files come from // Community Applications and hand edits, so they are not ours to trust. include/docs.php // applies the same rule to markdown links for the same reason. if (!preg_match('#^https?://#i', $url)) return ''; return $url; } // Fallback tier for each container, from FALLBACK__TIER1..4 in THIS host's own conf — // that list is what the partner starts for us, so a lower tier means it comes back sooner. // Keyed lowercase because conf spelling and docker's spelling of a name need not match. function vv_container_tier_map(): array { $me = strtoupper(vv_detect_host()); if ($me === 'UNKNOWN') return []; $raw = vv_read_host_conf_raw(strtolower($me)); if ($raw === '') return []; $map = []; for ($t = 1; $t <= 4; $t++) { foreach (vv_parse_conf_list($raw, "FALLBACK_{$me}_TIER{$t}") as $name) { $key = strtolower(trim($name)); if ($key !== '' && !isset($map[$key])) $map[$key] = $t; // lowest tier wins a duplicate } } return $map; } function vv_get_docker_folders(): array { // Varaverk's own docker_folders.json is the primary store (see include/docker.php) — // reading folder.view3's mirror directly here left this widget empty on any host // without that optional third-party plugin installed. $folderData = vv_dk_read_json(); $tierMap = vv_container_tier_map(); // 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), 'tier' => $tierMap[strtolower($cname)] ?? null, ]; } 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), 'tier' => $tierMap[strtolower($cname)] ?? null, ]; } usort($ungrouped, fn($a, $b) => strcmp($a['name'], $b['name'])); return [ 'available' => true, 'folders' => $folders, 'ungrouped' => $ungrouped, ]; }