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>
280 lines
13 KiB
Plaintext
280 lines
13 KiB
Plaintext
Menu="Tasks:66"
|
|
Type="xmenu"
|
|
Tabs="false"
|
|
Title="Podman"
|
|
Icon="podman"
|
|
---
|
|
<?php
|
|
/**
|
|
* Podman.page — Unraid Dynamix-style GUI page for unraid-podman.
|
|
*
|
|
* This file only renders the page shell: the header, the sub-navigation
|
|
* for the ten sections, and one (mostly empty) container per panel. Every
|
|
* panel fills itself in via AJAX after load — see javascript/app.js's
|
|
* router and each javascript/<panel>.js module — so this file has no
|
|
* business logic of its own and does not talk to PodmanClient directly.
|
|
* See docs/ARCHITECTURE.md section 18 for the overall WebUI design and
|
|
* webui/mockups/prototype.html for the approved visual reference this
|
|
* page's markup mirrors (same structure, same CSS classes, real data
|
|
* instead of static samples).
|
|
*/
|
|
|
|
/**
|
|
* Cache-busts every static asset with its own on-disk mtime. Unraid's
|
|
* webserver sends no explicit no-cache headers for /plugins/ static
|
|
* files, so without this, browsers can keep serving a stale app.js/
|
|
* podman.css for a long time after a plugin update — verified live: a
|
|
* bugfix to app.js's CSRF handling silently kept failing in a real
|
|
* browser after redeploy until this was added, even though the deployed
|
|
* file on disk was byte-for-byte correct.
|
|
*/
|
|
function podman_asset_version(string $relPath): string
|
|
{
|
|
$full = __DIR__ . $relPath;
|
|
return is_file($full) ? (string) filemtime($full) : '0';
|
|
}
|
|
?>
|
|
<link rel="stylesheet" type="text/css" href="/plugins/podman/styles/podman.css?v=<?=podman_asset_version('/styles/podman.css')?>">
|
|
|
|
<div class="podman-plugin">
|
|
|
|
<header class="podman-pagehead">
|
|
<div class="titlewrap">
|
|
<div class="icon" aria-hidden="true">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M12 2l8 4.5v9L12 20l-8-4.5v-9L12 2z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M12 11l8-4.5M12 11v9M12 11L4 6.5" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>
|
|
</div>
|
|
<div>
|
|
<h1>Podman</h1>
|
|
<div class="meta" id="podman-header-meta">Connecting…</div>
|
|
</div>
|
|
</div>
|
|
<div style="display:flex; gap:8px;">
|
|
<button class="podman-btn" id="podman-refresh-all">↻ Refresh</button>
|
|
</div>
|
|
</header>
|
|
|
|
<nav class="podman-subnav">
|
|
<button class="active" data-panel="dashboard">Dashboard</button>
|
|
<button data-panel="containers">Containers</button>
|
|
<button data-panel="templates">Templates</button>
|
|
<button data-panel="pods">Pods</button>
|
|
<button data-panel="images">Images</button>
|
|
<button data-panel="volumes">Volumes</button>
|
|
<button data-panel="networks">Networks</button>
|
|
<button data-panel="logs">Logs</button>
|
|
<button data-panel="terminal">Terminal</button>
|
|
<button data-panel="compose">Compose</button>
|
|
<button data-panel="settings">Settings</button>
|
|
</nav>
|
|
|
|
<main class="podman-main">
|
|
|
|
<!-- ============================= DASHBOARD ============================= -->
|
|
<section class="podman-panel active" id="podman-panel-dashboard">
|
|
<div class="podman-grid podman-stat-grid">
|
|
<div class="podman-stat"><div class="label">Running</div><div class="value tnum"><span id="stat-running">—</span> <small id="stat-running-total"></small></div></div>
|
|
<div class="podman-stat"><div class="label">Pods</div><div class="value tnum" id="stat-pods">—</div></div>
|
|
<div class="podman-stat"><div class="label">Images</div><div class="value tnum" id="stat-images">—</div></div>
|
|
<div class="podman-stat"><div class="label">Volumes</div><div class="value tnum" id="stat-volumes">—</div></div>
|
|
<div class="podman-stat"><div class="label">Networks</div><div class="value tnum" id="stat-networks">—</div></div>
|
|
<div class="podman-stat"><div class="label">Images on Disk</div><div class="value tnum" id="stat-images-size">—</div></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= CONTAINERS ============================= -->
|
|
<section class="podman-panel" id="podman-panel-containers">
|
|
<div class="podman-card">
|
|
<div class="podman-toolbar">
|
|
<input class="podman-search" id="containers-search" type="text" placeholder="Search containers by name or image…">
|
|
<div class="podman-segmented" id="containers-filterset">
|
|
<button class="active" data-filter="all" id="containers-count-all">All</button>
|
|
<button data-filter="running" id="containers-count-running">Running</button>
|
|
<button data-filter="stopped" id="containers-count-stopped">Stopped</button>
|
|
</div>
|
|
<button class="podman-btn podman-btn-primary" id="containers-check-updates-btn" style="margin-left:auto;">Check for Updates</button>
|
|
<button class="podman-btn podman-btn-primary" id="containers-update-all-btn">Update All</button>
|
|
<button class="podman-btn podman-btn-primary" id="containers-create-btn">+ New Container</button>
|
|
</div>
|
|
<div class="podman-table-wrap">
|
|
<table>
|
|
<thead><tr><th>Status</th><th>Name</th><th>Image</th><th>CPU/Mem</th><th>Ports</th><th>Uptime</th><th></th></tr></thead>
|
|
<tbody id="containers-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= TEMPLATES ============================= -->
|
|
<section class="podman-panel" id="podman-panel-templates"></section>
|
|
|
|
<!-- ============================= PODS ============================= -->
|
|
<section class="podman-panel" id="podman-panel-pods"></section>
|
|
|
|
<!-- ============================= IMAGES ============================= -->
|
|
<section class="podman-panel" id="podman-panel-images">
|
|
<div class="podman-card">
|
|
<div class="podman-toolbar">
|
|
<input class="podman-search" type="text" placeholder="Search images…" disabled title="Client-side filtering not yet wired up for Images">
|
|
<button class="podman-btn" id="images-prune-btn" style="margin-left:auto;">Prune unused</button>
|
|
<button class="podman-btn" id="images-pull-btn">⬇ Pull Image</button>
|
|
</div>
|
|
<div class="podman-table-wrap">
|
|
<table>
|
|
<thead><tr><th>Repository</th><th>Tag</th><th>Image ID</th><th>Size</th><th>Created</th><th>Used By</th><th></th></tr></thead>
|
|
<tbody id="images-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= VOLUMES ============================= -->
|
|
<section class="podman-panel" id="podman-panel-volumes">
|
|
<div class="podman-card">
|
|
<div class="podman-toolbar">
|
|
<div style="flex:1;"></div>
|
|
<button class="podman-btn podman-btn-primary" id="volumes-create-btn">+ New Volume</button>
|
|
</div>
|
|
<div class="podman-table-wrap">
|
|
<table>
|
|
<thead><tr><th>Name</th><th>Driver</th><th>Mountpoint</th><th>Used By</th><th></th></tr></thead>
|
|
<tbody id="volumes-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
<div class="podman-empty-note" style="border-top:1px solid var(--border); text-align:left; padding:12px 18px;">
|
|
App data mapped from Unraid shares (e.g. <span class="mono">/mnt/user/appdata/…</span>) is managed as bind mounts per-container and does not appear here.
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= NETWORKS ============================= -->
|
|
<section class="podman-panel" id="podman-panel-networks">
|
|
<div class="podman-card">
|
|
<div class="podman-toolbar">
|
|
<div style="flex:1;"></div>
|
|
<button class="podman-btn podman-btn-primary" id="networks-create-btn">+ New Network</button>
|
|
</div>
|
|
<div class="podman-table-wrap">
|
|
<table>
|
|
<thead><tr><th>Name</th><th>Driver</th><th>Subnet</th><th>Gateway</th><th>Containers</th><th></th></tr></thead>
|
|
<tbody id="networks-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= LOGS ============================= -->
|
|
<section class="podman-panel" id="podman-panel-logs">
|
|
<div class="podman-card">
|
|
<div class="podman-logs-layout">
|
|
<div class="podman-logs-side" id="logs-sidebar"></div>
|
|
<div>
|
|
<div class="podman-toolbar">
|
|
<input class="podman-search" id="logs-filter" type="text" placeholder="Filter log output…" style="max-width:280px;">
|
|
<span class="podman-segmented" id="logs-follow-toggle">
|
|
<button class="active" data-follow="true">Follow</button>
|
|
<button data-follow="false">Paused</button>
|
|
</span>
|
|
</div>
|
|
<div class="podman-log-pane" id="log-pane" role="log"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= TERMINAL ============================= -->
|
|
<section class="podman-panel" id="podman-panel-terminal">
|
|
<div class="podman-card">
|
|
<div class="podman-card-pad">
|
|
<div style="display:flex; gap:8px; align-items:center; margin-bottom:12px; font-size:12.5px; color:var(--text-dim);">
|
|
Exec into: <select id="term-container-select"></select>
|
|
</div>
|
|
<div class="podman-term" id="term-output"></div>
|
|
<input class="podman-term-input" id="term-input" type="text" placeholder="Type a command and press Enter… (one-shot exec — see Compose panel note on API scope)" autocomplete="off">
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= COMPOSE ============================= -->
|
|
<section class="podman-panel" id="podman-panel-compose">
|
|
<div class="podman-card">
|
|
<div class="podman-compose-layout">
|
|
<div class="podman-compose-side" id="compose-sidebar"></div>
|
|
<div>
|
|
<div class="podman-toolbar">
|
|
<strong id="compose-title" style="flex:1;">—</strong>
|
|
<button class="podman-btn" id="compose-action-pull">⬇ Pull</button>
|
|
<button class="podman-btn" id="compose-action-down">■ Down</button>
|
|
<button class="podman-btn podman-btn-primary" id="compose-action-up">▶ Up</button>
|
|
</div>
|
|
<pre class="podman-yaml" id="compose-yaml"></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= SETTINGS ============================= -->
|
|
<section class="podman-panel" id="podman-panel-settings">
|
|
<div class="podman-grid">
|
|
<div class="podman-card">
|
|
<div class="podman-card-head"><h2>Storage</h2></div>
|
|
<div class="podman-field-row">
|
|
<label for="settings-storage-path">Storage path</label>
|
|
<div>
|
|
<input type="text" id="settings-storage-path" class="mono">
|
|
<div class="hint">Cache pool or dedicated disk — never a path under /mnt/user (FUSE).</div>
|
|
</div>
|
|
</div>
|
|
<div class="podman-field-row">
|
|
<label for="settings-storage-size">podman.img size</label>
|
|
<div><input type="number" id="settings-storage-size" style="max-width:100px;"> <span style="font-size:12px;color:var(--text-dim);">GB</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="podman-card">
|
|
<div class="podman-card-head"><h2>Autostart & Lifecycle</h2></div>
|
|
<div class="podman-field-row">
|
|
<label for="settings-enabled">Start podman on array start</label>
|
|
<div><input type="checkbox" id="settings-enabled"></div>
|
|
</div>
|
|
<div class="podman-field-row">
|
|
<label for="settings-stop-timeout">Container stop timeout</label>
|
|
<div><input type="number" id="settings-stop-timeout" style="max-width:100px;"> <span style="font-size:12px;color:var(--text-dim);">seconds</span></div>
|
|
</div>
|
|
<div class="podman-field-row">
|
|
<label>Autostart order</label>
|
|
<div class="podman-table-wrap">
|
|
<table>
|
|
<thead><tr><th>#</th><th>Container</th><th></th></tr></thead>
|
|
<tbody id="autostart-tbody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="podman-field-row">
|
|
<label></label>
|
|
<div><button class="podman-btn podman-btn-primary" id="settings-save-btn">Save Settings</button></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="podman-card">
|
|
<div class="podman-card-head"><h2>Installed Packages</h2></div>
|
|
<div class="podman-field-row">
|
|
<label>Versions</label>
|
|
<div class="hint mono" id="settings-package-versions" style="max-width:none;">—</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</main>
|
|
</div>
|
|
|
|
<?php
|
|
foreach ([
|
|
'app', 'dashboard', 'containers', 'templates', 'pods', 'images', 'volumes',
|
|
'networks', 'logs', 'terminal', 'compose', 'settings',
|
|
] as $podmanJsModule) {
|
|
$podmanJsPath = "/javascript/{$podmanJsModule}.js";
|
|
echo '<script src="/plugins/podman' . $podmanJsPath . '?v=' . podman_asset_version($podmanJsPath) . '"></script>' . "\n";
|
|
}
|
|
?>
|