Files
Varaverk/Plugin/unraid/include/docker_folders.php
T
Gmer4Lfe fb50f94ed8 Escape what the monitor page renders, and put the helpers where every page can reach them
The page interpolated media titles, partner hostnames read over the mesh, and docker folder
names straight into innerHTML — its own header claimed otherwise, and the helpers that would
have fixed it were defined in a page it never loads. Container WebUI values now get a scheme
check before they reach window.open().
2026-08-07 10:17:45 -04:00

127 lines
5.3 KiB
PHP

<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
// Small compatibility layer over docker.php — resolves a container's WebUI URL and returns
// the folder grouping in the shape older callers expect.
//
// DESIGN PRINCIPLES
// Thin by intent. The folder store and inventory live in docker.php; this file only adapts
// their output. New work belongs there, not here.
//
// WebUI resolution reads the dockerMan template, which is where Unraid records the port and
// path a container's UI actually lives on — not guessed from the published ports.
//
// OPERATIONAL SAFEGUARDS
// A container with no template, or no WebUI declared, returns empty and simply renders
// without a link. Absence is normal, not an error.
//
// Read-only. Resolves and reshapes; the store is written only through docker.php.
//
// EXPORTS
// vv_container_webui() WebUI URL for one container, or empty
// vv_get_docker_folders() folder grouping in the legacy shape
//
// 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>(.*?)<\/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;
}
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();
// 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,
];
}