- 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>
950 lines
53 KiB
HTML
950 lines
53 KiB
HTML
<title>unraid-podman — WebUI Mockup</title>
|
|
<style>
|
|
/* =========================================================================
|
|
Design tokens
|
|
Grounded in Unraid's real webGUI chrome: near-black masthead, a warm
|
|
construction-orange brand accent, dense admin-tool typography via the
|
|
OS's own system font stack (Unraid itself ships no custom webfont).
|
|
Semantic status colors (good/warn/bad/neutral) are kept separate from
|
|
the brand accent, per the container-fleet domain's own vocabulary.
|
|
========================================================================= */
|
|
:root {
|
|
--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, 0.06), 0 4px 12px rgba(20, 22, 26, 0.05);
|
|
--mast-bg: #16181c;
|
|
--mast-fg: #d7dae0;
|
|
--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;
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--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, 0.3), 0 8px 24px rgba(0, 0, 0, 0.35);
|
|
--mast-bg: #0e0f11;
|
|
--mast-fg: #cdd1d7;
|
|
}
|
|
}
|
|
:root[data-theme="dark"] {
|
|
--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);
|
|
--mast-bg: #0e0f11; --mast-fg: #cdd1d7;
|
|
}
|
|
:root[data-theme="light"] {
|
|
--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);
|
|
--mast-bg: #16181c; --mast-fg: #d7dae0;
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
body {
|
|
margin: 0;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: var(--font-ui);
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
h1, h2, h3 { text-wrap: balance; margin: 0; font-weight: 600; }
|
|
::selection { background: var(--accent); color: var(--accent-contrast); }
|
|
a { color: inherit; }
|
|
button, input, select { font-family: inherit; font-size: inherit; color: inherit; }
|
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
|
|
.tnum { font-variant-numeric: tabular-nums; }
|
|
.mono { font-family: var(--font-mono); }
|
|
|
|
/* ---- Masthead (real Unraid top bar, non-interactive context) ---- */
|
|
.mast {
|
|
background: var(--mast-bg);
|
|
color: var(--mast-fg);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
padding: 0 16px;
|
|
height: 46px;
|
|
}
|
|
.mast .brand { display: flex; align-items: center; gap: 8px; font-weight: 700; letter-spacing: .02em; }
|
|
.mast .brand .mark {
|
|
width: 22px; height: 22px; border-radius: 5px;
|
|
background: linear-gradient(155deg, var(--accent), var(--accent-strong));
|
|
display: grid; place-items: center; color: #fff; font-size: 11px; font-weight: 800;
|
|
}
|
|
.mast .server { font-size: 12.5px; color: var(--mast-fg); opacity: .75; }
|
|
.mast .spacer { flex: 1; }
|
|
.mast .mast-icons { display: flex; gap: 16px; font-size: 13px; opacity: .7; }
|
|
|
|
.mainnav {
|
|
background: color-mix(in srgb, var(--mast-bg) 88%, var(--surface));
|
|
display: flex;
|
|
gap: 2px;
|
|
padding: 0 10px;
|
|
overflow-x: auto;
|
|
}
|
|
.mainnav a {
|
|
display: block;
|
|
padding: 10px 14px 8px;
|
|
font-size: 12px;
|
|
letter-spacing: .04em;
|
|
text-transform: uppercase;
|
|
color: var(--mast-fg);
|
|
opacity: .55;
|
|
text-decoration: none;
|
|
border-bottom: 2px solid transparent;
|
|
white-space: nowrap;
|
|
cursor: default;
|
|
}
|
|
.mainnav a.active {
|
|
opacity: 1;
|
|
color: var(--accent-strong);
|
|
border-bottom-color: var(--accent);
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* ---- Page header ---- */
|
|
.pagehead {
|
|
max-width: 1280px;
|
|
margin: 0 auto;
|
|
padding: 22px 24px 0;
|
|
display: flex;
|
|
align-items: flex-end;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.pagehead .titlewrap { display: flex; align-items: center; gap: 12px; }
|
|
.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);
|
|
}
|
|
.pagehead h1 { font-size: 21px; }
|
|
.pagehead .meta { font-size: 12.5px; color: var(--text-dim); margin-top: 2px; }
|
|
.pagehead .meta .dot { color: var(--good); }
|
|
|
|
.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;
|
|
}
|
|
.btn:hover { border-color: var(--text-faint); }
|
|
.btn-primary {
|
|
background: var(--accent);
|
|
border-color: var(--accent);
|
|
color: var(--accent-contrast);
|
|
}
|
|
.btn-primary:hover { background: var(--accent-strong); border-color: var(--accent-strong); }
|
|
.btn-danger { color: var(--bad); }
|
|
.btn-danger:hover { border-color: var(--bad); }
|
|
.btn-icon { padding: 6px 8px; }
|
|
.btn[disabled] { opacity: .4; cursor: not-allowed; }
|
|
|
|
/* ---- Sub navigation (the 10 sections) ---- */
|
|
.subnav {
|
|
max-width: 1280px;
|
|
margin: 14px auto 0;
|
|
padding: 0 24px;
|
|
display: flex;
|
|
gap: 4px;
|
|
border-bottom: 1px solid var(--border);
|
|
overflow-x: auto;
|
|
}
|
|
.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;
|
|
}
|
|
.subnav button:hover { color: var(--text); }
|
|
.subnav button.active { color: var(--accent-strong); border-bottom-color: var(--accent); }
|
|
.subnav button .n {
|
|
font-size: 10.5px; padding: 1px 5px; border-radius: 8px;
|
|
background: var(--surface-3); color: var(--text-dim);
|
|
}
|
|
|
|
.main { max-width: 1280px; margin: 0 auto; padding: 20px 24px 64px; }
|
|
.panel { display: none; animation: fade .15s ease; }
|
|
.panel.active { display: block; }
|
|
@keyframes fade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; } }
|
|
@media (prefers-reduced-motion: reduce) { .panel { animation: none; } }
|
|
|
|
/* ---- Generic building blocks ---- */
|
|
.card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
box-shadow: var(--shadow);
|
|
}
|
|
.card-pad { padding: 16px 18px; }
|
|
.card-head {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 14px 18px; border-bottom: 1px solid var(--border);
|
|
}
|
|
.card-head h2 { font-size: 14.5px; }
|
|
.card-head .sub { font-size: 12px; color: var(--text-faint); font-weight: 500; }
|
|
|
|
.grid { display: grid; gap: 14px; }
|
|
.stat-grid { grid-template-columns: repeat(6, 1fr); }
|
|
@media (max-width: 1080px) { .stat-grid { grid-template-columns: repeat(3, 1fr); } }
|
|
@media (max-width: 620px) { .stat-grid { grid-template-columns: repeat(2, 1fr); } }
|
|
|
|
.stat {
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
padding: 14px 16px;
|
|
box-shadow: var(--shadow);
|
|
}
|
|
.stat .label { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint); font-weight: 700; }
|
|
.stat .value { font-size: 24px; font-weight: 700; margin-top: 6px; }
|
|
.stat .value small { font-size: 13px; color: var(--text-dim); font-weight: 600; }
|
|
.stat .bar { height: 5px; border-radius: 3px; background: var(--surface-3); margin-top: 10px; overflow: hidden; }
|
|
.stat .bar > span { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
|
|
|
|
.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;
|
|
}
|
|
.chip .d { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
|
|
.chip-good { background: var(--good-bg); color: var(--good); }
|
|
.chip-warn { background: var(--warn-bg); color: var(--warn); }
|
|
.chip-bad { background: var(--bad-bg); color: var(--bad); }
|
|
.chip-neutral { background: var(--neutral-bg); color: var(--neutral); }
|
|
|
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
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);
|
|
}
|
|
tbody td { padding: 11px 18px; border-bottom: 1px solid var(--border); vertical-align: middle; }
|
|
tbody tr:last-child td { border-bottom: none; }
|
|
tbody tr:hover { background: var(--surface-2); }
|
|
.table-wrap { overflow-x: auto; }
|
|
.row-name { display: flex; align-items: center; gap: 10px; font-weight: 600; }
|
|
.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);
|
|
}
|
|
.row-sub { font-size: 11.5px; color: var(--text-faint); font-weight: 500; margin-top: 1px; }
|
|
.actions { display: flex; gap: 4px; justify-content: flex-end; }
|
|
|
|
.usage-mini { display: flex; align-items: center; gap: 8px; min-width: 110px; }
|
|
.usage-mini .track { flex: 1; height: 5px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
|
|
.usage-mini .track > span { display: block; height: 100%; background: var(--accent); }
|
|
.usage-mini .num { font-size: 11.5px; color: var(--text-dim); width: 34px; text-align: right; }
|
|
|
|
.toolbar { display: flex; align-items: center; gap: 10px; padding: 14px 18px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
|
|
.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);
|
|
}
|
|
.search::placeholder { color: var(--text-faint); }
|
|
.filterset { display: flex; gap: 4px; background: var(--surface-2); padding: 3px; border-radius: 8px; }
|
|
.filterset button {
|
|
appearance: none; border: none; background: none; color: var(--text-dim);
|
|
font-size: 12px; font-weight: 700; padding: 5px 10px; border-radius: 6px; cursor: pointer;
|
|
}
|
|
.filterset button.active { background: var(--surface); color: var(--text); box-shadow: var(--shadow); }
|
|
|
|
.two-col { display: grid; grid-template-columns: 1.3fr 1fr; gap: 14px; align-items: start; }
|
|
@media (max-width: 920px) { .two-col { grid-template-columns: 1fr; } }
|
|
|
|
.activity-list { list-style: none; margin: 0; padding: 6px 0; }
|
|
.activity-list li { display: flex; gap: 10px; padding: 9px 18px; border-bottom: 1px solid var(--border); font-size: 12.5px; }
|
|
.activity-list li:last-child { border-bottom: none; }
|
|
.activity-list .dot { width: 7px; height: 7px; border-radius: 50%; margin-top: 5px; flex: none; }
|
|
.activity-list .txt strong { font-weight: 600; }
|
|
.activity-list .when { color: var(--text-faint); font-size: 11px; margin-top: 1px; }
|
|
|
|
.empty-note { padding: 28px 18px; text-align: center; color: var(--text-faint); font-size: 13px; }
|
|
|
|
/* ---- Pods ---- */
|
|
.pod-card { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; margin-bottom: 14px; background: var(--surface); box-shadow: var(--shadow); }
|
|
.pod-card:last-child { margin-bottom: 0; }
|
|
.pod-head { display: flex; align-items: center; gap: 10px; padding: 13px 16px; background: var(--surface-2); border-bottom: 1px solid var(--border); }
|
|
.pod-head .name { font-weight: 700; font-size: 13.5px; }
|
|
.pod-head .infra { font-size: 11.5px; color: var(--text-faint); }
|
|
.pod-body table { font-size: 12.5px; }
|
|
.pod-body td, .pod-body th { padding: 8px 16px 8px 40px; }
|
|
|
|
/* ---- Logs ---- */
|
|
.logs-layout { display: grid; grid-template-columns: 200px 1fr; min-height: 460px; }
|
|
@media (max-width: 760px) { .logs-layout { grid-template-columns: 1fr; } }
|
|
.logs-side { border-right: 1px solid var(--border); }
|
|
.logs-side .item { padding: 10px 14px; font-size: 12.5px; border-bottom: 1px solid var(--border); cursor: default; display: flex; align-items: center; gap: 8px; }
|
|
.logs-side .item.active { background: var(--surface-2); font-weight: 700; box-shadow: inset 2px 0 0 var(--accent); }
|
|
.log-toolbar { display: flex; gap: 8px; align-items: center; padding: 10px 14px; border-bottom: 1px solid var(--border); }
|
|
.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;
|
|
}
|
|
:root:not([data-theme="dark"]) .log-pane { background: #14161a; }
|
|
.log-pane .l { white-space: pre-wrap; word-break: break-word; }
|
|
.log-pane .ts { color: #6b7280; }
|
|
.log-pane .lvl-info { color: #6fb2f5; }
|
|
.log-pane .lvl-warn { color: #e0b23d; }
|
|
.log-pane .lvl-error { color: #ef6470; }
|
|
|
|
/* ---- Terminal ---- */
|
|
.term-wrap { padding: 16px 18px; }
|
|
.term-select { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; font-size: 12.5px; color: var(--text-dim); }
|
|
.term-select select {
|
|
background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px; padding: 6px 9px; color: var(--text);
|
|
}
|
|
.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;
|
|
}
|
|
.term .prompt { color: #4cc785; }
|
|
.term .path { color: #6fb2f5; }
|
|
.term .cur { display: inline-block; width: 7px; height: 14px; background: #d7dbe0; vertical-align: text-bottom; animation: blink 1s step-end infinite; }
|
|
@media (prefers-reduced-motion: reduce) { .term .cur { animation: none; } }
|
|
@keyframes blink { 50% { opacity: 0; } }
|
|
|
|
/* ---- Compose ---- */
|
|
.compose-layout { display: grid; grid-template-columns: 230px 1fr; min-height: 480px; }
|
|
@media (max-width: 800px) { .compose-layout { grid-template-columns: 1fr; } }
|
|
.compose-side { border-right: 1px solid var(--border); }
|
|
.compose-proj { padding: 12px 14px; border-bottom: 1px solid var(--border); cursor: default; }
|
|
.compose-proj.active { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--accent); }
|
|
.compose-proj .name { font-weight: 700; font-size: 13px; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
.compose-proj .path { font-size: 11px; color: var(--text-faint); margin-top: 2px; font-family: var(--font-mono); }
|
|
.compose-body-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--border); gap: 10px; flex-wrap: wrap; }
|
|
.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; }
|
|
:root:not([data-theme="dark"]) .yaml { background: #14161a; }
|
|
.yaml .k { color: #ef9f76; }
|
|
.yaml .s { color: #9fd88a; }
|
|
.yaml .c { color: #6b7280; font-style: italic; }
|
|
.yaml .p { color: #6fb2f5; }
|
|
|
|
/* ---- Settings ---- */
|
|
.settings-grid { display: grid; gap: 14px; }
|
|
.field-row { display: grid; grid-template-columns: 220px 1fr; gap: 16px; padding: 14px 18px; border-bottom: 1px solid var(--border); align-items: start; }
|
|
.field-row:last-child { border-bottom: none; }
|
|
.field-row label { font-weight: 600; font-size: 13px; }
|
|
.field-row .hint { font-size: 11.5px; color: var(--text-faint); margin-top: 4px; max-width: 46ch; }
|
|
.field-row input[type="text"], .field-row input[type="number"], .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;
|
|
}
|
|
.field-row .unit { font-size: 12px; color: var(--text-dim); margin-left: 8px; }
|
|
.switch { position: relative; display: inline-block; width: 38px; height: 22px; }
|
|
.switch input { display: none; }
|
|
.switch .track { position: absolute; inset: 0; background: var(--surface-3); border: 1px solid var(--border); border-radius: 20px; transition: .15s; }
|
|
.switch .track::after { content: ""; position: absolute; width: 16px; height: 16px; border-radius: 50%; background: var(--surface); top: 2px; left: 2px; transition: .15s; box-shadow: 0 1px 2px rgba(0,0,0,.3); }
|
|
.switch input:checked + .track { background: var(--accent); border-color: var(--accent); }
|
|
.switch input:checked + .track::after { transform: translateX(16px); background: #fff; }
|
|
.danger-card { border-color: color-mix(in srgb, var(--bad) 40%, var(--border)); }
|
|
.danger-card .card-head { border-bottom-color: color-mix(in srgb, var(--bad) 30%, var(--border)); }
|
|
.danger-card .card-head h2 { color: var(--bad); }
|
|
|
|
.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; }
|
|
|
|
footer.note { max-width: 1280px; margin: 0 auto; padding: 4px 24px 40px; font-size: 11.5px; color: var(--text-faint); }
|
|
</style>
|
|
|
|
<!-- =========================================================================
|
|
Real Unraid chrome (masthead + main nav) — recreated for context, not
|
|
interactive. Only "Podman" is the actual subject of this mockup.
|
|
========================================================================= -->
|
|
<div class="mast">
|
|
<div class="brand"><span class="mark">U</span> unraid</div>
|
|
<div class="server">TOWER · 14d 6h uptime · Array Started</div>
|
|
<div class="spacer"></div>
|
|
<div class="mast-icons"><span>ⓘ Updates</span><span>🔔</span><span>👤 root</span></div>
|
|
</div>
|
|
<nav class="mainnav">
|
|
<a>Dashboard</a><a>Main</a><a>Shares</a><a>Users</a><a>Settings</a><a>Plugins</a><a>Docker</a><a>VMs</a>
|
|
<a class="active">Podman</a><a>Tools</a>
|
|
</nav>
|
|
|
|
<header class="pagehead">
|
|
<div class="titlewrap">
|
|
<div class="icon" aria-hidden="true">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M12 2l8 4.5v9L12 20l-8-4.5v-9L12 2z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M12 11l8-4.5M12 11v9M12 11L4 6.5" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>
|
|
</div>
|
|
<div>
|
|
<h1>Podman</h1>
|
|
<div class="meta"><span class="dot">●</span> podman.sock connected · 5 of 8 containers running · v6.0.1</div>
|
|
</div>
|
|
</div>
|
|
<div style="display:flex; gap:8px;">
|
|
<button class="btn">↻ Refresh</button>
|
|
<button class="btn btn-primary">+ New Container</button>
|
|
</div>
|
|
</header>
|
|
|
|
<nav class="subnav" id="subnav">
|
|
<button class="active" data-panel="dashboard">Dashboard</button>
|
|
<button data-panel="containers">Containers <span class="n">8</span></button>
|
|
<button data-panel="pods">Pods <span class="n">2</span></button>
|
|
<button data-panel="images">Images <span class="n">14</span></button>
|
|
<button data-panel="volumes">Volumes <span class="n">9</span></button>
|
|
<button data-panel="networks">Networks <span class="n">3</span></button>
|
|
<button data-panel="logs">Logs</button>
|
|
<button data-panel="terminal">Terminal</button>
|
|
<button data-panel="compose">Compose</button>
|
|
<button data-panel="settings">Settings</button>
|
|
</nav>
|
|
|
|
<main class="main">
|
|
|
|
<!-- ============================= DASHBOARD ============================= -->
|
|
<section class="panel active" id="panel-dashboard">
|
|
<div class="grid stat-grid">
|
|
<div class="stat"><div class="label">Running</div><div class="value tnum">5 <small>/ 8</small></div></div>
|
|
<div class="stat"><div class="label">Pods</div><div class="value tnum">2</div></div>
|
|
<div class="stat"><div class="label">Images</div><div class="value tnum">14</div></div>
|
|
<div class="stat"><div class="label">Volumes</div><div class="value tnum">9</div></div>
|
|
<div class="stat"><div class="label">Networks</div><div class="value tnum">3</div></div>
|
|
<div class="stat">
|
|
<div class="label">Storage Used</div>
|
|
<div class="value tnum">62.4 <small>/ 100 GB</small></div>
|
|
<div class="bar"><span style="width:62%"></span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="two-col" style="margin-top:14px;">
|
|
<div class="card">
|
|
<div class="card-head"><h2>Resource Usage</h2><span class="sub">last 60 min</span></div>
|
|
<div class="card-pad">
|
|
<svg viewBox="0 0 560 140" width="100%" height="140" preserveAspectRatio="none" role="img" aria-label="CPU and memory usage over the last 60 minutes">
|
|
<defs>
|
|
<linearGradient id="cpuFill" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0" stop-color="var(--accent)" stop-opacity="0.35"/>
|
|
<stop offset="1" stop-color="var(--accent)" stop-opacity="0"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<g stroke="var(--border)" stroke-width="1">
|
|
<line x1="0" y1="10" x2="560" y2="10"/><line x1="0" y1="45" x2="560" y2="45"/>
|
|
<line x1="0" y1="80" x2="560" y2="80"/><line x1="0" y1="115" x2="560" y2="115"/>
|
|
</g>
|
|
<path d="M0,100 L40,92 L80,96 L120,70 L160,74 L200,60 L240,66 L280,50 L320,58 L360,42 L400,48 L440,36 L480,40 L520,30 L560,34 L560,140 L0,140 Z" fill="url(#cpuFill)"/>
|
|
<path d="M0,100 L40,92 L80,96 L120,70 L160,74 L200,60 L240,66 L280,50 L320,58 L360,42 L400,48 L440,36 L480,40 L520,30 L560,34" fill="none" stroke="var(--accent)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>
|
|
<circle cx="560" cy="34" r="3.5" fill="var(--accent)"/>
|
|
<path d="M0,120 L40,118 L80,121 L120,112 L160,116 L200,108 L240,110 L280,104 L320,109 L360,100 L400,103 L440,96 L480,99 L520,92 L560,95" fill="none" stroke="var(--text-faint)" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="1 4"/>
|
|
<circle cx="560" cy="95" r="3" fill="var(--text-faint)"/>
|
|
</svg>
|
|
<div style="display:flex; gap:18px; margin-top:6px; font-size:11.5px; color:var(--text-dim);">
|
|
<span><span style="color:var(--accent);">●</span> CPU · 24%</span>
|
|
<span><span style="color:var(--text-faint);">●</span> Memory · 6.1 / 16 GB</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-head"><h2>Recent Activity</h2></div>
|
|
<ul class="activity-list">
|
|
<li><span class="dot" style="background:var(--good)"></span><div class="txt"><div><strong>sonarr</strong> started</div><div class="when">2 min ago</div></div></li>
|
|
<li><span class="dot" style="background:var(--bad)"></span><div class="txt"><div><strong>postgres</strong> health check failed</div><div class="when">11 min ago</div></div></li>
|
|
<li><span class="dot" style="background:var(--accent)"></span><div class="txt"><div>Pulled <strong>radarr:latest</strong> (image updated)</div><div class="when">38 min ago</div></div></li>
|
|
<li><span class="dot" style="background:var(--good)"></span><div class="txt"><div>Autostart chain completed (7/8)</div><div class="when">6h ago · array start</div></div></li>
|
|
<li><span class="dot" style="background:var(--warn)"></span><div class="txt"><div><strong>watchtower</strong> paused (autostart Safe-Mode)</div><div class="when">6h ago</div></div></li>
|
|
<li><span class="dot" style="background:var(--neutral)"></span><div class="txt"><div>Network <strong>media-net</strong> created</div><div class="when">2d ago</div></div></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" style="margin-top:14px;">
|
|
<div class="card-head"><h2>Autostart Queue</h2><span class="sub">/boot/config/plugins/podman/autostart</span></div>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead><tr><th>#</th><th>Container</th><th>Delay</th><th>Last Result</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="tnum">1</td><td>postgres</td><td class="tnum">—</td><td><span class="chip chip-good"><span class="d"></span>started</span></td></tr>
|
|
<tr><td class="tnum">2</td><td>nextcloud</td><td class="tnum">5s</td><td><span class="chip chip-good"><span class="d"></span>started</span></td></tr>
|
|
<tr><td class="tnum">3</td><td>sonarr</td><td class="tnum">—</td><td><span class="chip chip-good"><span class="d"></span>started</span></td></tr>
|
|
<tr><td class="tnum">4</td><td>radarr</td><td class="tnum">—</td><td><span class="chip chip-good"><span class="d"></span>started</span></td></tr>
|
|
<tr><td class="tnum">5</td><td>watchtower</td><td class="tnum">—</td><td><span class="chip chip-warn"><span class="d"></span>Safe-Mode (3 failures)</span></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= CONTAINERS ============================= -->
|
|
<section class="panel" id="panel-containers">
|
|
<div class="card">
|
|
<div class="toolbar">
|
|
<input class="search" type="text" placeholder="Search containers by name or image…">
|
|
<div class="filterset">
|
|
<button class="active">All 8</button><button>Running 5</button><button>Stopped 2</button><button>Unhealthy 1</button>
|
|
</div>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead><tr><th>Status</th><th>Name</th><th>Image</th><th>CPU</th><th>Memory</th><th>Ports</th><th>Uptime</th><th></th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><span class="chip chip-good"><span class="d"></span>running</span></td>
|
|
<td><div class="row-name"><span class="ico">Px</span>plex</div></td>
|
|
<td class="mono row-sub">lscr.io/linuxserver/plex:latest</td>
|
|
<td class="tnum">18%</td>
|
|
<td><div class="usage-mini"><div class="track"><span style="width:41%"></span></div><span class="num tnum">1.6G</span></div></td>
|
|
<td class="mono row-sub">32400/tcp</td>
|
|
<td class="tnum">14d 6h</td>
|
|
<td class="actions"><button class="btn btn-icon" title="Restart">↻</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Shell">⎋</button><button class="btn btn-icon" title="Stop">■</button></td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="chip chip-good"><span class="d"></span>running</span></td>
|
|
<td><div class="row-name"><span class="ico">So</span>sonarr</div></td>
|
|
<td class="mono row-sub">lscr.io/linuxserver/sonarr:4.0</td>
|
|
<td class="tnum">3%</td>
|
|
<td><div class="usage-mini"><div class="track"><span style="width:22%"></span></div><span class="num tnum">420M</span></div></td>
|
|
<td class="mono row-sub">8989/tcp</td>
|
|
<td class="tnum">6h 12m</td>
|
|
<td class="actions"><button class="btn btn-icon" title="Restart">↻</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Shell">⎋</button><button class="btn btn-icon" title="Stop">■</button></td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="chip chip-good"><span class="d"></span>running</span></td>
|
|
<td><div class="row-name"><span class="ico">Ra</span>radarr <span class="badge-update">update</span></div></td>
|
|
<td class="mono row-sub">lscr.io/linuxserver/radarr:5.2</td>
|
|
<td class="tnum">2%</td>
|
|
<td><div class="usage-mini"><div class="track"><span style="width:19%"></span></div><span class="num tnum">380M</span></div></td>
|
|
<td class="mono row-sub">7878/tcp</td>
|
|
<td class="tnum">6h 12m</td>
|
|
<td class="actions"><button class="btn btn-icon" title="Restart">↻</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Shell">⎋</button><button class="btn btn-icon" title="Stop">■</button></td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="chip chip-bad"><span class="d"></span>unhealthy</span></td>
|
|
<td><div class="row-name"><span class="ico">Pg</span>postgres</div></td>
|
|
<td class="mono row-sub">postgres:16-alpine</td>
|
|
<td class="tnum">1%</td>
|
|
<td><div class="usage-mini"><div class="track"><span style="width:34%"></span></div><span class="num tnum">210M</span></div></td>
|
|
<td class="mono row-sub">5432/tcp</td>
|
|
<td class="tnum">2d 1h</td>
|
|
<td class="actions"><button class="btn btn-icon" title="Restart">↻</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Shell">⎋</button><button class="btn btn-icon" title="Stop">■</button></td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="chip chip-good"><span class="d"></span>running</span></td>
|
|
<td><div class="row-name"><span class="ico">Nc</span>nextcloud</div></td>
|
|
<td class="mono row-sub">nextcloud:29-apache</td>
|
|
<td class="tnum">6%</td>
|
|
<td><div class="usage-mini"><div class="track"><span style="width:58%"></span></div><span class="num tnum">890M</span></div></td>
|
|
<td class="mono row-sub">443/tcp, 80/tcp</td>
|
|
<td class="tnum">6h 12m</td>
|
|
<td class="actions"><button class="btn btn-icon" title="Restart">↻</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Shell">⎋</button><button class="btn btn-icon" title="Stop">■</button></td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="chip chip-neutral"><span class="d"></span>exited</span></td>
|
|
<td><div class="row-name"><span class="ico">Pr</span>prowlarr</div></td>
|
|
<td class="mono row-sub">lscr.io/linuxserver/prowlarr:1</td>
|
|
<td class="tnum">—</td>
|
|
<td class="tnum">—</td>
|
|
<td class="mono row-sub">9696/tcp</td>
|
|
<td class="tnum">—</td>
|
|
<td class="actions"><button class="btn btn-icon" title="Start">▶</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Remove">🗑</button></td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="chip chip-warn"><span class="d"></span>Safe-Mode</span></td>
|
|
<td><div class="row-name"><span class="ico">Wt</span>watchtower</div></td>
|
|
<td class="mono row-sub">containrrr/watchtower:latest</td>
|
|
<td class="tnum">—</td>
|
|
<td class="tnum">—</td>
|
|
<td class="mono row-sub">—</td>
|
|
<td class="tnum">—</td>
|
|
<td class="actions"><button class="btn btn-icon" title="Start">▶</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Remove">🗑</button></td>
|
|
</tr>
|
|
<tr>
|
|
<td><span class="chip chip-good"><span class="d"></span>running</span></td>
|
|
<td><div class="row-name"><span class="ico">Ha</span>homeassistant</div></td>
|
|
<td class="mono row-sub">ghcr.io/home-assistant/home-assistant</td>
|
|
<td class="tnum">4%</td>
|
|
<td><div class="usage-mini"><div class="track"><span style="width:30%"></span></div><span class="num tnum">610M</span></div></td>
|
|
<td class="mono row-sub">8123/tcp</td>
|
|
<td class="tnum">3d 4h</td>
|
|
<td class="actions"><button class="btn btn-icon" title="Restart">↻</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Shell">⎋</button><button class="btn btn-icon" title="Stop">■</button></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= PODS ============================= -->
|
|
<section class="panel" id="panel-pods">
|
|
<div class="pod-card">
|
|
<div class="pod-head">
|
|
<span class="chip chip-good"><span class="d"></span>running</span>
|
|
<span class="name">media-stack</span>
|
|
<span class="infra">infra: localhost/podman-pause · shared network namespace</span>
|
|
</div>
|
|
<div class="pod-body table-wrap">
|
|
<table>
|
|
<thead><tr><th>Container</th><th>Image</th><th>Status</th><th>Ports</th><th></th></tr></thead>
|
|
<tbody>
|
|
<tr><td>sonarr</td><td class="mono row-sub">linuxserver/sonarr:4.0</td><td><span class="chip chip-good"><span class="d"></span>running</span></td><td class="mono row-sub">8989/tcp</td><td class="actions"><button class="btn btn-icon">📄</button></td></tr>
|
|
<tr><td>radarr</td><td class="mono row-sub">linuxserver/radarr:5.2</td><td><span class="chip chip-good"><span class="d"></span>running</span></td><td class="mono row-sub">7878/tcp</td><td class="actions"><button class="btn btn-icon">📄</button></td></tr>
|
|
<tr><td>prowlarr</td><td class="mono row-sub">linuxserver/prowlarr:1</td><td><span class="chip chip-neutral"><span class="d"></span>exited</span></td><td class="mono row-sub">9696/tcp</td><td class="actions"><button class="btn btn-icon">📄</button></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="pod-card">
|
|
<div class="pod-head">
|
|
<span class="chip chip-good"><span class="d"></span>running</span>
|
|
<span class="name">home-stack</span>
|
|
<span class="infra">infra: localhost/podman-pause · shared network namespace</span>
|
|
</div>
|
|
<div class="pod-body table-wrap">
|
|
<table>
|
|
<thead><tr><th>Container</th><th>Image</th><th>Status</th><th>Ports</th><th></th></tr></thead>
|
|
<tbody>
|
|
<tr><td>homeassistant</td><td class="mono row-sub">ghcr.io/home-assistant/home-assistant</td><td><span class="chip chip-good"><span class="d"></span>running</span></td><td class="mono row-sub">8123/tcp</td><td class="actions"><button class="btn btn-icon">📄</button></td></tr>
|
|
<tr><td>mosquitto</td><td class="mono row-sub">eclipse-mosquitto:2</td><td><span class="chip chip-good"><span class="d"></span>running</span></td><td class="mono row-sub">1883/tcp</td><td class="actions"><button class="btn btn-icon">📄</button></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= IMAGES ============================= -->
|
|
<section class="panel" id="panel-images">
|
|
<div class="card">
|
|
<div class="toolbar">
|
|
<input class="search" type="text" placeholder="Search images…">
|
|
<button class="btn">⬇ Pull Image</button>
|
|
<button class="btn">Prune Unused</button>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead><tr><th>Repository</th><th>Tag</th><th>Image ID</th><th>Size</th><th>Created</th><th>Used By</th><th></th></tr></thead>
|
|
<tbody>
|
|
<tr><td>lscr.io/linuxserver/plex</td><td class="mono">latest</td><td class="mono row-sub">a3f9c2e1b7d4</td><td class="tnum">1.42 GB</td><td class="tnum">3d ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr><td>lscr.io/linuxserver/sonarr</td><td class="mono">4.0</td><td class="mono row-sub">7e21ac9f0b12</td><td class="tnum">612 MB</td><td class="tnum">6h ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr><td>lscr.io/linuxserver/radarr <span class="badge-update">update</span></td><td class="mono">5.2</td><td class="mono row-sub">44b6f1d8ce90</td><td class="tnum">598 MB</td><td class="tnum">38m ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr><td>postgres</td><td class="mono">16-alpine</td><td class="mono row-sub">c1a08e4477fd</td><td class="tnum">243 MB</td><td class="tnum">9d ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr><td>nextcloud</td><td class="mono">29-apache</td><td class="mono row-sub">9d3c72b1a5e6</td><td class="tnum">890 MB</td><td class="tnum">12d ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr><td>containrrr/watchtower</td><td class="mono">latest</td><td class="mono row-sub">0f5b9a2d7c31</td><td class="tnum">28 MB</td><td class="tnum">40d ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr><td>ghcr.io/home-assistant/home-assistant</td><td class="mono">2026.7</td><td class="mono row-sub">b7e4f0a19c22</td><td class="tnum">1.1 GB</td><td class="tnum">3d ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr><td>eclipse-mosquitto</td><td class="mono">2</td><td class="mono row-sub">2a6d0e5f8b41</td><td class="tnum">14 MB</td><td class="tnum">40d ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr><td>lscr.io/linuxserver/prowlarr</td><td class="mono">1</td><td class="mono row-sub">d8f231b9a706</td><td class="tnum">340 MB</td><td class="tnum">11d ago</td><td class="tnum">0</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= VOLUMES ============================= -->
|
|
<section class="panel" id="panel-volumes">
|
|
<div class="card">
|
|
<div class="toolbar">
|
|
<input class="search" type="text" placeholder="Search volumes…">
|
|
<button class="btn btn-primary">+ New Volume</button>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead><tr><th>Name</th><th>Driver</th><th>Mountpoint</th><th>Size</th><th>Used By</th><th></th></tr></thead>
|
|
<tbody>
|
|
<tr><td>postgres-data</td><td class="chip chip-neutral">local</td><td class="mono row-sub">…/storage/volumes/postgres-data/_data</td><td class="tnum">1.8 GB</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">📁</button><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr><td>nextcloud-data</td><td class="chip chip-neutral">local</td><td class="mono row-sub">…/storage/volumes/nextcloud-data/_data</td><td class="tnum">14.2 GB</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">📁</button><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr><td>ha-config</td><td class="chip chip-neutral">local</td><td class="mono row-sub">…/storage/volumes/ha-config/_data</td><td class="tnum">340 MB</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">📁</button><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr><td>mosquitto-data</td><td class="chip chip-neutral">local</td><td class="mono row-sub">…/storage/volumes/mosquitto-data/_data</td><td class="tnum">4 MB</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">📁</button><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr><td>unused-cache</td><td class="chip chip-neutral">local</td><td class="mono row-sub">…/storage/volumes/unused-cache/_data</td><td class="tnum">610 MB</td><td class="tnum">0</td><td class="actions"><button class="btn btn-icon">📁</button><button class="btn btn-icon">🗑</button></td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="empty-note" style="border-top:1px solid var(--border); text-align:left; padding:12px 18px;">
|
|
App data mapped from Unraid shares (e.g. <span class="mono">/mnt/user/appdata/…</span>) is managed as bind mounts per-container, not listed here — see each container's edit dialog.
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= NETWORKS ============================= -->
|
|
<section class="panel" id="panel-networks">
|
|
<div class="card">
|
|
<div class="toolbar">
|
|
<input class="search" type="text" placeholder="Search networks…">
|
|
<button class="btn btn-primary">+ New Network</button>
|
|
</div>
|
|
<div class="table-wrap">
|
|
<table>
|
|
<thead><tr><th>Name</th><th>Driver</th><th>Subnet</th><th>Gateway</th><th>Containers</th><th></th></tr></thead>
|
|
<tbody>
|
|
<tr><td><strong>podman0</strong> <span class="row-sub">(default)</span></td><td class="chip chip-neutral">bridge</td><td class="mono">10.89.0.0/24</td><td class="mono">10.89.0.1</td><td class="tnum">3</td><td class="actions"><button class="btn btn-icon">✎</button></td></tr>
|
|
<tr><td>media-net</td><td class="chip chip-neutral">bridge</td><td class="mono">10.89.1.0/24</td><td class="mono">10.89.1.1</td><td class="tnum">3</td><td class="actions"><button class="btn btn-icon">✎</button><button class="btn btn-icon">🗑</button></td></tr>
|
|
<tr>
|
|
<td>iot-macvlan</td><td class="chip chip-neutral">macvlan</td><td class="mono">192.168.10.0/24</td><td class="mono">192.168.10.1</td><td class="tnum">1</td>
|
|
<td class="actions"><button class="btn btn-icon">✎</button><button class="btn btn-icon">🗑</button></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="empty-note" style="border-top:1px solid var(--border); text-align:left; padding:12px 18px; display:flex; gap:8px; align-items:flex-start;">
|
|
<span class="chip chip-warn" style="margin-top:1px;"><span class="d"></span>note</span>
|
|
<span>macvlan networks can trigger a known kernel issue with some NIC drivers on Unraid ("macvlan call trap"). See the docs before attaching containers that need host-network access on the same interface.</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= LOGS ============================= -->
|
|
<section class="panel" id="panel-logs">
|
|
<div class="card">
|
|
<div class="logs-layout">
|
|
<div class="logs-side">
|
|
<div class="item active">● postgres</div>
|
|
<div class="item">● plex</div>
|
|
<div class="item">● sonarr</div>
|
|
<div class="item">● radarr</div>
|
|
<div class="item">● nextcloud</div>
|
|
<div class="item">● homeassistant</div>
|
|
<div class="item">● mosquitto</div>
|
|
</div>
|
|
<div>
|
|
<div class="log-toolbar">
|
|
<input class="search" type="text" placeholder="Filter log output…" style="max-width:280px;">
|
|
<span class="filterset"><button class="active">Follow</button><button>Paused</button></span>
|
|
<div class="spacer" style="flex:1;"></div>
|
|
<button class="btn btn-icon" title="Download">⇩</button>
|
|
</div>
|
|
<div class="log-pane" role="log">
|
|
<div class="l"><span class="ts">2026-07-11T09:58:02Z</span> <span class="lvl-info">INFO</span> database system is ready to accept connections</div>
|
|
<div class="l"><span class="ts">2026-07-11T09:58:14Z</span> <span class="lvl-info">INFO</span> checkpoint starting: time</div>
|
|
<div class="l"><span class="ts">2026-07-11T10:04:41Z</span> <span class="lvl-warn">WARN</span> could not receive data from client: Connection reset by peer</div>
|
|
<div class="l"><span class="ts">2026-07-11T10:07:52Z</span> <span class="lvl-error">ERROR</span> connection to server was lost</div>
|
|
<div class="l"><span class="ts">2026-07-11T10:07:53Z</span> <span class="lvl-info">INFO</span> restartpoint starting: time</div>
|
|
<div class="l"><span class="ts">2026-07-11T10:07:58Z</span> <span class="lvl-info">INFO</span> database system is ready to accept connections</div>
|
|
<div class="l"><span class="ts">2026-07-11T10:11:03Z</span> <span class="lvl-warn">WARN</span> autovacuum: found orphan temp table in database "app"</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= TERMINAL ============================= -->
|
|
<section class="panel" id="panel-terminal">
|
|
<div class="card">
|
|
<div class="term-wrap">
|
|
<div class="term-select">
|
|
Exec into:
|
|
<select><option>sonarr</option><option>plex</option><option>postgres</option><option>nextcloud</option></select>
|
|
Shell: <select><option>/bin/bash</option><option>/bin/sh</option></select>
|
|
<button class="btn" style="margin-left:auto;">New Session</button>
|
|
</div>
|
|
<div class="term">
|
|
<div><span class="prompt">root@sonarr</span>:<span class="path">/config</span>$ podman exec -it sonarr /bin/bash</div>
|
|
<div>root@sonarr:/config# ls -la</div>
|
|
<div>drwxr-xr-x 6 abc abc 4096 Jul 11 09:12 .</div>
|
|
<div>drwxr-xr-x 1 root root 4096 Jun 02 17:03 ..</div>
|
|
<div>-rw-r--r-- 1 abc abc 22016 Jul 11 09:58 sonarr.db</div>
|
|
<div>drwxr-xr-x 2 abc abc 4096 Jul 11 06:00 logs</div>
|
|
<div> </div>
|
|
<div>root@sonarr:/config# tail -n 3 logs/sonarr.txt</div>
|
|
<div>10:11:02|Info|RssSyncService: Starting RSS Sync</div>
|
|
<div>10:11:04|Info|RssSyncService: RSS Sync Completed. Reported 12 releases</div>
|
|
<div>10:11:04|Info|DownloadService: Report sent to SABnzbd</div>
|
|
<div> </div>
|
|
<div><span class="prompt">root@sonarr</span>:<span class="path">/config</span># <span class="cur"></span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= COMPOSE ============================= -->
|
|
<section class="panel" id="panel-compose">
|
|
<div class="card">
|
|
<div class="compose-layout">
|
|
<div class="compose-side">
|
|
<div class="compose-proj active">
|
|
<div class="name">media-stack <span class="chip chip-good"><span class="d"></span>up</span></div>
|
|
<div class="path">/boot/config/plugins/podman/compose/media-stack</div>
|
|
</div>
|
|
<div class="compose-proj">
|
|
<div class="name">home-stack <span class="chip chip-good"><span class="d"></span>up</span></div>
|
|
<div class="path">/boot/config/plugins/podman/compose/home-stack</div>
|
|
</div>
|
|
<div class="compose-proj">
|
|
<div class="name">staging <span class="chip chip-neutral"><span class="d"></span>down</span></div>
|
|
<div class="path">/boot/config/plugins/podman/compose/staging</div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="compose-body-head">
|
|
<strong>media-stack / compose.yaml</strong>
|
|
<div style="display:flex; gap:8px;">
|
|
<button class="btn">⬇ Pull</button>
|
|
<button class="btn">■ Down</button>
|
|
<button class="btn btn-primary">▶ Up</button>
|
|
</div>
|
|
</div>
|
|
<div class="yaml"><span class="c"># managed via podman kube play — see docs/ARCHITECTURE.md</span>
|
|
<span class="k">services</span>:
|
|
<span class="k">sonarr</span>:
|
|
<span class="k">image</span>: <span class="s">lscr.io/linuxserver/sonarr:4.0</span>
|
|
<span class="k">ports</span>:
|
|
- <span class="s">"8989:8989"</span>
|
|
<span class="k">volumes</span>:
|
|
- <span class="p">/mnt/user/appdata/sonarr</span>:<span class="p">/config</span>
|
|
- <span class="p">/mnt/user/media</span>:<span class="p">/media</span>
|
|
<span class="k">environment</span>:
|
|
<span class="k">PUID</span>: <span class="s">"99"</span>
|
|
<span class="k">PGID</span>: <span class="s">"100"</span>
|
|
<span class="k">restart</span>: <span class="s">unless-stopped</span>
|
|
|
|
<span class="k">radarr</span>:
|
|
<span class="k">image</span>: <span class="s">lscr.io/linuxserver/radarr:5.2</span>
|
|
<span class="k">ports</span>:
|
|
- <span class="s">"7878:7878"</span>
|
|
<span class="k">volumes</span>:
|
|
- <span class="p">/mnt/user/appdata/radarr</span>:<span class="p">/config</span>
|
|
- <span class="p">/mnt/user/media</span>:<span class="p">/media</span>
|
|
<span class="k">restart</span>: <span class="s">unless-stopped</span>
|
|
|
|
<span class="k">prowlarr</span>:
|
|
<span class="k">image</span>: <span class="s">lscr.io/linuxserver/prowlarr:1</span>
|
|
<span class="k">ports</span>:
|
|
- <span class="s">"9696:9696"</span>
|
|
<span class="k">restart</span>: <span class="s">unless-stopped</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============================= SETTINGS ============================= -->
|
|
<section class="panel" id="panel-settings">
|
|
<div class="settings-grid">
|
|
<div class="card">
|
|
<div class="card-head"><h2>Storage</h2></div>
|
|
<div class="field-row">
|
|
<label>Storage path</label>
|
|
<div>
|
|
<input type="text" value="/mnt/cache/system/podman" class="mono">
|
|
<div class="hint">Cache pool or dedicated disk — never a path under /mnt/user (FUSE). See Architecture docs.</div>
|
|
</div>
|
|
</div>
|
|
<div class="field-row">
|
|
<label>podman.img size</label>
|
|
<div><input type="number" value="20" style="max-width:100px;"><span class="unit">GB</span>
|
|
<div class="bar" style="max-width:340px; margin-top:10px;"><span style="width:62%"></span></div>
|
|
<div class="hint">62.4 GB used of 20 GB nominal — image will need to grow soon.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-head"><h2>Networking</h2></div>
|
|
<div class="field-row"><label>Default bridge subnet</label><div><input type="text" value="10.89.0.0/24" class="mono"></div></div>
|
|
<div class="field-row"><label>DNS backend</label><div><select><option>aardvark-dns (recommended)</option><option>none</option></select></div></div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-head"><h2>Autostart & Lifecycle</h2></div>
|
|
<div class="field-row"><label>Start podman on array start</label><div><label class="switch"><input type="checkbox" checked><span class="track"></span></label></div></div>
|
|
<div class="field-row"><label>Container stop timeout</label><div><input type="number" value="10" style="max-width:100px;"><span class="unit">seconds</span></div></div>
|
|
<div class="field-row"><label>Autostart Safe-Mode threshold</label><div><input type="number" value="3" style="max-width:100px;"><span class="unit">consecutive failures</span>
|
|
<div class="hint">watchtower is currently paused — see Dashboard.</div></div></div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-head"><h2>Updates</h2><span class="sub">unraid-podman 0.1.0</span></div>
|
|
<div class="field-row"><label>Update channel</label><div><select><option>Stable</option><option>Pre-release</option></select></div></div>
|
|
<div class="field-row"><label>Package versions</label>
|
|
<div class="hint mono" style="max-width:none;">
|
|
podman 6.0.1 · conmon 2.2.1 · crun 1.28 · netavark 2.0.0 · aardvark-dns 2.0.0 · passt git6ef3d1c · fuse-overlayfs 1.17
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card danger-card">
|
|
<div class="card-head"><h2>Danger Zone</h2></div>
|
|
<div class="field-row">
|
|
<label>Roll back plugin</label>
|
|
<div><button class="btn">Restore previous version (0.0.9)</button></div>
|
|
</div>
|
|
<div class="field-row">
|
|
<label>Remove plugin</label>
|
|
<div>
|
|
<button class="btn btn-danger">Uninstall unraid-podman…</button>
|
|
<div class="hint">Your configuration, backups, and container storage are preserved by default. Full data removal requires a separate confirmation.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</main>
|
|
|
|
<footer class="note">Mockup only — static sample data, not connected to a live podman.sock. See webui/README.md.</footer>
|
|
|
|
<script>
|
|
// Tab switching: the only interactive behavior this static mockup needs.
|
|
const buttons = document.querySelectorAll('#subnav button');
|
|
buttons.forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
buttons.forEach(function (b) { b.classList.remove('active'); });
|
|
btn.classList.add('active');
|
|
document.querySelectorAll('.panel').forEach(function (p) { p.classList.remove('active'); });
|
|
const target = document.getElementById('panel-' + btn.dataset.panel);
|
|
if (target) target.classList.add('active');
|
|
});
|
|
});
|
|
</script>
|