Replaces every native confirm() with a shared P.confirm() modal (a hung
native dialog was found live to block the whole tab, including
auto-refresh, and once even double-confirmed an unrelated deletion).
Also fixes Edit Container silently resetting to Bridge/blanking the
Static IP for any container on a custom network, and a context menu
losing its anchor to a mid-read auto-refresh.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Dashboard:
- Plain-count stat tiles (Running/Stopped/Pods/Images/Volumes/Networks)
separated from a single "Resource Usage" card (CPU/Memory/Swap/Storage
meter rows) instead of forcing both into one tile grid, which produced
awkward spanning-tile/dead-cell layouts.
- Fixed CPU usage never changing (libpod's own cpuUtilization is computed
once and never resampled) by computing it from /proc/stat deltas instead.
- Fixed memory usage reading far too high by using /proc/meminfo's
MemAvailable instead of libpod's raw (non-reclaimable-aware) memFree.
- Added an Autostart Queue table reusing podman-autostart.sh's own
failure-counter files.
- Dashboard and Containers now auto-refresh every ~2s (paused when the
tab is hidden or a modal is open).
Toasts:
- Real success/warn/error/info toast notifications replacing every
alert() used for one-way feedback, across every panel.
Container detail modal:
- 5 new tabs: Resources, Logs, Console, Events, Healthcheck.
Containers panel:
- Folders to group containers (name + icon), stored in the plugin's own
folders.json — a folder's header always shows an icon+name+status chip
per member, matching Unraid's own Docker page folders. "Move to
Folder" becomes "Remove from Folder" once a container is already
grouped.
- Containers can carry an icon URL and a WebUI URL (small button next to
the name), both stored as container labels and auto-filled from
templates where applicable.
- Settings: an "Add container" control for the Autostart order table.
Fixes:
- Context menus now measure their own rendered size and flip above the
anchor when there isn't room below, instead of running off-screen.
- Containers table now uses table-layout:fixed with explicit column
widths — auto layout was shifting every column (and the header) on
every folder expand/collapse, and briefly again when a flex wrapper
was mistakenly placed directly on a <td>.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
.podman-log-pane's own multi-line white-space handling lived only on its
.l child divs (used by the Logs tab's per-line entries) — Settings sets
.textContent directly on the pane itself for rc.podman's raw output, so
without white-space: pre-wrap on the pane too, every real newline in
`rc.podman status`'s multi-line output collapsed into one paragraph.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
rc_podman()'s "is it running" check only recognized rc.podman status's
own "service: running (pid ..., socket ...)" wording — but
start/stop/restart print differently-worded messages of their own
("start: already running (pid ...)", "stop: stopped", ...), which never
matched that same regex. Found live: a successful Start Podman click
(podman already running, correctly a no-op) still turned the chip red
because its own success message didn't say "service:". Now always runs
a fresh `status` check after the requested verb (unless the verb WAS
status) to determine "running", instead of trying to regex-parse each
verb's differently-worded own output.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
podman_asset_version() has never actually cache-busted anything, all
session — found live: it kept returning '0' for every asset regardless
of a hard browser reload OR a full php-fpm restart (both ruled out
explicitly before looking further), which meant the mechanism itself was
broken, not caching around it.
Root cause: Unraid's PageBuilder runs every .page file's PHP through
eval() (webGui/include/DefaultPageLayout/evalContent.php literally does
`eval($evalContent)`), and __DIR__/__FILE__ inside eval()'d code resolve
to the eval() CALL SITE's own directory, not to Podman.page's real
location — a standard PHP gotcha. So `__DIR__ . $relPath` was always
pointing at a nonexistent path under webGui/include/DefaultPageLayout/,
is_file() always failed, and the function always fell back to '0'.
Every "do a hard refresh" instruction given throughout this session
worked only because Ctrl+Shift+R bypasses the browser's cache directly —
completely unrelated to this (non-functional) query-string mechanism.
Likely also the real explanation for containers getting stopped
unexpectedly during live testing just now: a stale, mismatched cached
JS/HTML combination made a "Start Podman" click actually run as a full
restart cycle (confirmed via plugin.log: a complete, uninterrupted
stop-then-start at that exact timestamp) — restarted manually afterward.
Fixed by hardcoding /usr/local/emhttp/plugins/podman instead of __DIR__ —
consistent with the rest of the codebase already assuming this exact
install path elsewhere (include/Config.php, plugin/podman.plg's event
hook paths).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same pattern already used for container update checking (P.openLogModal)
— these can take a while (storage checks, container stop grace periods,
...) and are exactly the actions someone reaches for when something's
actually wrong, so a button that just sits there disabled with no
feedback until it's done isn't good enough. "Refresh Status" stays inline
(quick, read-only, no modal needed).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rounds out the Podman Service card to the full start/stop/restart trio.
Stop and Restart both get a confirm() first — both actually stop every
running container (with its own configured grace period) before touching
the API service itself, not just the service process; worth surfacing
that explicitly rather than letting it be a surprise.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Root cause of a fresh-install "cannot reach the Podman API socket" report
(a friend's Unraid box, cache pool present and mounted): unlike
Docker-for-Unraid's docker.img path, this plugin never auto-created
STORAGE_PATH itself — only podman.img inside it. A perfectly normal,
already-mounted cache pool still failed preflight/storage-create with
"does not exist", just because its own .../system/podman subdirectory
had never been created. Fixed by walking up to the nearest existing
ancestor and checking whether it's on a different device than / (real
mount vs. nothing mounted at all) — see podman-common.sh's new
podman_path_has_real_mount_ancestor(), used by both podman-preflight.sh
and podman-storage.sh.
Settings gets a "Podman Service" card (status chip + Start/Restart,
backed by new ajax/settings.php service_status/start/restart actions
that just shell out to rc.podman) so a fresh install that failed to start
can be diagnosed and retried without SSH/terminal access at all — exactly
what was missing when this was first needed live.
Also adds "Format a Disk for Podman Storage" (new ajax/disks.php) for a
single-disk system with no cache pool at all. Only ever lists disks with
literally no existing partition/filesystem/RAID-or-ZFS-membership
signature and that aren't Unraid's boot flash — found live, twice, during
development: the boot USB (FAT, labeled "UNRAID") passed the initial
mounted-only check because this host's /boot is backed by a ZFS dataset
rather than a direct partition mount, and active RAID-member cache disks
passed a data-vs-blank *warning* rather than a hard exclusion. Both are
now excluded outright, not just flagged — see disks.php's
device_or_children_labeled_unraid() and the hasData exclusion in
list_candidate_disks(). A disk formatted this way is remounted by UUID on
every boot via a new plugin/sbin/podman-mount-managed-disk.sh, called
from plugin/event/disks_mounted before rc.podman start.
Unrelated fix bundled in: scripts/lib/slackbuild-common.sh now sets
SOURCE_DATE_EPOCH (derived from the repo's last commit) before calling
makepkg, so two separate builds of the same commit produce byte-identical
.txz files — makepkg already supports this (`--clamp-mtime` when
$SOURCE_DATE_EPOCH is set, confirmed by reading a real host's
/sbin/makepkg) but nothing was setting the variable, so release.yml's
"rebuild in CI and verify it matches the committed checksums" step was
guaranteed to fail on the first package it checked alphabetically
(observed live: aardvark-dns).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Terminal panel now opens a genuinely interactive shell (ttyd bound to a
unix socket, proxied through Unraid's own /logterminal/ nginx location —
the same mechanism Unraid's own Docker "Console" button uses) instead of
one-shot exec calls, shown inline with a Disconnect action; bash is the
default shell. Container/shell selectors and action buttons are now
correctly bottom-aligned (root cause: Unraid's theme puts a 10px margin
on every <button>, never reset before).
Destructive actions (Disconnect, Compose/Template Delete, Volumes/Images/
Networks Remove) get a consistent, solid red treatment at rest instead of
only tinting on hover, via new --bad-strong/--bad-contrast tokens.
Settings panel restructured: a real save toolbar instead of a button
buried in an empty-label row, card subtitles, a toggle switch instead of
a bare checkbox, and installed-package versions shown as chips.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The YAML view was read-only with no way to create a new project at
all. Add save/remove AJAX actions (validated via a real `podman
compose ... config` dry-run, written to a .new sibling and only
renamed into place on success) and a New Project/Save/Delete UI
backed by an editable textarea instead of a <pre>. Also strip ANSI
escape codes from compose command output so podman's own provider
banner doesn't show as literal garbage in error alerts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Pods panel could previously only list pods - there was no way to create
one, start/stop/restart it, or attach a container to it from the UI.
Adds a "New Pod" modal (name + port mappings), a per-pod lifecycle menu
(start/stop/restart/remove), and an optional "Pod" field on the Create
Container modal to join an existing pod's network namespace. Backend
verified live against the real podman socket (/pods/create, /pods/{name}/
restart, container "pod" field).
Also fixes three real bugs found via live testing:
- Podman.page used Menu="Podman" instead of Menu="Tasks:<rank>", so the
plugin never actually appeared in Unraid's top navigation (traced through
PageBuilder.php/DefaultPageLayout.php/Navigation/Main.php - only pages
registered under "Tasks" become top-level tabs).
- app.js's shared context-menu component mis-mapped every item positioned
after a 'separator' entry to the wrong DOM element (an off-by-one against
menu.children, which includes the separator <div>s) - so "Remove", which
always sits after a separator, silently did nothing when clicked. Fixed
by indexing into querySelectorAll('button') instead.
- That same menu was positioned via "position: absolute" math that assumed
a viewport-relative containing block, but Unraid's own page wrapper
(webGui/styles/default-base.css's ".content") sets position:relative,
so the menu rendered far from its anchor button. Switched to
"position: fixed" with viewport-relative coordinates.
Incidentally, pods add a hidden "infra" container that was leaking into
the plain Containers list with no working lifecycle of its own (always
"running", so its own Remove was permanently disabled) - now filtered out
via libpod's IsInfra flag. And every action-buttons table cell used
"display: flex" directly on the <td>, which browsers can size
inconsistently row to row - moved onto an inner wrapper div instead, and
bumped .podman-btn-icon's touch target size.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets users create containers from the WebUI (image/name/ports/volumes/
env/network mode/restart policy/privileged) instead of only managing
existing ones, and adds a Templates panel to save/reuse those configs
as Unraid-Docker-compatible template XML, including browsing and
importing the host's own existing Docker Manager templates directly.
Also fixes bugs found via live testing along the way: container names
with spaces/invalid characters now get a clear client- and server-side
error with a suggested fix instead of podman's raw API error, the New
Container modal's backdrop no longer renders transparent (was being
appended outside the .podman-plugin CSS scope), and modal buttons now
have real visual hierarchy (ghost/primary/danger) after Unraid's own
site-wide button theme was found to override plain single-class rules.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Clicking a container's name now opens a tabbed detail modal, fed entirely
by the existing inspect action's raw libpod data — no new backend needed.
Field names (Config.Env, Config.Labels, Mounts[].Source/Destination/RW,
NetworkSettings.Networks{}.IPAddress/Gateway/MacAddress, HostConfig.
RestartPolicy) verified against a real inspect response before building
the tabs around them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
First increment of the big WebUI feature-parity spec (see task list) —
row-level actions were about to run out of icon-button space, so this adds
a small anchored dropdown menu (app.js openContextMenu) for secondary
per-container actions instead of cramming more buttons into every row.
All four new actions verified live against the real podman API before
wiring up the UI (same discipline as the CSRF/pull/compose bugs found
earlier — field names and response shapes checked against the running
socket, not assumed from docs).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Package #9-11: catatonit (pod infra init), nftables (netavark firewall
backend), docker-compose (external compose provider for `podman compose`)
— all vendored prebuilt binaries, versions.env pinned, propagated through
build-packages.sh/release.sh/podman.plg/verify+update-packages.sh.
- Fix WebUI: every POST action was silently failing (empty response body)
because Unraid's own CSRF protection was never satisfied — app.js now
sends the page's csrf_token as X-CSRF-Token.
- Fix WebUI: PodmanClient::pullImage() assumed a single JSON response, but
/images/pull actually streams newline-delimited JSON — every successful
pull was throwing "Expected a JSON object/array response".
- Fix WebUI: compose.php's up/down status detection had the same
single-JSON-vs-NDJSON bug for `podman compose ps`, plus stderr was
corrupting the parse.
- Add cache-busting (?v=<mtime>) to Podman.page's script/style tags so a
redeployed JS/CSS fix isn't served stale from browser cache.
- Add a reusable modal dialog (app.js openFormModal) replacing
prompt()/alert() for New Volume/Network/Pull Image.
- Add host-path (bind-mount) support when creating a named volume.
- Add Create Container (image, name, network mode incl. custom networks,
ports, volumes, env, restart policy, privileged, start-after-create),
auto-pulling the image on first use since /containers/create doesn't.
All fixes verified live against a real podman system service and, where
reachable, via the actual WebUI over the real socket — not just unit-level.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Found by exercising the AJAX endpoints directly against a real running
podman service (php -d display_errors=1 -r '...containers.php...') —
containers.php's list action crashed with an uncaught TypeError the
moment a real container existed. podman's libpod API is inconsistent
about container/volume timestamp encoding: inspect-style endpoints
return RFC3339 strings, but list-style endpoints (containers/json,
volumes/json) return raw Unix-epoch integers for the same logical
field. podman_parse_time() only accepted ?string, so any list call
with a real container blew up outright rather than merely
mis-rendering. Widened it to string|int|null and handle both.
Verified: containers.php's list action now returns correct JSON for
real running/exited containers, including their createdAt timestamps.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 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>