Files
unraid-podman/webui/plugins/podman/styles/podman.css
T
maggesandClaude Sonnet 5 7e2ed451e3
Lint / ShellCheck (push) Successful in 14s
Lint / Validate .plg XML (push) Successful in 11s
Lint / EditorConfig (push) Successful in 6s
Add Apps/Store tab, template WebUI/URL import, container RO volumes/device passthrough/run-as-user, and in-app confirm dialogs
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>
2026-07-19 19:24:32 +00:00

736 lines
44 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* styles/podman.css
*
* Design tokens and component styles for the Podman plugin page. Ported
* directly from the approved mockup (webui/mockups/prototype.html) so the
* real implementation is visually identical to what was reviewed — the
* masthead/main-nav chrome from that mockup is NOT included here, since
* Podman.page renders inside Unraid's own real chrome, not a recreation
* of it.
*
* Token strategy: custom properties on :root, redefined under
* prefers-color-scheme (OS default) and under [data-theme] (explicit
* override, e.g. from Unraid's own theme setting) — component rules below
* only ever reference the tokens, never hardcode a color, so both themes
* stay in sync automatically. See docs/ARCHITECTURE.md section 18.
*/
.podman-plugin {
--bg: #eef0f2; --surface: #ffffff; --surface-2: #f4f5f7; --surface-3: #e9ebee;
--border: #dde1e6; --text: #1c2024; --text-dim: #5b6572; --text-faint: #8a94a1;
--accent: #d8541a; --accent-strong: #b8420f; --accent-contrast: #fff8f3;
--good: #1a8f4c; --good-bg: #e4f6ea; --warn: #9a6b00; --warn-bg: #fdf1d6;
--bad: #c22b3a; --bad-bg: #fbe6e8; --bad-strong: #9c1f2c; --bad-contrast: #fff5f6; --neutral: #5b6572; --neutral-bg: #e9ebee;
--shadow: 0 1px 2px rgba(20, 22, 26, .06), 0 4px 12px rgba(20, 22, 26, .05);
--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", Consolas, "Liberation Mono", monospace;
font-family: var(--font-ui);
color: var(--text);
font-size: 14px;
line-height: 1.5;
}
@media (prefers-color-scheme: dark) {
.podman-plugin {
--bg: #15171b; --surface: #1c1f24; --surface-2: #23262c; --surface-3: #2b2f36;
--border: #34383f; --text: #e7e9ec; --text-dim: #9aa1ab; --text-faint: #6b7280;
--accent: #ef7f3f; --accent-strong: #f6975f; --accent-contrast: #1a1002;
--good: #4cc785; --good-bg: #123322; --warn: #e0b23d; --warn-bg: #3a2e0d;
--bad: #ef6470; --bad-bg: #3a1519; --bad-strong: #f6838c; --bad-contrast: #2a0a0d; --neutral: #9aa1ab; --neutral-bg: #2b2f36;
--shadow: 0 1px 2px rgba(0,0,0,.3), 0 8px 24px rgba(0,0,0,.35);
}
}
:root[data-theme="dark"] .podman-plugin {
--bg: #15171b; --surface: #1c1f24; --surface-2: #23262c; --surface-3: #2b2f36;
--border: #34383f; --text: #e7e9ec; --text-dim: #9aa1ab; --text-faint: #6b7280;
--accent: #ef7f3f; --accent-strong: #f6975f; --accent-contrast: #1a1002;
--good: #4cc785; --good-bg: #123322; --warn: #e0b23d; --warn-bg: #3a2e0d;
--bad: #ef6470; --bad-bg: #3a1519; --bad-strong: #f6838c; --bad-contrast: #2a0a0d; --neutral: #9aa1ab; --neutral-bg: #2b2f36;
--shadow: 0 1px 2px rgba(0,0,0,.3), 0 8px 24px rgba(0,0,0,.35);
}
:root[data-theme="light"] .podman-plugin {
--bg: #eef0f2; --surface: #ffffff; --surface-2: #f4f5f7; --surface-3: #e9ebee;
--border: #dde1e6; --text: #1c2024; --text-dim: #5b6572; --text-faint: #8a94a1;
--accent: #d8541a; --accent-strong: #b8420f; --accent-contrast: #fff8f3;
--good: #1a8f4c; --good-bg: #e4f6ea; --warn: #9a6b00; --warn-bg: #fdf1d6;
--bad: #c22b3a; --bad-bg: #fbe6e8; --bad-strong: #9c1f2c; --bad-contrast: #fff5f6; --neutral: #5b6572; --neutral-bg: #e9ebee;
--shadow: 0 1px 2px rgba(20,22,26,.06), 0 4px 12px rgba(20,22,26,.05);
}
.podman-plugin * { box-sizing: border-box; }
.podman-plugin h1, .podman-plugin h2, .podman-plugin h3 { text-wrap: balance; margin: 0; font-weight: 600; }
.podman-plugin .tnum { font-variant-numeric: tabular-nums; }
.podman-plugin .mono { font-family: var(--font-mono); }
.podman-plugin :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
.podman-pagehead {
display: flex; align-items: flex-end; justify-content: space-between;
gap: 16px; flex-wrap: wrap; padding: 4px 0 0;
}
.podman-pagehead .titlewrap { display: flex; align-items: center; gap: 12px; }
.podman-pagehead .icon {
width: 38px; height: 38px; border-radius: 9px; background: var(--surface-3);
display: grid; place-items: center; color: var(--accent); border: 1px solid var(--border);
}
.podman-pagehead h1 { font-size: 21px; }
.podman-pagehead .meta { font-size: 12.5px; color: var(--text-dim); margin-top: 2px; }
.podman-pagehead .meta .dot-good { color: var(--good); }
.podman-pagehead .meta .dot-bad { color: var(--bad); }
/*
* margin: 0 — Unraid's own webGui theme applies a 10px top/bottom margin
* to plain <button> elements site-wide. Without resetting it, every
* .podman-btn carries an invisible 10px gap above and below its own box,
* which silently breaks flex cross-axis alignment anywhere a button sits
* next to a non-button sibling (e.g. align-items: flex-end next to a
* <select> — verified live: the button's margin, not its content, was
* what left it floating 10px above the dropdown it should line up with).
*/
.podman-btn {
appearance: none; border: 1px solid var(--border); background: var(--surface); color: var(--text);
padding: 8px 14px; border-radius: 7px; font-size: 13px; font-weight: 600; cursor: pointer;
display: inline-flex; align-items: center; gap: 6px; transition: border-color .12s, background .12s;
font-family: var(--font-ui); margin: 0;
}
.podman-btn:hover { border-color: var(--text-faint); }
/*
* !important here for the same reason as .podman-btn-ghost below: Unraid's
* own webGUI theme applies a default border/hover treatment to every
* <button> that otherwise silently wins over this rule at rest — verified
* live via a screen recording: without !important, "Create" only ever
* looked filled while under the mouse (Unraid's generic hover glow, applied
* to literally any button), never at rest, making it indistinguishable
* from Cancel except by coincidence of cursor position.
*/
.podman-btn-primary {
background: var(--accent) !important; border-color: var(--accent) !important; color: var(--accent-contrast) !important;
}
.podman-btn-primary:hover {
background: var(--accent-strong) !important; border-color: var(--accent-strong) !important;
}
.podman-btn-danger { color: var(--bad); }
.podman-btn-danger:hover { border-color: var(--bad); }
.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
* the same bordered/accent-colored treatment as Create, so nothing in a
* form stood out as THE primary action (found from a live screenshot AND
* a screen recording: Cancel/Create/+Add/× all read as equally weighted,
* with the exact same hover glow even). Unraid's own webGUI applies a
* site-wide default border+hover-gradient to every <button>, at higher
* effective priority than a plain single-class selector here — verified
* live: a bare `.podman-btn-ghost { border-color: transparent }` was
* silently losing to it, on both the rest AND hover state. !important is
* the only reliable way to guarantee this specific, deliberate style
* wins regardless of what Unraid's base theme does elsewhere.
*/
.podman-btn-ghost {
background: transparent !important; border-color: transparent !important;
color: var(--text-dim) !important; box-shadow: none !important;
}
.podman-btn-ghost:hover {
background: var(--surface-2) !important; border-color: transparent !important;
color: var(--text) !important; box-shadow: none !important;
}
/*
* A ghost button can still carry danger intent (Disconnect, Delete,
* template "Delete") — needs its own !important since .podman-btn-ghost's
* color/background/border would otherwise win by rule order. Solid fill
* at rest (not just a tint, and not just on hover) so it reads with the
* same weight as .podman-btn-primary, just in red instead of accent —
* a merely tinted/outlined button still read as "just another secondary
* action" per live feedback.
*/
.podman-btn-ghost.podman-btn-danger {
color: var(--bad-contrast) !important; background: var(--bad) !important; border-color: var(--bad) !important;
}
.podman-btn-ghost.podman-btn-danger:hover {
background: var(--bad-strong) !important; border-color: var(--bad-strong) !important; color: var(--bad-contrast) !important;
}
.podman-btn-ghost.podman-btn-danger[disabled] {
color: var(--text-faint) !important; background: transparent !important; border-color: var(--border) !important;
}
/* Icon-only danger buttons (row "remove" trash icons) carry an emoji
glyph, not text — .podman-btn-danger's `color` alone doesn't recolor an
emoji, so these get the same solid red fill instead, at rest not just
on hover, so "destructive" reads at a glance across a whole table. */
.podman-btn-icon.podman-btn-danger { border-color: var(--bad) !important; background: var(--bad) !important; }
.podman-btn-icon.podman-btn-danger:hover { background: var(--bad-strong) !important; border-color: var(--bad-strong) !important; }
.podman-btn-icon.podman-btn-danger[disabled] { border-color: var(--border) !important; background: transparent !important; }
.podman-subnav {
margin: 14px 0 0; padding: 0; display: flex; gap: 4px; border-bottom: 1px solid var(--border);
overflow-x: auto; list-style: none;
}
.podman-subnav button {
appearance: none; background: none; border: none; border-bottom: 2px solid transparent;
color: var(--text-dim); padding: 10px 12px; font-size: 13px; font-weight: 600; cursor: pointer;
white-space: nowrap; display: flex; align-items: center; gap: 6px; font-family: var(--font-ui);
}
.podman-subnav button:hover { color: var(--text); }
.podman-subnav button.active { color: var(--accent-strong); border-bottom-color: var(--accent); }
.podman-subnav button .n { font-size: 10.5px; padding: 1px 5px; border-radius: 8px; background: var(--surface-3); color: var(--text-dim); }
.podman-main { padding: 20px 0 48px; }
.podman-panel { display: none; }
.podman-panel.active { display: block; }
.podman-card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow); }
.podman-card-pad { padding: 16px 18px; }
.podman-card-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid var(--border); }
.podman-card-head h2 { font-size: 14.5px; }
.podman-card-head .sub { font-size: 12px; color: var(--text-faint); font-weight: 500; }
.podman-grid { display: grid; gap: 14px; }
.podman-stat-grid { grid-template-columns: repeat(6, 1fr); }
@media (max-width: 1080px) { .podman-stat-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 620px) { .podman-stat-grid { grid-template-columns: repeat(2, 1fr); } }
.podman-stat {
background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px;
box-shadow: var(--shadow); transition: transform .12s ease, box-shadow .12s ease;
}
.podman-stat:hover { transform: translateY(-1px); box-shadow: 0 2px 4px rgba(20, 22, 26, .08), 0 8px 20px rgba(20, 22, 26, .08); }
.podman-stat .label { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint); font-weight: 700; }
.podman-stat .value { font-size: 24px; font-weight: 700; margin-top: 6px; }
.podman-stat .value small { font-size: 13px; color: var(--text-dim); font-weight: 600; }
.podman-stat .tone-good { color: var(--good); }
.podman-stat .tone-warn { color: var(--warn); }
.podman-stat .tone-bad { color: var(--bad); }
.podman-usage-mini .track > span.warn { background: var(--warn); }
.podman-usage-mini .track > span.bad { background: var(--bad); }
.podman-resource-rows { margin-top: 8px; }
.podman-resource-row { display: flex; align-items: center; gap: 12px; padding: 7px 0; }
.podman-resource-row + .podman-resource-row { border-top: 1px solid var(--border); }
.podman-resource-row .k { font-size: 12.5px; color: var(--text-dim); font-weight: 600; min-width: 84px; flex: none; }
.podman-resource-row .v { font-size: 12.5px; color: var(--text-faint); font-weight: 600; white-space: nowrap; flex: none; font-variant-numeric: tabular-nums; }
.podman-chip {
display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: 100px;
font-size: 11.5px; font-weight: 700; letter-spacing: .01em; white-space: nowrap;
}
.podman-chip .d { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.podman-chip-good { background: var(--good-bg); color: var(--good); }
.podman-chip-warn { background: var(--warn-bg); color: var(--warn); }
.podman-chip-bad { background: var(--bad-bg); color: var(--bad); }
.podman-chip-neutral { background: var(--neutral-bg); color: var(--neutral); }
.podman-plugin table { width: 100%; border-collapse: collapse; font-size: 13px; }
.podman-plugin thead th {
text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
color: var(--text-faint); font-weight: 700; padding: 10px 18px; border-bottom: 1px solid var(--border);
}
.podman-plugin tbody td { padding: 11px 18px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.podman-plugin tbody tr:last-child td { border-bottom: none; }
.podman-plugin tbody tr:hover { background: var(--surface-2); }
.podman-table-wrap { overflow-x: auto; }
/*
* table-layout: auto (the default) sizes every column from the widest
* content across only the CURRENTLY VISIBLE rows — so expanding/
* collapsing a folder (which adds/removes rows) changed what counted as
* "widest" and shifted every column, including the header text, on
* every toggle (found live). Fixed widths (set on the <th>s in
* Podman.page) make column sizing depend only on those, never on row
* content, so toggling a folder can no longer move anything.
*/
#containers-table { table-layout: fixed; }
/* Only the plain-text columns truncate with an ellipsis (Image/Ports —
both are just escaped text directly in the <td>, so ellipsis renders
correctly) — NOT the Name column (its text sits inside a flex
button+icon, where overflow:hidden would hard-clip instead of
ellipsis-truncate), the folder-header row's colspan cell (its member
chips need to wrap, see .podman-folder-members), or the actions
column (icon buttons, not text, would otherwise get clipped if they
ever didn't quite fit). */
#containers-table tbody > tr:not(.podman-folder-row) > td:nth-child(3),
#containers-table tbody > tr:not(.podman-folder-row) > td:nth-child(5) {
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* .podman-name-cell is a <div> INSIDE the <td>, not the <td> itself —
display:flex directly on a table cell pulls it out of table-cell
layout entirely, so with table-layout:fixed it stops respecting the
column width the <th> assigns and throws off alignment with every
other column (found live: the whole Name column visibly drifted).
The div gives the same flex row (so a WebUI link, see
.podman-webui-link, sits on the same line as the name button instead
of wrapping below it — .podman-row-name-btn's own display:flex makes
IT block-level by default, which would otherwise push everything
after it in the cell onto its own line) without that side effect.
min-width:0 lets the button actually shrink instead of forcing the
cell wider than its fixed column width; .text (not the plain text
node it used to be) is what actually ellipsis-truncates, since
text-overflow only applies to the element whose own inline content
overflows, not a nested flex child's. */
.podman-name-cell { display: flex; align-items: center; gap: 6px; }
.podman-row-name { display: flex; align-items: center; gap: 10px; font-weight: 600; min-width: 0; }
.podman-row-name .text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.podman-row-name-btn {
/* !important for the same reason as .podman-btn-ghost/-primary — Unraid's
own site-wide button theme otherwise still shows its default border
at rest (only losing to plain rules on hover), so a name link one
click away from every table row still looked like a bordered button
forever, not a plain label. */
appearance: none; border: none !important; background: none !important; padding: 0; cursor: pointer;
color: var(--text); font-family: var(--font-ui); font-size: 13px; text-align: left; min-width: 0;
}
.podman-row-name-btn:hover { color: var(--accent-strong); }
.podman-row-name-btn:hover .ico { border-color: var(--accent); }
.podman-row-name .ico {
width: 26px; height: 26px; border-radius: 6px; flex: none; background: var(--surface-3);
display: grid; place-items: center; font-size: 12px; border: 1px solid var(--border); color: var(--text-dim);
overflow: hidden;
}
.podman-row-name .ico img { width: 100%; height: 100%; object-fit: cover; }
.podman-row-sub { font-size: 11.5px; color: var(--text-faint); font-weight: 500; margin-top: 1px; }
/*
* 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; }
/*
* Segmented toggle (Containers' All/Running/Stopped filter, Logs' Follow/
* Paused) — previously just an inline-styled wrapper <div> around plain
* <button>s with no CSS of their own at all, so every option (not just the
* active one) showed Unraid's own default button border permanently,
* all three chips looking identically "selected". !important for the same
* site-wide-theme-override reason as .podman-btn-ghost/-primary.
*/
.podman-segmented { display: flex; gap: 2px; background: var(--surface-3); border: 1px solid var(--text-faint); padding: 3px; border-radius: 8px; }
.podman-segmented button {
appearance: none; border: none !important; background: transparent !important; color: var(--text-dim) !important;
padding: 6px 12px; border-radius: 6px; font-size: 12px; font-weight: 700; cursor: pointer;
font-family: var(--font-ui); transition: background .12s, color .12s;
}
.podman-segmented button:hover { color: var(--text) !important; }
/* Filled with the accent color (not just a slightly different neutral
shade) — the previous var(--surface) vs. var(--surface-2) contrast
between active/inactive was too close in the dark theme to notice at a
glance (found live). */
.podman-segmented button.active { background: var(--accent) !important; color: var(--accent-contrast) !important; box-shadow: var(--shadow); }
.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; }
.podman-usage-mini .track > span { display: block; height: 100%; background: var(--accent); }
.podman-usage-mini .num { font-size: 11.5px; color: var(--text-dim); width: 34px; text-align: right; font-variant-numeric: tabular-nums; }
.podman-toolbar { display: flex; align-items: center; gap: 10px; padding: 14px 18px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
/*
* !important throughout: Unraid's own webGui/styles/default-base.css
* targets input[type="text"] with an attribute selector (higher
* specificity than our single .podman-search class, :where() around it
* notwithstanding) forcing border-width:0 / border-bottom-width:1px /
* background:transparent — an underline-only text field, not a boxed one.
* Found live: our border/background were being silently dropped even
* though this rule appears later in the stylesheet.
*/
.podman-search {
flex: 1; min-width: 180px; max-width: 320px; font-size: 13px; color: var(--text); font-family: var(--font-ui);
background: var(--surface-3) !important; border: 1px solid var(--text-faint) !important;
border-radius: 7px !important; padding: 7px 11px !important;
}
.podman-search::placeholder { color: var(--text-faint); }
.podman-two-col { display: grid; grid-template-columns: 1.3fr 1fr; gap: 14px; align-items: start; }
@media (max-width: 920px) { .podman-two-col { grid-template-columns: 1fr; } }
.podman-activity-list { list-style: none; margin: 0; padding: 6px 0; }
.podman-activity-list li { display: flex; gap: 10px; padding: 9px 18px; border-bottom: 1px solid var(--border); font-size: 12.5px; }
.podman-activity-list li:last-child { border-bottom: none; }
.podman-activity-list .dot { width: 7px; height: 7px; border-radius: 50%; margin-top: 5px; flex: none; }
.podman-activity-list .when { color: var(--text-faint); font-size: 11px; margin-top: 1px; }
.podman-empty-note { padding: 28px 18px; text-align: center; color: var(--text-faint); font-size: 13px; }
.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); 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; }
.podman-template-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; padding: 18px; }
.podman-template-grid .podman-empty-note { grid-column: 1 / -1; }
.podman-pager { display: flex; align-items: center; justify-content: center; gap: 14px; padding: 4px 18px 18px; font-size: 12.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.podman-template-card {
border: 1px solid var(--border); border-radius: 10px; padding: 14px; background: var(--surface);
display: flex; flex-direction: column; gap: 10px;
}
.podman-template-icon { width: 40px; height: 40px; border-radius: 8px; object-fit: cover; background: var(--surface-2); }
.podman-template-icon-fallback {
display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 16px;
color: var(--accent); border: 1px solid var(--border);
}
.podman-template-name { font-weight: 700; font-size: 13.5px; }
.podman-template-overview { font-size: 12px; color: var(--text-dim); line-height: 1.4; }
.podman-template-actions { display: flex; gap: 6px; margin-top: auto; padding-top: 4px; }
/*
* min-width: 0 overrides the flex-item default of min-width: auto, which
* otherwise refuses to shrink a button below its own label's intrinsic
* width — without it, "Delete" (the widest label, and uppercased by
* Unraid's own site-wide button theme) pushed past the card's right edge
* instead of actually sharing the row evenly with Use/Export (found live).
*/
.podman-template-actions .podman-btn {
flex: 1; min-width: 0; justify-content: center; padding: 6px 8px; font-size: 11.5px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.podman-local-template-list { max-height: 220px; overflow-y: auto; border: 1px solid var(--border); border-radius: 7px; margin-top: 8px; }
.podman-local-template-item {
display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-bottom: 1px solid var(--border);
}
.podman-local-template-item:last-child { border-bottom: none; }
.podman-local-template-name { font-weight: 600; font-size: 12.5px; white-space: nowrap; }
.podman-local-template-item .podman-row-sub { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.podman-local-template-item .podman-btn { flex: none; padding: 5px 10px; font-size: 11.5px; }
.podman-logs-layout { display: grid; grid-template-columns: 200px 1fr; min-height: 460px; }
@media (max-width: 760px) { .podman-logs-layout { grid-template-columns: 1fr; } }
.podman-logs-side { border-right: 1px solid var(--border); }
.podman-logs-side .item { padding: 10px 14px; font-size: 12.5px; border-bottom: 1px solid var(--border); cursor: pointer; display: flex; align-items: center; gap: 8px; }
.podman-logs-side .item.active { background: var(--surface-2); font-weight: 700; box-shadow: inset 2px 0 0 var(--accent); }
.podman-log-pane {
background: #0f1114; color: #c7ccd4; font-family: var(--font-mono); font-size: 12.3px;
padding: 14px 16px; height: 400px; overflow-y: auto; line-height: 1.65;
/* Settings' service log (and the log modal) set .textContent directly
on this element rather than wrapping every line in a child .l div —
needs its own white-space here too, or real newlines in rc.podman's
output collapse into one run-together line (found live: the whole
multi-line `rc.podman status` output rendered as a single paragraph). */
white-space: pre-wrap;
}
.podman-log-pane .l { white-space: pre-wrap; word-break: break-word; }
.podman-log-pane .ts { color: #6b7280; }
.podman-log-pane .lvl-warn { color: #e0b23d; }
.podman-log-pane .lvl-error { color: #ef6470; }
.podman-term-launcher {
display: flex; align-items: flex-end; gap: 16px; flex-wrap: wrap; margin-bottom: 14px;
padding: 12px 14px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
}
.podman-term-launcher label { display: flex; flex-direction: column; gap: 4px; font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
/*
* !important here for the same reason as .podman-search's: Unraid's own
* webGui/styles/default-base.css has `select:where(:not(.unapi *))` rules
* for background/border/padding that would otherwise still show through
* around this class's box-model properties.
*/
.podman-term-select {
min-width: 180px !important; padding: 7px 12px !important; font-size: 13px !important;
font-family: var(--font-mono) !important; color: var(--text) !important;
background: var(--surface-3) !important; border: 1px solid var(--accent) !important; border-radius: 6px !important;
}
.podman-term-frame { display: block; width: 100%; height: 480px; border: 1px solid var(--border); border-radius: 8px; background: #0f1114; }
.podman-compose-layout { display: grid; grid-template-columns: 230px 1fr; min-height: 480px; }
@media (max-width: 800px) { .podman-compose-layout { grid-template-columns: 1fr; } }
.podman-compose-side { border-right: 1px solid var(--border); }
.podman-compose-proj { padding: 12px 14px; border-bottom: 1px solid var(--border); cursor: pointer; }
.podman-compose-proj.active { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--accent); }
.podman-compose-proj .path { font-size: 11px; color: var(--text-faint); margin-top: 2px; font-family: var(--font-mono); }
.podman-yaml { background: #0f1114; color: #c7ccd4; font-family: var(--font-mono); font-size: 12.4px; padding: 16px 18px; height: 420px; overflow: auto; line-height: 1.7; white-space: pre-wrap; }
/*
* !important: this is now a real <textarea>, not a read-only <pre> —
* Unraid's own webGui/styles/default-base.css targets textarea the same
* way it targets input[type="text"] (see .podman-search's comment for
* the exact rule), forcing border-width:0/border-bottom-width:1px/
* background:transparent/border-radius:0, which would otherwise make the
* whole editor look like a barely-visible underline instead of an actual
* text area.
*/
.podman-yaml-editor {
display: block; width: 100%; box-sizing: border-box; resize: vertical;
border: none !important; border-radius: 0 !important; outline: none;
}
.podman-field-row { display: grid; grid-template-columns: 220px 1fr; gap: 16px; padding: 14px 18px; border-bottom: 1px solid var(--border); align-items: start; }
.podman-field-row:last-child { border-bottom: none; }
.podman-field-row label { font-weight: 600; font-size: 13px; }
.podman-field-row .hint { font-size: 11.5px; color: var(--text-faint); margin-top: 4px; max-width: 46ch; }
.podman-field-row input[type="text"], .podman-field-row input[type="number"], .podman-field-row select {
background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px; padding: 8px 10px;
font-size: 13px; color: var(--text); width: 100%; max-width: 340px; font-family: var(--font-ui);
}
.podman-danger-card { border-color: color-mix(in srgb, var(--bad) 40%, var(--border)); }
/* Settings panel: a shared save action above all cards (Storage's and
Autostart & Lifecycle's fields save together in one call — see
settings.js's save() — so one button belongs above both, not buried in
either card, and definitely not in its own row with an empty label).
Framed as its own small bar (background/border), not bare text+button
floating at the top of the page. */
.podman-settings-actions {
display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 14px;
padding: 12px 16px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px;
}
.podman-settings-actions .hint { margin: 0; max-width: 52ch; color: var(--text-dim); }
.podman-card-head .sub { margin-top: 3px; }
/* Number field + unit label (GB, seconds) — the input itself stays compact
instead of stretching to .podman-field-row's normal 340px text-field width. */
.podman-input-suffix { display: flex; align-items: center; gap: 8px; }
.podman-input-suffix input[type="number"] { max-width: 100px; width: auto; }
.podman-input-suffix span { font-size: 12px; color: var(--text-dim); }
.podman-service-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.podman-service-row:last-child { margin-bottom: 0; }
/*
* Toggle switch — a plain checkbox reads as a leftover form control next
* to everything else in this panel getting a designed treatment; this
* hides the native checkbox (still the real, accessible input driving
* state) and draws a track+thumb off its :checked state instead. Sized in
* em off the track's own font-size so it scales if that ever changes.
*/
.podman-switch { position: relative; display: inline-flex; align-items: center; cursor: pointer; font-size: 22px; }
.podman-switch input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.podman-switch-track {
display: inline-block; width: 1.9em; height: 1.05em; border-radius: 999px;
background: var(--surface-3); border: 1px solid var(--border); transition: background .15s, border-color .15s;
}
.podman-switch-thumb {
display: block; width: 0.75em; height: 0.75em; margin: 0.13em; border-radius: 50%;
background: var(--text-faint); transition: transform .15s, background .15s;
}
.podman-switch input:checked + .podman-switch-track { background: var(--accent); border-color: var(--accent); }
.podman-switch input:checked + .podman-switch-track .podman-switch-thumb { background: var(--accent-contrast); transform: translateX(0.85em); }
.podman-switch input:focus-visible + .podman-switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }
.podman-version-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.podman-version-chip {
font-size: 11.5px; font-family: var(--font-mono); background: var(--surface-3); color: var(--text-dim);
border: 1px solid var(--border); padding: 5px 11px; border-radius: 100px;
}
.podman-version-chip b { color: var(--text); font-weight: 600; margin-left: 5px; }
.podman-danger-card .podman-card-head { border-bottom-color: color-mix(in srgb, var(--bad) 30%, var(--border)); }
.podman-danger-card .podman-card-head h2 { color: var(--bad); }
.podman-badge-update { font-size: 10px; font-weight: 700; color: var(--accent-strong); background: color-mix(in srgb, var(--accent) 16%, transparent); padding: 2px 7px; border-radius: 100px; margin-left: 8px; }
.podman-webui-link {
display: inline-flex; align-items: center; justify-content: center; height: 20px; padding: 0 7px;
border-radius: 5px; color: var(--text-faint); text-decoration: none; font-size: 10px; font-weight: 700;
letter-spacing: .03em; flex: none; border: 1px solid var(--border); background: var(--surface-3);
vertical-align: middle;
}
.podman-webui-link:hover { color: var(--accent-strong); border-color: var(--accent); }
.podman-loading, .podman-error { padding: 32px 18px; text-align: center; color: var(--text-faint); font-size: 13px; }
/**
* Modal form dialog — replaces browser-native prompt()/confirm() for any
* action that needs more than a single yes/no (e.g. "New Volume" needs a
* name AND an optional host path together, which prompt() can't express
* as one coherent form). See app.js's openFormModal().
*/
.podman-modal-backdrop {
position: fixed; inset: 0; background: rgba(15, 17, 20, .55); z-index: 1000;
display: flex; align-items: center; justify-content: center; padding: 20px;
}
.podman-modal {
background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
box-shadow: var(--shadow); width: 100%; max-width: 420px; max-height: calc(100vh - 40px);
overflow-y: auto; color: var(--text); font-family: var(--font-ui);
}
.podman-modal-head { padding: 16px 20px; border-bottom: 1px solid var(--border); }
.podman-modal-head h3 { font-size: 15px; }
.podman-modal-body { padding: 16px 20px; display: grid; gap: 14px; }
.podman-modal-field label { display: block; font-weight: 600; font-size: 12.5px; margin-bottom: 6px; }
.podman-modal-field input[type="text"], .podman-modal-field input[type="url"] {
background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px; padding: 8px 10px;
font-size: 13px; color: var(--text); width: 100%; font-family: var(--font-ui);
}
.podman-modal-field .hint { font-size: 11.5px; color: var(--text-faint); margin-top: 4px; }
.podman-modal-field select {
background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px; padding: 8px 10px;
font-size: 13px; color: var(--text); font-family: var(--font-ui);
}
.podman-modal-checkbox label { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 12.5px; margin-bottom: 0; }
.podman-modal-error { font-size: 12.5px; color: var(--bad); background: var(--bad-bg); border-radius: 7px; padding: 8px 10px; }
.podman-modal-actions { padding: 14px 20px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 8px; }
.podman-error { color: var(--bad); }
/* Wider variant + repeatable row groups, for forms with more than 1-2 fields (e.g. Create Container). */
.podman-modal-wide { max-width: 640px; }
.podman-row-group { display: grid; gap: 8px; margin-bottom: 8px; }
.podman-row-group-item {
display: flex; align-items: center; gap: 8px;
}
.podman-row-group-item input[type="text"] {
background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px; padding: 7px 9px;
font-size: 12.5px; color: var(--text); font-family: var(--font-mono); flex: 1; min-width: 0;
}
.podman-row-group-item select {
background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px; padding: 7px 9px;
font-size: 12.5px; color: var(--text); font-family: var(--font-ui);
/* flex:none alone wasn't enough — its auto flex-basis still let the
select stretch to fill the row (verified live: "TCP"/"Volume"
dropdowns spanned almost the entire row width). An explicit width
pins it to content-appropriate size regardless. */
flex: none; width: 110px;
}
.podman-row-group-item span { color: var(--text-faint); font-size: 12px; flex: none; }
/*
* Port number fields share the row with a select + remove button, unlike
* the wide source/path/key/value fields elsewhere in these row groups —
* left on flex:1 like everything else, both port inputs fought the fixed-
* width select/button for space and got squeezed down to a few pixels
* (found live: they rendered as near-invisible slivers). Fixed width,
* not flex-grown.
*/
.podman-row-group-item input.podman-input-narrow { flex: none; width: 90px; }
.podman-row-checkbox-label {
display: flex; align-items: center; gap: 4px; flex: none; font-size: 12px; color: var(--text-dim);
white-space: nowrap; cursor: pointer;
}
/* Row-remove (x) reads as a normal button like everything else at full
.podman-btn weight — muted/borderless by default, only turning
"danger" red on hover, so it registers as a quiet per-row affordance
rather than competing with the form's actual actions. */
.podman-row-group-item .podman-row-remove-btn {
background: transparent; border-color: transparent; color: var(--text-faint); flex: none;
}
.podman-row-group-item .podman-row-remove-btn:hover { background: var(--bad-bg); border-color: transparent; color: var(--bad); }
/* Anchored dropdown context menu — see app.js openContextMenu(). */
.podman-context-menu {
/*
* "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 {
appearance: none; border: none; background: none; text-align: left; padding: 8px 10px;
font-size: 12.5px; font-weight: 600; color: var(--text); border-radius: 6px; cursor: pointer;
font-family: var(--font-ui); width: 100%;
}
.podman-context-menu button:hover { background: var(--surface-2); }
.podman-context-menu button.danger { color: var(--bad); background: var(--bad-bg); }
.podman-context-menu button.danger:hover { background: var(--bad); color: var(--bad-contrast); }
.podman-context-menu button[disabled] { opacity: .4; cursor: not-allowed; }
.podman-context-menu-sep { height: 1px; background: var(--border); margin: 4px 2px; }
/* Container detail modal — see containers.js openDetailModal(). */
.podman-modal-xwide { max-width: 760px; }
.podman-detail-tabs {
display: flex; gap: 2px; padding: 0 20px; border-bottom: 1px solid var(--border); overflow-x: auto;
}
.podman-detail-tabs button {
appearance: none; background: none; border: none; border-bottom: 2px solid transparent;
color: var(--text-dim); padding: 10px 12px; font-size: 12.5px; font-weight: 600; cursor: pointer;
white-space: nowrap; font-family: var(--font-ui);
}
.podman-detail-tabs button:hover { color: var(--text); }
.podman-detail-tabs button.active { color: var(--accent-strong); border-bottom-color: var(--accent); }
.podman-detail-body { padding: 16px 20px; max-height: 50vh; overflow-y: auto; }
.podman-detail-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.podman-detail-table td { padding: 6px 0; border-bottom: 1px solid var(--border); vertical-align: top; }
.podman-detail-table td:first-child { color: var(--text-faint); font-weight: 600; width: 160px; padding-right: 12px; }
.podman-detail-table td.mono { font-family: var(--font-mono); word-break: break-all; }
.podman-detail-json {
background: #0f1114; color: #c7ccd4; font-family: var(--font-mono); font-size: 11.8px;
padding: 14px 16px; border-radius: 8px; white-space: pre-wrap; word-break: break-all; line-height: 1.6;
}
.podman-detail-empty { color: var(--text-faint); font-size: 12.5px; padding: 8px 0; }
/*
* Toast notifications — replaces alert() for one-way feedback (saved,
* removed, failed, ...) across every panel. Confirmations stay native
* confirm() (a decision, not a notice); long-running command output
* stays in the existing log-modal pattern (a toast has to stay short to
* be readable while it's animating/auto-dismissing). z-index above the
* modal backdrop (1000) so a toast fired from within a modal's own
* action (e.g. "Save failed") is never hidden behind it.
*/
.podman-toast-container {
position: fixed; right: 20px; bottom: 20px; z-index: 1100;
display: flex; flex-direction: column-reverse; gap: 10px; pointer-events: none;
max-width: min(380px, calc(100vw - 40px));
}
.podman-toast {
pointer-events: auto; display: flex; align-items: flex-start; gap: 10px;
background: var(--surface); border: 1px solid var(--border); border-left: 3px solid var(--neutral);
border-radius: 9px; box-shadow: var(--shadow); padding: 12px 14px; font-size: 13px; color: var(--text);
animation: podman-toast-in .18s ease-out;
}
.podman-toast.leaving { animation: podman-toast-out .16s ease-in forwards; }
.podman-toast-success { border-left-color: var(--good); }
.podman-toast-warn { border-left-color: var(--warn); }
.podman-toast-error { border-left-color: var(--bad); }
.podman-toast .ico { flex: none; margin-top: 1px; font-size: 15px; line-height: 1; }
.podman-toast-success .ico { color: var(--good); }
.podman-toast-warn .ico { color: var(--warn); }
.podman-toast-error .ico { color: var(--bad); }
.podman-toast .msg { flex: 1; line-height: 1.45; word-break: break-word; }
.podman-toast .close {
appearance: none; background: none; border: none; color: var(--text-faint); cursor: pointer;
font-size: 15px; line-height: 1; padding: 0; flex: none; font-family: var(--font-ui);
}
.podman-toast .close:hover { color: var(--text); }
@keyframes podman-toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
@keyframes podman-toast-out { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(8px); } }
@media (prefers-reduced-motion: reduce) {
.podman-toast, .podman-toast.leaving { animation: none; }
}
/* Container folders — a group header row spanning the full table width,
sitting between normal <tr>s in the same <tbody> (see containers.js's
renderTable()) rather than a separate nested table, so column
alignment with member rows stays free. */
.podman-folder-row td { padding: 0; background: var(--surface-2); border-bottom: 1px solid var(--border); }
.podman-folder-head { display: flex; align-items: center; gap: 14px; padding: 9px 16px; flex-wrap: wrap; }
.podman-folder-toggle {
appearance: none; background: none; border: none; display: flex; align-items: center; gap: 10px;
cursor: pointer; font-family: var(--font-ui); color: var(--text); padding: 0; flex: none; text-align: left;
}
.podman-folder-toggle .chevron { color: var(--text-faint); font-size: 10px; width: 10px; flex: none; }
.podman-folder-toggle .ico {
width: 22px; height: 22px; border-radius: 6px; flex: none; background: var(--surface-3);
display: grid; place-items: center; font-size: 10.5px; border: 1px solid var(--border); color: var(--text-dim);
overflow: hidden;
}
.podman-folder-toggle .ico img { width: 100%; height: 100%; object-fit: cover; }
.podman-folder-toggle .name { font-weight: 700; font-size: 12.5px; }
.podman-folder-toggle .count {
font-size: 11px; color: var(--text-faint); font-weight: 700; background: var(--surface-3);
padding: 1px 8px; border-radius: 100px; font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* The always-visible per-member preview inside a folder's header row —
see containers.js's folderMemberChipHtml(). Each chip opens that
container's detail modal directly, without needing to expand the
folder first. */
.podman-folder-members { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; flex: 1; min-width: 0; }
.podman-folder-member {
appearance: none; display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--border);
background: var(--surface); border-radius: 100px; padding: 3px 10px 3px 4px; cursor: pointer;
font-family: var(--font-ui); color: var(--text-dim); font-size: 11.5px;
}
.podman-folder-member:hover { border-color: var(--accent); color: var(--text); }
.podman-folder-member .ico {
width: 18px; height: 18px; border-radius: 5px; font-size: 9px; flex: none; background: var(--surface-3);
display: grid; place-items: center; overflow: hidden;
}
.podman-folder-member .ico img { width: 100%; height: 100%; object-fit: cover; }
.podman-folder-member .name { font-weight: 600; }
.podman-folder-member .dot { width: 6px; height: 6px; border-radius: 50%; flex: none; }
.podman-folder-member .dot.good { background: var(--good); }
.podman-folder-member .dot.bad { background: var(--bad); }
.podman-folder-member .state { color: var(--text-faint); }