diff --git a/assets/design-export/MANIFEST.md b/assets/design-export/MANIFEST.md index 06b8a43cd..b09039f9c 100644 --- a/assets/design-export/MANIFEST.md +++ b/assets/design-export/MANIFEST.md @@ -2550,3 +2550,12 @@ JSON: `Print(JSON.stringify(Get("EkcbF", {depth: 12})))`, `python3 -m json.tool` | `ffmEd` | … (New password typed) | New copy of `OC804`. Masked typed value, no remove button, helper "Write-only". | `Ne5TA` is unaffected. All four have MCP `Get` JSON exports (depth 20, zero elisions; `OC804`/`QFEg9` patched from the prior export with the `Get` result for the changed field subtree) and 2× PNG exports via `Export`. Validated by grepping `Password will be removed` (only `h5clan`) and `Remove password` (`OC804`, `QFEg9`, `h5clan`). + +## Incremental export 2026-10-14 — Central management banner on Modules Catalog and Admin Authentication (PR #685) + +| Node | Name | Change | +| --- | --- | --- | +| `kK8Ji` | Screen/Modules Catalog | Re-exported (1440×1320, was 1440×1150). A copy of the Central management notice from `Wj0V4` (`QOOnO`) now sits in Main between the Top Bar and the Body (`I2bQk`). The App Sidebar instance (`DlcuA`, replaces `h2sWTB`) carries the same overrides as `Wj0V4`'s sidebar (`EXGen`): brand subtitle "Central management" and both "Clusters" and "Cluster" under ADMIN, with Modules as the active item. The frame grew 45px for the notice and a further 125px so Row 3 is no longer clipped. | +| `uMiwd` | Screen/Admin Settings — Authentication | Re-exported (1440×1636, was 1440×1591). Same notice copy (`UosI6`) and the same sidebar overrides as `EXGen` (`bbiZQ`, replaces `WrwBN`), with Settings as the active item. The frame grew 45px for the notice. | + +The app already renders the notice on `/modules` and `/admin`; the maintainer decided on 2026-10-12 that the design follows the app. The Top Bar and Body subtrees of both frames are unchanged, and no reusable component was touched. JSON: the committed export patched with `Get` results (depth 30, path geometry included) for the sidebar instance and the notice, then checked by length and FNV-1a hash against `JSON.stringify(Get(id, …))` of the whole frame on the canvas; `python3 -m json.tool` passes, zero `"..."` elisions. PNG: `Export(ids, "png", …, {scale: 2})` (2880×2640 and 2880×3272). Body-text check: `"Central management."` appears only in `kK8Ji.json`, `uMiwd.json` and `Wj0V4.json`. diff --git a/assets/design-export/json/kK8Ji.json b/assets/design-export/json/kK8Ji.json index e78cc9ff7..ef788a3d9 100644 --- a/assets/design-export/json/kK8Ji.json +++ b/assets/design-export/json/kK8Ji.json @@ -9,33 +9,348 @@ }, "clip": true, "width": 1440, - "height": 1150, + "height": 1320, "fill": "$background/background", "children": [ { - "id": "h2sWTB", + "id": "DlcuA", "type": "ref", "ref": "kKFX9", - "name": "App Sidebar", "height": "fill_container", + "layout": "vertical", + "name": "App Sidebar", "descendants": { - "iTbd8": { - "fill": "#00000000" + "Sci9k": { + "height": 70, + "padding": [ + 16, + 20 + ] + }, + "A42yg4": { + "gap": 8 }, - "iTbd8/ghUsw": { - "fill": "$foreground/foreground" + "t4vZR": { + "width": 32, + "height": 32 }, - "iTbd8/bpSQ7": { - "fill": "$foreground/foreground" + "BuhAS": { + "content": "Central management" }, - "sTSGR": { - "fill": "$accent/soft" + "ijsQu": { + "type": "frame", + "id": "wVY15", + "name": "Nav Content", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "gap": 2, + "padding": [ + 8, + 12 + ], + "children": [ + { + "id": "A8CqgC", + "type": "ref", + "ref": "uFDbF", + "name": "Section General", + "width": "fill_container", + "descendants": { + "gY8Nr": { + "content": "GENERAL" + } + } + }, + { + "id": "t8hOE", + "type": "ref", + "ref": "B5sYje", + "name": "navDashboard", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "ghUsw": { + "icon": "layout-dashboard", + "fill": "$muted" + }, + "bpSQ7": { + "content": "Dashboard", + "fill": "$muted" + } + } + }, + { + "id": "jV8uw", + "type": "ref", + "ref": "W8XiN", + "name": "navServers", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "server" + }, + "Ra9NK": { + "content": "Servers" + } + } + }, + { + "id": "raFQV", + "type": "ref", + "ref": "W8XiN", + "name": "navModules", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "$accent/soft", + "descendants": { + "Sr2Q5": { + "icon": "package", + "fill": "$accent/soft-foreground" + }, + "Ra9NK": { + "content": "Modules", + "fill": "$accent/soft-foreground" + } + } + }, + { + "id": "O0HLrA", + "type": "ref", + "ref": "W8XiN", + "name": "navBackups", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "archive" + }, + "Ra9NK": { + "content": "Backups" + } + } + }, + { + "id": "k0QQxc", + "type": "ref", + "ref": "uFDbF", + "name": "Section Admin", + "width": "fill_container", + "descendants": { + "gY8Nr": { + "content": "ADMIN" + } + } + }, + { + "id": "ubagu", + "type": "ref", + "ref": "W8XiN", + "name": "navCluster", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "network" + }, + "Ra9NK": { + "content": "Clusters" + } + } + }, + { + "id": "Ka2Xf", + "type": "ref", + "ref": "W8XiN", + "name": "Cluster inventory navigation", + "width": "fill_container", + "height": 36, + "padding": [ + 8, + 12 + ], + "gap": 12, + "cornerRadius": 6, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "server" + }, + "Ra9NK": { + "content": "Cluster" + } + } + }, + { + "id": "g7FxN", + "type": "ref", + "ref": "W8XiN", + "name": "navUsers", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "users" + }, + "Ra9NK": { + "content": "Users & RBAC" + } + } + }, + { + "id": "GbLXA", + "type": "ref", + "ref": "W8XiN", + "name": "navAudit", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "scroll-text" + }, + "Ra9NK": { + "content": "Audit log" + } + } + }, + { + "id": "Pd9d1", + "type": "ref", + "ref": "W8XiN", + "name": "navLogs", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "terminal" + }, + "Ra9NK": { + "content": "System logs" + } + } + }, + { + "id": "n2Gdq", + "type": "ref", + "ref": "W8XiN", + "name": "navSettings", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "settings" + }, + "Ra9NK": { + "content": "Settings" + } + } + } + ] }, - "sTSGR/Sr2Q5": { - "fill": "$accent/soft-foreground" + "NmfQu": { + "layout": "vertical" }, - "sTSGR/Ra9NK": { - "fill": "$accent/soft-foreground" + "oByLP": { + "type": "frame", + "id": "gVXWY", + "name": "Toggle Row", + "width": "fill_container", + "gap": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "id": "f5oXbp", + "type": "ref", + "ref": "iA2C8", + "name": "Appearance Toggle", + "fill": "$surface/secondary" + }, + { + "id": "NJR0y", + "type": "ref", + "ref": "owgrI", + "name": "Customize Theme", + "descendants": { + "FOBKk": { + "icon": "palette" + } + } + } + ] } } }, @@ -60,6 +375,49 @@ } } }, + { + "type": "frame", + "id": "I2bQk", + "name": "Central management notice", + "width": "fill_container", + "height": 45, + "fill": "$surface/surface", + "stroke": "$border/border", + "strokeWidth": { + "bottom": 1 + }, + "strokeAlignment": "inner", + "gap": 4, + "padding": [ + 12, + 24 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "aBBe4", + "name": "Central management label", + "fill": "$foreground/foreground", + "content": "Central management.", + "lineHeight": 1.4285714286, + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "500" + }, + { + "type": "text", + "id": "HWUr3", + "name": "Central management explanation", + "fill": "$muted", + "content": "This catalog, account or installation setting belongs to the central Gameplane installation. Server actions use each server’s location.", + "lineHeight": 1.4285714286, + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + } + ] + }, { "type": "frame", "id": "QLcwW", diff --git a/assets/design-export/json/uMiwd.json b/assets/design-export/json/uMiwd.json index d9e77d662..06da29d69 100644 --- a/assets/design-export/json/uMiwd.json +++ b/assets/design-export/json/uMiwd.json @@ -9,95 +9,347 @@ }, "clip": true, "width": 1440, - "height": 1591, + "height": 1636, "fill": "$background/background", "children": [ { - "id": "WrwBN", + "id": "bbiZQ", "type": "ref", "ref": "kKFX9", "height": "fill_container", + "layout": "vertical", "descendants": { - "iTbd8": { - "fill": "#00000000" - }, - "iTbd8/ghUsw": { - "fill": "$foreground/foreground" - }, - "iTbd8/bpSQ7": { - "fill": "$foreground/foreground" - }, - "i8Z1B": { - "fill": "#00000000" - }, - "i8Z1B/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "i8Z1B/Ra9NK": { - "fill": "$foreground/foreground" - }, - "sTSGR": { - "fill": "#00000000" - }, - "sTSGR/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "sTSGR/Ra9NK": { - "fill": "$foreground/foreground" - }, - "NADGL": { - "fill": "#00000000" - }, - "NADGL/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "NADGL/Ra9NK": { - "fill": "$foreground/foreground" - }, - "lZ6pP": { - "fill": "#00000000" - }, - "lZ6pP/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "lZ6pP/Ra9NK": { - "fill": "$foreground/foreground" - }, - "dNsbz": { - "fill": "#00000000" - }, - "dNsbz/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "dNsbz/Ra9NK": { - "fill": "$foreground/foreground" - }, - "uLhzP": { - "fill": "#00000000" - }, - "uLhzP/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "uLhzP/Ra9NK": { - "fill": "$foreground/foreground" + "Sci9k": { + "height": 70, + "padding": [ + 16, + 20 + ] }, - "zV6Z5": { - "fill": "#00000000" + "A42yg4": { + "gap": 8 }, - "zV6Z5/Sr2Q5": { - "fill": "$foreground/foreground" + "t4vZR": { + "width": 32, + "height": 32 }, - "zV6Z5/Ra9NK": { - "fill": "$foreground/foreground" + "BuhAS": { + "content": "Central management" }, - "ya5KB": { - "fill": "$accent/soft" + "ijsQu": { + "type": "frame", + "id": "UfWg3", + "name": "Nav Content", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "gap": 2, + "padding": [ + 8, + 12 + ], + "children": [ + { + "id": "AuPaR", + "type": "ref", + "ref": "uFDbF", + "name": "Section General", + "width": "fill_container", + "descendants": { + "gY8Nr": { + "content": "GENERAL" + } + } + }, + { + "id": "M83pNt", + "type": "ref", + "ref": "B5sYje", + "name": "navDashboard", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "ghUsw": { + "icon": "layout-dashboard", + "fill": "$muted" + }, + "bpSQ7": { + "content": "Dashboard", + "fill": "$muted" + } + } + }, + { + "id": "p4SLRN", + "type": "ref", + "ref": "W8XiN", + "name": "navServers", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "server" + }, + "Ra9NK": { + "content": "Servers" + } + } + }, + { + "id": "YxlR5", + "type": "ref", + "ref": "W8XiN", + "name": "navModules", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "package" + }, + "Ra9NK": { + "content": "Modules" + } + } + }, + { + "id": "Jb5er", + "type": "ref", + "ref": "W8XiN", + "name": "navBackups", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "archive" + }, + "Ra9NK": { + "content": "Backups" + } + } + }, + { + "id": "n2RpJu", + "type": "ref", + "ref": "uFDbF", + "name": "Section Admin", + "width": "fill_container", + "descendants": { + "gY8Nr": { + "content": "ADMIN" + } + } + }, + { + "id": "BKefb", + "type": "ref", + "ref": "W8XiN", + "name": "navCluster", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "network" + }, + "Ra9NK": { + "content": "Clusters" + } + } + }, + { + "id": "HtRdQ", + "type": "ref", + "ref": "W8XiN", + "name": "Cluster inventory navigation", + "width": "fill_container", + "height": 36, + "padding": [ + 8, + 12 + ], + "gap": 12, + "cornerRadius": 6, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "server" + }, + "Ra9NK": { + "content": "Cluster" + } + } + }, + { + "id": "E85j60", + "type": "ref", + "ref": "W8XiN", + "name": "navUsers", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "users" + }, + "Ra9NK": { + "content": "Users & RBAC" + } + } + }, + { + "id": "yuoW5", + "type": "ref", + "ref": "W8XiN", + "name": "navAudit", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "scroll-text" + }, + "Ra9NK": { + "content": "Audit log" + } + } + }, + { + "id": "C1XTGP", + "type": "ref", + "ref": "W8XiN", + "name": "navLogs", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "terminal" + }, + "Ra9NK": { + "content": "System logs" + } + } + }, + { + "id": "ec5Zd", + "type": "ref", + "ref": "W8XiN", + "name": "navSettings", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "$accent/soft", + "descendants": { + "Sr2Q5": { + "icon": "settings", + "fill": "$accent/soft-foreground" + }, + "Ra9NK": { + "content": "Settings", + "fill": "$accent/soft-foreground" + } + } + } + ] }, - "ya5KB/Sr2Q5": { - "fill": "$accent/soft-foreground" + "NmfQu": { + "layout": "vertical" }, - "ya5KB/Ra9NK": { - "fill": "$accent/soft-foreground" + "oByLP": { + "type": "frame", + "id": "qAhr6", + "name": "Toggle Row", + "width": "fill_container", + "gap": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "id": "d1Whiq", + "type": "ref", + "ref": "iA2C8", + "name": "Appearance Toggle", + "fill": "$surface/secondary" + }, + { + "id": "ugAHI", + "type": "ref", + "ref": "owgrI", + "name": "Customize Theme", + "descendants": { + "FOBKk": { + "icon": "palette" + } + } + } + ] } } }, @@ -121,6 +373,49 @@ } } }, + { + "type": "frame", + "id": "UosI6", + "name": "Central management notice", + "width": "fill_container", + "height": 45, + "fill": "$surface/surface", + "stroke": "$border/border", + "strokeWidth": { + "bottom": 1 + }, + "strokeAlignment": "inner", + "gap": 4, + "padding": [ + 12, + 24 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "TpnjI", + "name": "Central management label", + "fill": "$foreground/foreground", + "content": "Central management.", + "lineHeight": 1.4285714286, + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "500" + }, + { + "type": "text", + "id": "zpf0T", + "name": "Central management explanation", + "fill": "$muted", + "content": "This catalog, account or installation setting belongs to the central Gameplane installation. Server actions use each server’s location.", + "lineHeight": 1.4285714286, + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + } + ] + }, { "type": "frame", "id": "OSCt7", diff --git a/assets/design-export/screenshots/kK8Ji.png b/assets/design-export/screenshots/kK8Ji.png index cda832cbe..cd9f9e691 100644 Binary files a/assets/design-export/screenshots/kK8Ji.png and b/assets/design-export/screenshots/kK8Ji.png differ diff --git a/assets/design-export/screenshots/uMiwd.png b/assets/design-export/screenshots/uMiwd.png index 65506de4b..6aa7abe74 100644 Binary files a/assets/design-export/screenshots/uMiwd.png and b/assets/design-export/screenshots/uMiwd.png differ diff --git a/design.pen b/design.pen index 33c7e7d41..cfdbcf720 100644 --- a/design.pen +++ b/design.pen @@ -22862,229 +22862,587 @@ }, "clip": true, "width": 1440, - "height": 1150, + "height": 1320, "fill": "$background/background", "children": [ { - "id": "h2sWTB", + "id": "DlcuA", "type": "ref", "ref": "kKFX9", - "name": "App Sidebar", "height": "fill_container", + "layout": "vertical", + "name": "App Sidebar", "descendants": { - "iTbd8": { - "fill": "#00000000" - }, - "iTbd8/ghUsw": { - "fill": "$foreground/foreground" - }, - "iTbd8/bpSQ7": { - "fill": "$foreground/foreground" + "Sci9k": { + "height": 70, + "padding": [ + 16, + 20 + ] }, - "sTSGR": { - "fill": "$accent/soft" + "A42yg4": { + "gap": 8 }, - "sTSGR/Sr2Q5": { - "fill": "$accent/soft-foreground" + "t4vZR": { + "width": 32, + "height": 32 }, - "sTSGR/Ra9NK": { - "fill": "$accent/soft-foreground" - } - } - }, - { - "type": "frame", - "id": "Ek0FR", - "name": "Main", - "width": "fill_container", - "height": "fill_container", - "fill": "$background/background", - "layout": "vertical", - "children": [ - { - "id": "X6L1I", - "type": "ref", - "ref": "gu5WY", - "name": "Top Bar", - "width": "fill_container", - "descendants": { - "Gl7AY": { - "content": "Modules" - } - } + "BuhAS": { + "content": "Central management" }, - { + "ijsQu": { "type": "frame", - "id": "QLcwW", - "name": "Body", + "id": "wVY15", + "name": "Nav Content", "width": "fill_container", "height": "fill_container", - "fill": "$background/background", "layout": "vertical", - "gap": 24, - "padding": 24, + "gap": 2, + "padding": [ + 8, + 12 + ], "children": [ { - "id": "e3cbS", + "id": "A8CqgC", "type": "ref", - "ref": "xCDF7", - "name": "Page Header", + "ref": "uFDbF", + "name": "Section General", "width": "fill_container", "descendants": { - "spCmk": { - "content": "Modules" - }, - "VbHwU": { - "content": "Pre-packaged game-server templates pulled from your configured module sources." + "gY8Nr": { + "content": "GENERAL" + } + } + }, + { + "id": "t8hOE", + "type": "ref", + "ref": "B5sYje", + "name": "navDashboard", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "ghUsw": { + "icon": "layout-dashboard", + "fill": "$muted" }, - "TgdLz": { - "type": "frame", - "id": "JTfiJ", - "name": "phActions", - "height": 36, - "gap": 8, - "alignItems": "center", - "children": [ - { - "id": "CF440", - "type": "ref", - "ref": "i6gfu", - "name": "btnCreate", - "descendants": { - "FCYQ9": { - "icon": "plus" - }, - "GqVun": { - "content": "Create module" - } - } - }, - { - "id": "lQ1ua", - "type": "ref", - "ref": "i6gfu", - "name": "btnUpload", - "descendants": { - "FCYQ9": { - "icon": "upload" - }, - "GqVun": { - "content": "Upload module" - } - } - }, - { - "id": "luTx8", - "type": "ref", - "ref": "i6gfu", - "name": "btnSources", - "descendants": { - "FCYQ9": { - "icon": "settings-2" - }, - "GqVun": { - "content": "Manage sources" - } - } - } - ] + "bpSQ7": { + "content": "Dashboard", + "fill": "$muted" } } }, { - "type": "frame", - "id": "Q4xve2", - "name": "Filters", + "id": "jV8uw", + "type": "ref", + "ref": "W8XiN", + "name": "navServers", "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], "gap": 12, + "height": 36, "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "gApFT", - "name": "srcGroup", - "fill": "$surface/surface", - "cornerRadius": 6, - "stroke": "$border/border", - "strokeWidth": 1, - "strokeAlignment": "inner", - "gap": 4, - "padding": 4, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "V66sBz", - "name": "srcAll", - "fill": "$accent/soft", - "cornerRadius": 4, - "padding": [ - 6, - 12 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "EDDI8", - "fill": "$accent/accent", - "content": "All sources", - "fontFamily": "$typography/font-sans", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "mcPzn", - "name": "srcDefault", - "cornerRadius": 4, - "padding": [ - 6, - 12 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "qvGHs", - "fill": "$muted", - "content": "default", - "fontFamily": "$typography/font-sans", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "MgxDw", - "name": "srcCommunity", - "cornerRadius": 4, - "padding": [ - 6, - 12 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "jzTDN", - "fill": "$muted", - "content": "community", - "fontFamily": "$typography/font-sans", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "server" }, - { + "Ra9NK": { + "content": "Servers" + } + } + }, + { + "id": "raFQV", + "type": "ref", + "ref": "W8XiN", + "name": "navModules", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "$accent/soft", + "descendants": { + "Sr2Q5": { + "icon": "package", + "fill": "$accent/soft-foreground" + }, + "Ra9NK": { + "content": "Modules", + "fill": "$accent/soft-foreground" + } + } + }, + { + "id": "O0HLrA", + "type": "ref", + "ref": "W8XiN", + "name": "navBackups", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "archive" + }, + "Ra9NK": { + "content": "Backups" + } + } + }, + { + "id": "k0QQxc", + "type": "ref", + "ref": "uFDbF", + "name": "Section Admin", + "width": "fill_container", + "descendants": { + "gY8Nr": { + "content": "ADMIN" + } + } + }, + { + "id": "ubagu", + "type": "ref", + "ref": "W8XiN", + "name": "navCluster", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "network" + }, + "Ra9NK": { + "content": "Clusters" + } + } + }, + { + "id": "Ka2Xf", + "type": "ref", + "ref": "W8XiN", + "name": "Cluster inventory navigation", + "width": "fill_container", + "height": 36, + "padding": [ + 8, + 12 + ], + "gap": 12, + "cornerRadius": 6, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "server" + }, + "Ra9NK": { + "content": "Cluster" + } + } + }, + { + "id": "g7FxN", + "type": "ref", + "ref": "W8XiN", + "name": "navUsers", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "users" + }, + "Ra9NK": { + "content": "Users & RBAC" + } + } + }, + { + "id": "GbLXA", + "type": "ref", + "ref": "W8XiN", + "name": "navAudit", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "scroll-text" + }, + "Ra9NK": { + "content": "Audit log" + } + } + }, + { + "id": "Pd9d1", + "type": "ref", + "ref": "W8XiN", + "name": "navLogs", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "terminal" + }, + "Ra9NK": { + "content": "System logs" + } + } + }, + { + "id": "n2Gdq", + "type": "ref", + "ref": "W8XiN", + "name": "navSettings", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "settings" + }, + "Ra9NK": { + "content": "Settings" + } + } + } + ] + }, + "NmfQu": { + "layout": "vertical" + }, + "oByLP": { + "type": "frame", + "id": "gVXWY", + "name": "Toggle Row", + "width": "fill_container", + "gap": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "id": "f5oXbp", + "type": "ref", + "ref": "iA2C8", + "name": "Appearance Toggle", + "fill": "$surface/secondary" + }, + { + "id": "NJR0y", + "type": "ref", + "ref": "owgrI", + "name": "Customize Theme", + "descendants": { + "FOBKk": { + "icon": "palette" + } + } + } + ] + } + } + }, + { + "type": "frame", + "id": "Ek0FR", + "name": "Main", + "width": "fill_container", + "height": "fill_container", + "fill": "$background/background", + "layout": "vertical", + "children": [ + { + "id": "X6L1I", + "type": "ref", + "ref": "gu5WY", + "name": "Top Bar", + "width": "fill_container", + "descendants": { + "Gl7AY": { + "content": "Modules" + } + } + }, + { + "type": "frame", + "id": "I2bQk", + "name": "Central management notice", + "width": "fill_container", + "height": 45, + "fill": "$surface/surface", + "stroke": "$border/border", + "strokeWidth": { + "bottom": 1 + }, + "strokeAlignment": "inner", + "gap": 4, + "padding": [ + 12, + 24 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "aBBe4", + "name": "Central management label", + "fill": "$foreground/foreground", + "content": "Central management.", + "lineHeight": 1.4285714286, + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "500" + }, + { + "type": "text", + "id": "HWUr3", + "name": "Central management explanation", + "fill": "$muted", + "content": "This catalog, account or installation setting belongs to the central Gameplane installation. Server actions use each server’s location.", + "lineHeight": 1.4285714286, + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "QLcwW", + "name": "Body", + "width": "fill_container", + "height": "fill_container", + "fill": "$background/background", + "layout": "vertical", + "gap": 24, + "padding": 24, + "children": [ + { + "id": "e3cbS", + "type": "ref", + "ref": "xCDF7", + "name": "Page Header", + "width": "fill_container", + "descendants": { + "spCmk": { + "content": "Modules" + }, + "VbHwU": { + "content": "Pre-packaged game-server templates pulled from your configured module sources." + }, + "TgdLz": { + "type": "frame", + "id": "JTfiJ", + "name": "phActions", + "height": 36, + "gap": 8, + "alignItems": "center", + "children": [ + { + "id": "CF440", + "type": "ref", + "ref": "i6gfu", + "name": "btnCreate", + "descendants": { + "FCYQ9": { + "icon": "plus" + }, + "GqVun": { + "content": "Create module" + } + } + }, + { + "id": "lQ1ua", + "type": "ref", + "ref": "i6gfu", + "name": "btnUpload", + "descendants": { + "FCYQ9": { + "icon": "upload" + }, + "GqVun": { + "content": "Upload module" + } + } + }, + { + "id": "luTx8", + "type": "ref", + "ref": "i6gfu", + "name": "btnSources", + "descendants": { + "FCYQ9": { + "icon": "settings-2" + }, + "GqVun": { + "content": "Manage sources" + } + } + } + ] + } + } + }, + { + "type": "frame", + "id": "Q4xve2", + "name": "Filters", + "width": "fill_container", + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "gApFT", + "name": "srcGroup", + "fill": "$surface/surface", + "cornerRadius": 6, + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "gap": 4, + "padding": 4, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "V66sBz", + "name": "srcAll", + "fill": "$accent/soft", + "cornerRadius": 4, + "padding": [ + 6, + 12 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "EDDI8", + "fill": "$accent/accent", + "content": "All sources", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "mcPzn", + "name": "srcDefault", + "cornerRadius": 4, + "padding": [ + 6, + 12 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "qvGHs", + "fill": "$muted", + "content": "default", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "MgxDw", + "name": "srcCommunity", + "cornerRadius": 4, + "padding": [ + 6, + 12 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "jzTDN", + "fill": "$muted", + "content": "community", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { "type": "frame", "id": "r2Sb5t", "name": "catGroup", @@ -32419,114 +32777,366 @@ } ] } - ] + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "uMiwd", + "x": 3280, + "y": 21915, + "name": "Screen/Admin Settings — Authentication", + "theme": { + "semantic": "dark" + }, + "clip": true, + "width": 1440, + "height": 1636, + "fill": "$background/background", + "children": [ + { + "id": "bbiZQ", + "type": "ref", + "ref": "kKFX9", + "height": "fill_container", + "layout": "vertical", + "descendants": { + "Sci9k": { + "height": 70, + "padding": [ + 16, + 20 + ] + }, + "A42yg4": { + "gap": 8 + }, + "t4vZR": { + "width": 32, + "height": 32 + }, + "BuhAS": { + "content": "Central management" + }, + "ijsQu": { + "type": "frame", + "id": "UfWg3", + "name": "Nav Content", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "gap": 2, + "padding": [ + 8, + 12 + ], + "children": [ + { + "id": "AuPaR", + "type": "ref", + "ref": "uFDbF", + "name": "Section General", + "width": "fill_container", + "descendants": { + "gY8Nr": { + "content": "GENERAL" + } + } + }, + { + "id": "M83pNt", + "type": "ref", + "ref": "B5sYje", + "name": "navDashboard", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "ghUsw": { + "icon": "layout-dashboard", + "fill": "$muted" + }, + "bpSQ7": { + "content": "Dashboard", + "fill": "$muted" + } + } + }, + { + "id": "p4SLRN", + "type": "ref", + "ref": "W8XiN", + "name": "navServers", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "server" + }, + "Ra9NK": { + "content": "Servers" + } + } + }, + { + "id": "YxlR5", + "type": "ref", + "ref": "W8XiN", + "name": "navModules", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "package" + }, + "Ra9NK": { + "content": "Modules" + } + } + }, + { + "id": "Jb5er", + "type": "ref", + "ref": "W8XiN", + "name": "navBackups", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "archive" + }, + "Ra9NK": { + "content": "Backups" + } + } + }, + { + "id": "n2RpJu", + "type": "ref", + "ref": "uFDbF", + "name": "Section Admin", + "width": "fill_container", + "descendants": { + "gY8Nr": { + "content": "ADMIN" + } + } + }, + { + "id": "BKefb", + "type": "ref", + "ref": "W8XiN", + "name": "navCluster", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "network" + }, + "Ra9NK": { + "content": "Clusters" + } + } + }, + { + "id": "HtRdQ", + "type": "ref", + "ref": "W8XiN", + "name": "Cluster inventory navigation", + "width": "fill_container", + "height": 36, + "padding": [ + 8, + 12 + ], + "gap": 12, + "cornerRadius": 6, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "server" + }, + "Ra9NK": { + "content": "Cluster" + } + } + }, + { + "id": "E85j60", + "type": "ref", + "ref": "W8XiN", + "name": "navUsers", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "users" + }, + "Ra9NK": { + "content": "Users & RBAC" + } + } + }, + { + "id": "yuoW5", + "type": "ref", + "ref": "W8XiN", + "name": "navAudit", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "scroll-text" + }, + "Ra9NK": { + "content": "Audit log" + } + } + }, + { + "id": "C1XTGP", + "type": "ref", + "ref": "W8XiN", + "name": "navLogs", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "#00000000", + "descendants": { + "Sr2Q5": { + "icon": "terminal" + }, + "Ra9NK": { + "content": "System logs" + } + } + }, + { + "id": "ec5Zd", + "type": "ref", + "ref": "W8XiN", + "name": "navSettings", + "width": "fill_container", + "cornerRadius": 6, + "padding": [ + 8, + 12 + ], + "gap": 12, + "height": 36, + "alignItems": "center", + "fill": "$accent/soft", + "descendants": { + "Sr2Q5": { + "icon": "settings", + "fill": "$accent/soft-foreground" + }, + "Ra9NK": { + "content": "Settings", + "fill": "$accent/soft-foreground" + } + } } ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "uMiwd", - "x": 3280, - "y": 21915, - "name": "Screen/Admin Settings — Authentication", - "theme": { - "semantic": "dark" - }, - "clip": true, - "width": 1440, - "height": 1591, - "fill": "$background/background", - "children": [ - { - "id": "WrwBN", - "type": "ref", - "ref": "kKFX9", - "height": "fill_container", - "descendants": { - "iTbd8": { - "fill": "#00000000" - }, - "iTbd8/ghUsw": { - "fill": "$foreground/foreground" - }, - "iTbd8/bpSQ7": { - "fill": "$foreground/foreground" - }, - "i8Z1B": { - "fill": "#00000000" - }, - "i8Z1B/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "i8Z1B/Ra9NK": { - "fill": "$foreground/foreground" }, - "sTSGR": { - "fill": "#00000000" - }, - "sTSGR/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "sTSGR/Ra9NK": { - "fill": "$foreground/foreground" - }, - "NADGL": { - "fill": "#00000000" - }, - "NADGL/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "NADGL/Ra9NK": { - "fill": "$foreground/foreground" - }, - "lZ6pP": { - "fill": "#00000000" - }, - "lZ6pP/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "lZ6pP/Ra9NK": { - "fill": "$foreground/foreground" - }, - "dNsbz": { - "fill": "#00000000" - }, - "dNsbz/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "dNsbz/Ra9NK": { - "fill": "$foreground/foreground" - }, - "uLhzP": { - "fill": "#00000000" - }, - "uLhzP/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "uLhzP/Ra9NK": { - "fill": "$foreground/foreground" - }, - "zV6Z5": { - "fill": "#00000000" - }, - "zV6Z5/Sr2Q5": { - "fill": "$foreground/foreground" - }, - "zV6Z5/Ra9NK": { - "fill": "$foreground/foreground" - }, - "ya5KB": { - "fill": "$accent/soft" - }, - "ya5KB/Sr2Q5": { - "fill": "$accent/soft-foreground" + "NmfQu": { + "layout": "vertical" }, - "ya5KB/Ra9NK": { - "fill": "$accent/soft-foreground" + "oByLP": { + "type": "frame", + "id": "qAhr6", + "name": "Toggle Row", + "width": "fill_container", + "gap": 8, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "id": "d1Whiq", + "type": "ref", + "ref": "iA2C8", + "name": "Appearance Toggle", + "fill": "$surface/secondary" + }, + { + "id": "ugAHI", + "type": "ref", + "ref": "owgrI", + "name": "Customize Theme", + "descendants": { + "FOBKk": { + "icon": "palette" + } + } + } + ] } } }, @@ -32550,6 +33160,49 @@ } } }, + { + "type": "frame", + "id": "UosI6", + "name": "Central management notice", + "width": "fill_container", + "height": 45, + "fill": "$surface/surface", + "stroke": "$border/border", + "strokeWidth": { + "bottom": 1 + }, + "strokeAlignment": "inner", + "gap": 4, + "padding": [ + 12, + 24 + ], + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "TpnjI", + "name": "Central management label", + "fill": "$foreground/foreground", + "content": "Central management.", + "lineHeight": 1.4285714286, + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "500" + }, + { + "type": "text", + "id": "zpf0T", + "name": "Central management explanation", + "fill": "$muted", + "content": "This catalog, account or installation setting belongs to the central Gameplane installation. Server actions use each server’s location.", + "lineHeight": 1.4285714286, + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + } + ] + }, { "type": "frame", "id": "OSCt7", diff --git a/web/e2e/screenshots/slice-3.spec.ts b/web/e2e/screenshots/slice-3.spec.ts index a52e2a3be..687d07e40 100644 --- a/web/e2e/screenshots/slice-3.spec.ts +++ b/web/e2e/screenshots/slice-3.spec.ts @@ -152,18 +152,22 @@ test.describe("Slice 3: Create Server, Modules, Backups (Desktop — 1440x900) @ await capture(page, "UMJli"); }); - test("kK8Ji: Modules Catalog", async ({ page }) => { + test("kK8Ji: Modules Catalog", async ({ context, page }) => { + await context.addCookies([ + { name: "e2e_modules_variant", value: "design", domain: "localhost", path: "/" }, + ]); await page.goto("/modules"); await expect(page.getByRole("heading", { name: /^modules$/i })).toBeVisible({ timeout: 10_000, }); - // The catalog fixture (buildScreenshotHandlers' /modules/catalog) seeds - // an uninstalled Minecraft entry and an installed Valheim entry. + // The design variant (buildScreenshotHandlers' /modules/catalog, selected by + // the e2e_modules_variant=design cookie) seeds the 11-card design grid: + // installed Minecraft (Java Edition) and an available Factorio, among others. const grid = page.locator('[data-testid="modules-grid"]'); - await expect(grid.getByText("Minecraft (Vanilla)", { exact: true })).toBeVisible({ + await expect(grid.getByText("Minecraft (Java Edition)", { exact: true })).toBeVisible({ timeout: 10_000, }); - await expect(grid.getByText("Valheim", { exact: true })).toBeVisible(); + await expect(grid.getByText("Valheim (Dedicated)", { exact: true })).toBeVisible(); await page.waitForTimeout(200); await capture(page, "kK8Ji"); }); diff --git a/web/e2e/screenshots/slice4.spec.ts b/web/e2e/screenshots/slice4.spec.ts index 2ae2ee9f6..a07f9b672 100644 --- a/web/e2e/screenshots/slice4.spec.ts +++ b/web/e2e/screenshots/slice4.spec.ts @@ -93,7 +93,7 @@ async function clickSection(page: Page, name: string): Promise { // so the rest of the suite is unaffected; must be called before page.goto. async function setAdminConfigVariant( context: BrowserContext, - variant: "oidc" | "oidc-empty-mappings" | "empty-storage-class" | "registries-curseforge", + variant: "oidc" | "oidc-design" | "oidc-empty-mappings" | "empty-storage-class" | "registries-curseforge", ): Promise { await context.addCookies([ { name: "e2e_admin_config_variant", value: variant, domain: "localhost", path: "/" }, @@ -122,7 +122,7 @@ test.describe("Slice 4: Admin, Users, Audit, System logs, Cluster (Desktop — 1 }); test("uMiwd: Admin Settings — Authentication", async ({ page, context }) => { - await setAdminConfigVariant(context, "oidc"); + await setAdminConfigVariant(context, "oidc-design"); await page.goto("/admin"); await clickSection(page, "Authentication"); // Helm OIDC Provider Card (read-only, groups claim + default role). @@ -132,7 +132,7 @@ test.describe("Slice 4: Admin, Users, Audit, System logs, Cluster (Desktop — 1 // AdminSettings.tsx) since the Helm-seeded card above renders the same // group name as a plain read-only chip. await expect(page.getByText("Role mapping overrides")).toBeVisible(); - await expect(page.getByLabel("Admin role mapping").getByText("ops-leads")).toBeVisible(); + await expect(page.getByLabel("Admin role mapping").getByText("gameplane-admins")).toBeVisible(); await page.waitForTimeout(200); await capture(page, "uMiwd"); }); diff --git a/web/src/components/modules/ModuleCard.tsx b/web/src/components/modules/ModuleCard.tsx index 5cc4c0d31..9cab43681 100644 --- a/web/src/components/modules/ModuleCard.tsx +++ b/web/src/components/modules/ModuleCard.tsx @@ -16,6 +16,7 @@ import { useGameCodes } from "@/lib/useGameCodes"; import { resolveCategories } from "@/lib/games"; import type { EntryVerify } from "@/lib/verify"; import type { CatalogEntry } from "@/types"; +import { cn } from "@/lib/utils"; interface ModuleCardProps { entry: CatalogEntry; @@ -95,7 +96,7 @@ export function ModuleCard({ const digest = entry.appliedDigest ?? entry.digest; return ( - +
-
+
{entry.displayName ?? entry.name}
-
{versionLabel}
+
{versionLabel}
{digest && ( -
+
{shortDigest(digest)}
)} {entry.installed && entry.previousVersion && ( -
+
rollback target · v{entry.previousVersion}
)}
{resolveCategories(entry.categories, entry.game ?? entry.name).length > 0 && ( -
+
{resolveCategories(entry.categories, entry.game ?? entry.name).map((c) => ( - + {c} ))}
)} - -

+ +

{entry.summary ?? "No summary."}

{versionUnavailable ? ( @@ -159,7 +160,12 @@ export function ModuleCard({
) : null} - + {entry.sources.length === 1 ? `${entry.sources[0].name} (${entry.sources[0].type})` @@ -261,7 +267,7 @@ function VerifyBadge({ verify }: { verify: EntryVerify }) { size="sm" variant="soft" title={keyless ? "keyless (Fulcio) signature verified" : "signature verified"} - className="font-mono text-[10px] uppercase inline-flex items-center gap-1" + className="font-mono text-[10px] uppercase font-normal inline-flex items-center gap-1" data-color="success" > @@ -275,7 +281,7 @@ function VerifyBadge({ verify }: { verify: EntryVerify }) { size="sm" variant="soft" title={keyless ? "keyless (Fulcio) signature policy declared" : "signature policy declared"} - className="font-mono text-[10px] uppercase inline-flex items-center gap-1" + className="font-mono text-[10px] uppercase font-normal inline-flex items-center gap-1" data-color="success" > @@ -327,7 +333,7 @@ function StatusPill({ entry }: { entry: CatalogEntry }) { color={color} size="sm" variant="soft" - className="font-mono text-[10px] uppercase" + className="font-mono text-[10px] uppercase font-normal" > {label} diff --git a/web/src/components/server/ServerActionsCard.tsx b/web/src/components/server/ServerActionsCard.tsx index b5ec8d260..0e49c11dd 100644 --- a/web/src/components/server/ServerActionsCard.tsx +++ b/web/src/components/server/ServerActionsCard.tsx @@ -210,9 +210,9 @@ export function ServerActionsCard({ const running = phase === "Running"; const canStart = (phase === "Stopped" || phase === "Suspended" || phase === "Failed") && !asleep; return ( - - -

Quick actions

+ + +

Quick actions

{lifecycle.error && ( @@ -289,10 +289,10 @@ export function ServerActionsCard({ className="flex w-full items-start gap-3 rounded-md px-2 py-2 text-left hover:bg-surface disabled:cursor-not-allowed disabled:opacity-50" > - +
-
+
{a.displayName}
{a.description && ( @@ -304,11 +304,11 @@ export function ServerActionsCard({ }; return ( - - -

Quick actions

+ + +

Quick actions

- + {actions.some((a) => !transportReady(a)) && (

{consoleAttachable @@ -350,16 +350,16 @@ export function ServerActionsCard({ {groupOrder.length > 0 ? ( <> {groupOrder.map((groupName) => ( -

-
+
+
{groupName}
{grouped.get(groupName)!.map(renderActionButton)}
))} {ungrouped.length > 0 && ( -
-
+
+
Actions
{ungrouped.map(renderActionButton)} diff --git a/web/src/components/server/ServerSleepCard.tsx b/web/src/components/server/ServerSleepCard.tsx index 017db5766..a92ecdfd9 100644 --- a/web/src/components/server/ServerSleepCard.tsx +++ b/web/src/components/server/ServerSleepCard.tsx @@ -1,5 +1,5 @@ -import { Clock } from "lucide-react"; -import { Card, Alert } from "@heroui/react"; +import { CalendarClock, Megaphone, Moon } from "lucide-react"; +import { Card } from "@heroui/react"; import type { GameServer } from "@/types"; import { formatRelative, capitalize } from "@/lib/utils"; @@ -29,17 +29,15 @@ export function ServerSleepCard({ gs }: { gs?: GameServer }) { const neverSleeps = reason === "this game reports no player count"; return ( - + {/* Header */} -
-
- -

Sleep

-
+
+

Sleep

+
{/* Content */} -
+
{/* Asleep state */} {asleep && ( <> @@ -96,20 +94,18 @@ export function ServerSleepCard({ gs }: { gs?: GameServer }) { {!asleep && !emptySince && reason && ( <> {neverSleeps ? ( - - - ⚠ - - -
-
Will never sleep
-
- {capitalize(reason)}. Idle auto-sleep needs a live player count. This game - exposes none over RCON or a query protocol, so the countdown never starts. -
+ <> +
+ +
+
Will never sleep
+
{capitalize(reason)}.
- - +
+

+ Idle auto-sleep needs a live player count. This game exposes none over RCON or a query protocol, so the countdown never starts. +

+ ) : (
State
@@ -131,32 +127,30 @@ export function ServerSleepCard({ gs }: { gs?: GameServer }) { {/* Configuration */} {spec?.enabled && ( <> -
-
+
+
Configured
-
-
Sleep after
-
+
+
Sleep after
+
{afterMinutes} minute{afterMinutes === 1 ? "" : "s"}
-
-
Wake windows
+
{wakeWindows.length > 0 ? ( -
- {wakeWindows.map((cron, i) => ( -
- {cron} -
- ))} -
+ wakeWindows.map((cron, i) => ( +
+ + {cron} +
+ )) ) : (
Not configured
)} diff --git a/web/src/components/server/ServerStatusCard.tsx b/web/src/components/server/ServerStatusCard.tsx index 70da696d9..4315d4f08 100644 --- a/web/src/components/server/ServerStatusCard.tsx +++ b/web/src/components/server/ServerStatusCard.tsx @@ -53,13 +53,13 @@ export function ServerStatusCard({ ? String(status.agent.playersOnline) : "—"; return ( - -
-

Game status

+ +
+

Game status

-
-
+
+
[r.id, r])); return ( - -
-

Game status

+ +
+

Game status

-
-
+
+
{metrics.map((m) => { const reading = byId.get(m.id); const value = reading?.value?.trim(); return (
{m.displayName}
@@ -115,7 +115,7 @@ export function ServerStatusCard({ // summary — same visual row shape as the declared-metric rows above. function GenericStatusRow({ label, value }: { label: string; value: string }) { return ( -
+
{label}
{value}
diff --git a/web/src/routes/AdminSettings.tsx b/web/src/routes/AdminSettings.tsx index 75f8f8445..99726079f 100644 --- a/web/src/routes/AdminSettings.tsx +++ b/web/src/routes/AdminSettings.tsx @@ -1,7 +1,6 @@ import { useState, useEffect, useRef, type ChangeEvent, type ComponentType, type ReactNode, type SetStateAction } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { - AlertTriangle, Archive, Bell, BellRing, @@ -154,21 +153,35 @@ function SectionCard({ footer, children, className, + headerClassName, + titleClassName, + subtitleClassName, + contentClassName, + footerClassName, }: { title: string; subtitle?: string; footer?: ReactNode; children: ReactNode; className?: string; + headerClassName?: string; + titleClassName?: string; + subtitleClassName?: string; + contentClassName?: string; + footerClassName?: string; }) { return ( - -
{title}
- {subtitle &&
{subtitle}
} + +
{title}
+ {subtitle &&
{subtitle}
}
- {children} - {footer && {footer}} + {children} + {footer && ( + + {footer} + + )}
); } @@ -456,8 +469,14 @@ function AuthSection({ initial, general, installTimeSettings }: { initial?: Auth return (
@@ -471,10 +490,10 @@ function AuthSection({ initial, general, installTimeSettings }: { initial?: Auth
    {f.draft.providers.map((p, idx) => (
  • -
    +
    -
    +
    {p.displayName || p.name}
    {p.kind} @@ -491,8 +510,8 @@ function AuthSection({ initial, general, installTimeSettings }: { initial?: Auth : undefined } className={cn( - "rounded px-2 py-0.5 text-[10px] font-mono uppercase disabled:cursor-not-allowed disabled:opacity-60", - p.enabled ? "bg-success/15 text-success" : "bg-muted/15 text-muted", + "rounded-full px-1 py-0.5 text-[11px] font-medium font-sans uppercase disabled:cursor-not-allowed disabled:opacity-60", + p.enabled ? "bg-success-soft text-success-soft-foreground" : "bg-default text-muted", )} > {p.enabled ? "Enabled" : "Disabled"} @@ -511,23 +530,23 @@ function AuthSection({ initial, general, installTimeSettings }: { initial?: Auth ))} {helm && (
  • -
    +
    -
    +
    {helm.label}
    oidc · configured via Helm — manage in values.yaml
    - + Enabled
  • )}
{lastToggleLocked && ( -

+

At least one identity provider must stay enabled — the last enabled provider can't be turned off.

@@ -546,8 +565,13 @@ function AuthSection({ initial, general, installTimeSettings }: { initial?: Auth onClose={() => setAdding(false)} /> ) : ( - )} @@ -1925,40 +1949,50 @@ function HelmOIDCProviderCard({ provider }: { provider: OIDCHelmProvider }) { const adminMapped = provider.roleMappings?.admin && provider.roleMappings.admin.length > 0; return (
Provider
-

+

Groups claim and default role are seeded from the same Helm values and are not editable here.

Groups claim - {provider.groupsClaim} + {provider.groupsClaim}
Default role - + {provider.defaultRole}
{hasRoleMappings ? ( -
+
Helm-seeded role mappings
{["admin", "operator", "viewer"].map((role) => { const groups = provider.roleMappings?.[role as keyof typeof provider.roleMappings]; return ( -
- {role} -
+
+ {role} +
{groups && groups.length > 0 ? ( groups.map((group) => ( {group} @@ -1983,9 +2017,9 @@ function HelmOIDCProviderCard({ provider }: { provider: OIDCHelmProvider }) {
)} {adminMapped && ( -
- -
+
+ +
Helm-configured admin mapping

The group(s) on the Admin row above were mapped to admin via Helm values, not the dashboard, so there @@ -2133,10 +2167,15 @@ function RoleMappingOverridesCard({ -

+

Saved changes take effect on next login for affected users — no restart or reinstall needed.

@@ -2146,14 +2185,20 @@ function RoleMappingOverridesCard({ same text (e.g. a save error) appears twice in the DOM, breaking any singular getByText/findByText query for it — see the "surfaces a backend rejection when saving the auth section" test. */} -
} > -
+
{["admin", "operator", "viewer"].map((role) => { const typedRole = role as "admin" | "operator" | "viewer"; const groups = getRoleGroups(typedRole); @@ -2171,16 +2216,17 @@ function RoleMappingOverridesCard({ return (
{role !== "admin" &&
} -
+
- {role} + {role}
{hasOverride && (
{groups.length > 0 ? ( -
+
{groups.map((group) => ( ) : ( -
+
No groups mapped — nobody signs in as {role} via OIDC.
)} -
- setInput(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - handleAddGroup(typedRole, input); - } - }} - className="text-xs" - /> +
+
+ setInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + handleAddGroup(typedRole, input); + } + }} + className="h-8 text-[13px]" + /> +
diff --git a/web/src/routes/Modules.tsx b/web/src/routes/Modules.tsx index dbdb09c0e..9e122c741 100644 --- a/web/src/routes/Modules.tsx +++ b/web/src/routes/Modules.tsx @@ -129,13 +129,13 @@ export function ModulesPage() { }); return ( -
+
- {uploadSources.length > 0 && ( @@ -168,7 +168,7 @@ export function ModulesPage() { ))}
-
+
{catChips.map((c) => (
-
- - -

Connection

+
+ + + + +

Connection

- + {/* Tunnel row — only when a tunnel endpoint actually exists; per design (EZFW0) this row doesn't appear at all otherwise. */} {primary?.tunnelProvider && ( @@ -287,8 +289,6 @@ export function OverviewTab({ - -
@@ -333,34 +333,34 @@ function ResourceCard({ }) { const accentClass = { primary: "bg-accent", - success: "bg-success", + success: "bg-success-soft-foreground", warning: "bg-warning", violet: "bg-violet", }[accent ?? "primary"]; const accentText = { primary: "text-accent", - success: "text-success", + success: "text-success-soft-foreground", warning: "text-warning", violet: "text-violet", }[accent ?? "primary"]; const history = useMetricHistory(trend); return ( - -
+ +
{label} {icon}
-
{value}
- +
{value}
+ {progress !== undefined && ( -
+
)} - {sub &&
{sub}
} + {sub &&
{sub}
} ); } @@ -369,9 +369,9 @@ function ResourceCard({ // bordered rows for each connection value, most with a copy affordance. function EndpointRow({ label, children }: { label: string; children: ReactNode }) { return ( -
- {label} -
{children}
+
+ {label} +
{children}
); } @@ -392,29 +392,29 @@ function PlayersCard({ roster === undefined || roster.capabilities !== undefined; return ( - - + +
-

+

Players online ·{" "} - {onlineUnknown ? "—" : online} + {onlineUnknown ? "—" : online}

{rows.length > 0 && ( {rows.length} listed )}
- + {!supported ? ( -

+

Player list not supported for this game.

) : onlineUnknown ? ( -

Player count unknown.

+

Player count unknown.

) : online === 0 ? ( -

No players connected.

+

No players connected.

) : rows.length === 0 ? ( -

+

{online} online · names not yet available.

) : ( diff --git a/web/src/test/handlers.ts b/web/src/test/handlers.ts index dd6249304..29d74231f 100644 --- a/web/src/test/handlers.ts +++ b/web/src/test/handlers.ts @@ -29,6 +29,9 @@ import { screenshotConsoleOutput, screenshotSystemLogLines, screenshotConfigWithOidc, + screenshotConfigOidcDesign, + screenshotDesignCatalog, + screenshotDesignSources, screenshotConfigOidcEmptyMappings, screenshotConfigEmptyStorageClass, screenshotConfigCurseforgeOnly, @@ -974,11 +977,18 @@ export function buildScreenshotHandlers() { ...server, spec: { ...server.spec, - idle: { enabled: true, afterMinutes: 60 }, + idle: { ...server.spec.idle, enabled: true, afterMinutes: 30 }, }, status: { ...server.status, idle: { reason: "this game reports no player count" }, + agent: { + ...server.status?.agent, + memoryBytes: 1610612736, // 1.5 GiB -> "1.5 GB", 38% of limit + memoryLimitBytes: 4294967296, // 4.0 GiB -> "4.0 GB" + diskUsedBytes: 3972844749, // 3.7 GiB -> "3.7 GB", 13% of total + diskTotalBytes: 31138512896, // 29 GiB -> "29 GB" + }, }, }); } @@ -1344,8 +1354,12 @@ export function buildScreenshotHandlers() { // Modules http.get("/modules", () => HttpResponse.json({ items: [makeModule()] })), - http.get("/modules/catalog", () => - HttpResponse.json({ + http.get("/modules/catalog", ({ cookies }) => { + // kK8Ji design variant: selected by the e2e_modules_variant=design cookie. + if (cookies.e2e_modules_variant === "design") { + return HttpResponse.json({ items: screenshotDesignCatalog }); + } + return HttpResponse.json({ items: [ makeCatalog(), makeCatalog({ @@ -1360,11 +1374,14 @@ export function buildScreenshotHandlers() { latestVersion: "0.218", }), ], - }), - ), - http.get("/modules/sources", () => - HttpResponse.json({ items: [makeModuleSource()] }), - ), + }); + }), + http.get("/modules/sources", ({ cookies }) => { + if (cookies.e2e_modules_variant === "design") { + return HttpResponse.json({ items: screenshotDesignSources }); + } + return HttpResponse.json({ items: [makeModuleSource()] }); + }), http.get("/modules/:name", ({ params }) => HttpResponse.json(makeModule({ metadata: { name: String(params.name) } })), ), @@ -1383,8 +1400,11 @@ export function buildScreenshotHandlers() { http.delete("/modules/:name", () => new HttpResponse(null, { status: 204 })), // Server events: return events for the requested server - http.get("/servers/:name/events", ({ params }) => { + http.get("/servers/:name/events", ({ params, cookies }) => { const name = String(params.name); + if (name === "mc-survival" && cookies.e2e_server_variant === "never-sleeps") { + return HttpResponse.json([]); + } const serverEvents = data.events.filter((e) => e.object === name); return HttpResponse.json(serverEvents); }), @@ -1627,6 +1647,8 @@ export function buildScreenshotHandlers() { switch (cookies.e2e_admin_config_variant) { case "oidc": return HttpResponse.json(screenshotConfigWithOidc()); + case "oidc-design": + return HttpResponse.json(screenshotConfigOidcDesign()); case "oidc-empty-mappings": return HttpResponse.json(screenshotConfigOidcEmptyMappings()); case "empty-storage-class": diff --git a/web/src/test/screenshotData.ts b/web/src/test/screenshotData.ts index 6c4248bc4..8e7698fd9 100644 --- a/web/src/test/screenshotData.ts +++ b/web/src/test/screenshotData.ts @@ -11,6 +11,8 @@ import type { ServerEvent, InstalledMod, RegistryProject, + CatalogEntry, + ModuleSource, } from "@/types"; import type { AllConfig } from "@/lib/config"; import { @@ -1043,6 +1045,42 @@ export function screenshotConfigWithOidc(): AllConfig { }; } +// Variant of screenshotConfigWithOidc() matching the uMiwd design frame +// (Admin settings — Authentication). Used ONLY by uMiwd, selected via the +// "e2e_admin_config_variant=oidc-design" cookie. The shared "oidc" variant +// is kept as-is: the R65Xyx/Rwnu3/XL5ZU/vStkb/uw0dB design crops carry the +// ops-leads / ops-team / everyone chip text. +export function screenshotConfigOidcDesign(): AllConfig { + const base = screenshotConfig(); + return { + ...base, + auth: { + providers: [ + { name: "local", displayName: "Local accounts", kind: "local", enabled: true }, + ], + helmOverride: { + // operator key deliberately absent so its badge reads "From Helm values". + roleMappings: { + admin: ["gameplane-admins", "gameplane-sre"], + viewer: [], + }, + }, + }, + installTimeSettings: { + gameDataStorageClass: "fast-nvme", + oidcHelmProvider: { + groupsClaim: "teams", + defaultRole: "viewer", + roleMappings: { + admin: ["gameplane-admins"], + operator: ["gameplane-ops"], + viewer: ["gameplane-members"], + }, + }, + }, + }; +} + // Variant of screenshotConfig() with a Helm-seeded OIDC provider that // declares no role mappings at all, and no dashboard override either — // the "no OIDC mappings yet" empty state. Backs both nNGDX (the empty-state @@ -1387,6 +1425,215 @@ export const screenshotTemplateConfigSchemas: Record< ], }; +// ============================================================================ +// Modules catalog design variant (kK8Ji "Screen/Modules Catalog"). Served by +// buildScreenshotHandlers only when the "e2e_modules_variant=design" cookie is +// set; the default /modules/catalog and /modules/sources responses are kept +// as-is for every other screenshot spec. Order matches the design grid. +// ============================================================================ + +const DESIGN_CATALOG_SOURCE_DEFAULT = { name: "default", type: "git" as const }; +const DESIGN_CATALOG_SOURCE_COMMUNITY = { name: "community", type: "oci" as const }; + +export const screenshotDesignCatalog: CatalogEntry[] = [ + { + name: "minecraft-java", + displayName: "Minecraft (Java Edition)", + summary: "Vanilla / Paper / Spigot / Bukkit / Purpur / Forge / NeoForge / Fabric / Quilt servers with a version+loader picker and in-app Modrinth browse.", + game: "minecraft-java", + categories: ["Sandbox", "Survival", "Modded"], + sources: [DESIGN_CATALOG_SOURCE_DEFAULT], + versions: ["1.21.4", "1.21.3"], + latestVersion: "1.21.4", + installed: true, + installedVersion: "1.21.4", + installedFrom: "default", + moduleName: "minecraft-java", + phase: "Ready", + appliedDigest: "sha256:a1b2c3d4…9e2f", + verifiedDigest: "sha256:a1b2c3d4…9e2f", + verifyPolicy: "keyless", + previousVersion: "1.21.3", + }, + { + name: "minecraft-bedrock", + displayName: "Minecraft (Bedrock)", + summary: "Cross-platform edition for mobile, console and Windows clients. Allow-list and permissions managed from the wizard.", + game: "minecraft-bedrock", + categories: ["Sandbox", "Survival", "Creative"], + sources: [DESIGN_CATALOG_SOURCE_DEFAULT], + versions: ["1.21.5"], + latestVersion: "1.21.5", + installed: true, + installedVersion: "1.21.5", + installedFrom: "default", + moduleName: "minecraft-bedrock", + phase: "Ready", + appliedDigest: "sha256:5f0ab913…c47d", + }, + { + name: "valheim-default", + displayName: "Valheim (Dedicated)", + summary: "Valheim dedicated server (lloesche/valheim-server) with a stable/public-test channel picker, a BepInEx mod volume, and in-app Thunderstore browse. Crossplay-friendly.", + game: "valheim", + categories: ["Survival", "Co-op", "Building"], + sources: [DESIGN_CATALOG_SOURCE_DEFAULT], + versions: ["2.3.0"], + latestVersion: "2.3.0", + installed: true, + installedVersion: "2.3.0", + installedFrom: "default", + moduleName: "valheim", + phase: "Ready", + appliedDigest: "sha256:9c14e07b…3ba8", + }, + { + name: "factorio-vanilla", + displayName: "Factorio (Headless)", + summary: "Factorio headless server (factoriotools/factorio) with a stable/latest/pinned channel picker, server-settings managed from the wizard, and a mods volume with in-app Factorio mod portal browse.", + game: "factorio", + categories: ["Simulation", "Building", "Sandbox"], + sources: [DESIGN_CATALOG_SOURCE_DEFAULT], + versions: ["1.1.0"], + latestVersion: "1.1.0", + installed: false, + digest: "sha256:7d32e46a…1161", + }, + { + name: "palworld-default", + displayName: "Palworld (Dedicated)", + summary: "Palworld dedicated server (thijsvanloef/palworld-server-docker) with RCON console, save-aware graceful stop, and a .pak mod volume with archive install.", + game: "palworld", + categories: ["Survival", "Co-op", "Modded"], + sources: [DESIGN_CATALOG_SOURCE_DEFAULT], + versions: ["1.1.0"], + latestVersion: "1.1.0", + installed: true, + installedVersion: "1.1.0", + installedFrom: "default", + moduleName: "palworld", + phase: "Ready", + appliedDigest: "sha256:2e91fa04…8d17", + }, + { + name: "ark-ascended", + displayName: "ARK: Survival Evolved", + summary: "Dinosaur survival on the Unreal engine. Mod install via the Steam workshop; schedule onto a GPU node for acceptable tick rates.", + game: "ark", + categories: ["Survival", "Modded", "PvP"], + sources: [DESIGN_CATALOG_SOURCE_DEFAULT], + versions: ["0.9.2"], + latestVersion: "0.9.2", + installed: false, + digest: "sha256:b7440ce1…05af", + }, + { + name: "terraria-vanilla", + displayName: "Terraria", + summary: "Terraria + tModLoader dedicated server (passivelemon/terraria-docker) with a vanilla/tModLoader version picker and a tModLoader ModPacks volume. Console via container stdin.", + game: "terraria", + categories: ["Sandbox", "Survival", "Adventure"], + sources: [DESIGN_CATALOG_SOURCE_DEFAULT], + versions: ["2.1.0"], + latestVersion: "2.1.0", + installed: true, + installedVersion: "2.1.0", + installedFrom: "default", + moduleName: "terraria", + phase: "Ready", + appliedDigest: "sha256:c805d7f2…46e9", + }, + { + name: "cs2-competitive", + displayName: "Counter-Strike 2", + summary: "Competitive shooter with MatchZy and CounterStrikeSharp plugin support. Game-server login token supplied as a secret.", + game: "counter-strike-2", + categories: ["Shooter", "PvP"], + sources: [DESIGN_CATALOG_SOURCE_DEFAULT], + versions: ["0.4.1"], + latestVersion: "0.4.1", + installed: false, + digest: "sha256:1a6be390…f72c", + }, + { + name: "rust-vanilla", + displayName: "Rust", + summary: "Hardcore multiplayer survival with Oxide and Carbon mod loaders. Monthly forced wipe; schedule a backup before each one.", + game: "rust", + categories: ["Survival", "PvP", "Modded"], + sources: [DESIGN_CATALOG_SOURCE_COMMUNITY], + versions: ["0.7.3"], + latestVersion: "0.7.3", + installed: false, + digest: "sha256:3fd0817c…ba24", + }, + { + name: "7-days-to-die", + displayName: "7 Days to Die", + summary: "Zombie survival crafting in a destructible voxel world. Blood-moon horde nights are CPU-spiky — give it headroom.", + game: "7-days-to-die", + categories: ["Survival", "Horror", "Building"], + sources: [DESIGN_CATALOG_SOURCE_COMMUNITY], + versions: ["0.5.0"], + latestVersion: "0.5.0", + installed: false, + digest: "sha256:e2c93b58…7a0d", + }, + { + name: "satisfactory", + displayName: "Satisfactory (Dedicated)", + summary: "Satisfactory dedicated server (wolveix/satisfactory-server) with a stable/experimental branch picker. Env-configured; installs via steamcmd on first boot.", + game: "satisfactory", + categories: ["Simulation", "Building", "Sandbox"], + sources: [DESIGN_CATALOG_SOURCE_DEFAULT], + versions: ["1.0.0"], + latestVersion: "1.0.0", + installed: true, + installedVersion: "1.0.0", + installedFrom: "default", + moduleName: "satisfactory", + phase: "Ready", + appliedDigest: "sha256:4b7ecf19…d3e6", + }, +]; + +// Sources for the design variant: "default" (git, unverified), "community" +// (oci, keyless cosign policy: the POLICY badge source) and one upload-type +// source so the "Upload module" button renders (Modules.tsx gates it on +// upload sources). Chips come from catalog entries, not from this list. +export const screenshotDesignSources: ModuleSource[] = [ + { + metadata: { name: "default" }, + spec: { + type: "git", + git: { url: "https://github.com/GameplanePanel/module.git", ref: "main" }, + }, + status: { lastSync: "2026-10-12T08:00:00Z", modules: [] }, + }, + { + metadata: { name: "community" }, + spec: { + type: "oci", + oci: { + url: "ghcr.io/example/community-modules", + modules: [{ name: "rust-vanilla" }, { name: "7-days-to-die" }], + }, + verify: { + keyless: { + issuer: "https://token.actions.githubusercontent.com", + identity: "https://github.com/example/community-modules/.github/workflows/release.yml@refs/heads/main", + }, + }, + }, + status: { lastSync: "2026-10-12T08:00:00Z", modules: [] }, + }, + { + metadata: { name: "uploads" }, + spec: { type: "upload" }, + status: { lastSync: "2026-10-12T08:00:00Z", modules: [] }, + }, +]; + export function getScreenshotData() { return { templates: screenshotTemplates,