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>
476 lines
28 KiB
CSS
476 lines
28 KiB
CSS
/**
|
||
* 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; --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; --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; --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; --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); }
|
||
|
||
.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);
|
||
}
|
||
.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 (e.g. a template's
|
||
"Delete") — needs its own !important since .podman-btn-ghost's color
|
||
would otherwise win by rule order. */
|
||
.podman-btn-ghost.podman-btn-danger { color: var(--bad) !important; }
|
||
.podman-btn-ghost.podman-btn-danger:hover { background: var(--bad-bg) !important; color: var(--bad) !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); }
|
||
.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 .bar { height: 5px; border-radius: 3px; background: var(--surface-3); margin-top: 10px; overflow: hidden; }
|
||
.podman-stat .bar > span { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
|
||
|
||
.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; }
|
||
.podman-row-name { display: flex; align-items: center; gap: 10px; font-weight: 600; }
|
||
.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;
|
||
}
|
||
.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);
|
||
}
|
||
.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; }
|
||
|
||
.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-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;
|
||
}
|
||
.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 { background: #0f1114; color: #d7dbe0; font-family: var(--font-mono); font-size: 12.6px; border-radius: 8px; padding: 14px 16px; height: 380px; overflow-y: auto; line-height: 1.7; }
|
||
.podman-term .prompt { color: #4cc785; }
|
||
.podman-term .path { color: #6fb2f5; }
|
||
.podman-term-input {
|
||
width: 100%; margin-top: 10px; background: #0f1114; color: #d7dbe0; border: 1px solid var(--border);
|
||
border-radius: 6px; padding: 8px 10px; font-family: var(--font-mono); font-size: 12.6px;
|
||
}
|
||
|
||
.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; }
|
||
|
||
.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)); }
|
||
.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-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"] {
|
||
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; }
|
||
/* 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); }
|
||
.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; }
|