Add GPU/macvlan passthrough, container edit/update, image prune/tag
Create Container form: - GPU passthrough dropdown (AMD/Intel via /dev/dri detection, NVIDIA excluded since it needs a different runtime) - device paths strictly validated server-side against the host's own detected list. - Macvlan network support: selecting a macvlan network reveals a static IP field and hides port mappings (meaningless once the container has its own LAN address), matching Unraid Docker Manager's "Custom: br0" behavior. Networks panel gained a matching macvlan network-creation flow, with the parent-interface dropdown read from Unraid's own network.cfg so it lists exactly what Docker Manager itself offers. Containers panel: - Edit: reopens the create form pre-filled from the container's current config (image/ports/volumes/env/network/restart policy/GPU/static IP); saving stops+removes the old container and recreates it under the same settings, since podman/Docker have no in-place "modify" API for most of this. - Update: same stop/remove/recreate flow, but pulls the current image first. "Check for Updates" compares each in-use image's local digest against its origin registry (Docker Hub/GHCR/self-hosted registries all verified live) with no podman-side feature backing it - implemented via the registry's own HTTP API. A small log-modal shows progress for both actions instead of a silent wait. - Fixed a real bug hit live: PodmanClient's flat 15s HTTP timeout aborted real image pulls/container creates mid-request; bumped to 600s (nginx already allows up to 640s for this plugin's requests). Images panel: - "Prune unused" (removes every image with zero containers referencing it, not just dangling ones - confirmation copy says so explicitly since this is more aggressive than it sounds) and per-image "Tag". Also several real UI bugs found via live screenshots: unused-image prune having no visible effect until reloaded, table action-button columns drifting row to row (a bare "display:flex" on a <td> was fighting the table layout algorithm), Templates category badges dumping raw multi-tag strings from real Unraid templates, and low-contrast search/filter controls that were nearly invisible against the card background. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,9 +7,11 @@
|
||||
* from Docker's own docker0/custom-network space.
|
||||
*
|
||||
* Actions (?action=...):
|
||||
* list GET -> normalized network list with subnet/gateway/usage
|
||||
* create POST {"name": "...", "driver": "bridge", "subnet": "...", "gateway": "..."}
|
||||
* remove POST {"name": "...", "force": false}
|
||||
* list GET -> normalized network list with subnet/gateway/usage
|
||||
* list_parent_interfaces GET -> host bridge/VLAN interfaces available as a macvlan parent
|
||||
* create POST {"name": "...", "driver": "bridge"|"macvlan", "subnet": "...",
|
||||
* "gateway": "...", "parentInterface": "br0"}
|
||||
* remove POST {"name": "...", "force": false}
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
@@ -23,17 +25,40 @@ switch ($action) {
|
||||
podman_json_response(networks_list($client));
|
||||
break;
|
||||
|
||||
case 'list_parent_interfaces':
|
||||
podman_json_response(macvlan_parent_interfaces());
|
||||
break;
|
||||
|
||||
case 'create':
|
||||
$body = podman_read_json_body();
|
||||
$name = (string) ($body['name'] ?? '');
|
||||
if ($name === '') {
|
||||
podman_json_error('Missing name in request body', 400);
|
||||
}
|
||||
$driver = (string) ($body['driver'] ?? 'bridge');
|
||||
|
||||
$parentInterface = null;
|
||||
if ($driver === 'macvlan') {
|
||||
$parentInterface = (string) ($body['parentInterface'] ?? '');
|
||||
// Only ever accept an interface this same host reported via
|
||||
// macvlan_parent_interfaces() — the boundary preventing a
|
||||
// tampered request from asking podman to attach to an
|
||||
// arbitrary/unexpected interface name.
|
||||
$known = array_column(macvlan_parent_interfaces(), 'interface');
|
||||
if (!in_array($parentInterface, $known, true)) {
|
||||
podman_json_error('Unknown parent interface — refresh the page and try again.', 400);
|
||||
}
|
||||
if (!isset($body['subnet']) || (string) $body['subnet'] === '') {
|
||||
podman_json_error('Subnet is required for a macvlan network.', 400);
|
||||
}
|
||||
}
|
||||
|
||||
podman_json_response($client->createNetwork(
|
||||
$name,
|
||||
(string) ($body['driver'] ?? 'bridge'),
|
||||
$driver,
|
||||
isset($body['subnet']) ? (string) $body['subnet'] : null,
|
||||
isset($body['gateway']) ? (string) $body['gateway'] : null
|
||||
isset($body['gateway']) ? (string) $body['gateway'] : null,
|
||||
$parentInterface
|
||||
));
|
||||
break;
|
||||
|
||||
@@ -54,6 +79,61 @@ switch ($action) {
|
||||
podman_json_error("Unknown action '{$action}'", 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads Unraid's own /boot/config/network.cfg (BRNAME[i]/VLANID[i,j]/
|
||||
* DESCRIPTION[i,j]) to list the same host bridge + VLAN interfaces
|
||||
* Unraid's own Docker Manager offers as "Custom: br0" / "Custom: br0.3
|
||||
* (VPN)" network types — reusing Unraid's own config instead of guessing
|
||||
* from raw `ip link` output, so the list always matches what Docker
|
||||
* Manager shows for the same host. Verified live: this host's
|
||||
* network.cfg has BRNAME[0]="br0" and VLANID[0,1]="3"/DESCRIPTION[0,1]=
|
||||
* "VPN", producing "br0" and "br0.3 (VPN)" — matching the interface
|
||||
* names shown in that other plugin's own network-type dropdown exactly.
|
||||
* Each candidate is confirmed to actually exist in /sys/class/net before
|
||||
* being offered, in case network.cfg mentions an interface that isn't
|
||||
* currently up.
|
||||
*
|
||||
* @return array<int,array{interface:string,label:string}>
|
||||
*/
|
||||
function macvlan_parent_interfaces(): array
|
||||
{
|
||||
$cfgFile = '/boot/config/network.cfg';
|
||||
if (!is_file($cfgFile)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$cfg = [];
|
||||
foreach (file($cfgFile, FILE_IGNORE_NEW_LINES) ?: [] as $line) {
|
||||
if (preg_match('/^([A-Z0-9_]+)\[(\d+)(?:,(\d+))?\]="([^"]*)"$/', $line, $m) !== 1) {
|
||||
continue;
|
||||
}
|
||||
[, $key, $i, $j, $value] = $m + [3 => ''];
|
||||
$i = (int) $i;
|
||||
if ($j === '') {
|
||||
$cfg[$key][$i] = $value;
|
||||
} else {
|
||||
$cfg[$key][$i][(int) $j] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach (($cfg['BRNAME'] ?? []) as $i => $brname) {
|
||||
if (!is_string($brname) || $brname === '' || !is_dir("/sys/class/net/{$brname}")) {
|
||||
continue;
|
||||
}
|
||||
$out[] = ['interface' => $brname, 'label' => $brname];
|
||||
foreach (($cfg['VLANID'][$i] ?? []) as $j => $vlanId) {
|
||||
$iface = "{$brname}.{$vlanId}";
|
||||
if (!is_dir("/sys/class/net/{$iface}")) {
|
||||
continue;
|
||||
}
|
||||
$desc = $cfg['DESCRIPTION'][$i][$j] ?? '';
|
||||
$out[] = ['interface' => $iface, 'label' => $iface . ($desc !== '' ? " ({$desc})" : '')];
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** @return array<int,array<string,mixed>> */
|
||||
function networks_list(PodmanClient $client): array
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user