Add Pod lifecycle management, fix nav registration and context-menu bugs

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>
This commit is contained in:
2026-07-12 16:26:49 +00:00
co-authored by Claude Sonnet 5
parent 45e27f8575
commit 8ac9cde621
12 changed files with 325 additions and 24 deletions
+25 -4
View File
@@ -100,7 +100,7 @@
}
.podman-btn-danger { color: var(--bad); }
.podman-btn-danger:hover { border-color: var(--bad); }
.podman-btn-icon { padding: 6px 8px; }
.podman-btn-icon { padding: 6px 8px; min-width: 32px; min-height: 32px; justify-content: center; font-size: 15px; line-height: 1; }
.podman-btn[disabled] { opacity: .4; cursor: not-allowed; }
/*
* Secondary action (Cancel, "+ Add row") — every button previously shared
@@ -195,7 +195,17 @@
display: grid; place-items: center; font-size: 12px; border: 1px solid var(--border); color: var(--text-dim);
}
.podman-row-sub { font-size: 11.5px; color: var(--text-faint); font-weight: 500; margin-top: 1px; }
.podman-actions { display: flex; gap: 4px; justify-content: flex-end; }
/*
* The actions <td> itself stays a plain table-cell (default display) so
* every row's column width is computed the same way by the table's layout
* algorithm — putting "display: flex" directly on the <td> used to take it
* out of that algorithm, so browsers could size/position it slightly
* differently row to row (found live: the trash-can button in Images drifted
* a few pixels between rows instead of lining up in one column). The actual
* flex/gap/alignment lives on this inner wrapper instead.
*/
.podman-actions { text-align: right; white-space: nowrap; }
.podman-actions-row { display: inline-flex; gap: 4px; justify-content: flex-end; }
.podman-usage-mini { display: flex; align-items: center; gap: 8px; min-width: 110px; }
.podman-usage-mini .track { flex: 1; height: 5px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
@@ -220,7 +230,7 @@
.podman-pod-card { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; margin-bottom: 14px; background: var(--surface); box-shadow: var(--shadow); }
.podman-pod-head { display: flex; align-items: center; gap: 10px; padding: 13px 16px; background: var(--surface-2); border-bottom: 1px solid var(--border); }
.podman-pod-head .name { font-weight: 700; font-size: 13.5px; }
.podman-pod-head .infra { font-size: 11.5px; color: var(--text-faint); }
.podman-pod-head .infra { font-size: 11.5px; color: var(--text-faint); margin-right: auto; }
.podman-badge { display: inline-block; font-size: 10.5px; font-weight: 700; color: var(--text-dim); background: var(--surface-3); padding: 2px 8px; border-radius: 100px; margin-top: 6px; }
@@ -368,7 +378,18 @@
/* Anchored dropdown context menu — see app.js openContextMenu(). */
.podman-context-menu {
position: absolute; width: 180px; background: var(--surface); border: 1px solid var(--border);
/*
* "fixed", not "absolute": this menu is appended to .podman-plugin, not
* document.body, and Unraid's own page wrapper around .podman-plugin
* turned out to have its own positioned ancestor — with "absolute" the
* menu was positioning itself relative to THAT ancestor's box while the
* JS math (getBoundingClientRect + scrollY/X) assumed the viewport,
* so it rendered far from the button that opened it (found live: it
* appeared well below and to the side of the anchor). "fixed" is always
* viewport-relative regardless of any ancestor, which is what the JS
* math actually assumes.
*/
position: fixed; width: 180px; background: var(--surface); border: 1px solid var(--border);
border-radius: 9px; box-shadow: var(--shadow); z-index: 1001; padding: 4px; display: grid; gap: 1px;
}
.podman-context-menu button {