Files
unraid-podman/webui/plugins/podman/styles/podman.css
T
maggesandClaude Sonnet 5 5b47b4cc0a Add catatonit/nftables/docker-compose packages, fix CSRF/streaming/storage bugs found by live testing
- 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>
2026-07-12 11:51:17 +00:00

274 lines
17 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); }
.podman-btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-contrast); }
.podman-btn-primary:hover { background: var(--accent-strong); border-color: var(--accent-strong); }
.podman-btn-danger { color: var(--bad); }
.podman-btn-danger:hover { border-color: var(--bad); }
.podman-btn-icon { padding: 6px 8px; }
.podman-btn[disabled] { opacity: .4; cursor: not-allowed; }
.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 .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; }
.podman-actions { display: 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; }
.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; }
.podman-search { flex: 1; min-width: 180px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px; padding: 7px 11px; font-size: 13px; color: var(--text); font-family: var(--font-ui); }
.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); }
.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;
}
.podman-row-group-item span { color: var(--text-faint); font-size: 12px; flex: none; }