Add reproducible build system, native Unraid plugin, and WebUI
- versions.env pins podman, conmon, crun, netavark, aardvark-dns, passt, and fuse-overlayfs to verified upstream source checksums; SlackBuild recipes, scripts/build-packages.sh, checksums.sh, release.sh, and update-versions.sh implement the reproducible pipeline; GitHub Actions workflows build in a Slackware container and publish releases without committing any binaries. - plugin/podman.plg installs/updates/removes all eight packages (the seven components plus the plugin's own unraid-podman scaffolding package) via upgradepkg, using the official Unraid array-event hook mechanism (event/disks_mounted, event/stopping) instead of editing /boot/config/go. rc.podman and the sbin/ helper scripts implement storage creation, config seeding/sync, preflight checks, autostart with per-container Safe-Mode, and package verify/update/rollback. - webui/plugins/podman implements the Dashboard, Containers, Pods, Images, Volumes, Networks, Logs, Terminal, Compose, and Settings panels against the approved mockup (webui/mockups/prototype.html), talking to podman system service exclusively via PodmanClient.php (libpod REST API over the Unix socket), with two documented exceptions: Terminal's one-shot exec model and Compose's use of the podman compose CLI, since libpod has no REST equivalent for either. - docs/ARCHITECTURE.md and docs/ROADMAP.md record the design decisions and honest current status (syntax-checked, unit- and integration-tested against fake sockets/servers; not yet run against a real Unraid/Podman/Slackware system). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* 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; }
|
||||
.podman-error { color: var(--bad); }
|
||||
Reference in New Issue
Block a user