Add reproducible build system, native Unraid plugin, and WebUI
- versions.env pins podman, conmon, crun, netavark, aardvark-dns, passt, and fuse-overlayfs to verified upstream source checksums; SlackBuild recipes, scripts/build-packages.sh, checksums.sh, release.sh, and update-versions.sh implement the reproducible pipeline; GitHub Actions workflows build in a Slackware container and publish releases without committing any binaries. - plugin/podman.plg installs/updates/removes all eight packages (the seven components plus the plugin's own unraid-podman scaffolding package) via upgradepkg, using the official Unraid array-event hook mechanism (event/disks_mounted, event/stopping) instead of editing /boot/config/go. rc.podman and the sbin/ helper scripts implement storage creation, config seeding/sync, preflight checks, autostart with per-container Safe-Mode, and package verify/update/rollback. - webui/plugins/podman implements the Dashboard, Containers, Pods, Images, Volumes, Networks, Logs, Terminal, Compose, and Settings panels against the approved mockup (webui/mockups/prototype.html), talking to podman system service exclusively via PodmanClient.php (libpod REST API over the Unix socket), with two documented exceptions: Terminal's one-shot exec model and Compose's use of the podman compose CLI, since libpod has no REST equivalent for either. - docs/ARCHITECTURE.md and docs/ROADMAP.md record the design decisions and honest current status (syntax-checked, unit- and integration-tested against fake sockets/servers; not yet run against a real Unraid/Podman/Slackware system). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
Menu="Podman"
|
||||
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).
|
||||
*/
|
||||
?>
|
||||
<link rel="stylesheet" type="text/css" href="/plugins/podman/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="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="filterset" id="containers-filterset" style="display:flex; gap:4px; background:var(--surface-2); padding:3px; border-radius:8px;">
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<!-- ============================= 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-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 id="logs-follow-toggle" style="display:flex; gap:4px; background:var(--surface-2); padding:3px; border-radius:8px;">
|
||||
<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>
|
||||
|
||||
<script src="/plugins/podman/javascript/app.js"></script>
|
||||
<script src="/plugins/podman/javascript/dashboard.js"></script>
|
||||
<script src="/plugins/podman/javascript/containers.js"></script>
|
||||
<script src="/plugins/podman/javascript/pods.js"></script>
|
||||
<script src="/plugins/podman/javascript/images.js"></script>
|
||||
<script src="/plugins/podman/javascript/volumes.js"></script>
|
||||
<script src="/plugins/podman/javascript/networks.js"></script>
|
||||
<script src="/plugins/podman/javascript/logs.js"></script>
|
||||
<script src="/plugins/podman/javascript/terminal.js"></script>
|
||||
<script src="/plugins/podman/javascript/compose.js"></script>
|
||||
<script src="/plugins/podman/javascript/settings.js"></script>
|
||||
Reference in New Issue
Block a user