diff --git a/assets/design-export/MANIFEST.md b/assets/design-export/MANIFEST.md index 86e555500..21d0c4060 100644 --- a/assets/design-export/MANIFEST.md +++ b/assets/design-export/MANIFEST.md @@ -2779,3 +2779,21 @@ Redraws the Spec 019 language pass on the regrouped canvas. It replaces the 2026 **Re-exported:** `n6Xlo` was re-exported in full from `Get(id, {depth: 30, includePathGeometry: true})` (sink Language rows added). **Verification method:** for each of the 16 spec 019 JSON exports (`lWvcv`, `T1wkiT`, `N1GkB`, `gX7um`, `WZdnw`, `g2HLxz`, `vp8BJ`, `HKEl2`, `lCTci`, `GsofJ`, `SUcad`, `z3mtgc`, `S0P4bl`, `Ii9Hy`, `TSXYO`, `n6Xlo`), the live export `JSON.stringify(Get(id, {depth: 30, includePathGeometry: true}))` and the file `JSON.stringify(JSON.parse(file))` were compared by length and FNV-1a 32-bit hash over UTF-16 code units. All 16 match. None contains the `"..."` truncation marker. + +## Incremental export 2026-10-31 — Reserve less than the limit (Burstable QoS) on Resources settings and Create Server step 3 (PR #815) + +| Node | Name | Change | +| --- | --- | --- | +| `E0ypH` | Screen/Server Detail — Settings · Resources | Re-exported (1440×900, unchanged size; y 41967 → 42130). New row `xuyWG` "Field Reserve less" under CPU cores and Memory (GiB): a copy of the Lifecycle "Field Auto-restart" row with the label "Reserve less than the limit", the help text "Off: the server reserves its full CPU and memory (Guaranteed QoS). On: …", and a `Gameplane/Switch/Off` (`rh2QH`) instance. The Auto-restart row's state text is not copied: the app shows the switch alone. | +| `NqaaU` | Screen/Server Detail — Settings · Resources (Reserve less on) | New copy of `E0ypH` to its right (1440×1000). Switch is `Gameplane/Switch/On` (`hl7R3`); CPU and memory hints read "Limit: the most CPU/memory this server can use."; two new rows built from the CPU and memory rows (slider + number + unit): "Reserved CPU" (1 core, hint "Counted when the server is placed on a node. 0 reserves nothing.") and "Reserved memory (GiB)" (2 GiB, hint "When a node runs out of memory, servers above their reservation are stopped first. Keep it close to real use."). The frame is 100px taller so StorageClass is not clipped. | +| `vUqMl` | Screen/Create Server — Step 3 Configure | Re-exported (1440×1989, was 1440×1920). New block `pL1xq` "ReserveLess" under the CPU / Memory pair: a `Gameplane/Switch/Off` instance with "Reserve less than the limit" beside it and the 11px helper "Only the reserved amount counts when the server is placed on a node, so more servers fit; …". The frame and ModalContainer grew 69px to keep the modal's 77px margins. | +| `NLu2I` | Screen/Create Server — Step 3 Configure (Reserve less on) | New copy of `vUqMl` to its right (1440×2083). Switch is `Gameplane/Switch/On`; a second two-column row `w0lzM` "ReservedRow", a copy of the CPU / Memory "ResourceRow", reads "Reserved CPU" (1 core) and "Reserved memory" (2 GiB) with half-filled sliders and no "Max … (largest node)" helper, as in `CreateServer.tsx`. | + +Source: `web/src/routes/tabs/settings/Resources.tsx` and the Configure step of `web/src/routes/CreateServer.tsx` on branch `feat/web-resource-reservation`. No reusable component was changed. `pen-componentize` `audit.js`: nothing new on the four frames; the findings it lists for `vUqMl`/`NLu2I` (`CodeBlockWrap` ≈ `Gameplane/YAML Block`, the "Node placement" segmented control) predate this change and are left as they were. `check-layout.js`: `NO ISSUES`. + +**Exports:** JSON from `Get(id, {depth: 80, includePathGeometry: true})`, pretty-printed (zero `"..."` elisions, `python3 -m json.tool` passes; `vUqMl.json` keeps its CRLF line endings). PNG from `Export(ids, "png", …, {scale: 2})`: `E0ypH` 2880×1800, `NqaaU` 2880×2000, `vUqMl` 2880×3978, `NLu2I` 2880×4166. Done in a headless `pen interactive` session (pen.dev CLI 0.3.14); unpkg.com is blocked in that sandbox, so the lucide icon font was served from the `lucide-static@0.563.0` npm tarball (same file unpkg serves). Body-text checks: `"Counted when the server is placed on a node"` only in `NqaaU.json`; `"Off: the server reserves its full CPU"` in `E0ypH.json` and `NqaaU.json`; `"Only the reserved amount counts when"` in `vUqMl.json` and `NLu2I.json`; `"Reserved memory"` in `NLu2I.json` and `NqaaU.json`. + +**Row moves (JSON root `y` only, no PNG change):** Row 07 grew from 1920 to 2083 (tallest item `NLu2I`) and Row 23 from 900 to 1000 (tallest item `NqaaU`), so every root node below Row 07 moved down 163px and every root node below Row 23 a further 100px (pen-canvas-layout procedure A). The committed exports of the moved nodes were patched in place: + +- +163 (Rows 08–23): `a7o9C`, `akr4I`, `b4eaUf`, `Bbnga`, `BguST`, `Burtr`, `dBILX`, `dPP50`, `e9FnC`, `eGbsg`, `EZFW0`, `f1Vga`, `FtdkI`, `GayoL`, `hLB9Z`, `hlwx3`, `Hy9r0`, `IzuY2`, `j88bl0`, `KhYNc`, `kPmoo`, `lhKzN`, `m5kOm4`, `O08uaD`, `o4LH8W`, `OC804`, `oMuyw`, `P08Uw`, `pssCT`, `QFEg9`, `QQtUD`, `Ss0Yr`, `sSISK`, `SUtGZ`, `swxkJ`, `sZtDi`, `TE2jI`, `tY6RD`, `U9xSg`, `uCA23`, `UMJli`, `V1VhGE`, `VctzT`, `w1QYmf`, `wxINm`, `X405l`, `x7MJI`, `Xn5ns`, `xvlB6`, `YN8cE`, `ZU1BG` +- +263 (Rows 24–57): `aMU7O`, `Be7ZW`, `Bq2Yg`, `bYDHC`, `C2LQE4`, `C9bR6`, `Cb3eC`, `cDiNb`, `D9QYZ`, `DBQyF`, `dhPtX`, `Dpb9f`, `DPrYX`, `dQV9N`, `dxdEi`, `DxKOh`, `e9lV4`, `EcoGD`, `EKJyM`, `epZO2`, `EWrn0`, `fbBqA`, `FBiMN`, `fK8Bi`, `FOVMf`, `g5mEpx`, `gWqMD`, `hmPL7`, `HVhmb`, `i1bLR`, `i8wib`, `IdbiB`, `iLm38`, `IMSD5`, `IyMFM`, `iYUWc`, `J5pjJ3`, `j9W8A`, `jXykV`, `K5XinZ`, `KaRFX`, `kK8Ji`, `l64bd`, `l7TRz9`, `lCTci`, `lWvcv`, `M2sA4u`, `n2qdi`, `n6Xlo`, `Ne5TA`, `nGJE1`, `NKiqf`, `nNGDX`, `O5kaV`, `oPF1n`, `p8QSPq`, `pnAbF`, `q31B6w`, `qFLfB`, `QgW58`, `QpEvu`, `r0heh`, `r139UP`, `RC3Kf`, `RodrS`, `RpHBg`, `RS0KZ`, `SUcad`, `T1wkiT`, `TBvTC`, `tr6cE`, `tSOeI`, `tTSdi`, `ugDSa`, `uMiwd`, `uoxQW`, `Uzzje`, `VfB0Y`, `Wj0V4`, `WrmU5`, `WZdnw`, `xCJlu`, `XR0f9`, `y3YcQ`, `Y5cmvI`, `yrxK2`, `Z0oyh`, `ZlLf0`, `zM0VF`, `zqzr4` diff --git a/assets/design-export/json/Bbnga.json b/assets/design-export/json/Bbnga.json index 90220b4e1..89d87181b 100644 --- a/assets/design-export/json/Bbnga.json +++ b/assets/design-export/json/Bbnga.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Bbnga", "x": 0, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture (Not enabled)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/Be7ZW.json b/assets/design-export/json/Be7ZW.json index 0e581ac32..c111fa6e0 100644 --- a/assets/design-export/json/Be7ZW.json +++ b/assets/design-export/json/Be7ZW.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Be7ZW", "x": 4920, - "y": 63669, + "y": 63932, "name": "Screen/Standalone — Cluster inventory (No selection)", "context": "PR #630. Selection is explicit and only registered remotes are listed. No implicit local target; no workload requests until installation and selected target are known.", "theme": { diff --git a/assets/design-export/json/BguST.json b/assets/design-export/json/BguST.json index 375cfb6c4..1995a9295 100644 --- a/assets/design-export/json/BguST.json +++ b/assets/design-export/json/BguST.json @@ -2,7 +2,7 @@ "type": "frame", "id": "BguST", "x": 6560, - "y": 40607, + "y": 40770, "name": "Screen/Server Detail — Settings · Game configuration (Password will be removed)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/Bq2Yg.json b/assets/design-export/json/Bq2Yg.json index a00242ed2..25a1b7aab 100644 --- a/assets/design-export/json/Bq2Yg.json +++ b/assets/design-export/json/Bq2Yg.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Bq2Yg", "x": 0, - "y": 72769, + "y": 73032, "name": "Screen/Admin — System Logs", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/Burtr.json b/assets/design-export/json/Burtr.json index f8c9ec7ca..e41fd5ac6 100644 --- a/assets/design-export/json/Burtr.json +++ b/assets/design-export/json/Burtr.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Burtr", "x": 0, - "y": 30207, + "y": 30370, "name": "Screen/Server Detail — Files", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/C2LQE4.json b/assets/design-export/json/C2LQE4.json index a05ee9873..01093d144 100644 --- a/assets/design-export/json/C2LQE4.json +++ b/assets/design-export/json/C2LQE4.json @@ -2,7 +2,7 @@ "type": "frame", "id": "C2LQE4", "x": 0, - "y": 91265, + "y": 91528, "name": "Screen/Share Link — Up", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/C9bR6.json b/assets/design-export/json/C9bR6.json index 286343018..19b2e9332 100644 --- a/assets/design-export/json/C9bR6.json +++ b/assets/design-export/json/C9bR6.json @@ -4,7 +4,7 @@ "ref": "x3beP", "name": "Gameplane/Dialog/Edit user — Standalone (Remote grant)", "x": 2320, - "y": 66269, + "y": 66532, "theme": { "semantic": "dark" }, diff --git a/assets/design-export/json/Cb3eC.json b/assets/design-export/json/Cb3eC.json index aa02d23a1..56794339b 100644 --- a/assets/design-export/json/Cb3eC.json +++ b/assets/design-export/json/Cb3eC.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Cb3eC", "x": 1640, - "y": 60819, + "y": 61082, "name": "Screen/Backups — Schedules filters open", "context": "Implemented at 8e08a60f. Filter fields are drafts; Apply commits, Clear resets drafts, dismissal discards edits. Server choices retain location and namespace.", "theme": { diff --git a/assets/design-export/json/D9QYZ.json b/assets/design-export/json/D9QYZ.json index a02192bfb..4f4fd8fd5 100644 --- a/assets/design-export/json/D9QYZ.json +++ b/assets/design-export/json/D9QYZ.json @@ -2,7 +2,7 @@ "type": "frame", "id": "D9QYZ", "x": 1640, - "y": 81968, + "y": 82231, "name": "Screen/Standalone — Settings · Backup destinations", "context": "PR #630 standalone installation. No local Kubernetes metadata or local workload fallback. Master-key storage and offline rotation have no dashboard controls.", "theme": { diff --git a/assets/design-export/json/DBQyF.json b/assets/design-export/json/DBQyF.json index 992ace422..7569113b7 100644 --- a/assets/design-export/json/DBQyF.json +++ b/assets/design-export/json/DBQyF.json @@ -2,7 +2,7 @@ "type": "frame", "id": "DBQyF", "x": 6560, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry (disabled)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/DPrYX.json b/assets/design-export/json/DPrYX.json index a4f237464..f2195ae3d 100644 --- a/assets/design-export/json/DPrYX.json +++ b/assets/design-export/json/DPrYX.json @@ -2,7 +2,7 @@ "type": "frame", "id": "DPrYX", "x": 0, - "y": 59519, + "y": 59782, "name": "Screen/Backups — Index", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/Dpb9f.json b/assets/design-export/json/Dpb9f.json index 7ae99c1e7..025d309f4 100644 --- a/assets/design-export/json/Dpb9f.json +++ b/assets/design-export/json/Dpb9f.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Dpb9f", "x": 0, - "y": 70169, + "y": 70432, "name": "Screen/Users & RBAC — Identity providers", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/DxKOh.json b/assets/design-export/json/DxKOh.json index 4295822a4..e2d52e668 100644 --- a/assets/design-export/json/DxKOh.json +++ b/assets/design-export/json/DxKOh.json @@ -2,7 +2,7 @@ "type": "frame", "id": "DxKOh", "x": 0, - "y": 71469, + "y": 71732, "name": "Screen/Audit Log", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/E0ypH.json b/assets/design-export/json/E0ypH.json index 20e96a8e7..b01dfcc8b 100644 --- a/assets/design-export/json/E0ypH.json +++ b/assets/design-export/json/E0ypH.json @@ -2,7 +2,7 @@ "type": "frame", "id": "E0ypH", "x": 0, - "y": 41967, + "y": 42130, "name": "Screen/Server Detail — Settings · Resources", "theme": { "semantic": "dark" @@ -369,6 +369,66 @@ } ] }, + { + "type": "frame", + "id": "xuyWG", + "name": "Field Reserve less", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "oWMYX", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "AWqw1", + "name": "t", + "fill": "$foreground/foreground", + "content": "Reserve less than the limit", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "Ce3T8", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "Off: the server reserves its full CPU and memory (Guaranteed QoS). On: it reserves less, so more servers fit on a node, and uses more up to its limit when the node has room (Burstable QoS).", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "q9ZQKc", + "name": "ctrl", + "width": "fill_container", + "height": 28, + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "XFdCR", + "type": "ref", + "ref": "rh2QH", + "name": "Switch", + "width": 36, + "height": 20 + } + ] + } + ] + }, { "type": "frame", "id": "djYXg", diff --git a/assets/design-export/json/EKJyM.json b/assets/design-export/json/EKJyM.json index 4a718d42a..f43c588d4 100644 --- a/assets/design-export/json/EKJyM.json +++ b/assets/design-export/json/EKJyM.json @@ -2,7 +2,7 @@ "type": "frame", "id": "EKJyM", "x": 8200, - "y": 43267, + "y": 43530, "name": "Server Detail · Settings · Networking · Provider + credentials complete", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/EWrn0.json b/assets/design-export/json/EWrn0.json index 6ef5653c7..a75f36c57 100644 --- a/assets/design-export/json/EWrn0.json +++ b/assets/design-export/json/EWrn0.json @@ -2,7 +2,7 @@ "type": "frame", "id": "EWrn0", "x": 4920, - "y": 55687, + "y": 55950, "name": "Screen/Standalone — Modules (East selected)", "context": "Background refetch failures display an inline retry banner and retain existing data plus open install/upload/builder/source drafts. Only an initial failure replaces loading content. Explicit target changes discard drafts.", "theme": { diff --git a/assets/design-export/json/EZFW0.json b/assets/design-export/json/EZFW0.json index ea6af2585..251cb37c0 100644 --- a/assets/design-export/json/EZFW0.json +++ b/assets/design-export/json/EZFW0.json @@ -2,7 +2,7 @@ "type": "frame", "id": "EZFW0", "x": 0, - "y": 24182, + "y": 24345, "name": "Screen/Server Detail — Overview", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/EcoGD.json b/assets/design-export/json/EcoGD.json index 00411e999..99c1d1aad 100644 --- a/assets/design-export/json/EcoGD.json +++ b/assets/design-export/json/EcoGD.json @@ -2,7 +2,7 @@ "type": "frame", "id": "EcoGD", "x": 4920, - "y": 91265, + "y": 91528, "name": "Screen/Share Link — Starting", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/FBiMN.json b/assets/design-export/json/FBiMN.json index e0248d2f5..1323aee89 100644 --- a/assets/design-export/json/FBiMN.json +++ b/assets/design-export/json/FBiMN.json @@ -2,7 +2,7 @@ "type": "frame", "id": "FBiMN", "x": 3280, - "y": 91265, + "y": 91528, "name": "Screen/Share Link — Asleep (Start cooldown)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/FOVMf.json b/assets/design-export/json/FOVMf.json index 36369ffb6..ea41d3ea1 100644 --- a/assets/design-export/json/FOVMf.json +++ b/assets/design-export/json/FOVMf.json @@ -2,7 +2,7 @@ "type": "frame", "id": "FOVMf", "x": 1640, - "y": 89965, + "y": 90228, "name": "Screen/Standalone — Settings · About", "context": "Panel build version comes from authenticated installation metadata; standalone About does not fetch local Kubernetes metadata.", "theme": { diff --git a/assets/design-export/json/FtdkI.json b/assets/design-export/json/FtdkI.json index 65a15b137..581f8a438 100644 --- a/assets/design-export/json/FtdkI.json +++ b/assets/design-export/json/FtdkI.json @@ -2,7 +2,7 @@ "type": "frame", "id": "FtdkI", "x": 1640, - "y": 28907, + "y": 29070, "name": "Screen/Server Detail — Logs (Failed)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/GayoL.json b/assets/design-export/json/GayoL.json index 333576bea..4f9c87768 100644 --- a/assets/design-export/json/GayoL.json +++ b/assets/design-export/json/GayoL.json @@ -2,7 +2,7 @@ "type": "frame", "id": "GayoL", "x": 3280, - "y": 31507, + "y": 31670, "name": "Screen/Server Detail — Mods — Browse (added)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/HVhmb.json b/assets/design-export/json/HVhmb.json index bf2053afb..bb92aab0b 100644 --- a/assets/design-export/json/HVhmb.json +++ b/assets/design-export/json/HVhmb.json @@ -2,7 +2,7 @@ "type": "frame", "id": "HVhmb", "x": 8200, - "y": 63669, + "y": 63932, "name": "Screen/Standalone — Remove cluster", "context": "Removal errors stay inside the confirmation and reset when dismissed or reopened. Successful removal clears a matching current selection to empty without selecting local or another remote.", "theme": { diff --git a/assets/design-export/json/Hy9r0.json b/assets/design-export/json/Hy9r0.json index e3d1bad98..f492edc2e 100644 --- a/assets/design-export/json/Hy9r0.json +++ b/assets/design-export/json/Hy9r0.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Hy9r0", "x": 6560, - "y": 24182, + "y": 24345, "name": "Screen/Server Detail — Overview (Idle armed)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/IMSD5.json b/assets/design-export/json/IMSD5.json index 6cfd4b3eb..52bc2e038 100644 --- a/assets/design-export/json/IMSD5.json +++ b/assets/design-export/json/IMSD5.json @@ -2,7 +2,7 @@ "type": "frame", "id": "IMSD5", "x": 3280, - "y": 54387, + "y": 54650, "name": "Screen/Server Detail \u2014 Settings \u00b7 Danger zone (Owner-only)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/IdbiB.json b/assets/design-export/json/IdbiB.json index 9a6dc30f7..174a5f428 100644 --- a/assets/design-export/json/IdbiB.json +++ b/assets/design-export/json/IdbiB.json @@ -2,7 +2,7 @@ "type": "frame", "id": "IdbiB", "x": 0, - "y": 58137, + "y": 58400, "name": "Screen/Dialog/Build Module — Step 1 (Preset & Metadata)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/IyMFM.json b/assets/design-export/json/IyMFM.json index 022ffa530..e4ec56a63 100644 --- a/assets/design-export/json/IyMFM.json +++ b/assets/design-export/json/IyMFM.json @@ -2,7 +2,7 @@ "type": "frame", "id": "IyMFM", "x": 9840, - "y": 43267, + "y": 43530, "name": "J5pjJ3 states/Tunnel enabled", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/IzuY2.json b/assets/design-export/json/IzuY2.json index 1fd8e490d..b4fcb5f58 100644 --- a/assets/design-export/json/IzuY2.json +++ b/assets/design-export/json/IzuY2.json @@ -2,7 +2,7 @@ "type": "frame", "id": "IzuY2", "x": 3280, - "y": 24182, + "y": 24345, "name": "Screen/Server Detail — Overview (Never sleeps)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/J5pjJ3.json b/assets/design-export/json/J5pjJ3.json index eead28622..e716eb0ee 100644 --- a/assets/design-export/json/J5pjJ3.json +++ b/assets/design-export/json/J5pjJ3.json @@ -2,7 +2,7 @@ "type": "frame", "id": "J5pjJ3", "x": 0, - "y": 43267, + "y": 43530, "name": "Server Detail · Settings · Networking · Complete", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/K5XinZ.json b/assets/design-export/json/K5XinZ.json index 78bd19f0b..8f89fdf0c 100644 --- a/assets/design-export/json/K5XinZ.json +++ b/assets/design-export/json/K5XinZ.json @@ -2,7 +2,7 @@ "type": "frame", "id": "K5XinZ", "x": 8200, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry (none)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/KaRFX.json b/assets/design-export/json/KaRFX.json index a70068e6d..a2a876de1 100644 --- a/assets/design-export/json/KaRFX.json +++ b/assets/design-export/json/KaRFX.json @@ -2,7 +2,7 @@ "type": "frame", "id": "KaRFX", "x": 0, - "y": 47887, + "y": 48150, "name": "Screen/Server Detail — Settings · Scheduled backups", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/KhYNc.json b/assets/design-export/json/KhYNc.json index 8b7dfbb54..b4a796572 100644 --- a/assets/design-export/json/KhYNc.json +++ b/assets/design-export/json/KhYNc.json @@ -2,7 +2,7 @@ "type": "frame", "id": "KhYNc", "x": 1640, - "y": 31507, + "y": 31670, "name": "Screen/Server Detail — Mods (by ID)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/M2sA4u.json b/assets/design-export/json/M2sA4u.json index b3267bca0..cbf309b45 100644 --- a/assets/design-export/json/M2sA4u.json +++ b/assets/design-export/json/M2sA4u.json @@ -2,7 +2,7 @@ "type": "frame", "id": "M2sA4u", "x": 0, - "y": 88665, + "y": 88928, "name": "Screen/Admin Settings — Updates", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/NKiqf.json b/assets/design-export/json/NKiqf.json index 30b07891e..40a57a1e7 100644 --- a/assets/design-export/json/NKiqf.json +++ b/assets/design-export/json/NKiqf.json @@ -2,7 +2,7 @@ "type": "frame", "id": "NKiqf", "x": 3280, - "y": 59519, + "y": 59782, "name": "Screen/Mobile — Backups filters open", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/NLu2I.json b/assets/design-export/json/NLu2I.json new file mode 100644 index 000000000..0e8b087e8 --- /dev/null +++ b/assets/design-export/json/NLu2I.json @@ -0,0 +1,1718 @@ +{ + "type": "frame", + "id": "NLu2I", + "x": 1640, + "y": 18792, + "name": "Screen/Create Server — Step 3 Configure (Reserve less on)", + "theme": { + "semantic": "dark" + }, + "clip": true, + "width": 1440, + "height": 2083, + "fill": "$background/background", + "children": [ + { + "id": "X6KaEm", + "type": "ref", + "ref": "kKFX9", + "name": "Sidebar", + "height": "fill_container", + "descendants": { + "iTbd8": { + "fill": "#00000000" + }, + "iTbd8/ghUsw": { + "fill": "$foreground/foreground" + }, + "iTbd8/bpSQ7": { + "fill": "$foreground/foreground" + }, + "i8Z1B": { + "fill": "$accent/soft" + }, + "i8Z1B/Sr2Q5": { + "fill": "$accent/soft-foreground" + }, + "i8Z1B/Ra9NK": { + "fill": "$accent/soft-foreground" + } + } + }, + { + "type": "frame", + "id": "h0BQw", + "name": "Main", + "width": "fill_container", + "height": "fill_container", + "fill": "$background/background", + "layout": "vertical", + "children": [ + { + "id": "rNbjK", + "type": "ref", + "ref": "gu5WY", + "name": "TopBar", + "width": "fill_container", + "descendants": { + "Gl7AY": { + "content": "New" + } + } + } + ] + }, + { + "type": "frame", + "id": "ZPGUD", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "ModalContainer", + "width": 1440, + "height": 2083, + "fill": "#000000B3", + "layout": "vertical", + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "P0qcMy", + "name": "Modal", + "clip": true, + "width": 960, + "fill": "$surface/surface", + "cornerRadius": 12, + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#00000080", + "offset": { + "x": 0, + "y": 24 + }, + "blur": 60 + }, + "layout": "vertical", + "children": [ + { + "id": "OEG8W", + "type": "ref", + "ref": "opmcF", + "name": "Header", + "width": "fill_container", + "descendants": { + "gcM6g": { + "content": "Step 3 of 5 · Configure" + } + } + }, + { + "type": "frame", + "id": "nZAjc", + "name": "Location placement", + "context": "Only authorized template/location combinations; changing location starts a fresh draft. Freeze chosen target during submission.", + "width": "fill_container", + "stroke": "$border/border", + "strokeWidth": { + "bottom": 1 + }, + "layout": "vertical", + "gap": 5, + "padding": [ + 12, + 24 + ], + "children": [ + { + "type": "text", + "id": "mtHaM", + "name": "Location label", + "fill": "$foreground/foreground", + "content": "Location", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "id": "vX9vX", + "type": "ref", + "ref": "AT7ya", + "name": "Creation location", + "width": "fill_container", + "descendants": { + "cFsFk": { + "content": "remote-lab / gameplane-games", + "fill": "$foreground/foreground" + } + } + }, + { + "type": "text", + "id": "O4SCTZ", + "name": "Changing location help", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Changing location starts a fresh draft. The chosen location handles all creation steps.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "id": "TktvJ", + "type": "ref", + "ref": "oROhg", + "name": "Stepper", + "width": "fill_container", + "descendants": { + "F3Au7j": { + "fill": "#00000000", + "stroke": "$border/border" + }, + "jq5x7": { + "fill": "$muted" + }, + "bSERJ": { + "fill": "$muted", + "fontWeight": "normal" + }, + "xzVlE": { + "fill": "$accent/soft", + "stroke": "$accent/accent" + }, + "F3O7DI": { + "fill": "$accent/accent" + }, + "FqG7u": { + "fill": "$foreground/foreground", + "fontWeight": "500" + } + } + }, + { + "type": "frame", + "id": "Srvqs", + "name": "Body", + "width": "fill_container", + "gap": 12, + "padding": 24, + "children": [ + { + "type": "frame", + "id": "jigt8", + "name": "StepBody", + "width": "fill_container", + "layout": "vertical", + "gap": 12, + "children": [ + { + "id": "SVtQw", + "type": "ref", + "ref": "mhMaM", + "name": "Field Server name", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "Server name" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner" + }, + "O0VQ2": { + "content": "mc-survival", + "fill": "$foreground/foreground" + }, + "kl54d": { + "width": "fill_container" + }, + "vdTcx": { + "content": "Used as pod name and subdomain. Lowercase, dash-separated.", + "textGrowth": "fixed-width", + "width": "fill_container", + "lineHeight": 1.4 + } + } + }, + { + "id": "NwxYN", + "type": "ref", + "ref": "LCQU3", + "name": "Field Description", + "width": "fill_container", + "descendants": { + "it5w4": { + "content": "Description" + }, + "DzfNP": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 72 + }, + "usWsj": { + "content": "Hardcore survival realm with curated mods. Invite only.", + "fill": "$foreground/foreground" + }, + "Z9i98U": { + "opacity": 0, + "height": 0 + } + } + }, + { + "type": "frame", + "id": "q8Et7T", + "name": "ResourceRow", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "r92klp", + "name": "Field", + "width": "fill_container", + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "gLxq2", + "name": "Label", + "opacity": 0, + "fill": "$muted", + "content": "CPU", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "gTZ7d", + "name": "CtrlRow", + "width": "fill_container", + "gap": 8, + "alignItems": "center", + "children": [ + { + "id": "Aitgu", + "type": "ref", + "ref": "KWcnQ", + "name": "CpuSlider", + "width": "fill_container", + "descendants": { + "x2jDfP": { + "content": "CPU" + }, + "lJsFB": { + "width": 170 + } + } + }, + { + "id": "KzWwE", + "type": "ref", + "ref": "aRqX6", + "name": "CpuValue", + "width": 64, + "descendants": { + "ANxU1": { + "opacity": 0, + "height": 0 + }, + "K5pqlT": { + "height": 36, + "justifyContent": "center", + "fill": "$field/background", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner" + }, + "hokJ3": { + "content": "2", + "fill": "$foreground/foreground", + "fontWeight": "500" + }, + "OiyAM": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "M89954", + "type": "ref", + "ref": "gNnkz", + "name": "CpuUnit", + "width": 110, + "descendants": { + "s5vl0": { + "opacity": 0, + "height": 0 + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "cores", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted", + "x": 82, + "y": 10 + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + } + ] + }, + { + "type": "text", + "id": "B8NdB", + "name": "Helper", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Max 4 (largest node)", + "lineHeight": 1.4, + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "YOXcw", + "name": "Field", + "width": "fill_container", + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "YPxHO", + "name": "Label", + "opacity": 0, + "fill": "$muted", + "content": "Memory", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "S1V2ab", + "name": "CtrlRow", + "width": "fill_container", + "gap": 8, + "alignItems": "center", + "children": [ + { + "id": "jTSnP", + "type": "ref", + "ref": "KWcnQ", + "name": "MemSlider", + "width": "fill_container", + "descendants": { + "x2jDfP": { + "content": "Memory" + }, + "lJsFB": { + "width": 156 + } + } + }, + { + "id": "TpLw6", + "type": "ref", + "ref": "aRqX6", + "name": "MemValue", + "width": 64, + "descendants": { + "ANxU1": { + "opacity": 0, + "height": 0 + }, + "K5pqlT": { + "height": 36, + "justifyContent": "center", + "fill": "$field/background", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner" + }, + "hokJ3": { + "content": "4", + "fill": "$foreground/foreground", + "fontWeight": "500" + }, + "OiyAM": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "Qx9tv", + "type": "ref", + "ref": "gNnkz", + "name": "MemUnit", + "width": 110, + "descendants": { + "s5vl0": { + "opacity": 0, + "height": 0 + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "GiB", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted", + "x": 82, + "y": 10 + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + } + ] + }, + { + "type": "text", + "id": "nRzf7", + "name": "Helper", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Max 13 GiB (largest node)", + "lineHeight": 1.4, + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "f1qkX", + "name": "ReserveLess", + "width": "fill_container", + "layout": "vertical", + "gap": 6, + "children": [ + { + "type": "frame", + "id": "OkCYS", + "name": "SwitchRow", + "width": "fill_container", + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "rOrhH", + "type": "ref", + "ref": "hl7R3", + "name": "Switch", + "width": 36, + "height": 20 + }, + { + "type": "text", + "id": "jKGK6", + "name": "Label", + "fill": "$foreground/foreground", + "content": "Reserve less than the limit", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + } + ] + }, + { + "type": "text", + "id": "F7PXER", + "name": "Helper", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Only the reserved amount counts when the server is placed on a node, so more servers fit; it can use more, up to the limit, when the node has room (Burstable QoS).", + "lineHeight": 1.4, + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "w0lzM", + "name": "ReservedRow", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "aBe3q", + "name": "Field", + "width": "fill_container", + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "e73GX", + "name": "Label", + "opacity": 0, + "fill": "$muted", + "content": "Reserved CPU", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "zV9Wc", + "name": "CtrlRow", + "width": "fill_container", + "gap": 8, + "alignItems": "center", + "children": [ + { + "id": "gYGPQ", + "type": "ref", + "ref": "KWcnQ", + "name": "ReservedCpuSlider", + "width": "fill_container", + "descendants": { + "x2jDfP": { + "content": "Reserved CPU" + }, + "lJsFB": { + "width": 58 + }, + "tES7p": { + "x": 45 + } + } + }, + { + "id": "BiGfM", + "type": "ref", + "ref": "aRqX6", + "name": "ReservedCpuValue", + "width": 64, + "descendants": { + "ANxU1": { + "opacity": 0, + "height": 0 + }, + "K5pqlT": { + "height": 36, + "justifyContent": "center", + "fill": "$field/background", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner" + }, + "hokJ3": { + "content": "1", + "fill": "$foreground/foreground", + "fontWeight": "500" + }, + "OiyAM": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "eYGak", + "type": "ref", + "ref": "gNnkz", + "name": "ReservedCpuUnit", + "width": 110, + "descendants": { + "s5vl0": { + "opacity": 0, + "height": 0 + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "cores", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted", + "x": 82, + "y": 10 + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "lH7lt", + "name": "Field", + "width": "fill_container", + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "sfe01", + "name": "Label", + "opacity": 0, + "fill": "$muted", + "content": "Reserved memory", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "qeJ1m", + "name": "CtrlRow", + "width": "fill_container", + "gap": 8, + "alignItems": "center", + "children": [ + { + "id": "zKPtD", + "type": "ref", + "ref": "KWcnQ", + "name": "ReservedMemSlider", + "width": "fill_container", + "descendants": { + "x2jDfP": { + "content": "Reserved memory" + }, + "lJsFB": { + "width": 58 + }, + "tES7p": { + "x": 45 + } + } + }, + { + "id": "QdGZi", + "type": "ref", + "ref": "aRqX6", + "name": "ReservedMemValue", + "width": 64, + "descendants": { + "ANxU1": { + "opacity": 0, + "height": 0 + }, + "K5pqlT": { + "height": 36, + "justifyContent": "center", + "fill": "$field/background", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner" + }, + "hokJ3": { + "content": "2", + "fill": "$foreground/foreground", + "fontWeight": "500" + }, + "OiyAM": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "zmspD", + "type": "ref", + "ref": "gNnkz", + "name": "ReservedMemUnit", + "width": 110, + "descendants": { + "s5vl0": { + "opacity": 0, + "height": 0 + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "GiB", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted", + "x": 82, + "y": 10 + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "i9SAhi", + "name": "StoragePlacementRow", + "width": "fill_container", + "gap": 16, + "children": [ + { + "id": "ns3t0", + "type": "ref", + "ref": "mhMaM", + "name": "Field Persistent storage", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "Persistent storage" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "20Gi", + "fill": "$foreground/foreground" + }, + "kl54d": { + "width": "fill_container" + }, + "vdTcx": { + "content": "Mounted at /data (RWO PVC).", + "textGrowth": "fixed-width", + "width": "fill_container", + "lineHeight": 1.4 + } + } + }, + { + "type": "frame", + "id": "qRtCa", + "name": "NodePlacement", + "width": "fill_container", + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "UnwYg", + "name": "Label", + "fill": "$muted", + "content": "Node placement", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "v7FdYO", + "name": "Segmented", + "fill": "#00000000", + "cornerRadius": 6, + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "gap": 4, + "padding": 4, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "mA6So", + "name": "Seg", + "height": 28, + "fill": "$accent/soft", + "cornerRadius": 4, + "padding": [ + 0, + 12 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "p81wg8", + "name": "Label", + "fill": "$accent/accent", + "content": "Auto (scheduler)", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "tKLyt", + "name": "Seg", + "height": 28, + "cornerRadius": 4, + "padding": [ + 0, + 12 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "ZNoac", + "name": "Label", + "fill": "$muted", + "content": "Pin to node", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "KaPuu", + "name": "Seg", + "height": 28, + "cornerRadius": 4, + "padding": [ + 0, + 12 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "v2ASt", + "name": "Label", + "fill": "$muted", + "content": "GPU-enabled", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "text", + "id": "DSX7s", + "name": "Helper", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Picks node with most free memory, tolerations or no taints.", + "lineHeight": 1.4, + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "yLDMY", + "name": "TemplateConfigSection", + "width": "fill_container", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "text", + "id": "Ig1Y9", + "name": "SectionLabel", + "fill": "$muted", + "content": "TEMPLATE CONFIGURATION", + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "500" + }, + { + "id": "FcgZ7", + "type": "ref", + "ref": "gNnkz", + "name": "Field Difficulty", + "width": "fill_container", + "descendants": { + "s5vl0": { + "content": "Difficulty", + "opacity": 1, + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "easy", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted" + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "JhPiI", + "type": "ref", + "ref": "gNnkz", + "name": "Field Game mode", + "width": "fill_container", + "descendants": { + "s5vl0": { + "content": "Game mode", + "opacity": 1, + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "survival", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted" + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "JOCqo", + "type": "ref", + "ref": "mhMaM", + "name": "Field Max players", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "Max players", + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "20", + "fill": "$foreground/foreground" + }, + "kl54d": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "ijbEY", + "type": "ref", + "ref": "mhMaM", + "name": "Field MOTD", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "MOTD", + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "A Gameplane Minecraft server", + "fill": "$foreground/foreground" + }, + "kl54d": { + "width": "fill_container" + }, + "vdTcx": { + "content": "Server description shown in the multiplayer list.", + "textGrowth": "fixed-width", + "width": "fill_container", + "lineHeight": 1.4 + } + } + }, + { + "id": "g9fuEp", + "type": "ref", + "ref": "mhMaM", + "name": "Field World seed", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "World seed", + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "", + "fill": "$field/placeholder" + }, + "kl54d": { + "width": "fill_container" + }, + "vdTcx": { + "content": "Leave blank for a random seed.", + "textGrowth": "fixed-width", + "width": "fill_container", + "lineHeight": 1.4 + } + } + }, + { + "id": "MYk2Z", + "type": "ref", + "ref": "gNnkz", + "name": "Field Enforce whitelist", + "width": "fill_container", + "descendants": { + "s5vl0": { + "content": "Enforce whitelist", + "opacity": 1, + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "false", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted" + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "WIB9C", + "type": "ref", + "ref": "gNnkz", + "name": "Field PvP enabled", + "width": "fill_container", + "descendants": { + "s5vl0": { + "content": "PvP enabled", + "opacity": 1, + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "true", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted" + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "xJfWl", + "type": "ref", + "ref": "gNnkz", + "name": "Field Hardcore mode", + "width": "fill_container", + "descendants": { + "s5vl0": { + "content": "Hardcore mode", + "opacity": 1, + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "false", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted" + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "aR0J1", + "type": "ref", + "ref": "gNnkz", + "name": "Field Online mode", + "width": "fill_container", + "descendants": { + "s5vl0": { + "content": "Online mode", + "opacity": 1, + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "true", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted" + }, + "Qx06J": { + "content": "Verify players with Mojang. Disable only for offline/LAN setups." + } + } + }, + { + "id": "qXglj", + "type": "ref", + "ref": "mhMaM", + "name": "Field View distance (chunks)", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "View distance (chunks)", + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "10", + "fill": "$foreground/foreground" + }, + "kl54d": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "Umoe1", + "type": "ref", + "ref": "mhMaM", + "name": "Field JVM max heap", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "JVM max heap", + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "Auto: 75% of the memory limit", + "fill": "$field/placeholder" + }, + "kl54d": { + "width": "fill_container" + }, + "vdTcx": { + "content": "Java heap ceiling (-Xmx, e.g. 2G, 3072M). Leave empty to size it automatically to 75% of the container memory limit — it then tracks the limit whenever you resize the server. If you set it yourself, keep roughly 25% below the limit: the JVM needs headroom for non-heap memory, and exceeding the limit gets the server OOM-killed.", + "textGrowth": "fixed-width", + "width": "fill_container", + "lineHeight": 1.4 + } + } + }, + { + "id": "pvoOT", + "type": "ref", + "ref": "mhMaM", + "name": "Field JVM initial heap", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "JVM initial heap", + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "Auto: 75% of the memory limit", + "fill": "$field/placeholder" + }, + "kl54d": { + "width": "fill_container" + }, + "vdTcx": { + "content": "Starting heap size (-Xms), at most JVM max heap. Leave empty to match the automatic max heap (a fixed heap avoids GC resizes).", + "textGrowth": "fixed-width", + "width": "fill_container", + "lineHeight": 1.4 + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "u42GK", + "name": "Preview", + "clip": true, + "width": 273, + "fill": "$surface/surface", + "cornerRadius": 8, + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "JdmfO", + "name": "PreviewHeader", + "width": "fill_container", + "stroke": "$border/border", + "strokeWidth": { + "bottom": 1 + }, + "strokeAlignment": "inner", + "strokeLinejoin": "round", + "gap": 8, + "padding": [ + 12, + 14 + ], + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "C4qTzk", + "name": "IconWrap", + "width": 28, + "height": 28, + "fill": "$default/default", + "cornerRadius": 6, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "axnLm", + "name": "GameIcon", + "width": 16, + "height": 16, + "icon": "settings", + "library": "Material Symbols Sharp", + "weight": 400, + "fill": "$muted" + } + ] + }, + { + "type": "frame", + "id": "rYygo", + "name": "TitleGroup", + "layout": "vertical", + "gap": 2, + "children": [ + { + "type": "text", + "id": "KAMsb", + "name": "Title", + "fill": "$foreground/foreground", + "content": "Minecraft (Java Edition)", + "fontFamily": "$typography/font-sans", + "fontSize": 13, + "fontWeight": "500" + }, + { + "type": "text", + "id": "BzBnC", + "name": "Subtitle", + "fill": "$muted", + "content": "Template: minecraft-java", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "g88dd", + "name": "CodeBlockWrap", + "width": "fill_container", + "fill": "#0a0a0a", + "layout": "vertical", + "gap": 2, + "padding": [ + 12, + 14 + ], + "children": [ + { + "type": "text", + "id": "Owk4f", + "name": "L0", + "fill": "$muted", + "content": "apiVersion: gameplane.local/v1alpha1", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "G6GfW", + "name": "L1", + "fill": "$muted", + "content": "kind: GameServer", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "F12pr", + "name": "L2", + "fill": "$muted", + "content": "metadata:", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "fFgyz", + "name": "L3", + "fill": "$muted", + "content": " name: mc-survival", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "WyHg3", + "name": "L4", + "fill": "$muted", + "content": "spec:", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "D3vnEw", + "name": "L5", + "fill": "$muted", + "content": " template: minecraft-java", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "Mhjne", + "name": "L6", + "fill": "$muted", + "content": " version: fabric-1.21.4", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "K2E9Zu", + "name": "L7", + "fill": "$muted", + "content": " resources:", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "szBfM", + "name": "L8", + "fill": "$muted", + "content": " cpu: 2000m", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "GCfWV", + "name": "L9", + "fill": "$muted", + "content": " memory: 6Gi", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "QIYPA", + "name": "L10", + "fill": "$muted", + "content": " storage: 50Gi", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "YYT4O", + "name": "L11", + "fill": "$muted", + "content": " config:", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "EVNoR", + "name": "L12", + "fill": "$muted", + "content": " motd: \"A Gameplane server\"", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "kcWFZ", + "name": "L13", + "fill": "$muted", + "content": " maxPlayers: 20", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "uVshr", + "name": "L14", + "fill": "$muted", + "content": " difficulty: hard", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "gS7l4", + "name": "L15", + "fill": "$muted", + "content": " gamemode: survival", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "R0WN43", + "name": "L16", + "fill": "$muted", + "content": " onlineMode: true", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "hpdCH", + "name": "MemoryTip", + "width": "fill_container", + "stroke": "$border/border", + "strokeWidth": { + "top": 1 + }, + "strokeAlignment": "inner", + "strokeLinejoin": "round", + "padding": [ + 12, + 14 + ], + "children": [ + { + "type": "text", + "id": "nNx5n", + "name": "TipText", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Memory tip — factor in mod/plugin overhead; Minecraft Vanilla uses ~1.5 GB at 4 players.", + "lineHeight": 1.4, + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "id": "Or9ff", + "type": "ref", + "ref": "IoDHI", + "name": "Footer", + "width": "fill_container" + } + ] + } + ] + } + ] +} diff --git a/assets/design-export/json/Ne5TA.json b/assets/design-export/json/Ne5TA.json index d5029fd52..5f223257e 100644 --- a/assets/design-export/json/Ne5TA.json +++ b/assets/design-export/json/Ne5TA.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Ne5TA", "x": 0, - "y": 92565, + "y": 92828, "name": "Screen/Not Found", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/NqaaU.json b/assets/design-export/json/NqaaU.json new file mode 100644 index 000000000..6f3120089 --- /dev/null +++ b/assets/design-export/json/NqaaU.json @@ -0,0 +1,808 @@ +{ + "type": "frame", + "id": "NqaaU", + "x": 1640, + "y": 42130, + "name": "Screen/Server Detail — Settings · Resources (Reserve less on)", + "theme": { + "semantic": "dark" + }, + "clip": true, + "width": 1440, + "height": 1000, + "fill": "$background/background", + "children": [ + { + "id": "AZLll", + "type": "ref", + "ref": "kKFX9", + "height": "fill_container", + "descendants": { + "iTbd8": { + "fill": "#00000000" + }, + "iTbd8/ghUsw": { + "fill": "$foreground/foreground" + }, + "iTbd8/bpSQ7": { + "fill": "$foreground/foreground" + }, + "i8Z1B": { + "fill": "$accent/soft" + }, + "i8Z1B/Sr2Q5": { + "fill": "$accent/soft-foreground" + }, + "i8Z1B/Ra9NK": { + "fill": "$accent/soft-foreground" + } + } + }, + { + "type": "frame", + "id": "p8hHD", + "name": "Main", + "width": "fill_container", + "height": "fill_container", + "fill": "$background/background", + "layout": "vertical", + "children": [ + { + "id": "nAKSm", + "type": "ref", + "ref": "gu5WY", + "width": "fill_container", + "descendants": { + "Gl7AY": { + "content": "mc-survival" + } + } + }, + { + "type": "frame", + "id": "KBvJo", + "name": "Body", + "width": "fill_container", + "height": "fill_container", + "fill": "$background/background", + "layout": "vertical", + "gap": 24, + "padding": 24, + "children": [ + { + "id": "DPwOC", + "type": "ref", + "ref": "S4k0x", + "name": "Detail Header", + "width": "fill_container" + }, + { + "id": "GUwll", + "type": "ref", + "ref": "I9kvlZ", + "name": "Tabs Bar", + "width": "fill_container", + "padding": [ + 0, + 0, + 0, + 24 + ], + "descendants": { + "Wp6q1": { + "fill": "#00000000" + }, + "Wp6q1/YUR8N": { + "fill": "$muted" + }, + "Y4QES": { + "fill": "$segment" + }, + "Y4QES/VcxMI": { + "fill": "$foreground/segment" + } + } + }, + { + "type": "frame", + "id": "b6dV3C", + "name": "Settings Content", + "width": "fill_container", + "height": "fill_container", + "children": [ + { + "id": "QnrW1", + "type": "ref", + "ref": "EV8mp", + "name": "Settings Sub Nav", + "width": 224, + "height": "fill_container", + "descendants": { + "axMXL": { + "fill": "#00000000" + }, + "nnoXy": { + "fill": "$surface/secondary" + }, + "bHNH0": { + "fill": "$foreground/foreground" + } + } + }, + { + "type": "frame", + "id": "k7WeNU", + "name": "Settings Panel Wrap", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "z8HDA", + "name": "Settings Form", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "Put6Q", + "name": "Form Body", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "gap": 20, + "padding": 24, + "children": [ + { + "type": "frame", + "id": "bqZJN", + "name": "Field CPU", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "q7Ul7", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "hZfcD", + "name": "t", + "fill": "$foreground/foreground", + "content": "CPU cores", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "rHjtv", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "Limit: the most CPU this server can use.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "hy9GV", + "name": "ctrl", + "width": "fill_container", + "height": 36, + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "z6Plw", + "type": "ref", + "ref": "KWcnQ", + "name": "slider", + "width": "fill_container", + "descendants": { + "Gky2W": { + "enabled": false + }, + "lJsFB": { + "width": 70 + }, + "tES7p": { + "x": 57 + } + } + }, + { + "id": "K5byY", + "type": "ref", + "ref": "D0cDM", + "name": "Input", + "width": 64, + "descendants": { + "PJERm": { + "content": "2", + "fill": "$foreground/foreground" + } + } + }, + { + "id": "OtD8Z", + "type": "ref", + "ref": "cY9fV", + "name": "unit", + "width": 110, + "descendants": { + "mgenn": { + "enabled": false + }, + "WhOPt": { + "width": "fill_container" + }, + "c94wTj": { + "content": "cores" + }, + "t1Te9C": { + "x": 82 + }, + "d7Cyay": { + "enabled": false + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "K3jnh", + "name": "Field Memory", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "AoCgz", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "cNMed", + "name": "t", + "fill": "$foreground/foreground", + "content": "Memory (GiB)", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "jYTdv", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "Limit: the most memory this server can use.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "TPorp", + "name": "ctrl", + "width": "fill_container", + "height": 36, + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "c2cPe8", + "type": "ref", + "ref": "KWcnQ", + "name": "slider", + "width": "fill_container", + "descendants": { + "Gky2W": { + "enabled": false + }, + "lJsFB": { + "width": 46 + }, + "tES7p": { + "x": 33 + } + } + }, + { + "id": "eywZh", + "type": "ref", + "ref": "D0cDM", + "name": "Input", + "width": 64, + "descendants": { + "PJERm": { + "content": "4", + "fill": "$foreground/foreground" + } + } + }, + { + "id": "Zypbr", + "type": "ref", + "ref": "cY9fV", + "name": "unit", + "width": 110, + "descendants": { + "mgenn": { + "enabled": false + }, + "WhOPt": { + "width": "fill_container" + }, + "c94wTj": { + "content": "GiB" + }, + "t1Te9C": { + "x": 82 + }, + "d7Cyay": { + "enabled": false + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "QXA0O", + "name": "Field Reserve less", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "kcSdJ", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "EJ9HI", + "name": "t", + "fill": "$foreground/foreground", + "content": "Reserve less than the limit", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "z1W8J", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "Off: the server reserves its full CPU and memory (Guaranteed QoS). On: it reserves less, so more servers fit on a node, and uses more up to its limit when the node has room (Burstable QoS).", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "ceSyZ", + "name": "ctrl", + "width": "fill_container", + "height": 28, + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "IeaMp", + "type": "ref", + "ref": "hl7R3", + "name": "Switch", + "width": 36, + "height": 20 + } + ] + } + ] + }, + { + "type": "frame", + "id": "yWH5R", + "name": "Field Reserved CPU", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "Y3vPi", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "Bl9Wr", + "name": "t", + "fill": "$foreground/foreground", + "content": "Reserved CPU", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "kmsMn", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "Counted when the server is placed on a node. 0 reserves nothing.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "fST1a", + "name": "ctrl", + "width": "fill_container", + "height": 36, + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "FiwFm", + "type": "ref", + "ref": "KWcnQ", + "name": "slider", + "width": "fill_container", + "descendants": { + "Gky2W": { + "enabled": false + }, + "lJsFB": { + "width": 223 + }, + "tES7p": { + "x": 210 + } + } + }, + { + "id": "eLtw3", + "type": "ref", + "ref": "D0cDM", + "name": "Input", + "width": 64, + "descendants": { + "PJERm": { + "content": "1", + "fill": "$foreground/foreground" + } + } + }, + { + "id": "vPmPR", + "type": "ref", + "ref": "cY9fV", + "name": "unit", + "width": 110, + "descendants": { + "mgenn": { + "enabled": false + }, + "WhOPt": { + "width": "fill_container" + }, + "c94wTj": { + "content": "cores" + }, + "t1Te9C": { + "x": 82 + }, + "d7Cyay": { + "enabled": false + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "KFNkW", + "name": "Field Reserved memory", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "kvxqF", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "DoL4L", + "name": "t", + "fill": "$foreground/foreground", + "content": "Reserved memory (GiB)", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "u47KX", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "When a node runs out of memory, servers above their reservation are stopped first. Keep it close to real use.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "CEuDC", + "name": "ctrl", + "width": "fill_container", + "height": 36, + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "sPVr4", + "type": "ref", + "ref": "KWcnQ", + "name": "slider", + "width": "fill_container", + "descendants": { + "Gky2W": { + "enabled": false + }, + "lJsFB": { + "width": 223 + }, + "tES7p": { + "x": 210 + } + } + }, + { + "id": "YkJgP", + "type": "ref", + "ref": "D0cDM", + "name": "Input", + "width": 64, + "descendants": { + "PJERm": { + "content": "2", + "fill": "$foreground/foreground" + } + } + }, + { + "id": "Iip2K", + "type": "ref", + "ref": "cY9fV", + "name": "unit", + "width": 110, + "descendants": { + "mgenn": { + "enabled": false + }, + "WhOPt": { + "width": "fill_container" + }, + "c94wTj": { + "content": "GiB" + }, + "t1Te9C": { + "x": 82 + }, + "d7Cyay": { + "enabled": false + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "KAs9Y", + "name": "Field Storage size", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "cepHh", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "n5UuK", + "name": "t", + "fill": "$foreground/foreground", + "content": "Storage size", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "R7tWk", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "K8s quantity (e.g. 10Gi, 200Gi). Resizing requires a StorageClass with allowVolumeExpansion: true.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "id": "m6SVG", + "type": "ref", + "ref": "D0cDM", + "name": "Input", + "width": "fill_container", + "descendants": { + "PJERm": { + "content": "20Gi", + "fill": "$foreground/foreground" + } + } + } + ] + }, + { + "type": "frame", + "id": "Dj2nY", + "name": "Field StorageClass", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "urfz4", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "Pkd0L", + "name": "t", + "fill": "$foreground/foreground", + "content": "StorageClass", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "QK3yQ", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "Leave blank to use the cluster default.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "id": "BztzG", + "type": "ref", + "ref": "D0cDM", + "name": "Input", + "width": "fill_container", + "descendants": { + "PJERm": { + "content": "fast-ssd", + "fill": "$foreground/foreground" + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "x1ZNpu", + "name": "Form Footer", + "width": "fill_container", + "stroke": "$border/border", + "strokeWidth": { + "top": 1 + }, + "strokeAlignment": "inner", + "strokeLinejoin": "round", + "gap": 12, + "padding": [ + 12, + 24 + ], + "justifyContent": "end", + "alignItems": "center", + "children": [ + { + "id": "YiqwR", + "type": "ref", + "ref": "J09iP", + "name": "Btn Discard" + }, + { + "id": "GaXjM", + "type": "ref", + "ref": "z9ShNE", + "name": "Btn Save Changes", + "descendants": { + "nWTJb": { + "enabled": false + }, + "IVgQ8/U1xvSo": { + "content": "Save changes" + } + } + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/assets/design-export/json/O08uaD.json b/assets/design-export/json/O08uaD.json index f915eab52..c41b280c2 100644 --- a/assets/design-export/json/O08uaD.json +++ b/assets/design-export/json/O08uaD.json @@ -2,7 +2,7 @@ "type": "frame", "id": "O08uaD", "x": 6560, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture — Start capture", "context": "Start Capture for remote-lab. Values illustrate that selected site: defaults and ceilings come from its capability response, independently of central settings.", "theme": { diff --git a/assets/design-export/json/O5kaV.json b/assets/design-export/json/O5kaV.json index 0d29ea8d8..1e218d192 100644 --- a/assets/design-export/json/O5kaV.json +++ b/assets/design-export/json/O5kaV.json @@ -2,7 +2,7 @@ "type": "frame", "id": "O5kaV", "x": 1000, - "y": 58137, + "y": 58400, "name": "Screen/Dialog/Build Module — Step 2 (Container & Ports)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/OC804.json b/assets/design-export/json/OC804.json index 3fdb84aaa..f50924995 100644 --- a/assets/design-export/json/OC804.json +++ b/assets/design-export/json/OC804.json @@ -2,7 +2,7 @@ "type": "frame", "id": "OC804", "x": 0, - "y": 40607, + "y": 40770, "name": "Screen/Server Detail — Settings · Game configuration", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/P08Uw.json b/assets/design-export/json/P08Uw.json index 58d0d639e..9f9a8f170 100644 --- a/assets/design-export/json/P08Uw.json +++ b/assets/design-export/json/P08Uw.json @@ -2,7 +2,7 @@ "type": "frame", "id": "P08Uw", "x": 0, - "y": 26307, + "y": 26470, "name": "Screen/Server Detail — Events", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/QFEg9.json b/assets/design-export/json/QFEg9.json index 3ee75e6b1..e20988399 100644 --- a/assets/design-export/json/QFEg9.json +++ b/assets/design-export/json/QFEg9.json @@ -2,7 +2,7 @@ "type": "frame", "id": "QFEg9", "x": 1640, - "y": 40607, + "y": 40770, "name": "Screen/Server Detail — Settings · Game configuration (Invalid value)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/QQtUD.json b/assets/design-export/json/QQtUD.json index 66ef5f7f3..dd91d5818 100644 --- a/assets/design-export/json/QQtUD.json +++ b/assets/design-export/json/QQtUD.json @@ -2,7 +2,7 @@ "type": "frame", "id": "QQtUD", "x": 1640, - "y": 21112, + "y": 21275, "name": "Screen/Create Server — Step 4 Network (as built)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/QgW58.json b/assets/design-export/json/QgW58.json index bcf2fc981..05e776157 100644 --- a/assets/design-export/json/QgW58.json +++ b/assets/design-export/json/QgW58.json @@ -2,7 +2,7 @@ "type": "frame", "id": "QgW58", "x": 3280, - "y": 78259, + "y": 78522, "name": "Screen/Admin Settings — Authentication (Save rejected)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/QpEvu.json b/assets/design-export/json/QpEvu.json index 871222eee..6f93f5050 100644 --- a/assets/design-export/json/QpEvu.json +++ b/assets/design-export/json/QpEvu.json @@ -2,7 +2,7 @@ "type": "frame", "id": "QpEvu", "x": 1640, - "y": 51787, + "y": 52050, "name": "Screen/Server Detail — Settings · Access", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/RC3Kf.json b/assets/design-export/json/RC3Kf.json index 0135ebc3f..968866764 100644 --- a/assets/design-export/json/RC3Kf.json +++ b/assets/design-export/json/RC3Kf.json @@ -2,7 +2,7 @@ "type": "frame", "id": "RC3Kf", "x": 0, - "y": 81968, + "y": 82231, "name": "Screen/Admin Settings — Backup destinations", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/RS0KZ.json b/assets/design-export/json/RS0KZ.json index 85baf9e6a..071fe1a8f 100644 --- a/assets/design-export/json/RS0KZ.json +++ b/assets/design-export/json/RS0KZ.json @@ -2,7 +2,7 @@ "type": "frame", "id": "RS0KZ", "x": 9840, - "y": 63669, + "y": 63932, "name": "State/Standalone — Cluster selection and request states", "context": "Interaction reference, not a single dashboard screen. No workload queries before capabilities and selection resolve. Retry retains the selected remote.", "theme": { diff --git a/assets/design-export/json/RodrS.json b/assets/design-export/json/RodrS.json index a6c159091..f29f54808 100644 --- a/assets/design-export/json/RodrS.json +++ b/assets/design-export/json/RodrS.json @@ -2,7 +2,7 @@ "type": "frame", "id": "RodrS", "x": 0, - "y": 49187, + "y": 49450, "name": "Screen/Server Detail — Settings · Network capture", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/RpHBg.json b/assets/design-export/json/RpHBg.json index 7d160ba7c..7c9fca11b 100644 --- a/assets/design-export/json/RpHBg.json +++ b/assets/design-export/json/RpHBg.json @@ -2,7 +2,7 @@ "type": "frame", "id": "RpHBg", "x": 6560, - "y": 63669, + "y": 63932, "name": "Screen/Standalone — Register cluster", "context": "PR #630. Modal actions use central cluster-management permission. Dismissing is disabled during submission.", "theme": { diff --git a/assets/design-export/json/SUcad.json b/assets/design-export/json/SUcad.json index 6c892fb26..87541ff04 100644 --- a/assets/design-export/json/SUcad.json +++ b/assets/design-export/json/SUcad.json @@ -2,7 +2,7 @@ "type": "frame", "id": "SUcad", "x": 1640, - "y": 85868, + "y": 86131, "name": "Screen/Admin Settings — Notifications (Add sink)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/SUtGZ.json b/assets/design-export/json/SUtGZ.json index 5b16c7cb7..e843567ad 100644 --- a/assets/design-export/json/SUtGZ.json +++ b/assets/design-export/json/SUtGZ.json @@ -2,7 +2,7 @@ "type": "frame", "id": "SUtGZ", "x": 12840, - "y": 36707, + "y": 36870, "name": "Screen/Mobile — Remote server Capture", "context": "375px viewport. Server actions wrap. The tab navigation is independently scrolled to Capture; the page itself never overflows horizontally.", "theme": { diff --git a/assets/design-export/json/Ss0Yr.json b/assets/design-export/json/Ss0Yr.json index 8c4a84b97..bc3cbe7f6 100644 --- a/assets/design-export/json/Ss0Yr.json +++ b/assets/design-export/json/Ss0Yr.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Ss0Yr", "x": 4920, - "y": 31507, + "y": 31670, "name": "Screen/Server Detail — Mods — Install mod (from URL)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/T1wkiT.json b/assets/design-export/json/T1wkiT.json index 132ad96b1..7895930c1 100644 --- a/assets/design-export/json/T1wkiT.json +++ b/assets/design-export/json/T1wkiT.json @@ -2,7 +2,7 @@ "type": "frame", "id": "T1wkiT", "x": 1640, - "y": 75369, + "y": 75632, "name": "Screen/Theme Settings — Warnings", "theme": { "semantic": "light" diff --git a/assets/design-export/json/TBvTC.json b/assets/design-export/json/TBvTC.json index 06a554021..23b37400d 100644 --- a/assets/design-export/json/TBvTC.json +++ b/assets/design-export/json/TBvTC.json @@ -2,7 +2,7 @@ "type": "frame", "id": "TBvTC", "x": 0, - "y": 68869, + "y": 69132, "name": "Screen/Users & RBAC — Service accounts", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/TE2jI.json b/assets/design-export/json/TE2jI.json index 0e458ab86..35a059723 100644 --- a/assets/design-export/json/TE2jI.json +++ b/assets/design-export/json/TE2jI.json @@ -2,7 +2,7 @@ "type": "frame", "id": "TE2jI", "x": 4920, - "y": 24182, + "y": 24345, "name": "Screen/Server Detail — Overview (Asleep)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/U9xSg.json b/assets/design-export/json/U9xSg.json index 05c20f8cf..82ad600d4 100644 --- a/assets/design-export/json/U9xSg.json +++ b/assets/design-export/json/U9xSg.json @@ -2,7 +2,7 @@ "type": "frame", "id": "U9xSg", "x": 1640, - "y": 34107, + "y": 34270, "name": "Screen/Server Detail — Players (Nuclear Option)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/UMJli.json b/assets/design-export/json/UMJli.json index 208a89dac..b24abf37f 100644 --- a/assets/design-export/json/UMJli.json +++ b/assets/design-export/json/UMJli.json @@ -2,7 +2,7 @@ "type": "frame", "id": "UMJli", "x": 0, - "y": 22782, + "y": 22945, "name": "Screen/Create Server — Step 5 Review", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/Uzzje.json b/assets/design-export/json/Uzzje.json index 2ae40bc77..ffe533d20 100644 --- a/assets/design-export/json/Uzzje.json +++ b/assets/design-export/json/Uzzje.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Uzzje", "x": 1640, - "y": 88665, + "y": 88928, "name": "Screen/Standalone — Settings · Updates", "context": "PR #630 standalone installation. No local Kubernetes metadata or local workload fallback. Master-key storage and offline rotation have no dashboard controls.", "theme": { diff --git a/assets/design-export/json/V1VhGE.json b/assets/design-export/json/V1VhGE.json index 9837d0916..6ef55a655 100644 --- a/assets/design-export/json/V1VhGE.json +++ b/assets/design-export/json/V1VhGE.json @@ -2,7 +2,7 @@ "type": "frame", "id": "V1VhGE", "x": 6560, - "y": 31507, + "y": 31670, "name": "Screen/Server Detail — Mods — Install mod (upload)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/VctzT.json b/assets/design-export/json/VctzT.json index 3ca79b6aa..0fbfd75bf 100644 --- a/assets/design-export/json/VctzT.json +++ b/assets/design-export/json/VctzT.json @@ -2,7 +2,7 @@ "type": "frame", "id": "VctzT", "x": 1640, - "y": 39307, + "y": 39470, "name": "Screen/Server Detail — Settings · Version", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/VfB0Y.json b/assets/design-export/json/VfB0Y.json index db24ef312..c5b493f54 100644 --- a/assets/design-export/json/VfB0Y.json +++ b/assets/design-export/json/VfB0Y.json @@ -2,7 +2,7 @@ "type": "frame", "id": "VfB0Y", "x": 0, - "y": 51787, + "y": 52050, "name": "Screen/Server Detail — Settings · RBAC & access", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/WZdnw.json b/assets/design-export/json/WZdnw.json index eac14c701..3ed77777b 100644 --- a/assets/design-export/json/WZdnw.json +++ b/assets/design-export/json/WZdnw.json @@ -2,7 +2,7 @@ "type": "frame", "id": "WZdnw", "x": 0, - "y": 74069, + "y": 74332, "name": "Screen/Admin Settings", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/Wj0V4.json b/assets/design-export/json/Wj0V4.json index 67ee92e79..279e10b0d 100644 --- a/assets/design-export/json/Wj0V4.json +++ b/assets/design-export/json/Wj0V4.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Wj0V4", "x": 0, - "y": 84568, + "y": 84831, "name": "Screen/Admin Settings — Mod registries", "context": "Central Mod registries administration. The banner describes central ownership; navigation includes registered Clusters and Cluster inventory. Exported against the unchanged screenshot scenario.", "theme": { diff --git a/assets/design-export/json/WrmU5.json b/assets/design-export/json/WrmU5.json index 6aa1b0d60..8aeeccdd4 100644 --- a/assets/design-export/json/WrmU5.json +++ b/assets/design-export/json/WrmU5.json @@ -2,7 +2,7 @@ "type": "frame", "id": "WrmU5", "x": 3280, - "y": 63669, + "y": 63932, "name": "Screen/Standalone — Clusters (Registered)", "context": "PR #630 standalone deployment; matches current dashboard behavior.", "theme": { diff --git a/assets/design-export/json/X405l.json b/assets/design-export/json/X405l.json index 7d906a31b..214f11540 100644 --- a/assets/design-export/json/X405l.json +++ b/assets/design-export/json/X405l.json @@ -2,7 +2,7 @@ "type": "frame", "id": "X405l", "x": 1640, - "y": 27607, + "y": 27770, "name": "Screen/Server Detail — Console (not running – Stopped/Suspended)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/XR0f9.json b/assets/design-export/json/XR0f9.json index 1399f50f8..fc4038580 100644 --- a/assets/design-export/json/XR0f9.json +++ b/assets/design-export/json/XR0f9.json @@ -2,7 +2,7 @@ "type": "frame", "id": "XR0f9", "x": 1640, - "y": 54387, + "y": 54650, "name": "Screen/Server Detail \u2014 Settings \u00b7 Danger zone", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/Xn5ns.json b/assets/design-export/json/Xn5ns.json index 0a5d95da0..c1aa22344 100644 --- a/assets/design-export/json/Xn5ns.json +++ b/assets/design-export/json/Xn5ns.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Xn5ns", "x": 0, - "y": 27607, + "y": 27770, "name": "Screen/Server Detail — Console", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/Y5cmvI.json b/assets/design-export/json/Y5cmvI.json index 4b2996052..f96fbbcf3 100644 --- a/assets/design-export/json/Y5cmvI.json +++ b/assets/design-export/json/Y5cmvI.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Y5cmvI", "x": 0, - "y": 50487, + "y": 50750, "name": "Screen/Server Detail — Settings · Placement", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/YN8cE.json b/assets/design-export/json/YN8cE.json index 78103ee76..5387240f2 100644 --- a/assets/design-export/json/YN8cE.json +++ b/assets/design-export/json/YN8cE.json @@ -2,7 +2,7 @@ "type": "frame", "id": "YN8cE", "x": 8200, - "y": 40607, + "y": 40770, "name": "Screen/Server Detail — Settings · Game configuration (New password typed)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/Z0oyh.json b/assets/design-export/json/Z0oyh.json index a664b9f56..584a00214 100644 --- a/assets/design-export/json/Z0oyh.json +++ b/assets/design-export/json/Z0oyh.json @@ -2,7 +2,7 @@ "type": "frame", "id": "Z0oyh", "x": 1640, - "y": 55687, + "y": 55950, "name": "Screen/Standalone — Modules (No selection)", "context": "PR #630. Selection is explicit and only registered remotes are listed. No implicit local target; no workload requests until installation and selected target are known.", "theme": { diff --git a/assets/design-export/json/ZU1BG.json b/assets/design-export/json/ZU1BG.json index ccf1d2494..f548dbe47 100644 --- a/assets/design-export/json/ZU1BG.json +++ b/assets/design-export/json/ZU1BG.json @@ -2,7 +2,7 @@ "type": "frame", "id": "ZU1BG", "x": 6560, - "y": 21112, + "y": 21275, "name": "Create Server — Step 4 Network · Tunnel enabled, provider selected", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/ZlLf0.json b/assets/design-export/json/ZlLf0.json index ab5a91dee..b0e4b6e3c 100644 --- a/assets/design-export/json/ZlLf0.json +++ b/assets/design-export/json/ZlLf0.json @@ -2,7 +2,7 @@ "type": "frame", "id": "ZlLf0", "x": 3280, - "y": 55687, + "y": 55950, "name": "Screen/Standalone — Modules (Permission denied)", "context": "PR #630. Selection is explicit and only registered remotes are listed. No implicit local target; no workload requests until installation and selected target are known.", "theme": { diff --git a/assets/design-export/json/a7o9C.json b/assets/design-export/json/a7o9C.json index 1a0c3e010..a31bdcd76 100644 --- a/assets/design-export/json/a7o9C.json +++ b/assets/design-export/json/a7o9C.json @@ -2,7 +2,7 @@ "type": "frame", "id": "a7o9C", "x": 4920, - "y": 21112, + "y": 21275, "name": "Create Server — Step 4 Network · Tunnel enabled, no provider", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/aMU7O.json b/assets/design-export/json/aMU7O.json index a93b46a4e..62838df12 100644 --- a/assets/design-export/json/aMU7O.json +++ b/assets/design-export/json/aMU7O.json @@ -4,7 +4,7 @@ "ref": "x3beP", "name": "Gameplane/Dialog/Edit user — Standalone (No target)", "x": 1640, - "y": 66269, + "y": 66532, "theme": { "semantic": "dark" }, diff --git a/assets/design-export/json/akr4I.json b/assets/design-export/json/akr4I.json index 288ac25da..d6efcf6b6 100644 --- a/assets/design-export/json/akr4I.json +++ b/assets/design-export/json/akr4I.json @@ -2,7 +2,7 @@ "type": "frame", "id": "akr4I", "x": 9840, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail \u2014 Capture (No access)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/b4eaUf.json b/assets/design-export/json/b4eaUf.json index ca008478a..da8638328 100644 --- a/assets/design-export/json/b4eaUf.json +++ b/assets/design-export/json/b4eaUf.json @@ -2,7 +2,7 @@ "type": "frame", "id": "b4eaUf", "x": 8200, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture — Start capture (Invalid filter)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/bYDHC.json b/assets/design-export/json/bYDHC.json index 2feecaa97..2afbeada8 100644 --- a/assets/design-export/json/bYDHC.json +++ b/assets/design-export/json/bYDHC.json @@ -2,7 +2,7 @@ "type": "frame", "id": "bYDHC", "x": 0, - "y": 66269, + "y": 66532, "name": "Screen/Users & RBAC", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/cDiNb.json b/assets/design-export/json/cDiNb.json index 5bc51ebc7..225d71304 100644 --- a/assets/design-export/json/cDiNb.json +++ b/assets/design-export/json/cDiNb.json @@ -2,7 +2,7 @@ "type": "frame", "id": "cDiNb", "x": 4920, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry (bundled)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/dBILX.json b/assets/design-export/json/dBILX.json index 5ec01bf86..9a70e2054 100644 --- a/assets/design-export/json/dBILX.json +++ b/assets/design-export/json/dBILX.json @@ -2,7 +2,7 @@ "type": "frame", "id": "dBILX", "x": 1640, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture (Empty)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/dPP50.json b/assets/design-export/json/dPP50.json index a77f45719..2ce6908f8 100644 --- a/assets/design-export/json/dPP50.json +++ b/assets/design-export/json/dPP50.json @@ -2,7 +2,7 @@ "type": "frame", "id": "dPP50", "x": 0, - "y": 34107, + "y": 34270, "name": "Screen/Server Detail — Players", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/dQV9N.json b/assets/design-export/json/dQV9N.json index dee3fda80..14119a14b 100644 --- a/assets/design-export/json/dQV9N.json +++ b/assets/design-export/json/dQV9N.json @@ -2,7 +2,7 @@ "type": "frame", "id": "dQV9N", "x": 1640, - "y": 53087, + "y": 53350, "name": "Screen/Server Detail — Settings · Share links (Empty)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/dhPtX.json b/assets/design-export/json/dhPtX.json index ce06ac989..8fde68f34 100644 --- a/assets/design-export/json/dhPtX.json +++ b/assets/design-export/json/dhPtX.json @@ -2,7 +2,7 @@ "type": "frame", "id": "dhPtX", "x": 3280, - "y": 43267, + "y": 43530, "name": "Server Detail · Settings · Networking · Tunnel initial enable", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/dxdEi.json b/assets/design-export/json/dxdEi.json index 5ac36429e..d04bee123 100644 --- a/assets/design-export/json/dxdEi.json +++ b/assets/design-export/json/dxdEi.json @@ -2,7 +2,7 @@ "type": "frame", "id": "dxdEi", "x": 1640, - "y": 64969, + "y": 65232, "name": "Screen/Cluster Settings — Default storage class", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/e9FnC.json b/assets/design-export/json/e9FnC.json index c4047b90a..2d3185504 100644 --- a/assets/design-export/json/e9FnC.json +++ b/assets/design-export/json/e9FnC.json @@ -2,7 +2,7 @@ "type": "frame", "id": "e9FnC", "x": 4920, - "y": 40607, + "y": 40770, "name": "Screen/Server Detail — Settings · Game configuration (Orphaned key)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/e9lV4.json b/assets/design-export/json/e9lV4.json index ac5bbe561..4b15f4590 100644 --- a/assets/design-export/json/e9lV4.json +++ b/assets/design-export/json/e9lV4.json @@ -2,7 +2,7 @@ "type": "frame", "id": "e9lV4", "x": 0, - "y": 67569, + "y": 67832, "name": "Screen/Users & RBAC — Roles", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/eGbsg.json b/assets/design-export/json/eGbsg.json index 604c693c6..7e6fb7af9 100644 --- a/assets/design-export/json/eGbsg.json +++ b/assets/design-export/json/eGbsg.json @@ -2,7 +2,7 @@ "type": "frame", "id": "eGbsg", "x": 8200, - "y": 21112, + "y": 21275, "name": "Create Server — Step 4 Network · Tunnel enabled, complete", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/epZO2.json b/assets/design-export/json/epZO2.json index eeb796a76..d25751760 100644 --- a/assets/design-export/json/epZO2.json +++ b/assets/design-export/json/epZO2.json @@ -2,7 +2,7 @@ "type": "frame", "id": "epZO2", "x": 6560, - "y": 91265, + "y": 91528, "name": "Screen/Share Link — Invalid or expired", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/f1Vga.json b/assets/design-export/json/f1Vga.json index 7917951bb..ec27b9107 100644 --- a/assets/design-export/json/f1Vga.json +++ b/assets/design-export/json/f1Vga.json @@ -2,7 +2,7 @@ "type": "frame", "id": "f1Vga", "x": 0, - "y": 21112, + "y": 21275, "name": "Screen/Create Server — Step 4 Network", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/fK8Bi.json b/assets/design-export/json/fK8Bi.json index 76e1651dc..932c98d4f 100644 --- a/assets/design-export/json/fK8Bi.json +++ b/assets/design-export/json/fK8Bi.json @@ -2,7 +2,7 @@ "type": "frame", "id": "fK8Bi", "x": 0, - "y": 60819, + "y": 61082, "name": "Screen/Backups — Schedules", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/fbBqA.json b/assets/design-export/json/fbBqA.json index 5163d7a1a..a10b7dfc0 100644 --- a/assets/design-export/json/fbBqA.json +++ b/assets/design-export/json/fbBqA.json @@ -2,7 +2,7 @@ "type": "frame", "id": "fbBqA", "x": 1640, - "y": 62369, + "y": 62632, "name": "Screen/Backups — Restores filters open", "context": "Implemented at 8e08a60f. Filter fields are drafts; Apply commits, Clear resets drafts, dismissal discards edits. Server choices retain location and namespace.", "theme": { diff --git a/assets/design-export/json/g5mEpx.json b/assets/design-export/json/g5mEpx.json index 09e9dc03d..d4619188a 100644 --- a/assets/design-export/json/g5mEpx.json +++ b/assets/design-export/json/g5mEpx.json @@ -2,7 +2,7 @@ "type": "frame", "id": "g5mEpx", "x": 0, - "y": 83268, + "y": 83531, "name": "Screen/Admin Settings — Module sources", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/gWqMD.json b/assets/design-export/json/gWqMD.json index fef1bdc88..16ab28582 100644 --- a/assets/design-export/json/gWqMD.json +++ b/assets/design-export/json/gWqMD.json @@ -2,7 +2,7 @@ "type": "frame", "id": "gWqMD", "x": 3280, - "y": 51787, + "y": 52050, "name": "Screen/Server Detail — Settings · Access (Read-only)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/hLB9Z.json b/assets/design-export/json/hLB9Z.json index 9e282659a..7c9af8435 100644 --- a/assets/design-export/json/hLB9Z.json +++ b/assets/design-export/json/hLB9Z.json @@ -2,7 +2,7 @@ "type": "frame", "id": "hLB9Z", "x": 0, - "y": 38007, + "y": 38170, "name": "Screen/Server Detail — Settings", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/hlwx3.json b/assets/design-export/json/hlwx3.json index 078ff6963..32e422841 100644 --- a/assets/design-export/json/hlwx3.json +++ b/assets/design-export/json/hlwx3.json @@ -2,7 +2,7 @@ "type": "frame", "id": "hlwx3", "x": 9840, - "y": 24182, + "y": 24345, "name": "State/Server Actions Menu \u2014 Owner-only disabled", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/hmPL7.json b/assets/design-export/json/hmPL7.json index bce5e6c95..dcaa2e080 100644 --- a/assets/design-export/json/hmPL7.json +++ b/assets/design-export/json/hmPL7.json @@ -2,7 +2,7 @@ "type": "frame", "id": "hmPL7", "x": 2000, - "y": 58137, + "y": 58400, "name": "Screen/Dialog/Build Module — Step 3 (Review & Export)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/i1bLR.json b/assets/design-export/json/i1bLR.json index 578967032..3c0fa6b25 100644 --- a/assets/design-export/json/i1bLR.json +++ b/assets/design-export/json/i1bLR.json @@ -2,7 +2,7 @@ "type": "frame", "id": "i1bLR", "x": 0, - "y": 45867, + "y": 46130, "name": "Screen/Server Detail — Settings · Lifecycle", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/i8wib.json b/assets/design-export/json/i8wib.json index b564c3358..1e74761e5 100644 --- a/assets/design-export/json/i8wib.json +++ b/assets/design-export/json/i8wib.json @@ -2,7 +2,7 @@ "type": "frame", "id": "i8wib", "x": 0, - "y": 54387, + "y": 54650, "name": "Screen/Server Detail — Settings · Danger zone", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/iLm38.json b/assets/design-export/json/iLm38.json index 70d1cd9c3..272bf888a 100644 --- a/assets/design-export/json/iLm38.json +++ b/assets/design-export/json/iLm38.json @@ -2,7 +2,7 @@ "type": "frame", "id": "iLm38", "x": 1640, - "y": 44567, + "y": 44830, "name": "Screen/Server Detail — Settings · Environment", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/iYUWc.json b/assets/design-export/json/iYUWc.json index 130e41c52..2e7a467fc 100644 --- a/assets/design-export/json/iYUWc.json +++ b/assets/design-export/json/iYUWc.json @@ -2,7 +2,7 @@ "type": "frame", "id": "iYUWc", "x": 0, - "y": 63669, + "y": 63932, "name": "Screen/Clusters — Registered locations", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/j88bl0.json b/assets/design-export/json/j88bl0.json index 1ef5cc1c4..ce4b7a1c8 100644 --- a/assets/design-export/json/j88bl0.json +++ b/assets/design-export/json/j88bl0.json @@ -2,7 +2,7 @@ "type": "frame", "id": "j88bl0", "x": 3280, - "y": 27607, + "y": 27770, "name": "Screen/Server Detail — Console (not running – Pod failure)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/j9W8A.json b/assets/design-export/json/j9W8A.json index f3d406591..64f8a8177 100644 --- a/assets/design-export/json/j9W8A.json +++ b/assets/design-export/json/j9W8A.json @@ -2,7 +2,7 @@ "type": "frame", "id": "j9W8A", "x": 0, - "y": 64969, + "y": 65232, "name": "Screen/Cluster Settings", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/jXykV.json b/assets/design-export/json/jXykV.json index a57f5f965..4d441c57e 100644 --- a/assets/design-export/json/jXykV.json +++ b/assets/design-export/json/jXykV.json @@ -2,7 +2,7 @@ "type": "frame", "id": "jXykV", "x": 10540, - "y": 43267, + "y": 43530, "name": "J5pjJ3 states/Alerts", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/kK8Ji.json b/assets/design-export/json/kK8Ji.json index 683bb18a8..8e28f1ba9 100644 --- a/assets/design-export/json/kK8Ji.json +++ b/assets/design-export/json/kK8Ji.json @@ -2,7 +2,7 @@ "type": "frame", "id": "kK8Ji", "x": 0, - "y": 55687, + "y": 55950, "name": "Screen/Modules Catalog", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/kPmoo.json b/assets/design-export/json/kPmoo.json index f5274b73e..cee7cb21f 100644 --- a/assets/design-export/json/kPmoo.json +++ b/assets/design-export/json/kPmoo.json @@ -2,7 +2,7 @@ "type": "frame", "id": "kPmoo", "x": 0, - "y": 28907, + "y": 29070, "name": "Screen/Server Detail — Logs", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/l64bd.json b/assets/design-export/json/l64bd.json index fac41d98f..ce457722f 100644 --- a/assets/design-export/json/l64bd.json +++ b/assets/design-export/json/l64bd.json @@ -2,7 +2,7 @@ "type": "frame", "id": "l64bd", "x": 1640, - "y": 83268, + "y": 83531, "name": "Screen/Standalone — Settings · Module sources", "context": "PR #630 standalone installation. No local Kubernetes metadata or local workload fallback. Master-key storage and offline rotation have no dashboard controls.", "theme": { diff --git a/assets/design-export/json/l7TRz9.json b/assets/design-export/json/l7TRz9.json index 4e95ffd95..0ca581eeb 100644 --- a/assets/design-export/json/l7TRz9.json +++ b/assets/design-export/json/l7TRz9.json @@ -2,7 +2,7 @@ "type": "frame", "id": "l7TRz9", "x": 1640, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry (default)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/lCTci.json b/assets/design-export/json/lCTci.json index 9590b338c..fbc4443ac 100644 --- a/assets/design-export/json/lCTci.json +++ b/assets/design-export/json/lCTci.json @@ -2,7 +2,7 @@ "type": "frame", "id": "lCTci", "x": 3280, - "y": 75369, + "y": 75632, "name": "Screen/Mobile — Theme Settings · Language", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/lWvcv.json b/assets/design-export/json/lWvcv.json index 1e682356c..d7e386640 100644 --- a/assets/design-export/json/lWvcv.json +++ b/assets/design-export/json/lWvcv.json @@ -2,7 +2,7 @@ "type": "frame", "id": "lWvcv", "x": 0, - "y": 75369, + "y": 75632, "name": "Screen/Theme Settings", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/lhKzN.json b/assets/design-export/json/lhKzN.json index d15416e70..974acfd60 100644 --- a/assets/design-export/json/lhKzN.json +++ b/assets/design-export/json/lhKzN.json @@ -2,7 +2,7 @@ "type": "frame", "id": "lhKzN", "x": 3280, - "y": 21112, + "y": 21275, "name": "Create Server — Step 4 Network · Tunnel disabled", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/m5kOm4.json b/assets/design-export/json/m5kOm4.json index c7dc24daf..b933c67e4 100644 --- a/assets/design-export/json/m5kOm4.json +++ b/assets/design-export/json/m5kOm4.json @@ -2,7 +2,7 @@ "type": "frame", "id": "m5kOm4", "x": 4920, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture (List)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/n2qdi.json b/assets/design-export/json/n2qdi.json index 9a4d76d6f..4de24ab11 100644 --- a/assets/design-export/json/n2qdi.json +++ b/assets/design-export/json/n2qdi.json @@ -2,7 +2,7 @@ "type": "frame", "id": "n2qdi", "x": 1640, - "y": 63669, + "y": 63932, "name": "Screen/Standalone — Clusters (Empty)", "context": "PR #630 standalone deployment; matches current dashboard behavior.", "theme": { diff --git a/assets/design-export/json/n6Xlo.json b/assets/design-export/json/n6Xlo.json index 120b07f98..d2ab404d2 100644 --- a/assets/design-export/json/n6Xlo.json +++ b/assets/design-export/json/n6Xlo.json @@ -2,7 +2,7 @@ "type": "frame", "id": "n6Xlo", "x": 0, - "y": 85868, + "y": 86131, "name": "Screen/Admin Settings — Notifications", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/nGJE1.json b/assets/design-export/json/nGJE1.json index af953820b..30b028f1d 100644 --- a/assets/design-export/json/nGJE1.json +++ b/assets/design-export/json/nGJE1.json @@ -2,7 +2,7 @@ "type": "frame", "id": "nGJE1", "x": 1640, - "y": 43267, + "y": 43530, "name": "Server Detail · Settings · Networking · Tunnel disabled", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/nNGDX.json b/assets/design-export/json/nNGDX.json index b75603241..f1d951deb 100644 --- a/assets/design-export/json/nNGDX.json +++ b/assets/design-export/json/nNGDX.json @@ -2,7 +2,7 @@ "type": "frame", "id": "nNGDX", "x": 1640, - "y": 78259, + "y": 78522, "name": "Screen/Admin Settings — Authentication (No OIDC mappings)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/o4LH8W.json b/assets/design-export/json/o4LH8W.json index 0f6c1fd92..2ff2adab1 100644 --- a/assets/design-export/json/o4LH8W.json +++ b/assets/design-export/json/o4LH8W.json @@ -2,7 +2,7 @@ "type": "frame", "id": "o4LH8W", "x": 8200, - "y": 24182, + "y": 24345, "name": "Screen/Server Detail — Overview (PVC Provisioning Failed)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/oMuyw.json b/assets/design-export/json/oMuyw.json index b910e1652..638a313cd 100644 --- a/assets/design-export/json/oMuyw.json +++ b/assets/design-export/json/oMuyw.json @@ -4,7 +4,7 @@ "ref": "JLaGB", "name": "Gameplane/Dialog/New File (Invalid name)", "x": 1640, - "y": 30207, + "y": 30370, "descendants": { "q9QPFd": { "type": "frame", diff --git a/assets/design-export/json/oPF1n.json b/assets/design-export/json/oPF1n.json index 013138bf7..55532075d 100644 --- a/assets/design-export/json/oPF1n.json +++ b/assets/design-export/json/oPF1n.json @@ -1 +1 @@ -{"id":"oPF1n","type":"ref","ref":"x3beP","name":"Screen/Share links — Create link (Custom date)","x":3960,"y":53087,"descendants":{"E7Whx":{"content":"Create share link for mc-survival"},"qzcst":{"content":"Anyone with this link can check the server's status and connection address without signing in."},"Nht52":{"type":"frame","id":"wSgOc","name":"mBody","width":"fill_container","layout":"vertical","gap":16,"padding":[16,0,0,0],"children":[{"type":"frame","id":"a0IB6F","name":"fExpiry","width":"fill_container","layout":"vertical","gap":4,"children":[{"type":"text","id":"tiLqO","name":"labelExpiry","fill":"$foreground/foreground","content":"Expires in","fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"500"},{"id":"wFuNO","type":"ref","ref":"AT7ya","name":"Select","width":"fill_container","descendants":{"cFsFk":{"content":"Custom"}}},{"type":"text","id":"YurQg","name":"labelExpiresOn","fill":"$foreground/foreground","content":"Expires on","fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"500"},{"id":"Bhx1e","type":"ref","ref":"D0cDM","name":"dateField","width":"fill_container","height":36,"cornerRadius":12,"descendants":{"PJERm":{"type":"frame","id":"uMBXs","name":"valueRow","width":"fill_container","justifyContent":"space_between","alignItems":"center","children":[{"type":"text","id":"bfuPo","name":"dateValue","fill":"$field/placeholder","content":"2027-11-04","fontFamily":"$typography/font-sans","fontSize":14,"fontWeight":"normal"},{"type":"icon","id":"WzfpJ","name":"calendarIcon","width":16,"height":16,"icon":"calendar","library":"lucide","fill":"$field/placeholder"}]}}},{"type":"text","id":"H9bFlJ","name":"warningLongLived","fill":"$warning/warning","content":"Long-lived link — it stays valid for over a year unless you revoke it.","fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"normal"}]},{"type":"frame","id":"F3RWF","name":"fCanStart","width":"fill_container","gap":12,"justifyContent":"space_between","alignItems":"center","children":[{"id":"FMBCY","type":"ref","ref":"rh2QH","name":"canStartSwitch","width":40},{"type":"frame","id":"j2Rgp","name":"canStartText","width":"fill_container","layout":"vertical","children":[{"type":"text","id":"VaofP","name":"canStartTitle","fill":"$foreground/foreground","content":"Allow starting the server","fontFamily":"$typography/font-sans","fontSize":14,"fontWeight":"500"},{"type":"text","id":"T9AuV","name":"canStartDesc","fill":"$muted","textGrowth":"fixed-width","width":"fill_container","content":"The link holder can wake mc-survival from asleep. Without this, they can only view its status.","lineHeight":1.4,"fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"normal"}]}]}]},"GCwAF":{"type":"frame","id":"y5WVd","name":"mFooter","width":"fill_container","gap":8,"padding":[20,0,0,0],"justifyContent":"end","alignItems":"center","children":[{"id":"DgNzL","type":"ref","ref":"rkF0p","name":"btnCancel","cornerRadius":6,"descendants":{"WtPjS":{"enabled":false},"r4VbAi":{"content":"Cancel","fontSize":13}}},{"id":"rMZDU","type":"ref","ref":"j9c5W","name":"btnPrimary","cornerRadius":6,"descendants":{"n8Hmwk":{"icon":"link","width":14,"height":14},"U1xvSo":{"content":"Create link","fontSize":13}}}]}}} +{"id":"oPF1n","type":"ref","ref":"x3beP","name":"Screen/Share links — Create link (Custom date)","x":3960,"y":53350,"descendants":{"E7Whx":{"content":"Create share link for mc-survival"},"qzcst":{"content":"Anyone with this link can check the server's status and connection address without signing in."},"Nht52":{"type":"frame","id":"wSgOc","name":"mBody","width":"fill_container","layout":"vertical","gap":16,"padding":[16,0,0,0],"children":[{"type":"frame","id":"a0IB6F","name":"fExpiry","width":"fill_container","layout":"vertical","gap":4,"children":[{"type":"text","id":"tiLqO","name":"labelExpiry","fill":"$foreground/foreground","content":"Expires in","fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"500"},{"id":"wFuNO","type":"ref","ref":"AT7ya","name":"Select","width":"fill_container","descendants":{"cFsFk":{"content":"Custom"}}},{"type":"text","id":"YurQg","name":"labelExpiresOn","fill":"$foreground/foreground","content":"Expires on","fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"500"},{"id":"Bhx1e","type":"ref","ref":"D0cDM","name":"dateField","width":"fill_container","height":36,"cornerRadius":12,"descendants":{"PJERm":{"type":"frame","id":"uMBXs","name":"valueRow","width":"fill_container","justifyContent":"space_between","alignItems":"center","children":[{"type":"text","id":"bfuPo","name":"dateValue","fill":"$field/placeholder","content":"2027-11-04","fontFamily":"$typography/font-sans","fontSize":14,"fontWeight":"normal"},{"type":"icon","id":"WzfpJ","name":"calendarIcon","width":16,"height":16,"icon":"calendar","library":"lucide","fill":"$field/placeholder"}]}}},{"type":"text","id":"H9bFlJ","name":"warningLongLived","fill":"$warning/warning","content":"Long-lived link — it stays valid for over a year unless you revoke it.","fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"normal"}]},{"type":"frame","id":"F3RWF","name":"fCanStart","width":"fill_container","gap":12,"justifyContent":"space_between","alignItems":"center","children":[{"id":"FMBCY","type":"ref","ref":"rh2QH","name":"canStartSwitch","width":40},{"type":"frame","id":"j2Rgp","name":"canStartText","width":"fill_container","layout":"vertical","children":[{"type":"text","id":"VaofP","name":"canStartTitle","fill":"$foreground/foreground","content":"Allow starting the server","fontFamily":"$typography/font-sans","fontSize":14,"fontWeight":"500"},{"type":"text","id":"T9AuV","name":"canStartDesc","fill":"$muted","textGrowth":"fixed-width","width":"fill_container","content":"The link holder can wake mc-survival from asleep. Without this, they can only view its status.","lineHeight":1.4,"fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"normal"}]}]}]},"GCwAF":{"type":"frame","id":"y5WVd","name":"mFooter","width":"fill_container","gap":8,"padding":[20,0,0,0],"justifyContent":"end","alignItems":"center","children":[{"id":"DgNzL","type":"ref","ref":"rkF0p","name":"btnCancel","cornerRadius":6,"descendants":{"WtPjS":{"enabled":false},"r4VbAi":{"content":"Cancel","fontSize":13}}},{"id":"rMZDU","type":"ref","ref":"j9c5W","name":"btnPrimary","cornerRadius":6,"descendants":{"n8Hmwk":{"icon":"link","width":14,"height":14},"U1xvSo":{"content":"Create link","fontSize":13}}}]}}} diff --git a/assets/design-export/json/p8QSPq.json b/assets/design-export/json/p8QSPq.json index 9cb88f625..bb239f5b3 100644 --- a/assets/design-export/json/p8QSPq.json +++ b/assets/design-export/json/p8QSPq.json @@ -2,7 +2,7 @@ "type": "frame", "id": "p8QSPq", "x": 6560, - "y": 55687, + "y": 55950, "name": "Screen/Standalone — Modules (Manage sources)", "context": "Background refetch failures display an inline retry banner and retain existing data plus open install/upload/builder/source drafts. Only an initial failure replaces loading content. Explicit target changes discard drafts.", "theme": { diff --git a/assets/design-export/json/pnAbF.json b/assets/design-export/json/pnAbF.json index 48a59f543..40d02247e 100644 --- a/assets/design-export/json/pnAbF.json +++ b/assets/design-export/json/pnAbF.json @@ -2,7 +2,7 @@ "type": "frame", "id": "pnAbF", "x": 1640, - "y": 59519, + "y": 59782, "name": "Screen/Backups — Filters open", "context": "Implemented at 8e08a60f. Filter fields are drafts; Apply commits, Clear resets drafts, dismissal discards edits. Server choices retain location and namespace.", "theme": { diff --git a/assets/design-export/json/pssCT.json b/assets/design-export/json/pssCT.json index 5dfc07e98..1c3bc869a 100644 --- a/assets/design-export/json/pssCT.json +++ b/assets/design-export/json/pssCT.json @@ -2,7 +2,7 @@ "type": "frame", "id": "pssCT", "x": 0, - "y": 35407, + "y": 35570, "name": "Screen/Server Detail — Backups", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/q31B6w.json b/assets/design-export/json/q31B6w.json index 6b3c83674..05f1b85a7 100644 --- a/assets/design-export/json/q31B6w.json +++ b/assets/design-export/json/q31B6w.json @@ -2,7 +2,7 @@ "type": "frame", "id": "q31B6w", "x": 1640, - "y": 91265, + "y": 91528, "name": "Screen/Share Link — Asleep (can start)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/qFLfB.json b/assets/design-export/json/qFLfB.json index 60f52c82e..61e1957c8 100644 --- a/assets/design-export/json/qFLfB.json +++ b/assets/design-export/json/qFLfB.json @@ -2,7 +2,7 @@ "type": "frame", "id": "qFLfB", "x": 8200, - "y": 91265, + "y": 91528, "name": "Historical/Share Link — Asleep (view only)", "context": "Historical reference. The public resolve response does not expose start capability; an asleep poll must not infer denied permission. Current behavior retains Start, with a cooldown after transient Start failures.", "theme": { diff --git a/assets/design-export/json/r0heh.json b/assets/design-export/json/r0heh.json index e1175a17e..bdce6a81d 100644 --- a/assets/design-export/json/r0heh.json +++ b/assets/design-export/json/r0heh.json @@ -2,7 +2,7 @@ "type": "frame", "id": "r0heh", "x": 3280, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry (custom)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/r139UP.json b/assets/design-export/json/r139UP.json index 515242695..c1fc1296b 100644 --- a/assets/design-export/json/r139UP.json +++ b/assets/design-export/json/r139UP.json @@ -2,7 +2,7 @@ "type": "frame", "id": "r139UP", "x": 1640, - "y": 72769, + "y": 73032, "name": "Screen/Standalone — System logs", "context": "PR #630 standalone installation. No local Kubernetes metadata or local workload fallback. Master-key storage and offline rotation have no dashboard controls.", "theme": { diff --git a/assets/design-export/json/sSISK.json b/assets/design-export/json/sSISK.json index 00c45b1f9..ff4777a9f 100644 --- a/assets/design-export/json/sSISK.json +++ b/assets/design-export/json/sSISK.json @@ -2,7 +2,7 @@ "type": "frame", "id": "sSISK", "x": 1640, - "y": 24182, + "y": 24345, "name": "Screen/Server Detail — Overview (Light)", "theme": { "semantic": "light" diff --git a/assets/design-export/json/sZtDi.json b/assets/design-export/json/sZtDi.json index 9e6e5abb2..e107a0a4b 100644 --- a/assets/design-export/json/sZtDi.json +++ b/assets/design-export/json/sZtDi.json @@ -2,7 +2,7 @@ "type": "frame", "id": "sZtDi", "x": 0, - "y": 31507, + "y": 31670, "name": "Screen/Server Detail — Mods", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/swxkJ.json b/assets/design-export/json/swxkJ.json index 7d0184cfb..e13ffd23b 100644 --- a/assets/design-export/json/swxkJ.json +++ b/assets/design-export/json/swxkJ.json @@ -2,7 +2,7 @@ "type": "frame", "id": "swxkJ", "x": 0, - "y": 39307, + "y": 39470, "name": "Screen/Server Detail — Settings · Version", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/tSOeI.json b/assets/design-export/json/tSOeI.json index 68ba3c6f9..9597df7ca 100644 --- a/assets/design-export/json/tSOeI.json +++ b/assets/design-export/json/tSOeI.json @@ -2,7 +2,7 @@ "type": "frame", "id": "tSOeI", "x": 4920, - "y": 43267, + "y": 43530, "name": "Server Detail · Settings · Networking · Provider selected", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/tTSdi.json b/assets/design-export/json/tTSdi.json index 4076bf49b..b8fdf45a9 100644 --- a/assets/design-export/json/tTSdi.json +++ b/assets/design-export/json/tTSdi.json @@ -2,7 +2,7 @@ "type": "frame", "id": "tTSdi", "x": 0, - "y": 62369, + "y": 62632, "name": "Screen/Backups — Restores", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/tY6RD.json b/assets/design-export/json/tY6RD.json index 4fb253f7d..9a0c097a2 100644 --- a/assets/design-export/json/tY6RD.json +++ b/assets/design-export/json/tY6RD.json @@ -2,7 +2,7 @@ "type": "frame", "id": "tY6RD", "x": 0, - "y": 32807, + "y": 32970, "name": "Screen/Server Detail — Modpacks", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/tr6cE.json b/assets/design-export/json/tr6cE.json index 8cfb792ce..ac9b8e4fb 100644 --- a/assets/design-export/json/tr6cE.json +++ b/assets/design-export/json/tr6cE.json @@ -1 +1 @@ -{"id":"tr6cE","type":"ref","ref":"x3beP","name":"Screen/Share links — Create link (No expiry)","x":3280,"y":53087,"descendants":{"E7Whx":{"content":"Create share link for mc-survival"},"qzcst":{"content":"Anyone with this link can check the server's status and connection address without signing in."},"Nht52":{"type":"frame","id":"npdLj","name":"mBody","width":"fill_container","layout":"vertical","gap":16,"padding":[16,0,0,0],"children":[{"type":"frame","id":"qQuWO","name":"fExpiry","width":"fill_container","layout":"vertical","gap":4,"children":[{"type":"text","id":"owzfC","name":"labelExpiry","fill":"$foreground/foreground","content":"Expires in","fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"500"},{"id":"NGuon","type":"ref","ref":"AT7ya","name":"Select","width":"fill_container","descendants":{"cFsFk":{"content":"No expiry"}}},{"type":"text","id":"oB60N","name":"warningNoExpiry","fill":"$warning/warning","content":"This link works until you revoke it.","fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"normal"}]},{"type":"frame","id":"Pjy1j","name":"fCanStart","width":"fill_container","gap":12,"justifyContent":"space_between","alignItems":"center","children":[{"id":"Sjm8y","type":"ref","ref":"rh2QH","name":"canStartSwitch","width":40},{"type":"frame","id":"HzOd6","name":"canStartText","width":"fill_container","layout":"vertical","children":[{"type":"text","id":"Mywn6","name":"canStartTitle","fill":"$foreground/foreground","content":"Allow starting the server","fontFamily":"$typography/font-sans","fontSize":14,"fontWeight":"500"},{"type":"text","id":"NRqHN","name":"canStartDesc","fill":"$muted","textGrowth":"fixed-width","width":"fill_container","content":"The link holder can wake mc-survival from asleep. Without this, they can only view its status.","lineHeight":1.4,"fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"normal"}]}]}]},"GCwAF":{"type":"frame","id":"YqOBW","name":"mFooter","width":"fill_container","gap":8,"padding":[20,0,0,0],"justifyContent":"end","alignItems":"center","children":[{"id":"eay4r","type":"ref","ref":"rkF0p","name":"btnCancel","cornerRadius":6,"descendants":{"WtPjS":{"enabled":false},"r4VbAi":{"content":"Cancel","fontSize":13}}},{"id":"I54mdI","type":"ref","ref":"j9c5W","name":"btnPrimary","cornerRadius":6,"descendants":{"n8Hmwk":{"icon":"link","width":14,"height":14},"U1xvSo":{"content":"Create link","fontSize":13}}}]}}} +{"id":"tr6cE","type":"ref","ref":"x3beP","name":"Screen/Share links — Create link (No expiry)","x":3280,"y":53350,"descendants":{"E7Whx":{"content":"Create share link for mc-survival"},"qzcst":{"content":"Anyone with this link can check the server's status and connection address without signing in."},"Nht52":{"type":"frame","id":"npdLj","name":"mBody","width":"fill_container","layout":"vertical","gap":16,"padding":[16,0,0,0],"children":[{"type":"frame","id":"qQuWO","name":"fExpiry","width":"fill_container","layout":"vertical","gap":4,"children":[{"type":"text","id":"owzfC","name":"labelExpiry","fill":"$foreground/foreground","content":"Expires in","fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"500"},{"id":"NGuon","type":"ref","ref":"AT7ya","name":"Select","width":"fill_container","descendants":{"cFsFk":{"content":"No expiry"}}},{"type":"text","id":"oB60N","name":"warningNoExpiry","fill":"$warning/warning","content":"This link works until you revoke it.","fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"normal"}]},{"type":"frame","id":"Pjy1j","name":"fCanStart","width":"fill_container","gap":12,"justifyContent":"space_between","alignItems":"center","children":[{"id":"Sjm8y","type":"ref","ref":"rh2QH","name":"canStartSwitch","width":40},{"type":"frame","id":"HzOd6","name":"canStartText","width":"fill_container","layout":"vertical","children":[{"type":"text","id":"Mywn6","name":"canStartTitle","fill":"$foreground/foreground","content":"Allow starting the server","fontFamily":"$typography/font-sans","fontSize":14,"fontWeight":"500"},{"type":"text","id":"NRqHN","name":"canStartDesc","fill":"$muted","textGrowth":"fixed-width","width":"fill_container","content":"The link holder can wake mc-survival from asleep. Without this, they can only view its status.","lineHeight":1.4,"fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"normal"}]}]}]},"GCwAF":{"type":"frame","id":"YqOBW","name":"mFooter","width":"fill_container","gap":8,"padding":[20,0,0,0],"justifyContent":"end","alignItems":"center","children":[{"id":"eay4r","type":"ref","ref":"rkF0p","name":"btnCancel","cornerRadius":6,"descendants":{"WtPjS":{"enabled":false},"r4VbAi":{"content":"Cancel","fontSize":13}}},{"id":"I54mdI","type":"ref","ref":"j9c5W","name":"btnPrimary","cornerRadius":6,"descendants":{"n8Hmwk":{"icon":"link","width":14,"height":14},"U1xvSo":{"content":"Create link","fontSize":13}}}]}}} diff --git a/assets/design-export/json/uCA23.json b/assets/design-export/json/uCA23.json index 85ad3d915..df73d7ca0 100644 --- a/assets/design-export/json/uCA23.json +++ b/assets/design-export/json/uCA23.json @@ -2,7 +2,7 @@ "type": "frame", "id": "uCA23", "x": 1640, - "y": 38007, + "y": 38170, "name": "Screen/Server Detail — Settings · General", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/uMiwd.json b/assets/design-export/json/uMiwd.json index 679e07870..4f4bef769 100644 --- a/assets/design-export/json/uMiwd.json +++ b/assets/design-export/json/uMiwd.json @@ -2,7 +2,7 @@ "type": "frame", "id": "uMiwd", "x": 0, - "y": 78259, + "y": 78522, "name": "Screen/Admin Settings — Authentication", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/ugDSa.json b/assets/design-export/json/ugDSa.json index 42143ff8c..abb419171 100644 --- a/assets/design-export/json/ugDSa.json +++ b/assets/design-export/json/ugDSa.json @@ -2,7 +2,7 @@ "type": "frame", "id": "ugDSa", "x": 0, - "y": 44567, + "y": 44830, "name": "Screen/Server Detail — Settings · Environment", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/uoxQW.json b/assets/design-export/json/uoxQW.json index a75a29b82..3043b1bad 100644 --- a/assets/design-export/json/uoxQW.json +++ b/assets/design-export/json/uoxQW.json @@ -2,7 +2,7 @@ "type": "frame", "id": "uoxQW", "x": 0, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/vUqMl.json b/assets/design-export/json/vUqMl.json index 3e172bb37..91cb1c182 100644 --- a/assets/design-export/json/vUqMl.json +++ b/assets/design-export/json/vUqMl.json @@ -9,7 +9,7 @@ }, "clip": true, "width": 1440, - "height": 1920, + "height": 1989, "fill": "$background/background", "children": [ { @@ -70,7 +70,7 @@ "y": 0, "name": "ModalContainer", "width": 1440, - "height": 1920, + "height": 1989, "fill": "#000000B3", "layout": "vertical", "justifyContent": "center", @@ -523,6 +523,57 @@ } ] }, + { + "type": "frame", + "id": "pL1xq", + "name": "ReserveLess", + "width": "fill_container", + "layout": "vertical", + "gap": 6, + "children": [ + { + "type": "frame", + "id": "ZCdGq", + "name": "SwitchRow", + "width": "fill_container", + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "Ogs2f", + "type": "ref", + "ref": "rh2QH", + "name": "Switch", + "width": 36, + "height": 20 + }, + { + "type": "text", + "id": "qwB4J", + "name": "Label", + "fill": "$foreground/foreground", + "content": "Reserve less than the limit", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + } + ] + }, + { + "type": "text", + "id": "TvCeZ", + "name": "Helper", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Only the reserved amount counts when the server is placed on a node, so more servers fit; it can use more, up to the limit, when the node has room (Burstable QoS).", + "lineHeight": 1.4, + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "normal" + } + ] + }, { "type": "frame", "id": "Pzn4Y", diff --git a/assets/design-export/json/w1QYmf.json b/assets/design-export/json/w1QYmf.json index 35d3bd759..c450d9bb5 100644 --- a/assets/design-export/json/w1QYmf.json +++ b/assets/design-export/json/w1QYmf.json @@ -2,7 +2,7 @@ "type": "frame", "id": "w1QYmf", "x": 11480, - "y": 36707, + "y": 36870, "name": "States/Remote Capture — Access and capability", "context": "Actual CaptureWidget states from 8e08a60f. Resolve user access separately from selected-site capability. Existing files remain readable when only new captures are disabled.", "theme": { diff --git a/assets/design-export/json/wxINm.json b/assets/design-export/json/wxINm.json index 26f10ead9..e32dbfb0c 100644 --- a/assets/design-export/json/wxINm.json +++ b/assets/design-export/json/wxINm.json @@ -2,7 +2,7 @@ "type": "frame", "id": "wxINm", "x": 3280, - "y": 40607, + "y": 40770, "name": "Screen/Server Detail — Settings · Game configuration (Read-only)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/x7MJI.json b/assets/design-export/json/x7MJI.json index 462b6af86..6320abeaa 100644 --- a/assets/design-export/json/x7MJI.json +++ b/assets/design-export/json/x7MJI.json @@ -2,7 +2,7 @@ "type": "frame", "id": "x7MJI", "x": 10560, - "y": 24182, + "y": 24345, "name": "Gameplane/Connection Card — Tunnel States (reference)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/xCJlu.json b/assets/design-export/json/xCJlu.json index 211eff9e5..09e9b6ad3 100644 --- a/assets/design-export/json/xCJlu.json +++ b/assets/design-export/json/xCJlu.json @@ -1 +1 @@ -{"type":"frame","id":"xCJlu","x":0,"y":53087,"name":"Screen/Server Detail — Settings · Share links","theme":{"semantic":"dark"},"clip":true,"width":1440,"height":900,"fill":"$background/background","children":[{"id":"oTwRW","type":"ref","ref":"kKFX9","height":"fill_container","descendants":{"iTbd8":{"fill":"#00000000"},"iTbd8/ghUsw":{"fill":"$foreground/foreground"},"iTbd8/bpSQ7":{"fill":"$foreground/foreground"},"i8Z1B":{"fill":"$accent/soft"},"i8Z1B/Sr2Q5":{"fill":"$accent/soft-foreground"},"i8Z1B/Ra9NK":{"fill":"$accent/soft-foreground"}}},{"type":"frame","id":"blozd","name":"Main","width":"fill_container","height":"fill_container","fill":"$background/background","layout":"vertical","children":[{"id":"H3zBj","type":"ref","ref":"gu5WY","width":"fill_container","descendants":{"Gl7AY":{"content":"mc-survival"}}},{"type":"frame","id":"zTKKm","name":"Body","width":"fill_container","height":"fill_container","fill":"$background/background","layout":"vertical","gap":24,"padding":24,"children":[{"id":"thUaP","type":"ref","ref":"S4k0x","name":"Detail Header","width":"fill_container"},{"id":"u0xtqP","type":"ref","ref":"I9kvlZ","name":"Tabs Bar","width":"fill_container","padding":[0,0,0,24],"descendants":{"Wp6q1":{"fill":"#00000000"},"Wp6q1/YUR8N":{"fill":"$muted"},"Y4QES":{"fill":"$segment"},"Y4QES/VcxMI":{"fill":"$foreground/segment"}}},{"type":"frame","id":"hG7GD","name":"Settings Content","width":"fill_container","height":"fill_container","children":[{"id":"yEFMI","type":"ref","ref":"EV8mp","name":"Settings Sub Nav","width":224,"height":"fill_container","descendants":{"axMXL":{"fill":"#00000000"},"JlFDJ":{"fill":"$surface/secondary"},"Qbxnz":{"fill":"$foreground/foreground"}}},{"type":"frame","id":"jq8Ik","name":"Settings Panel Wrap","width":"fill_container","height":"fill_container","layout":"vertical","children":[{"type":"frame","id":"oo5Et","name":"Settings Form","width":"fill_container","height":"fill_container","layout":"vertical","children":[{"type":"frame","id":"xlCWU","name":"Form Body","width":"fill_container","height":"fill_container","layout":"vertical","gap":16,"padding":24,"children":[{"type":"frame","id":"FSQFv","name":"Share Links Header","width":"fill_container","justifyContent":"space_between","alignItems":"center","children":[{"type":"frame","id":"wrMwX","name":"titleCol","layout":"vertical","gap":4,"children":[{"type":"text","id":"S8qldJ","fill":"$foreground/foreground","content":"Share links","fontFamily":"$typography/font-sans","fontSize":17,"fontWeight":"500"},{"type":"text","id":"FCOnx","fill":"$muted","textGrowth":"fixed-width","width":520,"content":"Let people without a Gameplane account check this server's status and connection address. Only the owner can create or revoke links.","fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"normal"}]},{"id":"EmHnL","type":"ref","ref":"z9ShNE","name":"btnCreateLink","fill":"#00000000","padding":0,"height":"fit_content","cornerRadius":0,"descendants":{"nWTJb":{"icon":"link"},"IVgQ8/U1xvSo":{"content":"Create link"}}}]},{"id":"j0MM9","type":"ref","ref":"ECbbo","name":"Share Links Table","width":"fill_container","stroke":"$border/border","strokeWidth":1,"cornerRadius":8,"children":[{"id":"C6RWz","type":"ref","ref":"eafUs","name":"Table Header","width":"fill_container","layout":"horizontal","children":[{"id":"oCSgI","type":"ref","ref":"DYYKO","name":"Column - Created","width":"fill_container","descendants":{"SIu74":{"content":"Created","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}},{"id":"Yjl8P","type":"ref","ref":"DYYKO","name":"Column - Expires","width":"fill_container","descendants":{"SIu74":{"content":"Expires","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}},{"id":"kAy8Y","type":"ref","ref":"DYYKO","name":"Column - Can start","width":120,"descendants":{"SIu74":{"content":"Can start","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}},{"id":"gCkZa","type":"ref","ref":"DYYKO","name":"Column - Status","width":110,"descendants":{"SIu74":{"content":"Status","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}},{"id":"s87yP","type":"ref","ref":"DYYKO","name":"Column - Actions","width":140,"descendants":{"SIu74":{"content":"","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"vbxAw","type":"ref","ref":"GUHQo","name":"Table Body","width":"fill_container","children":[{"id":"CCCbH","type":"ref","ref":"tDY4O","name":"Link Row","width":"fill_container","layout":"horizontal","fill":"#00000000","stroke":"$border/border","strokeWidth":1,"children":[{"id":"KsML2","type":"ref","ref":"TDRJE","name":"Created Cell","width":"fill_container","height":"fill_container","descendants":{"HNAOu":{"content":"Jul 28, 2026","fontFamily":"$typography/font-mono","fontSize":13,"fontWeight":"normal"}}},{"id":"IiHOi","type":"ref","ref":"TDRJE","name":"Expires Cell","width":"fill_container","height":"fill_container","descendants":{"HNAOu":{"content":"Never","fontFamily":"$typography/font-mono","fontSize":13,"fontWeight":"normal"}}},{"id":"Z0lTbG","type":"ref","ref":"TDRJE","name":"Can Start Cell","width":120,"height":"fill_container","children":[{"id":"dBlhj","type":"ref","ref":"kGH2O","name":"Capability Chip","descendants":{"O1PqF5":{"content":"Can start","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"eY4sa","type":"ref","ref":"TDRJE","name":"Status Cell","width":110,"height":"fill_container","children":[{"id":"BxcGi","type":"ref","ref":"UOoQW","name":"Status Chip","descendants":{"YXoPG":{"content":"Active","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"nFDwY","type":"ref","ref":"TDRJE","name":"Action Cell","width":140,"height":"fill_container","children":[{"id":"NJjup","type":"ref","ref":"IU7OG","name":"Revoke Button","height":32,"fill":"#00000000","padding":0,"cornerRadius":0,"descendants":{"E4lQce/gsuz1":{"icon":"link-2-off"},"E4lQce/IUtnC":{"content":"Revoke"}}}]}]},{"id":"WkLza","type":"ref","ref":"tDY4O","name":"Link Row","width":"fill_container","layout":"horizontal","fill":"#00000000","stroke":"$border/border","strokeWidth":1,"children":[{"id":"Hwsid","type":"ref","ref":"TDRJE","name":"Created Cell","width":"fill_container","descendants":{"HNAOu":{"content":"Jun 1, 2026","fontFamily":"$typography/font-mono","fontSize":13,"fontWeight":"normal"}}},{"id":"d5nGN","type":"ref","ref":"TDRJE","name":"Expires Cell","width":"fill_container","descendants":{"HNAOu":{"content":"Jun 8, 2026","fontFamily":"$typography/font-mono","fontSize":13,"fontWeight":"normal"}}},{"id":"RTbzF","type":"ref","ref":"TDRJE","name":"Can Start Cell","width":120,"children":[{"id":"ZwHE7","type":"ref","ref":"KmZnF","name":"Capability Chip","descendants":{"xZ9ah":{"content":"View only","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"a3Fu3h","type":"ref","ref":"TDRJE","name":"Status Cell","width":110,"children":[{"id":"ZhlaX","type":"ref","ref":"KmZnF","name":"Status Chip","descendants":{"xZ9ah":{"content":"Expired","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"K4Ci1","type":"ref","ref":"TDRJE","name":"Action Cell","width":140,"descendants":{"HNAOu":{"content":"—","fill":"$muted","fontFamily":"$typography/font-sans","fontSize":13,"fontWeight":"normal"}}}]},{"id":"lg05a","type":"ref","ref":"tDY4O","name":"Link Row","width":"fill_container","layout":"horizontal","fill":"#00000000","stroke":"$border/border","strokeWidth":1,"children":[{"id":"h8xYb","type":"ref","ref":"TDRJE","name":"Created Cell","width":"fill_container","descendants":{"HNAOu":{"content":"May 12, 2026","fontFamily":"$typography/font-mono","fontSize":13,"fontWeight":"normal"}}},{"id":"j0ZTmU","type":"ref","ref":"TDRJE","name":"Expires Cell","width":"fill_container","descendants":{"HNAOu":{"content":"May 19, 2026","fontFamily":"$typography/font-mono","fontSize":13,"fontWeight":"normal"}}},{"id":"F0aJg","type":"ref","ref":"TDRJE","name":"Can Start Cell","width":120,"children":[{"id":"ph6wx","type":"ref","ref":"kGH2O","name":"Capability Chip","descendants":{"O1PqF5":{"content":"Can start","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"d4Y37L","type":"ref","ref":"TDRJE","name":"Status Cell","width":110,"children":[{"id":"gPEJX","type":"ref","ref":"eg3vM","name":"Status Chip","descendants":{"S2JwD":{"content":"Revoked","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"d4skfd","type":"ref","ref":"TDRJE","name":"Action Cell","width":140,"descendants":{"HNAOu":{"content":"—","fill":"$muted","fontFamily":"$typography/font-sans","fontSize":13,"fontWeight":"normal"}}}]}]}]}]}]}]}]}]}]}]} +{"type":"frame","id":"xCJlu","x":0,"y":53350,"name":"Screen/Server Detail — Settings · Share links","theme":{"semantic":"dark"},"clip":true,"width":1440,"height":900,"fill":"$background/background","children":[{"id":"oTwRW","type":"ref","ref":"kKFX9","height":"fill_container","descendants":{"iTbd8":{"fill":"#00000000"},"iTbd8/ghUsw":{"fill":"$foreground/foreground"},"iTbd8/bpSQ7":{"fill":"$foreground/foreground"},"i8Z1B":{"fill":"$accent/soft"},"i8Z1B/Sr2Q5":{"fill":"$accent/soft-foreground"},"i8Z1B/Ra9NK":{"fill":"$accent/soft-foreground"}}},{"type":"frame","id":"blozd","name":"Main","width":"fill_container","height":"fill_container","fill":"$background/background","layout":"vertical","children":[{"id":"H3zBj","type":"ref","ref":"gu5WY","width":"fill_container","descendants":{"Gl7AY":{"content":"mc-survival"}}},{"type":"frame","id":"zTKKm","name":"Body","width":"fill_container","height":"fill_container","fill":"$background/background","layout":"vertical","gap":24,"padding":24,"children":[{"id":"thUaP","type":"ref","ref":"S4k0x","name":"Detail Header","width":"fill_container"},{"id":"u0xtqP","type":"ref","ref":"I9kvlZ","name":"Tabs Bar","width":"fill_container","padding":[0,0,0,24],"descendants":{"Wp6q1":{"fill":"#00000000"},"Wp6q1/YUR8N":{"fill":"$muted"},"Y4QES":{"fill":"$segment"},"Y4QES/VcxMI":{"fill":"$foreground/segment"}}},{"type":"frame","id":"hG7GD","name":"Settings Content","width":"fill_container","height":"fill_container","children":[{"id":"yEFMI","type":"ref","ref":"EV8mp","name":"Settings Sub Nav","width":224,"height":"fill_container","descendants":{"axMXL":{"fill":"#00000000"},"JlFDJ":{"fill":"$surface/secondary"},"Qbxnz":{"fill":"$foreground/foreground"}}},{"type":"frame","id":"jq8Ik","name":"Settings Panel Wrap","width":"fill_container","height":"fill_container","layout":"vertical","children":[{"type":"frame","id":"oo5Et","name":"Settings Form","width":"fill_container","height":"fill_container","layout":"vertical","children":[{"type":"frame","id":"xlCWU","name":"Form Body","width":"fill_container","height":"fill_container","layout":"vertical","gap":16,"padding":24,"children":[{"type":"frame","id":"FSQFv","name":"Share Links Header","width":"fill_container","justifyContent":"space_between","alignItems":"center","children":[{"type":"frame","id":"wrMwX","name":"titleCol","layout":"vertical","gap":4,"children":[{"type":"text","id":"S8qldJ","fill":"$foreground/foreground","content":"Share links","fontFamily":"$typography/font-sans","fontSize":17,"fontWeight":"500"},{"type":"text","id":"FCOnx","fill":"$muted","textGrowth":"fixed-width","width":520,"content":"Let people without a Gameplane account check this server's status and connection address. Only the owner can create or revoke links.","fontFamily":"$typography/font-sans","fontSize":12,"fontWeight":"normal"}]},{"id":"EmHnL","type":"ref","ref":"z9ShNE","name":"btnCreateLink","fill":"#00000000","padding":0,"height":"fit_content","cornerRadius":0,"descendants":{"nWTJb":{"icon":"link"},"IVgQ8/U1xvSo":{"content":"Create link"}}}]},{"id":"j0MM9","type":"ref","ref":"ECbbo","name":"Share Links Table","width":"fill_container","stroke":"$border/border","strokeWidth":1,"cornerRadius":8,"children":[{"id":"C6RWz","type":"ref","ref":"eafUs","name":"Table Header","width":"fill_container","layout":"horizontal","children":[{"id":"oCSgI","type":"ref","ref":"DYYKO","name":"Column - Created","width":"fill_container","descendants":{"SIu74":{"content":"Created","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}},{"id":"Yjl8P","type":"ref","ref":"DYYKO","name":"Column - Expires","width":"fill_container","descendants":{"SIu74":{"content":"Expires","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}},{"id":"kAy8Y","type":"ref","ref":"DYYKO","name":"Column - Can start","width":120,"descendants":{"SIu74":{"content":"Can start","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}},{"id":"gCkZa","type":"ref","ref":"DYYKO","name":"Column - Status","width":110,"descendants":{"SIu74":{"content":"Status","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}},{"id":"s87yP","type":"ref","ref":"DYYKO","name":"Column - Actions","width":140,"descendants":{"SIu74":{"content":"","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"vbxAw","type":"ref","ref":"GUHQo","name":"Table Body","width":"fill_container","children":[{"id":"CCCbH","type":"ref","ref":"tDY4O","name":"Link Row","width":"fill_container","layout":"horizontal","fill":"#00000000","stroke":"$border/border","strokeWidth":1,"children":[{"id":"KsML2","type":"ref","ref":"TDRJE","name":"Created Cell","width":"fill_container","height":"fill_container","descendants":{"HNAOu":{"content":"Jul 28, 2026","fontFamily":"$typography/font-mono","fontSize":13,"fontWeight":"normal"}}},{"id":"IiHOi","type":"ref","ref":"TDRJE","name":"Expires Cell","width":"fill_container","height":"fill_container","descendants":{"HNAOu":{"content":"Never","fontFamily":"$typography/font-mono","fontSize":13,"fontWeight":"normal"}}},{"id":"Z0lTbG","type":"ref","ref":"TDRJE","name":"Can Start Cell","width":120,"height":"fill_container","children":[{"id":"dBlhj","type":"ref","ref":"kGH2O","name":"Capability Chip","descendants":{"O1PqF5":{"content":"Can start","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"eY4sa","type":"ref","ref":"TDRJE","name":"Status Cell","width":110,"height":"fill_container","children":[{"id":"BxcGi","type":"ref","ref":"UOoQW","name":"Status Chip","descendants":{"YXoPG":{"content":"Active","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"nFDwY","type":"ref","ref":"TDRJE","name":"Action Cell","width":140,"height":"fill_container","children":[{"id":"NJjup","type":"ref","ref":"IU7OG","name":"Revoke Button","height":32,"fill":"#00000000","padding":0,"cornerRadius":0,"descendants":{"E4lQce/gsuz1":{"icon":"link-2-off"},"E4lQce/IUtnC":{"content":"Revoke"}}}]}]},{"id":"WkLza","type":"ref","ref":"tDY4O","name":"Link Row","width":"fill_container","layout":"horizontal","fill":"#00000000","stroke":"$border/border","strokeWidth":1,"children":[{"id":"Hwsid","type":"ref","ref":"TDRJE","name":"Created Cell","width":"fill_container","descendants":{"HNAOu":{"content":"Jun 1, 2026","fontFamily":"$typography/font-mono","fontSize":13,"fontWeight":"normal"}}},{"id":"d5nGN","type":"ref","ref":"TDRJE","name":"Expires Cell","width":"fill_container","descendants":{"HNAOu":{"content":"Jun 8, 2026","fontFamily":"$typography/font-mono","fontSize":13,"fontWeight":"normal"}}},{"id":"RTbzF","type":"ref","ref":"TDRJE","name":"Can Start Cell","width":120,"children":[{"id":"ZwHE7","type":"ref","ref":"KmZnF","name":"Capability Chip","descendants":{"xZ9ah":{"content":"View only","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"a3Fu3h","type":"ref","ref":"TDRJE","name":"Status Cell","width":110,"children":[{"id":"ZhlaX","type":"ref","ref":"KmZnF","name":"Status Chip","descendants":{"xZ9ah":{"content":"Expired","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"K4Ci1","type":"ref","ref":"TDRJE","name":"Action Cell","width":140,"descendants":{"HNAOu":{"content":"—","fill":"$muted","fontFamily":"$typography/font-sans","fontSize":13,"fontWeight":"normal"}}}]},{"id":"lg05a","type":"ref","ref":"tDY4O","name":"Link Row","width":"fill_container","layout":"horizontal","fill":"#00000000","stroke":"$border/border","strokeWidth":1,"children":[{"id":"h8xYb","type":"ref","ref":"TDRJE","name":"Created Cell","width":"fill_container","descendants":{"HNAOu":{"content":"May 12, 2026","fontFamily":"$typography/font-mono","fontSize":13,"fontWeight":"normal"}}},{"id":"j0ZTmU","type":"ref","ref":"TDRJE","name":"Expires Cell","width":"fill_container","descendants":{"HNAOu":{"content":"May 19, 2026","fontFamily":"$typography/font-mono","fontSize":13,"fontWeight":"normal"}}},{"id":"F0aJg","type":"ref","ref":"TDRJE","name":"Can Start Cell","width":120,"children":[{"id":"ph6wx","type":"ref","ref":"kGH2O","name":"Capability Chip","descendants":{"O1PqF5":{"content":"Can start","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"d4Y37L","type":"ref","ref":"TDRJE","name":"Status Cell","width":110,"children":[{"id":"gPEJX","type":"ref","ref":"eg3vM","name":"Status Chip","descendants":{"S2JwD":{"content":"Revoked","fontFamily":"$typography/font-mono","fontSize":14,"fontWeight":"normal"}}}]},{"id":"d4skfd","type":"ref","ref":"TDRJE","name":"Action Cell","width":140,"descendants":{"HNAOu":{"content":"—","fill":"$muted","fontFamily":"$typography/font-sans","fontSize":13,"fontWeight":"normal"}}}]}]}]}]}]}]}]}]}]}]} diff --git a/assets/design-export/json/xvlB6.json b/assets/design-export/json/xvlB6.json index ae19e250c..0331b8e6f 100644 --- a/assets/design-export/json/xvlB6.json +++ b/assets/design-export/json/xvlB6.json @@ -2,7 +2,7 @@ "type": "frame", "id": "xvlB6", "x": 3280, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture (Running)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/y3YcQ.json b/assets/design-export/json/y3YcQ.json index b7aeaf0cf..ecac32504 100644 --- a/assets/design-export/json/y3YcQ.json +++ b/assets/design-export/json/y3YcQ.json @@ -2,7 +2,7 @@ "type": "frame", "id": "y3YcQ", "x": 6560, - "y": 43267, + "y": 43530, "name": "Server Detail · Settings · Networking · Credentials entering", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/yrxK2.json b/assets/design-export/json/yrxK2.json index 82bb53679..66e9ee340 100644 --- a/assets/design-export/json/yrxK2.json +++ b/assets/design-export/json/yrxK2.json @@ -2,7 +2,7 @@ "type": "frame", "id": "yrxK2", "x": 9840, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry (Reset ID confirm)", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/zM0VF.json b/assets/design-export/json/zM0VF.json index 73d61298c..3c3c16032 100644 --- a/assets/design-export/json/zM0VF.json +++ b/assets/design-export/json/zM0VF.json @@ -2,7 +2,7 @@ "type": "frame", "id": "zM0VF", "x": 0, - "y": 89965, + "y": 90228, "name": "Screen/Admin Settings — About", "theme": { "semantic": "dark" diff --git a/assets/design-export/json/zqzr4.json b/assets/design-export/json/zqzr4.json index 36a744366..7a7504a32 100644 --- a/assets/design-export/json/zqzr4.json +++ b/assets/design-export/json/zqzr4.json @@ -2,7 +2,7 @@ "type": "frame", "id": "zqzr4", "x": 4920, - "y": 78259, + "y": 78522, "name": "Screen/Admin Settings — Authentication (Admin mapping warning)", "theme": { "semantic": "dark" diff --git a/assets/design-export/screenshots/E0ypH.png b/assets/design-export/screenshots/E0ypH.png index dd314b157..2522eeddb 100644 Binary files a/assets/design-export/screenshots/E0ypH.png and b/assets/design-export/screenshots/E0ypH.png differ diff --git a/assets/design-export/screenshots/NLu2I.png b/assets/design-export/screenshots/NLu2I.png new file mode 100644 index 000000000..059d02480 Binary files /dev/null and b/assets/design-export/screenshots/NLu2I.png differ diff --git a/assets/design-export/screenshots/NqaaU.png b/assets/design-export/screenshots/NqaaU.png new file mode 100644 index 000000000..c152eb229 Binary files /dev/null and b/assets/design-export/screenshots/NqaaU.png differ diff --git a/assets/design-export/screenshots/vUqMl.png b/assets/design-export/screenshots/vUqMl.png index 7b51ef3c6..6824ac7e1 100644 Binary files a/assets/design-export/screenshots/vUqMl.png and b/assets/design-export/screenshots/vUqMl.png differ diff --git a/design.pen b/design.pen index 40c97d716..ee7f6091e 100644 --- a/design.pen +++ b/design.pen @@ -1138,7 +1138,7 @@ "type": "frame", "id": "EZFW0", "x": 0, - "y": 24182, + "y": 24345, "name": "Screen/Server Detail — Overview", "theme": { "semantic": "dark" @@ -2752,7 +2752,7 @@ "type": "frame", "id": "P08Uw", "x": 0, - "y": 26307, + "y": 26470, "name": "Screen/Server Detail — Events", "theme": { "semantic": "dark" @@ -3319,7 +3319,7 @@ "type": "frame", "id": "Xn5ns", "x": 0, - "y": 27607, + "y": 27770, "name": "Screen/Server Detail — Console", "theme": { "semantic": "dark" @@ -3519,7 +3519,7 @@ "type": "frame", "id": "kPmoo", "x": 0, - "y": 28907, + "y": 29070, "name": "Screen/Server Detail — Logs", "theme": { "semantic": "dark" @@ -3952,7 +3952,7 @@ "type": "frame", "id": "FtdkI", "x": 1640, - "y": 28907, + "y": 29070, "name": "Screen/Server Detail — Logs (Failed)", "theme": { "semantic": "dark" @@ -4245,7 +4245,7 @@ "type": "frame", "id": "Burtr", "x": 0, - "y": 30207, + "y": 30370, "name": "Screen/Server Detail — Files", "theme": { "semantic": "dark" @@ -5746,7 +5746,7 @@ "type": "frame", "id": "sZtDi", "x": 0, - "y": 31507, + "y": 31670, "name": "Screen/Server Detail — Mods", "theme": { "semantic": "dark" @@ -6004,7 +6004,7 @@ "type": "frame", "id": "GayoL", "x": 3280, - "y": 31507, + "y": 31670, "name": "Screen/Server Detail — Mods — Browse (added)", "theme": { "semantic": "dark" @@ -7143,7 +7143,7 @@ "type": "frame", "id": "Ss0Yr", "x": 4920, - "y": 31507, + "y": 31670, "name": "Screen/Server Detail — Mods — Install mod (from URL)", "theme": { "semantic": "dark" @@ -7539,7 +7539,7 @@ "type": "frame", "id": "V1VhGE", "x": 6560, - "y": 31507, + "y": 31670, "name": "Screen/Server Detail — Mods — Install mod (upload)", "theme": { "semantic": "dark" @@ -7932,7 +7932,7 @@ "type": "frame", "id": "tY6RD", "x": 0, - "y": 32807, + "y": 32970, "name": "Screen/Server Detail — Modpacks", "theme": { "semantic": "dark" @@ -8739,7 +8739,7 @@ "type": "frame", "id": "dPP50", "x": 0, - "y": 34107, + "y": 34270, "name": "Screen/Server Detail — Players", "theme": { "semantic": "dark" @@ -9067,7 +9067,7 @@ "type": "frame", "id": "pssCT", "x": 0, - "y": 35407, + "y": 35570, "name": "Screen/Server Detail — Backups", "theme": { "semantic": "dark" @@ -9501,7 +9501,7 @@ "type": "frame", "id": "hLB9Z", "x": 0, - "y": 38007, + "y": 38170, "name": "Screen/Server Detail — Settings", "theme": { "semantic": "dark" @@ -9990,7 +9990,7 @@ "type": "frame", "id": "swxkJ", "x": 0, - "y": 39307, + "y": 39470, "name": "Screen/Server Detail — Settings · Version", "theme": { "semantic": "dark" @@ -10685,7 +10685,7 @@ "type": "frame", "id": "E0ypH", "x": 0, - "y": 41967, + "y": 42130, "name": "Screen/Server Detail — Settings · Resources", "theme": { "semantic": "dark" @@ -11052,6 +11052,66 @@ } ] }, + { + "type": "frame", + "id": "xuyWG", + "name": "Field Reserve less", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "oWMYX", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "AWqw1", + "name": "t", + "fill": "$foreground/foreground", + "content": "Reserve less than the limit", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "Ce3T8", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "Off: the server reserves its full CPU and memory (Guaranteed QoS). On: it reserves less, so more servers fit on a node, and uses more up to its limit when the node has room (Burstable QoS).", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "q9ZQKc", + "name": "ctrl", + "width": "fill_container", + "height": 28, + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "XFdCR", + "type": "ref", + "ref": "rh2QH", + "name": "Switch", + "width": 36, + "height": 20 + } + ] + } + ] + }, { "type": "frame", "id": "djYXg", @@ -11219,7 +11279,7 @@ "type": "frame", "id": "J5pjJ3", "x": 0, - "y": 43267, + "y": 43530, "name": "Server Detail · Settings · Networking · Complete", "theme": { "semantic": "dark" @@ -12029,7 +12089,7 @@ "type": "frame", "id": "ugDSa", "x": 0, - "y": 44567, + "y": 44830, "name": "Screen/Server Detail — Settings · Environment", "theme": { "semantic": "dark" @@ -12272,7 +12332,7 @@ "type": "frame", "id": "i1bLR", "x": 0, - "y": 45867, + "y": 46130, "name": "Screen/Server Detail — Settings · Lifecycle", "theme": { "semantic": "dark" @@ -13696,7 +13756,7 @@ "type": "frame", "id": "KaRFX", "x": 0, - "y": 47887, + "y": 48150, "name": "Screen/Server Detail — Settings · Scheduled backups", "theme": { "semantic": "dark" @@ -14022,7 +14082,7 @@ "type": "frame", "id": "Y5cmvI", "x": 0, - "y": 50487, + "y": 50750, "name": "Screen/Server Detail — Settings · Placement", "theme": { "semantic": "dark" @@ -14453,7 +14513,7 @@ "type": "frame", "id": "VfB0Y", "x": 0, - "y": 51787, + "y": 52050, "name": "Screen/Server Detail — Settings · RBAC & access", "theme": { "semantic": "dark" @@ -14766,7 +14826,7 @@ "type": "frame", "id": "i8wib", "x": 0, - "y": 54387, + "y": 54650, "name": "Screen/Server Detail — Settings · Danger zone", "theme": { "semantic": "dark" @@ -17058,7 +17118,7 @@ }, "clip": true, "width": 1440, - "height": 1920, + "height": 1989, "fill": "$background/background", "children": [ { @@ -17119,7 +17179,7 @@ "y": 0, "name": "ModalContainer", "width": 1440, - "height": 1920, + "height": 1989, "fill": "#000000B3", "layout": "vertical", "justifyContent": "center", @@ -17572,6 +17632,57 @@ } ] }, + { + "type": "frame", + "id": "pL1xq", + "name": "ReserveLess", + "width": "fill_container", + "layout": "vertical", + "gap": 6, + "children": [ + { + "type": "frame", + "id": "ZCdGq", + "name": "SwitchRow", + "width": "fill_container", + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "Ogs2f", + "type": "ref", + "ref": "rh2QH", + "name": "Switch", + "width": 36, + "height": 20 + }, + { + "type": "text", + "id": "qwB4J", + "name": "Label", + "fill": "$foreground/foreground", + "content": "Reserve less than the limit", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + } + ] + }, + { + "type": "text", + "id": "TvCeZ", + "name": "Helper", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Only the reserved amount counts when the server is placed on a node, so more servers fit; it can use more, up to the limit, when the node has room (Burstable QoS).", + "lineHeight": 1.4, + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "normal" + } + ] + }, { "type": "frame", "id": "Pzn4Y", @@ -18485,7 +18596,7 @@ "type": "frame", "id": "f1Vga", "x": 0, - "y": 21112, + "y": 21275, "name": "Screen/Create Server — Step 4 Network", "theme": { "semantic": "dark" @@ -19050,7 +19161,7 @@ "type": "frame", "id": "UMJli", "x": 0, - "y": 22782, + "y": 22945, "name": "Screen/Create Server — Step 5 Review", "theme": { "semantic": "dark" @@ -20099,7 +20210,7 @@ "type": "frame", "id": "kK8Ji", "x": 0, - "y": 55687, + "y": 55950, "name": "Screen/Modules Catalog", "theme": { "semantic": "dark" @@ -21769,7 +21880,7 @@ "type": "frame", "id": "DPrYX", "x": 0, - "y": 59519, + "y": 59782, "name": "Screen/Backups — Index", "theme": { "semantic": "dark" @@ -22046,7 +22157,7 @@ "type": "frame", "id": "fK8Bi", "x": 0, - "y": 60819, + "y": 61082, "name": "Screen/Backups — Schedules", "theme": { "semantic": "dark" @@ -23116,7 +23227,7 @@ "type": "frame", "id": "tTSdi", "x": 0, - "y": 62369, + "y": 62632, "name": "Screen/Backups — Restores", "theme": { "semantic": "dark" @@ -23635,7 +23746,7 @@ "type": "frame", "id": "j9W8A", "x": 0, - "y": 64969, + "y": 65232, "name": "Screen/Cluster Settings", "theme": { "semantic": "dark" @@ -23877,7 +23988,7 @@ "type": "frame", "id": "bYDHC", "x": 0, - "y": 66269, + "y": 66532, "name": "Screen/Users & RBAC", "theme": { "semantic": "dark" @@ -24401,7 +24512,7 @@ "type": "frame", "id": "e9lV4", "x": 0, - "y": 67569, + "y": 67832, "name": "Screen/Users & RBAC — Roles", "theme": { "semantic": "dark" @@ -24845,7 +24956,7 @@ "type": "frame", "id": "TBvTC", "x": 0, - "y": 68869, + "y": 69132, "name": "Screen/Users & RBAC — Service accounts", "theme": { "semantic": "dark" @@ -25007,7 +25118,7 @@ "type": "frame", "id": "Dpb9f", "x": 0, - "y": 70169, + "y": 70432, "name": "Screen/Users & RBAC — Identity providers", "theme": { "semantic": "dark" @@ -25169,7 +25280,7 @@ "type": "frame", "id": "DxKOh", "x": 0, - "y": 71469, + "y": 71732, "name": "Screen/Audit Log", "theme": { "semantic": "dark" @@ -26453,7 +26564,7 @@ "type": "frame", "id": "Bq2Yg", "x": 0, - "y": 72769, + "y": 73032, "name": "Screen/Admin — System Logs", "theme": { "semantic": "dark" @@ -27845,7 +27956,7 @@ "type": "frame", "id": "WZdnw", "x": 0, - "y": 74069, + "y": 74332, "name": "Screen/Admin Settings", "theme": { "semantic": "dark" @@ -28324,7 +28435,7 @@ "type": "frame", "id": "uMiwd", "x": 0, - "y": 78259, + "y": 78522, "name": "Screen/Admin Settings — Authentication", "theme": { "semantic": "dark" @@ -29827,7 +29938,7 @@ "type": "frame", "id": "RC3Kf", "x": 0, - "y": 81968, + "y": 82231, "name": "Screen/Admin Settings — Backup destinations", "theme": { "semantic": "dark" @@ -30068,7 +30179,7 @@ "type": "frame", "id": "g5mEpx", "x": 0, - "y": 83268, + "y": 83531, "name": "Screen/Admin Settings — Module sources", "theme": { "semantic": "dark" @@ -30627,7 +30738,7 @@ "type": "frame", "id": "n6Xlo", "x": 0, - "y": 85868, + "y": 86131, "name": "Screen/Admin Settings — Notifications", "theme": { "semantic": "dark" @@ -31132,7 +31243,7 @@ "type": "frame", "id": "uoxQW", "x": 0, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry", "theme": { "semantic": "dark" @@ -31364,7 +31475,7 @@ "type": "frame", "id": "M2sA4u", "x": 0, - "y": 88665, + "y": 88928, "name": "Screen/Admin Settings — Updates", "theme": { "semantic": "dark" @@ -31582,7 +31693,7 @@ "type": "frame", "id": "zM0VF", "x": 0, - "y": 89965, + "y": 90228, "name": "Screen/Admin Settings — About", "theme": { "semantic": "dark" @@ -33805,7 +33916,7 @@ "type": "frame", "id": "Wj0V4", "x": 0, - "y": 84568, + "y": 84831, "name": "Screen/Admin Settings — Mod registries", "context": "Central Mod registries administration. The banner describes central ownership; navigation includes registered Clusters and Cluster inventory. Exported against the unchanged screenshot scenario.", "theme": { @@ -34871,7 +34982,7 @@ "type": "frame", "id": "KhYNc", "x": 1640, - "y": 31507, + "y": 31670, "name": "Screen/Server Detail — Mods (by ID)", "theme": { "semantic": "dark" @@ -35647,7 +35758,7 @@ "type": "frame", "id": "IzuY2", "x": 3280, - "y": 24182, + "y": 24345, "name": "Screen/Server Detail — Overview (Never sleeps)", "theme": { "semantic": "dark" @@ -36705,7 +36816,7 @@ "type": "frame", "id": "TE2jI", "x": 4920, - "y": 24182, + "y": 24345, "name": "Screen/Server Detail — Overview (Asleep)", "theme": { "semantic": "dark" @@ -37950,7 +38061,7 @@ "type": "frame", "id": "x7MJI", "x": 10560, - "y": 24182, + "y": 24345, "name": "Gameplane/Connection Card — Tunnel States (reference)", "theme": { "semantic": "dark" @@ -38502,7 +38613,7 @@ "type": "frame", "id": "xCJlu", "x": 0, - "y": 53087, + "y": 53350, "name": "Screen/Server Detail — Settings · Share links", "theme": { "semantic": "dark" @@ -39167,7 +39278,7 @@ "type": "frame", "id": "dQV9N", "x": 1640, - "y": 53087, + "y": 53350, "name": "Screen/Server Detail — Settings · Share links (Empty)", "theme": { "semantic": "dark" @@ -39465,7 +39576,7 @@ "type": "frame", "id": "C2LQE4", "x": 0, - "y": 91265, + "y": 91528, "name": "Screen/Share Link — Up", "theme": { "semantic": "dark" @@ -39491,7 +39602,7 @@ "type": "frame", "id": "q31B6w", "x": 1640, - "y": 91265, + "y": 91528, "name": "Screen/Share Link — Asleep (can start)", "theme": { "semantic": "dark" @@ -39585,7 +39696,7 @@ "type": "frame", "id": "qFLfB", "x": 8200, - "y": 91265, + "y": 91528, "name": "Historical/Share Link — Asleep (view only)", "context": "Historical reference. The public resolve response does not expose start capability; an asleep poll must not infer denied permission. Current behavior retains Start, with a cooldown after transient Start failures.", "theme": { @@ -39665,7 +39776,7 @@ "type": "frame", "id": "EcoGD", "x": 4920, - "y": 91265, + "y": 91528, "name": "Screen/Share Link — Starting", "theme": { "semantic": "dark" @@ -39777,7 +39888,7 @@ "type": "frame", "id": "epZO2", "x": 6560, - "y": 91265, + "y": 91528, "name": "Screen/Share Link — Invalid or expired", "theme": { "semantic": "dark" @@ -39842,7 +39953,7 @@ "type": "frame", "id": "Bbnga", "x": 0, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture (Not enabled)", "theme": { "semantic": "dark" @@ -40092,7 +40203,7 @@ "type": "frame", "id": "dBILX", "x": 1640, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture (Empty)", "theme": { "semantic": "dark" @@ -40378,7 +40489,7 @@ "type": "frame", "id": "xvlB6", "x": 3280, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture (Running)", "theme": { "semantic": "dark" @@ -40845,7 +40956,7 @@ "type": "frame", "id": "m5kOm4", "x": 4920, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture (List)", "theme": { "semantic": "dark" @@ -42078,7 +42189,7 @@ "type": "frame", "id": "O08uaD", "x": 6560, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture — Start capture", "context": "Start Capture for remote-lab. Values illustrate that selected site: defaults and ceilings come from its capability response, independently of central settings.", "theme": { @@ -43485,7 +43596,7 @@ "type": "frame", "id": "b4eaUf", "x": 8200, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture — Start capture (Invalid filter)", "theme": { "semantic": "dark" @@ -44849,7 +44960,7 @@ "type": "frame", "id": "RodrS", "x": 0, - "y": 49187, + "y": 49450, "name": "Screen/Server Detail — Settings · Network capture", "theme": { "semantic": "dark" @@ -45268,7 +45379,7 @@ "type": "frame", "id": "nNGDX", "x": 1640, - "y": 78259, + "y": 78522, "name": "Screen/Admin Settings — Authentication (No OIDC mappings)", "theme": { "semantic": "dark" @@ -46568,7 +46679,7 @@ "type": "frame", "id": "dxdEi", "x": 1640, - "y": 64969, + "y": 65232, "name": "Screen/Cluster Settings — Default storage class", "theme": { "semantic": "dark" @@ -46816,7 +46927,7 @@ "type": "note", "id": "m8wjom", "x": 0, - "y": 79975, + "y": 80238, "name": "Note — Helm admin mapping banner is conditional", "width": 640, "height": 0, @@ -46826,7 +46937,7 @@ "type": "note", "id": "I96cW", "x": 0, - "y": 80355, + "y": 80618, "name": "Note — Confirm Admin Mapping trigger rule", "width": 560, "height": 0, @@ -46836,7 +46947,7 @@ "type": "frame", "id": "QgW58", "x": 3280, - "y": 78259, + "y": 78522, "name": "Screen/Admin Settings — Authentication (Save rejected)", "theme": { "semantic": "dark" @@ -48373,7 +48484,7 @@ "type": "note", "id": "R6iab", "x": 3280, - "y": 79999, + "y": 80262, "name": "Note — Role mapping overrides error states", "width": 700, "height": 0, @@ -48383,7 +48494,7 @@ "type": "frame", "id": "zqzr4", "x": 4920, - "y": 78259, + "y": 78522, "name": "Screen/Admin Settings — Authentication (Admin mapping warning)", "theme": { "semantic": "dark" @@ -50258,7 +50369,7 @@ "type": "note", "id": "x71Cb", "x": 4920, - "y": 80827, + "y": 81090, "name": "Note — FR-015 confirm step on this surface", "width": 640, "height": 0, @@ -50268,7 +50379,7 @@ "type": "frame", "id": "o4LH8W", "x": 8200, - "y": 24182, + "y": 24345, "name": "Screen/Server Detail — Overview (PVC Provisioning Failed)", "theme": { "semantic": "dark" @@ -66709,7 +66820,7 @@ "type": "frame", "id": "Hy9r0", "x": 6560, - "y": 24182, + "y": 24345, "name": "Screen/Server Detail — Overview (Idle armed)", "theme": { "semantic": "dark" @@ -68471,7 +68582,7 @@ "type": "frame", "id": "sSISK", "x": 1640, - "y": 24182, + "y": 24345, "name": "Screen/Server Detail — Overview (Light)", "theme": { "semantic": "light" @@ -70093,7 +70204,7 @@ "type": "note", "id": "zfi3t", "x": 10560, - "y": 24605, + "y": 24768, "name": "Reference frame", "width": 260, "height": 219, @@ -71269,7 +71380,7 @@ "type": "frame", "id": "IdbiB", "x": 0, - "y": 58137, + "y": 58400, "name": "Screen/Dialog/Build Module — Step 1 (Preset & Metadata)", "theme": { "semantic": "dark" @@ -72277,7 +72388,7 @@ "type": "frame", "id": "O5kaV", "x": 1000, - "y": 58137, + "y": 58400, "name": "Screen/Dialog/Build Module — Step 2 (Container & Ports)", "theme": { "semantic": "dark" @@ -73169,7 +73280,7 @@ "type": "frame", "id": "hmPL7", "x": 2000, - "y": 58137, + "y": 58400, "name": "Screen/Dialog/Build Module — Step 3 (Review & Export)", "theme": { "semantic": "dark" @@ -73968,7 +74079,7 @@ "type": "frame", "id": "uCA23", "x": 1640, - "y": 38007, + "y": 38170, "name": "Screen/Server Detail — Settings · General", "theme": { "semantic": "dark" @@ -74607,7 +74718,7 @@ "type": "frame", "id": "VctzT", "x": 1640, - "y": 39307, + "y": 39470, "name": "Screen/Server Detail — Settings · Version", "theme": { "semantic": "dark" @@ -74928,7 +75039,7 @@ "type": "frame", "id": "iLm38", "x": 1640, - "y": 44567, + "y": 44830, "name": "Screen/Server Detail — Settings · Environment", "theme": { "semantic": "dark" @@ -75193,7 +75304,7 @@ "type": "frame", "id": "QpEvu", "x": 1640, - "y": 51787, + "y": 52050, "name": "Screen/Server Detail — Settings · Access", "theme": { "semantic": "dark" @@ -75548,7 +75659,7 @@ "type": "frame", "id": "XR0f9", "x": 1640, - "y": 54387, + "y": 54650, "name": "Screen/Server Detail — Settings · Danger zone", "theme": { "semantic": "dark" @@ -75928,7 +76039,7 @@ "type": "frame", "id": "QQtUD", "x": 1640, - "y": 21112, + "y": 21275, "name": "Screen/Create Server — Step 4 Network (as built)", "theme": { "semantic": "dark" @@ -76449,7 +76560,7 @@ "type": "frame", "id": "IyMFM", "x": 9840, - "y": 43267, + "y": 43530, "name": "J5pjJ3 states/Tunnel enabled", "theme": { "semantic": "dark" @@ -76798,7 +76909,7 @@ "type": "frame", "id": "jXykV", "x": 10540, - "y": 43267, + "y": 43530, "name": "J5pjJ3 states/Alerts", "theme": { "semantic": "dark" @@ -76863,7 +76974,7 @@ "ref": "x3beP", "name": "Screen/Share links — Create link (No expiry)", "x": 3280, - "y": 53087, + "y": 53350, "descendants": { "E7Whx": { "content": "Create share link for mc-survival" @@ -77038,7 +77149,7 @@ "ref": "x3beP", "name": "Screen/Share links — Create link (Custom date)", "x": 3960, - "y": 53087, + "y": 53350, "descendants": { "E7Whx": { "content": "Create share link for mc-survival" @@ -77262,7 +77373,7 @@ "type": "frame", "id": "X405l", "x": 1640, - "y": 27607, + "y": 27770, "name": "Screen/Server Detail — Console (not running – Stopped/Suspended)", "theme": { "semantic": "dark" @@ -77524,7 +77635,7 @@ "type": "frame", "id": "j88bl0", "x": 3280, - "y": 27607, + "y": 27770, "name": "Screen/Server Detail — Console (not running – Pod failure)", "theme": { "semantic": "dark" @@ -77748,7 +77859,7 @@ "type": "frame", "id": "lWvcv", "x": 0, - "y": 75369, + "y": 75632, "name": "Screen/Theme Settings", "theme": { "semantic": "dark" @@ -79190,7 +79301,7 @@ "type": "frame", "id": "T1wkiT", "x": 1640, - "y": 75369, + "y": 75632, "name": "Screen/Theme Settings — Warnings", "theme": { "semantic": "light" @@ -80728,7 +80839,7 @@ "type": "frame", "id": "lhKzN", "x": 3280, - "y": 21112, + "y": 21275, "name": "Create Server — Step 4 Network · Tunnel disabled", "theme": { "semantic": "dark" @@ -81239,7 +81350,7 @@ "type": "frame", "id": "a7o9C", "x": 4920, - "y": 21112, + "y": 21275, "name": "Create Server — Step 4 Network · Tunnel enabled, no provider", "theme": { "semantic": "dark" @@ -81750,7 +81861,7 @@ "type": "frame", "id": "ZU1BG", "x": 6560, - "y": 21112, + "y": 21275, "name": "Create Server — Step 4 Network · Tunnel enabled, provider selected", "theme": { "semantic": "dark" @@ -82261,7 +82372,7 @@ "type": "frame", "id": "eGbsg", "x": 8200, - "y": 21112, + "y": 21275, "name": "Create Server — Step 4 Network · Tunnel enabled, complete", "theme": { "semantic": "dark" @@ -82772,7 +82883,7 @@ "type": "frame", "id": "nGJE1", "x": 1640, - "y": 43267, + "y": 43530, "name": "Server Detail · Settings · Networking · Tunnel disabled", "theme": { "semantic": "dark" @@ -83582,7 +83693,7 @@ "type": "frame", "id": "dhPtX", "x": 3280, - "y": 43267, + "y": 43530, "name": "Server Detail · Settings · Networking · Tunnel initial enable", "theme": { "semantic": "dark" @@ -84392,7 +84503,7 @@ "type": "frame", "id": "tSOeI", "x": 4920, - "y": 43267, + "y": 43530, "name": "Server Detail · Settings · Networking · Provider selected", "theme": { "semantic": "dark" @@ -85202,7 +85313,7 @@ "type": "frame", "id": "y3YcQ", "x": 6560, - "y": 43267, + "y": 43530, "name": "Server Detail · Settings · Networking · Credentials entering", "theme": { "semantic": "dark" @@ -86012,7 +86123,7 @@ "type": "frame", "id": "EKJyM", "x": 8200, - "y": 43267, + "y": 43530, "name": "Server Detail · Settings · Networking · Provider + credentials complete", "theme": { "semantic": "dark" @@ -86822,7 +86933,7 @@ "type": "frame", "id": "akr4I", "x": 9840, - "y": 36707, + "y": 36870, "name": "Screen/Server Detail — Capture (No access)", "theme": { "semantic": "dark" @@ -87075,7 +87186,7 @@ "type": "frame", "id": "hlwx3", "x": 9840, - "y": 24182, + "y": 24345, "name": "State/Server Actions Menu — Owner-only disabled", "theme": { "semantic": "dark" @@ -87124,7 +87235,7 @@ "type": "frame", "id": "IMSD5", "x": 3280, - "y": 54387, + "y": 54650, "name": "Screen/Server Detail — Settings · Danger zone (Owner-only)", "theme": { "semantic": "dark" @@ -87530,7 +87641,7 @@ "type": "frame", "id": "gWqMD", "x": 3280, - "y": 51787, + "y": 52050, "name": "Screen/Server Detail — Settings · Access (Read-only)", "theme": { "semantic": "dark" @@ -89037,7 +89148,7 @@ "type": "frame", "id": "pnAbF", "x": 1640, - "y": 59519, + "y": 59782, "name": "Screen/Backups — Filters open", "context": "Implemented at 8e08a60f. Filter fields are drafts; Apply commits, Clear resets drafts, dismissal discards edits. Server choices retain location and namespace.", "theme": { @@ -89325,7 +89436,7 @@ "type": "frame", "id": "Cb3eC", "x": 1640, - "y": 60819, + "y": 61082, "name": "Screen/Backups — Schedules filters open", "context": "Implemented at 8e08a60f. Filter fields are drafts; Apply commits, Clear resets drafts, dismissal discards edits. Server choices retain location and namespace.", "theme": { @@ -90412,7 +90523,7 @@ "type": "frame", "id": "fbBqA", "x": 1640, - "y": 62369, + "y": 62632, "name": "Screen/Backups — Restores filters open", "context": "Implemented at 8e08a60f. Filter fields are drafts; Apply commits, Clear resets drafts, dismissal discards edits. Server choices retain location and namespace.", "theme": { @@ -91167,7 +91278,7 @@ "type": "frame", "id": "w1QYmf", "x": 11480, - "y": 36707, + "y": 36870, "name": "States/Remote Capture — Access and capability", "context": "Actual CaptureWidget states from 8e08a60f. Resolve user access separately from selected-site capability. Existing files remain readable when only new captures are disabled.", "theme": { @@ -91496,7 +91607,7 @@ "type": "frame", "id": "SUtGZ", "x": 12840, - "y": 36707, + "y": 36870, "name": "Screen/Mobile — Remote server Capture", "context": "375px viewport. Server actions wrap. The tab navigation is independently scrolled to Capture; the page itself never overflows horizontally.", "theme": { @@ -92799,7 +92910,7 @@ "type": "frame", "id": "NKiqf", "x": 3280, - "y": 59519, + "y": 59782, "name": "Screen/Mobile — Backups filters open", "theme": { "semantic": "dark" @@ -92952,7 +93063,7 @@ "type": "frame", "id": "iYUWc", "x": 0, - "y": 63669, + "y": 63932, "name": "Screen/Clusters — Registered locations", "theme": { "semantic": "dark" @@ -93265,7 +93376,7 @@ "ref": "JLaGB", "name": "Gameplane/Dialog/New File (Invalid name)", "x": 1640, - "y": 30207, + "y": 30370, "descendants": { "q9QPFd": { "type": "frame", @@ -93323,7 +93434,7 @@ "type": "frame", "id": "OC804", "x": 0, - "y": 40607, + "y": 40770, "name": "Screen/Server Detail — Settings · Game configuration", "theme": { "semantic": "dark" @@ -94049,7 +94160,7 @@ "type": "frame", "id": "QFEg9", "x": 1640, - "y": 40607, + "y": 40770, "name": "Screen/Server Detail — Settings · Game configuration (Invalid value)", "theme": { "semantic": "dark" @@ -94787,7 +94898,7 @@ "type": "frame", "id": "wxINm", "x": 3280, - "y": 40607, + "y": 40770, "name": "Screen/Server Detail — Settings · Game configuration (Read-only)", "theme": { "semantic": "dark" @@ -95467,7 +95578,7 @@ "type": "frame", "id": "e9FnC", "x": 4920, - "y": 40607, + "y": 40770, "name": "Screen/Server Detail — Settings · Game configuration (Orphaned key)", "theme": { "semantic": "dark" @@ -96255,7 +96366,7 @@ "type": "frame", "id": "Ne5TA", "x": 0, - "y": 92565, + "y": 92828, "name": "Screen/Not Found", "theme": { "semantic": "dark" @@ -96414,7 +96525,7 @@ "type": "frame", "id": "FBiMN", "x": 3280, - "y": 91265, + "y": 91528, "name": "Screen/Share Link — Asleep (Start cooldown)", "theme": { "semantic": "dark" @@ -96694,7 +96805,7 @@ "type": "frame", "id": "l7TRz9", "x": 1640, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry (default)", "theme": { "semantic": "dark" @@ -97417,7 +97528,7 @@ "type": "frame", "id": "r0heh", "x": 3280, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry (custom)", "theme": { "semantic": "dark" @@ -97986,7 +98097,7 @@ "type": "frame", "id": "cDiNb", "x": 4920, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry (bundled)", "theme": { "semantic": "dark" @@ -98529,7 +98640,7 @@ "type": "frame", "id": "DBQyF", "x": 6560, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry (disabled)", "theme": { "semantic": "dark" @@ -99064,7 +99175,7 @@ "type": "frame", "id": "K5XinZ", "x": 8200, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry (none)", "theme": { "semantic": "dark" @@ -99597,7 +99708,7 @@ "type": "frame", "id": "yrxK2", "x": 9840, - "y": 87233, + "y": 87496, "name": "Screen/Admin Settings — Telemetry (Reset ID confirm)", "theme": { "semantic": "dark" @@ -100172,7 +100283,7 @@ "type": "frame", "id": "n2qdi", "x": 1640, - "y": 63669, + "y": 63932, "name": "Screen/Standalone — Clusters (Empty)", "context": "PR #630 standalone deployment; matches current dashboard behavior.", "theme": { @@ -100333,7 +100444,7 @@ "type": "frame", "id": "WrmU5", "x": 3280, - "y": 63669, + "y": 63932, "name": "Screen/Standalone — Clusters (Registered)", "context": "PR #630 standalone deployment; matches current dashboard behavior.", "theme": { @@ -100761,7 +100872,7 @@ "type": "frame", "id": "Be7ZW", "x": 4920, - "y": 63669, + "y": 63932, "name": "Screen/Standalone — Cluster inventory (No selection)", "context": "PR #630. Selection is explicit and only registered remotes are listed. No implicit local target; no workload requests until installation and selected target are known.", "theme": { @@ -100938,7 +101049,7 @@ "type": "frame", "id": "Z0oyh", "x": 1640, - "y": 55687, + "y": 55950, "name": "Screen/Standalone — Modules (No selection)", "context": "PR #630. Selection is explicit and only registered remotes are listed. No implicit local target; no workload requests until installation and selected target are known.", "theme": { @@ -101128,7 +101239,7 @@ "type": "frame", "id": "ZlLf0", "x": 3280, - "y": 55687, + "y": 55950, "name": "Screen/Standalone — Modules (Permission denied)", "context": "PR #630. Selection is explicit and only registered remotes are listed. No implicit local target; no workload requests until installation and selected target are known.", "theme": { @@ -101292,7 +101403,7 @@ "type": "frame", "id": "RpHBg", "x": 6560, - "y": 63669, + "y": 63932, "name": "Screen/Standalone — Register cluster", "context": "PR #630. Modal actions use central cluster-management permission. Dismissing is disabled during submission.", "theme": { @@ -101477,7 +101588,7 @@ "type": "frame", "id": "HVhmb", "x": 8200, - "y": 63669, + "y": 63932, "name": "Screen/Standalone — Remove cluster", "context": "Removal errors stay inside the confirmation and reset when dismissed or reopened. Successful removal clears a matching current selection to empty without selecting local or another remote.", "theme": { @@ -101981,7 +102092,7 @@ "type": "frame", "id": "RS0KZ", "x": 9840, - "y": 63669, + "y": 63932, "name": "State/Standalone — Cluster selection and request states", "context": "Interaction reference, not a single dashboard screen. No workload queries before capabilities and selection resolve. Retry retains the selected remote.", "theme": { @@ -102471,7 +102582,7 @@ "type": "frame", "id": "D9QYZ", "x": 1640, - "y": 81968, + "y": 82231, "name": "Screen/Standalone — Settings · Backup destinations", "context": "PR #630 standalone installation. No local Kubernetes metadata or local workload fallback. Master-key storage and offline rotation have no dashboard controls.", "theme": { @@ -102754,7 +102865,7 @@ "type": "frame", "id": "l64bd", "x": 1640, - "y": 83268, + "y": 83531, "name": "Screen/Standalone — Settings · Module sources", "context": "PR #630 standalone installation. No local Kubernetes metadata or local workload fallback. Master-key storage and offline rotation have no dashboard controls.", "theme": { @@ -103037,7 +103148,7 @@ "type": "frame", "id": "Uzzje", "x": 1640, - "y": 88665, + "y": 88928, "name": "Screen/Standalone — Settings · Updates", "context": "PR #630 standalone installation. No local Kubernetes metadata or local workload fallback. Master-key storage and offline rotation have no dashboard controls.", "theme": { @@ -103297,7 +103408,7 @@ "type": "frame", "id": "FOVMf", "x": 1640, - "y": 89965, + "y": 90228, "name": "Screen/Standalone — Settings · About", "context": "Panel build version comes from authenticated installation metadata; standalone About does not fetch local Kubernetes metadata.", "theme": { @@ -103571,7 +103682,7 @@ "type": "frame", "id": "r139UP", "x": 1640, - "y": 72769, + "y": 73032, "name": "Screen/Standalone — System logs", "context": "PR #630 standalone installation. No local Kubernetes metadata or local workload fallback. Master-key storage and offline rotation have no dashboard controls.", "theme": { @@ -103804,7 +103915,7 @@ "type": "frame", "id": "EWrn0", "x": 4920, - "y": 55687, + "y": 55950, "name": "Screen/Standalone — Modules (East selected)", "context": "Background refetch failures display an inline retry banner and retain existing data plus open install/upload/builder/source drafts. Only an initial failure replaces loading content. Explicit target changes discard drafts.", "theme": { @@ -105246,7 +105357,7 @@ "type": "frame", "id": "p8QSPq", "x": 6560, - "y": 55687, + "y": 55950, "name": "Screen/Standalone — Modules (Manage sources)", "context": "Background refetch failures display an inline retry banner and retain existing data plus open install/upload/builder/source drafts. Only an initial failure replaces loading content. Explicit target changes discard drafts.", "theme": { @@ -107114,7 +107225,7 @@ "ref": "x3beP", "name": "Gameplane/Dialog/Edit user — Standalone (No target)", "x": 1640, - "y": 66269, + "y": 66532, "theme": { "semantic": "dark" }, @@ -107411,7 +107522,7 @@ "ref": "x3beP", "name": "Gameplane/Dialog/Edit user — Standalone (Remote grant)", "x": 2320, - "y": 66269, + "y": 66532, "theme": { "semantic": "dark" }, @@ -108210,7 +108321,7 @@ "type": "frame", "id": "U9xSg", "x": 1640, - "y": 34107, + "y": 34270, "name": "Screen/Server Detail — Players (Nuclear Option)", "theme": { "semantic": "dark" @@ -108761,7 +108872,7 @@ "type": "frame", "id": "BguST", "x": 6560, - "y": 40607, + "y": 40770, "name": "Screen/Server Detail — Settings · Game configuration (Password will be removed)", "theme": { "semantic": "dark" @@ -109490,7 +109601,7 @@ "type": "frame", "id": "YN8cE", "x": 8200, - "y": 40607, + "y": 40770, "name": "Screen/Server Detail — Settings · Game configuration (New password typed)", "theme": { "semantic": "dark" @@ -127297,7 +127408,7 @@ "type": "note", "id": "pi8JL", "x": -1400, - "y": 21112, + "y": 21275, "name": "Row 08 · Create server · Step 4 Network", "width": 1000, "height": 240, @@ -127307,7 +127418,7 @@ "type": "note", "id": "x4uMd", "x": -1400, - "y": 22782, + "y": 22945, "name": "Row 09 · Create server · Step 5 Review", "width": 1000, "height": 240, @@ -127317,7 +127428,7 @@ "type": "note", "id": "qO22k", "x": -1400, - "y": 24182, + "y": 24345, "name": "Row 10 · Server detail · Overview", "width": 1000, "height": 240, @@ -127327,7 +127438,7 @@ "type": "note", "id": "E3lyu", "x": -1400, - "y": 26307, + "y": 26470, "name": "Row 11 · Server detail · Events", "width": 1000, "height": 240, @@ -127337,7 +127448,7 @@ "type": "note", "id": "w2kUI", "x": -1400, - "y": 27607, + "y": 27770, "name": "Row 12 · Server detail · Console", "width": 1000, "height": 240, @@ -127347,7 +127458,7 @@ "type": "note", "id": "QDwYN", "x": -1400, - "y": 28907, + "y": 29070, "name": "Row 13 · Server detail · Logs", "width": 1000, "height": 240, @@ -127357,7 +127468,7 @@ "type": "note", "id": "lQzZC", "x": -1400, - "y": 30207, + "y": 30370, "name": "Row 14 · Server detail · Files", "width": 1000, "height": 240, @@ -127367,7 +127478,7 @@ "type": "note", "id": "zuaLh", "x": -1400, - "y": 31507, + "y": 31670, "name": "Row 15 · Server detail · Mods", "width": 1000, "height": 240, @@ -127377,7 +127488,7 @@ "type": "note", "id": "HOOwr", "x": -1400, - "y": 32807, + "y": 32970, "name": "Row 16 · Server detail · Modpacks", "width": 1000, "height": 240, @@ -127387,7 +127498,7 @@ "type": "note", "id": "NtNAz", "x": -1400, - "y": 34107, + "y": 34270, "name": "Row 17 · Server detail · Players", "width": 1000, "height": 240, @@ -127397,7 +127508,7 @@ "type": "note", "id": "dqvWz", "x": -1400, - "y": 35407, + "y": 35570, "name": "Row 18 · Server detail · Backups", "width": 1000, "height": 240, @@ -127407,7 +127518,7 @@ "type": "note", "id": "U0OGtp", "x": -1400, - "y": 36707, + "y": 36870, "name": "Row 19 · Server detail · Capture", "width": 1000, "height": 240, @@ -127417,7 +127528,7 @@ "type": "note", "id": "FuwJW", "x": -1400, - "y": 38007, + "y": 38170, "name": "Row 20 · Server settings · General", "width": 1000, "height": 240, @@ -127427,7 +127538,7 @@ "type": "note", "id": "T3YX5", "x": -1400, - "y": 39307, + "y": 39470, "name": "Row 21 · Server settings · Version", "width": 1000, "height": 240, @@ -127437,7 +127548,7 @@ "type": "note", "id": "Sshd6", "x": -1400, - "y": 40607, + "y": 40770, "name": "Row 22 · Server settings · Game configuration", "width": 1000, "height": 240, @@ -127447,7 +127558,7 @@ "type": "note", "id": "sMJbC", "x": -1400, - "y": 41967, + "y": 42130, "name": "Row 23 · Server settings · Resources", "width": 1000, "height": 240, @@ -127457,7 +127568,7 @@ "type": "note", "id": "MHWc5", "x": -1400, - "y": 43267, + "y": 43530, "name": "Row 24 · Server settings · Networking", "width": 1000, "height": 240, @@ -127467,7 +127578,7 @@ "type": "note", "id": "YGwuK", "x": -1400, - "y": 44567, + "y": 44830, "name": "Row 25 · Server settings · Environment", "width": 1000, "height": 240, @@ -127477,7 +127588,7 @@ "type": "note", "id": "D4jA9F", "x": -1400, - "y": 45867, + "y": 46130, "name": "Row 26 · Server settings · Lifecycle", "width": 1000, "height": 240, @@ -127487,7 +127598,7 @@ "type": "note", "id": "F9xN2", "x": -1400, - "y": 47887, + "y": 48150, "name": "Row 27 · Server settings · Scheduled backups", "width": 1000, "height": 240, @@ -127497,7 +127608,7 @@ "type": "note", "id": "KXO7u", "x": -1400, - "y": 49187, + "y": 49450, "name": "Row 28 · Server settings · Network capture", "width": 1000, "height": 240, @@ -127507,7 +127618,7 @@ "type": "note", "id": "xlK5c", "x": -1400, - "y": 50487, + "y": 50750, "name": "Row 29 · Server settings · Placement", "width": 1000, "height": 240, @@ -127517,7 +127628,7 @@ "type": "note", "id": "JgubZ", "x": -1400, - "y": 51787, + "y": 52050, "name": "Row 30 · Server settings · RBAC & access", "width": 1000, "height": 240, @@ -127527,7 +127638,7 @@ "type": "note", "id": "ryyvW", "x": -1400, - "y": 53087, + "y": 53350, "name": "Row 31 · Server settings · Share links", "width": 1000, "height": 240, @@ -127537,7 +127648,7 @@ "type": "note", "id": "eoNeU", "x": -1400, - "y": 54387, + "y": 54650, "name": "Row 32 · Server settings · Danger zone", "width": 1000, "height": 240, @@ -127547,7 +127658,7 @@ "type": "note", "id": "m6XxM6", "x": -1400, - "y": 55687, + "y": 55950, "name": "Row 33 · Modules", "width": 1000, "height": 240, @@ -127557,7 +127668,7 @@ "type": "note", "id": "W9aA6", "x": -1400, - "y": 58137, + "y": 58400, "name": "Row 34 · Modules · Build module dialog", "width": 1000, "height": 240, @@ -127567,7 +127678,7 @@ "type": "note", "id": "HG21t", "x": -1400, - "y": 59519, + "y": 59782, "name": "Row 35 · Backups · Index", "width": 1000, "height": 240, @@ -127577,7 +127688,7 @@ "type": "note", "id": "s7wiph", "x": -1400, - "y": 60819, + "y": 61082, "name": "Row 36 · Backups · Schedules", "width": 1000, "height": 240, @@ -127587,7 +127698,7 @@ "type": "note", "id": "TaRwQ", "x": -1400, - "y": 62369, + "y": 62632, "name": "Row 37 · Backups · Restores", "width": 1000, "height": 240, @@ -127597,7 +127708,7 @@ "type": "note", "id": "pSXSc", "x": -1400, - "y": 63669, + "y": 63932, "name": "Row 38 · Clusters", "width": 1000, "height": 240, @@ -127607,7 +127718,7 @@ "type": "note", "id": "NZeQ5", "x": -1400, - "y": 64969, + "y": 65232, "name": "Row 39 · Cluster settings", "width": 1000, "height": 240, @@ -127617,7 +127728,7 @@ "type": "note", "id": "ahs1q", "x": -1400, - "y": 66269, + "y": 66532, "name": "Row 40 · Users & RBAC · Users", "width": 1000, "height": 240, @@ -127627,7 +127738,7 @@ "type": "note", "id": "trxDY", "x": -1400, - "y": 67569, + "y": 67832, "name": "Row 41 · Users & RBAC · Roles", "width": 1000, "height": 240, @@ -127637,7 +127748,7 @@ "type": "note", "id": "Mu4PV", "x": -1400, - "y": 68869, + "y": 69132, "name": "Row 42 · Users & RBAC · Service accounts", "width": 1000, "height": 240, @@ -127647,7 +127758,7 @@ "type": "note", "id": "B8R3vY", "x": -1400, - "y": 70169, + "y": 70432, "name": "Row 43 · Users & RBAC · Identity providers", "width": 1000, "height": 240, @@ -127657,7 +127768,7 @@ "type": "note", "id": "yfRYR", "x": -1400, - "y": 71469, + "y": 71732, "name": "Row 44 · Audit log", "width": 1000, "height": 240, @@ -127667,7 +127778,7 @@ "type": "note", "id": "vwnjE", "x": -1400, - "y": 72769, + "y": 73032, "name": "Row 45 · System logs", "width": 1000, "height": 240, @@ -127677,7 +127788,7 @@ "type": "note", "id": "e8Uwa", "x": -1400, - "y": 74069, + "y": 74332, "name": "Row 46 · Admin settings · General", "width": 1000, "height": 240, @@ -127687,7 +127798,7 @@ "type": "note", "id": "gSmfE", "x": -1400, - "y": 75369, + "y": 75632, "name": "Row 47 · Admin settings · Theme", "width": 1000, "height": 240, @@ -127697,7 +127808,7 @@ "type": "note", "id": "QZcdP", "x": -1400, - "y": 78259, + "y": 78522, "name": "Row 48 · Admin settings · Authentication", "width": 1000, "height": 240, @@ -127707,7 +127818,7 @@ "type": "note", "id": "i0LdOM", "x": -1400, - "y": 81968, + "y": 82231, "name": "Row 49 · Admin settings · Backup destinations", "width": 1000, "height": 240, @@ -127717,7 +127828,7 @@ "type": "note", "id": "su0IR", "x": -1400, - "y": 83268, + "y": 83531, "name": "Row 50 · Admin settings · Module sources", "width": 1000, "height": 240, @@ -127727,7 +127838,7 @@ "type": "note", "id": "T40Bd", "x": -1400, - "y": 84568, + "y": 84831, "name": "Row 51 · Admin settings · Mod registries", "width": 1000, "height": 240, @@ -127737,7 +127848,7 @@ "type": "note", "id": "RGitr", "x": -1400, - "y": 85868, + "y": 86131, "name": "Row 52 · Admin settings · Notifications", "width": 1000, "height": 240, @@ -127747,7 +127858,7 @@ "type": "note", "id": "W6YgwP", "x": -1400, - "y": 87233, + "y": 87496, "name": "Row 53 · Admin settings · Telemetry", "width": 1000, "height": 240, @@ -127757,7 +127868,7 @@ "type": "note", "id": "Mfaof", "x": -1400, - "y": 88665, + "y": 88928, "name": "Row 54 · Admin settings · Updates", "width": 1000, "height": 240, @@ -127767,7 +127878,7 @@ "type": "note", "id": "SeCFN", "x": -1400, - "y": 89965, + "y": 90228, "name": "Row 55 · Admin settings · About", "width": 1000, "height": 240, @@ -127777,7 +127888,7 @@ "type": "note", "id": "FtgQt", "x": -1400, - "y": 91265, + "y": 91528, "name": "Row 56 · Share link (public)", "width": 1000, "height": 240, @@ -127787,7 +127898,7 @@ "type": "note", "id": "R2nH3", "x": -1400, - "y": 92565, + "y": 92828, "name": "Row 57 · Not found", "width": 1000, "height": 240, @@ -127911,7 +128022,7 @@ "type": "frame", "id": "lCTci", "x": 3280, - "y": 75369, + "y": 75632, "name": "Screen/Mobile — Theme Settings · Language", "theme": { "semantic": "dark" @@ -128010,7 +128121,7 @@ "type": "frame", "id": "SUcad", "x": 1640, - "y": 85868, + "y": 86131, "name": "Screen/Admin Settings — Notifications (Add sink)", "theme": { "semantic": "dark" @@ -128974,6 +129085,2532 @@ ] } ] + }, + { + "type": "frame", + "id": "NqaaU", + "x": 1640, + "y": 42130, + "name": "Screen/Server Detail — Settings · Resources (Reserve less on)", + "theme": { + "semantic": "dark" + }, + "clip": true, + "width": 1440, + "height": 1000, + "fill": "$background/background", + "children": [ + { + "id": "AZLll", + "type": "ref", + "ref": "kKFX9", + "height": "fill_container", + "descendants": { + "iTbd8": { + "fill": "#00000000" + }, + "iTbd8/ghUsw": { + "fill": "$foreground/foreground" + }, + "iTbd8/bpSQ7": { + "fill": "$foreground/foreground" + }, + "i8Z1B": { + "fill": "$accent/soft" + }, + "i8Z1B/Sr2Q5": { + "fill": "$accent/soft-foreground" + }, + "i8Z1B/Ra9NK": { + "fill": "$accent/soft-foreground" + } + } + }, + { + "type": "frame", + "id": "p8hHD", + "name": "Main", + "width": "fill_container", + "height": "fill_container", + "fill": "$background/background", + "layout": "vertical", + "children": [ + { + "id": "nAKSm", + "type": "ref", + "ref": "gu5WY", + "width": "fill_container", + "descendants": { + "Gl7AY": { + "content": "mc-survival" + } + } + }, + { + "type": "frame", + "id": "KBvJo", + "name": "Body", + "width": "fill_container", + "height": "fill_container", + "fill": "$background/background", + "layout": "vertical", + "gap": 24, + "padding": 24, + "children": [ + { + "id": "DPwOC", + "type": "ref", + "ref": "S4k0x", + "name": "Detail Header", + "width": "fill_container" + }, + { + "id": "GUwll", + "type": "ref", + "ref": "I9kvlZ", + "name": "Tabs Bar", + "width": "fill_container", + "padding": [ + 0, + 0, + 0, + 24 + ], + "descendants": { + "Wp6q1": { + "fill": "#00000000" + }, + "Wp6q1/YUR8N": { + "fill": "$muted" + }, + "Y4QES": { + "fill": "$segment" + }, + "Y4QES/VcxMI": { + "fill": "$foreground/segment" + } + } + }, + { + "type": "frame", + "id": "b6dV3C", + "name": "Settings Content", + "width": "fill_container", + "height": "fill_container", + "children": [ + { + "id": "QnrW1", + "type": "ref", + "ref": "EV8mp", + "name": "Settings Sub Nav", + "width": 224, + "height": "fill_container", + "descendants": { + "axMXL": { + "fill": "#00000000" + }, + "nnoXy": { + "fill": "$surface/secondary" + }, + "bHNH0": { + "fill": "$foreground/foreground" + } + } + }, + { + "type": "frame", + "id": "k7WeNU", + "name": "Settings Panel Wrap", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "z8HDA", + "name": "Settings Form", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "Put6Q", + "name": "Form Body", + "width": "fill_container", + "height": "fill_container", + "layout": "vertical", + "gap": 20, + "padding": 24, + "children": [ + { + "type": "frame", + "id": "bqZJN", + "name": "Field CPU", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "q7Ul7", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "hZfcD", + "name": "t", + "fill": "$foreground/foreground", + "content": "CPU cores", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "rHjtv", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "Limit: the most CPU this server can use.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "hy9GV", + "name": "ctrl", + "width": "fill_container", + "height": 36, + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "z6Plw", + "type": "ref", + "ref": "KWcnQ", + "name": "slider", + "width": "fill_container", + "descendants": { + "Gky2W": { + "enabled": false + }, + "lJsFB": { + "width": 70 + }, + "tES7p": { + "x": 57 + } + } + }, + { + "id": "K5byY", + "type": "ref", + "ref": "D0cDM", + "name": "Input", + "width": 64, + "descendants": { + "PJERm": { + "content": "2", + "fill": "$foreground/foreground" + } + } + }, + { + "id": "OtD8Z", + "type": "ref", + "ref": "cY9fV", + "name": "unit", + "width": 110, + "descendants": { + "mgenn": { + "enabled": false + }, + "WhOPt": { + "width": "fill_container" + }, + "c94wTj": { + "content": "cores" + }, + "t1Te9C": { + "x": 82 + }, + "d7Cyay": { + "enabled": false + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "K3jnh", + "name": "Field Memory", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "AoCgz", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "cNMed", + "name": "t", + "fill": "$foreground/foreground", + "content": "Memory (GiB)", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "jYTdv", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "Limit: the most memory this server can use.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "TPorp", + "name": "ctrl", + "width": "fill_container", + "height": 36, + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "c2cPe8", + "type": "ref", + "ref": "KWcnQ", + "name": "slider", + "width": "fill_container", + "descendants": { + "Gky2W": { + "enabled": false + }, + "lJsFB": { + "width": 46 + }, + "tES7p": { + "x": 33 + } + } + }, + { + "id": "eywZh", + "type": "ref", + "ref": "D0cDM", + "name": "Input", + "width": 64, + "descendants": { + "PJERm": { + "content": "4", + "fill": "$foreground/foreground" + } + } + }, + { + "id": "Zypbr", + "type": "ref", + "ref": "cY9fV", + "name": "unit", + "width": 110, + "descendants": { + "mgenn": { + "enabled": false + }, + "WhOPt": { + "width": "fill_container" + }, + "c94wTj": { + "content": "GiB" + }, + "t1Te9C": { + "x": 82 + }, + "d7Cyay": { + "enabled": false + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "QXA0O", + "name": "Field Reserve less", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "kcSdJ", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "EJ9HI", + "name": "t", + "fill": "$foreground/foreground", + "content": "Reserve less than the limit", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "z1W8J", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "Off: the server reserves its full CPU and memory (Guaranteed QoS). On: it reserves less, so more servers fit on a node, and uses more up to its limit when the node has room (Burstable QoS).", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "ceSyZ", + "name": "ctrl", + "width": "fill_container", + "height": 28, + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "IeaMp", + "type": "ref", + "ref": "hl7R3", + "name": "Switch", + "width": 36, + "height": 20 + } + ] + } + ] + }, + { + "type": "frame", + "id": "yWH5R", + "name": "Field Reserved CPU", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "Y3vPi", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "Bl9Wr", + "name": "t", + "fill": "$foreground/foreground", + "content": "Reserved CPU", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "kmsMn", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "Counted when the server is placed on a node. 0 reserves nothing.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "fST1a", + "name": "ctrl", + "width": "fill_container", + "height": 36, + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "FiwFm", + "type": "ref", + "ref": "KWcnQ", + "name": "slider", + "width": "fill_container", + "descendants": { + "Gky2W": { + "enabled": false + }, + "lJsFB": { + "width": 223 + }, + "tES7p": { + "x": 210 + } + } + }, + { + "id": "eLtw3", + "type": "ref", + "ref": "D0cDM", + "name": "Input", + "width": 64, + "descendants": { + "PJERm": { + "content": "1", + "fill": "$foreground/foreground" + } + } + }, + { + "id": "vPmPR", + "type": "ref", + "ref": "cY9fV", + "name": "unit", + "width": 110, + "descendants": { + "mgenn": { + "enabled": false + }, + "WhOPt": { + "width": "fill_container" + }, + "c94wTj": { + "content": "cores" + }, + "t1Te9C": { + "x": 82 + }, + "d7Cyay": { + "enabled": false + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "KFNkW", + "name": "Field Reserved memory", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "kvxqF", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "DoL4L", + "name": "t", + "fill": "$foreground/foreground", + "content": "Reserved memory (GiB)", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "u47KX", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "When a node runs out of memory, servers above their reservation are stopped first. Keep it close to real use.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "CEuDC", + "name": "ctrl", + "width": "fill_container", + "height": 36, + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "sPVr4", + "type": "ref", + "ref": "KWcnQ", + "name": "slider", + "width": "fill_container", + "descendants": { + "Gky2W": { + "enabled": false + }, + "lJsFB": { + "width": 223 + }, + "tES7p": { + "x": 210 + } + } + }, + { + "id": "YkJgP", + "type": "ref", + "ref": "D0cDM", + "name": "Input", + "width": 64, + "descendants": { + "PJERm": { + "content": "2", + "fill": "$foreground/foreground" + } + } + }, + { + "id": "Iip2K", + "type": "ref", + "ref": "cY9fV", + "name": "unit", + "width": 110, + "descendants": { + "mgenn": { + "enabled": false + }, + "WhOPt": { + "width": "fill_container" + }, + "c94wTj": { + "content": "GiB" + }, + "t1Te9C": { + "x": 82 + }, + "d7Cyay": { + "enabled": false + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "KAs9Y", + "name": "Field Storage size", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "cepHh", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "n5UuK", + "name": "t", + "fill": "$foreground/foreground", + "content": "Storage size", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "R7tWk", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "K8s quantity (e.g. 10Gi, 200Gi). Resizing requires a StorageClass with allowVolumeExpansion: true.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "id": "m6SVG", + "type": "ref", + "ref": "D0cDM", + "name": "Input", + "width": "fill_container", + "descendants": { + "PJERm": { + "content": "20Gi", + "fill": "$foreground/foreground" + } + } + } + ] + }, + { + "type": "frame", + "id": "Dj2nY", + "name": "Field StorageClass", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "urfz4", + "name": "lbl", + "width": 200, + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "Pkd0L", + "name": "t", + "fill": "$foreground/foreground", + "content": "StorageClass", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "QK3yQ", + "name": "h", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": 200, + "content": "Leave blank to use the cluster default.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "id": "BztzG", + "type": "ref", + "ref": "D0cDM", + "name": "Input", + "width": "fill_container", + "descendants": { + "PJERm": { + "content": "fast-ssd", + "fill": "$foreground/foreground" + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "x1ZNpu", + "name": "Form Footer", + "width": "fill_container", + "stroke": "$border/border", + "strokeWidth": { + "top": 1 + }, + "strokeAlignment": "inner", + "strokeLinejoin": "round", + "gap": 12, + "padding": [ + 12, + 24 + ], + "justifyContent": "end", + "alignItems": "center", + "children": [ + { + "id": "YiqwR", + "type": "ref", + "ref": "J09iP", + "name": "Btn Discard" + }, + { + "id": "GaXjM", + "type": "ref", + "ref": "z9ShNE", + "name": "Btn Save Changes", + "descendants": { + "nWTJb": { + "enabled": false + }, + "IVgQ8/U1xvSo": { + "content": "Save changes" + } + } + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "NLu2I", + "x": 1640, + "y": 18792, + "name": "Screen/Create Server — Step 3 Configure (Reserve less on)", + "theme": { + "semantic": "dark" + }, + "clip": true, + "width": 1440, + "height": 2083, + "fill": "$background/background", + "children": [ + { + "id": "X6KaEm", + "type": "ref", + "ref": "kKFX9", + "name": "Sidebar", + "height": "fill_container", + "descendants": { + "iTbd8": { + "fill": "#00000000" + }, + "iTbd8/ghUsw": { + "fill": "$foreground/foreground" + }, + "iTbd8/bpSQ7": { + "fill": "$foreground/foreground" + }, + "i8Z1B": { + "fill": "$accent/soft" + }, + "i8Z1B/Sr2Q5": { + "fill": "$accent/soft-foreground" + }, + "i8Z1B/Ra9NK": { + "fill": "$accent/soft-foreground" + } + } + }, + { + "type": "frame", + "id": "h0BQw", + "name": "Main", + "width": "fill_container", + "height": "fill_container", + "fill": "$background/background", + "layout": "vertical", + "children": [ + { + "id": "rNbjK", + "type": "ref", + "ref": "gu5WY", + "name": "TopBar", + "width": "fill_container", + "descendants": { + "Gl7AY": { + "content": "New" + } + } + } + ] + }, + { + "type": "frame", + "id": "ZPGUD", + "layoutPosition": "absolute", + "x": 0, + "y": 0, + "name": "ModalContainer", + "width": 1440, + "height": 2083, + "fill": "#000000B3", + "layout": "vertical", + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "P0qcMy", + "name": "Modal", + "clip": true, + "width": 960, + "fill": "$surface/surface", + "cornerRadius": 12, + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "effect": { + "type": "shadow", + "shadowType": "outer", + "color": "#00000080", + "offset": { + "x": 0, + "y": 24 + }, + "blur": 60 + }, + "layout": "vertical", + "children": [ + { + "id": "OEG8W", + "type": "ref", + "ref": "opmcF", + "name": "Header", + "width": "fill_container", + "descendants": { + "gcM6g": { + "content": "Step 3 of 5 · Configure" + } + } + }, + { + "type": "frame", + "id": "nZAjc", + "name": "Location placement", + "context": "Only authorized template/location combinations; changing location starts a fresh draft. Freeze chosen target during submission.", + "width": "fill_container", + "stroke": "$border/border", + "strokeWidth": { + "bottom": 1 + }, + "layout": "vertical", + "gap": 5, + "padding": [ + 12, + 24 + ], + "children": [ + { + "type": "text", + "id": "mtHaM", + "name": "Location label", + "fill": "$foreground/foreground", + "content": "Location", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "id": "vX9vX", + "type": "ref", + "ref": "AT7ya", + "name": "Creation location", + "width": "fill_container", + "descendants": { + "cFsFk": { + "content": "remote-lab / gameplane-games", + "fill": "$foreground/foreground" + } + } + }, + { + "type": "text", + "id": "O4SCTZ", + "name": "Changing location help", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Changing location starts a fresh draft. The chosen location handles all creation steps.", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "id": "TktvJ", + "type": "ref", + "ref": "oROhg", + "name": "Stepper", + "width": "fill_container", + "descendants": { + "F3Au7j": { + "fill": "#00000000", + "stroke": "$border/border" + }, + "jq5x7": { + "fill": "$muted" + }, + "bSERJ": { + "fill": "$muted", + "fontWeight": "normal" + }, + "xzVlE": { + "fill": "$accent/soft", + "stroke": "$accent/accent" + }, + "F3O7DI": { + "fill": "$accent/accent" + }, + "FqG7u": { + "fill": "$foreground/foreground", + "fontWeight": "500" + } + } + }, + { + "type": "frame", + "id": "Srvqs", + "name": "Body", + "width": "fill_container", + "gap": 12, + "padding": 24, + "children": [ + { + "type": "frame", + "id": "jigt8", + "name": "StepBody", + "width": "fill_container", + "layout": "vertical", + "gap": 12, + "children": [ + { + "id": "SVtQw", + "type": "ref", + "ref": "mhMaM", + "name": "Field Server name", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "Server name" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner" + }, + "O0VQ2": { + "content": "mc-survival", + "fill": "$foreground/foreground" + }, + "kl54d": { + "width": "fill_container" + }, + "vdTcx": { + "content": "Used as pod name and subdomain. Lowercase, dash-separated.", + "textGrowth": "fixed-width", + "width": "fill_container", + "lineHeight": 1.4 + } + } + }, + { + "id": "NwxYN", + "type": "ref", + "ref": "LCQU3", + "name": "Field Description", + "width": "fill_container", + "descendants": { + "it5w4": { + "content": "Description" + }, + "DzfNP": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 72 + }, + "usWsj": { + "content": "Hardcore survival realm with curated mods. Invite only.", + "fill": "$foreground/foreground" + }, + "Z9i98U": { + "opacity": 0, + "height": 0 + } + } + }, + { + "type": "frame", + "id": "q8Et7T", + "name": "ResourceRow", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "r92klp", + "name": "Field", + "width": "fill_container", + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "gLxq2", + "name": "Label", + "opacity": 0, + "fill": "$muted", + "content": "CPU", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "gTZ7d", + "name": "CtrlRow", + "width": "fill_container", + "gap": 8, + "alignItems": "center", + "children": [ + { + "id": "Aitgu", + "type": "ref", + "ref": "KWcnQ", + "name": "CpuSlider", + "width": "fill_container", + "descendants": { + "x2jDfP": { + "content": "CPU" + }, + "lJsFB": { + "width": 170 + } + } + }, + { + "id": "KzWwE", + "type": "ref", + "ref": "aRqX6", + "name": "CpuValue", + "width": 64, + "descendants": { + "ANxU1": { + "opacity": 0, + "height": 0 + }, + "K5pqlT": { + "height": 36, + "justifyContent": "center", + "fill": "$field/background", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner" + }, + "hokJ3": { + "content": "2", + "fill": "$foreground/foreground", + "fontWeight": "500" + }, + "OiyAM": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "M89954", + "type": "ref", + "ref": "gNnkz", + "name": "CpuUnit", + "width": 110, + "descendants": { + "s5vl0": { + "opacity": 0, + "height": 0 + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "cores", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted", + "x": 82, + "y": 10 + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + } + ] + }, + { + "type": "text", + "id": "B8NdB", + "name": "Helper", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Max 4 (largest node)", + "lineHeight": 1.4, + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "YOXcw", + "name": "Field", + "width": "fill_container", + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "YPxHO", + "name": "Label", + "opacity": 0, + "fill": "$muted", + "content": "Memory", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "S1V2ab", + "name": "CtrlRow", + "width": "fill_container", + "gap": 8, + "alignItems": "center", + "children": [ + { + "id": "jTSnP", + "type": "ref", + "ref": "KWcnQ", + "name": "MemSlider", + "width": "fill_container", + "descendants": { + "x2jDfP": { + "content": "Memory" + }, + "lJsFB": { + "width": 156 + } + } + }, + { + "id": "TpLw6", + "type": "ref", + "ref": "aRqX6", + "name": "MemValue", + "width": 64, + "descendants": { + "ANxU1": { + "opacity": 0, + "height": 0 + }, + "K5pqlT": { + "height": 36, + "justifyContent": "center", + "fill": "$field/background", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner" + }, + "hokJ3": { + "content": "4", + "fill": "$foreground/foreground", + "fontWeight": "500" + }, + "OiyAM": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "Qx9tv", + "type": "ref", + "ref": "gNnkz", + "name": "MemUnit", + "width": 110, + "descendants": { + "s5vl0": { + "opacity": 0, + "height": 0 + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "GiB", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted", + "x": 82, + "y": 10 + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + } + ] + }, + { + "type": "text", + "id": "nRzf7", + "name": "Helper", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Max 13 GiB (largest node)", + "lineHeight": 1.4, + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "f1qkX", + "name": "ReserveLess", + "width": "fill_container", + "layout": "vertical", + "gap": 6, + "children": [ + { + "type": "frame", + "id": "OkCYS", + "name": "SwitchRow", + "width": "fill_container", + "gap": 12, + "alignItems": "center", + "children": [ + { + "id": "rOrhH", + "type": "ref", + "ref": "hl7R3", + "name": "Switch", + "width": 36, + "height": 20 + }, + { + "type": "text", + "id": "jKGK6", + "name": "Label", + "fill": "$foreground/foreground", + "content": "Reserve less than the limit", + "fontFamily": "$typography/font-sans", + "fontSize": 14, + "fontWeight": "normal" + } + ] + }, + { + "type": "text", + "id": "F7PXER", + "name": "Helper", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Only the reserved amount counts when the server is placed on a node, so more servers fit; it can use more, up to the limit, when the node has room (Burstable QoS).", + "lineHeight": 1.4, + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "w0lzM", + "name": "ReservedRow", + "width": "fill_container", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "aBe3q", + "name": "Field", + "width": "fill_container", + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "e73GX", + "name": "Label", + "opacity": 0, + "fill": "$muted", + "content": "Reserved CPU", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "zV9Wc", + "name": "CtrlRow", + "width": "fill_container", + "gap": 8, + "alignItems": "center", + "children": [ + { + "id": "gYGPQ", + "type": "ref", + "ref": "KWcnQ", + "name": "ReservedCpuSlider", + "width": "fill_container", + "descendants": { + "x2jDfP": { + "content": "Reserved CPU" + }, + "lJsFB": { + "width": 58 + }, + "tES7p": { + "x": 45 + } + } + }, + { + "id": "BiGfM", + "type": "ref", + "ref": "aRqX6", + "name": "ReservedCpuValue", + "width": 64, + "descendants": { + "ANxU1": { + "opacity": 0, + "height": 0 + }, + "K5pqlT": { + "height": 36, + "justifyContent": "center", + "fill": "$field/background", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner" + }, + "hokJ3": { + "content": "1", + "fill": "$foreground/foreground", + "fontWeight": "500" + }, + "OiyAM": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "eYGak", + "type": "ref", + "ref": "gNnkz", + "name": "ReservedCpuUnit", + "width": 110, + "descendants": { + "s5vl0": { + "opacity": 0, + "height": 0 + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "cores", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted", + "x": 82, + "y": 10 + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "lH7lt", + "name": "Field", + "width": "fill_container", + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "sfe01", + "name": "Label", + "opacity": 0, + "fill": "$muted", + "content": "Reserved memory", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "qeJ1m", + "name": "CtrlRow", + "width": "fill_container", + "gap": 8, + "alignItems": "center", + "children": [ + { + "id": "zKPtD", + "type": "ref", + "ref": "KWcnQ", + "name": "ReservedMemSlider", + "width": "fill_container", + "descendants": { + "x2jDfP": { + "content": "Reserved memory" + }, + "lJsFB": { + "width": 58 + }, + "tES7p": { + "x": 45 + } + } + }, + { + "id": "QdGZi", + "type": "ref", + "ref": "aRqX6", + "name": "ReservedMemValue", + "width": 64, + "descendants": { + "ANxU1": { + "opacity": 0, + "height": 0 + }, + "K5pqlT": { + "height": 36, + "justifyContent": "center", + "fill": "$field/background", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner" + }, + "hokJ3": { + "content": "2", + "fill": "$foreground/foreground", + "fontWeight": "500" + }, + "OiyAM": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "zmspD", + "type": "ref", + "ref": "gNnkz", + "name": "ReservedMemUnit", + "width": 110, + "descendants": { + "s5vl0": { + "opacity": 0, + "height": 0 + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "GiB", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted", + "x": 82, + "y": 10 + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "i9SAhi", + "name": "StoragePlacementRow", + "width": "fill_container", + "gap": 16, + "children": [ + { + "id": "ns3t0", + "type": "ref", + "ref": "mhMaM", + "name": "Field Persistent storage", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "Persistent storage" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "20Gi", + "fill": "$foreground/foreground" + }, + "kl54d": { + "width": "fill_container" + }, + "vdTcx": { + "content": "Mounted at /data (RWO PVC).", + "textGrowth": "fixed-width", + "width": "fill_container", + "lineHeight": 1.4 + } + } + }, + { + "type": "frame", + "id": "qRtCa", + "name": "NodePlacement", + "width": "fill_container", + "layout": "vertical", + "gap": 4, + "children": [ + { + "type": "text", + "id": "UnwYg", + "name": "Label", + "fill": "$muted", + "content": "Node placement", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "v7FdYO", + "name": "Segmented", + "fill": "#00000000", + "cornerRadius": 6, + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "gap": 4, + "padding": 4, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "mA6So", + "name": "Seg", + "height": 28, + "fill": "$accent/soft", + "cornerRadius": 4, + "padding": [ + 0, + 12 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "p81wg8", + "name": "Label", + "fill": "$accent/accent", + "content": "Auto (scheduler)", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "500" + } + ] + }, + { + "type": "frame", + "id": "tKLyt", + "name": "Seg", + "height": 28, + "cornerRadius": 4, + "padding": [ + 0, + 12 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "ZNoac", + "name": "Label", + "fill": "$muted", + "content": "Pin to node", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "KaPuu", + "name": "Seg", + "height": 28, + "cornerRadius": 4, + "padding": [ + 0, + 12 + ], + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "v2ASt", + "name": "Label", + "fill": "$muted", + "content": "GPU-enabled", + "fontFamily": "$typography/font-sans", + "fontSize": 12, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "text", + "id": "DSX7s", + "name": "Helper", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Picks node with most free memory, tolerations or no taints.", + "lineHeight": 1.4, + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "yLDMY", + "name": "TemplateConfigSection", + "width": "fill_container", + "layout": "vertical", + "gap": 12, + "children": [ + { + "type": "text", + "id": "Ig1Y9", + "name": "SectionLabel", + "fill": "$muted", + "content": "TEMPLATE CONFIGURATION", + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "500" + }, + { + "id": "FcgZ7", + "type": "ref", + "ref": "gNnkz", + "name": "Field Difficulty", + "width": "fill_container", + "descendants": { + "s5vl0": { + "content": "Difficulty", + "opacity": 1, + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "easy", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted" + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "JhPiI", + "type": "ref", + "ref": "gNnkz", + "name": "Field Game mode", + "width": "fill_container", + "descendants": { + "s5vl0": { + "content": "Game mode", + "opacity": 1, + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "survival", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted" + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "JOCqo", + "type": "ref", + "ref": "mhMaM", + "name": "Field Max players", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "Max players", + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "20", + "fill": "$foreground/foreground" + }, + "kl54d": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "ijbEY", + "type": "ref", + "ref": "mhMaM", + "name": "Field MOTD", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "MOTD", + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "A Gameplane Minecraft server", + "fill": "$foreground/foreground" + }, + "kl54d": { + "width": "fill_container" + }, + "vdTcx": { + "content": "Server description shown in the multiplayer list.", + "textGrowth": "fixed-width", + "width": "fill_container", + "lineHeight": 1.4 + } + } + }, + { + "id": "g9fuEp", + "type": "ref", + "ref": "mhMaM", + "name": "Field World seed", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "World seed", + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "", + "fill": "$field/placeholder" + }, + "kl54d": { + "width": "fill_container" + }, + "vdTcx": { + "content": "Leave blank for a random seed.", + "textGrowth": "fixed-width", + "width": "fill_container", + "lineHeight": 1.4 + } + } + }, + { + "id": "MYk2Z", + "type": "ref", + "ref": "gNnkz", + "name": "Field Enforce whitelist", + "width": "fill_container", + "descendants": { + "s5vl0": { + "content": "Enforce whitelist", + "opacity": 1, + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "false", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted" + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "WIB9C", + "type": "ref", + "ref": "gNnkz", + "name": "Field PvP enabled", + "width": "fill_container", + "descendants": { + "s5vl0": { + "content": "PvP enabled", + "opacity": 1, + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "true", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted" + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "xJfWl", + "type": "ref", + "ref": "gNnkz", + "name": "Field Hardcore mode", + "width": "fill_container", + "descendants": { + "s5vl0": { + "content": "Hardcore mode", + "opacity": 1, + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "false", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted" + }, + "ghgxf": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "aR0J1", + "type": "ref", + "ref": "gNnkz", + "name": "Field Online mode", + "width": "fill_container", + "descendants": { + "s5vl0": { + "content": "Online mode", + "opacity": 1, + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "XBKI5": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "lS7Cr": { + "content": "true", + "fill": "$foreground/foreground" + }, + "iNdHQ": { + "fill": "$muted" + }, + "Qx06J": { + "content": "Verify players with Mojang. Disable only for offline/LAN setups." + } + } + }, + { + "id": "qXglj", + "type": "ref", + "ref": "mhMaM", + "name": "Field View distance (chunks)", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "View distance (chunks)", + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "10", + "fill": "$foreground/foreground" + }, + "kl54d": { + "opacity": 0, + "height": 0 + } + } + }, + { + "id": "Umoe1", + "type": "ref", + "ref": "mhMaM", + "name": "Field JVM max heap", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "JVM max heap", + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "Auto: 75% of the memory limit", + "fill": "$field/placeholder" + }, + "kl54d": { + "width": "fill_container" + }, + "vdTcx": { + "content": "Java heap ceiling (-Xmx, e.g. 2G, 3072M). Leave empty to size it automatically to 75% of the container memory limit — it then tracks the limit whenever you resize the server. If you set it yourself, keep roughly 25% below the limit: the JVM needs headroom for non-heap memory, and exceeding the limit gets the server OOM-killed.", + "textGrowth": "fixed-width", + "width": "fill_container", + "lineHeight": 1.4 + } + } + }, + { + "id": "pvoOT", + "type": "ref", + "ref": "mhMaM", + "name": "Field JVM initial heap", + "width": "fill_container", + "descendants": { + "peFYu": { + "content": "JVM initial heap", + "fill": "$muted", + "fontSize": 12, + "fontWeight": "normal" + }, + "RflGC": { + "fill": "$surface/secondary", + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "height": 36 + }, + "O0VQ2": { + "content": "Auto: 75% of the memory limit", + "fill": "$field/placeholder" + }, + "kl54d": { + "width": "fill_container" + }, + "vdTcx": { + "content": "Starting heap size (-Xms), at most JVM max heap. Leave empty to match the automatic max heap (a fixed heap avoids GC resizes).", + "textGrowth": "fixed-width", + "width": "fill_container", + "lineHeight": 1.4 + } + } + } + ] + } + ] + }, + { + "type": "frame", + "id": "u42GK", + "name": "Preview", + "clip": true, + "width": 273, + "fill": "$surface/surface", + "cornerRadius": 8, + "stroke": "$border/border", + "strokeWidth": 1, + "strokeAlignment": "inner", + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "JdmfO", + "name": "PreviewHeader", + "width": "fill_container", + "stroke": "$border/border", + "strokeWidth": { + "bottom": 1 + }, + "strokeAlignment": "inner", + "strokeLinejoin": "round", + "gap": 8, + "padding": [ + 12, + 14 + ], + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "C4qTzk", + "name": "IconWrap", + "width": 28, + "height": 28, + "fill": "$default/default", + "cornerRadius": 6, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "axnLm", + "name": "GameIcon", + "width": 16, + "height": 16, + "icon": "settings", + "library": "Material Symbols Sharp", + "weight": 400, + "fill": "$muted" + } + ] + }, + { + "type": "frame", + "id": "rYygo", + "name": "TitleGroup", + "layout": "vertical", + "gap": 2, + "children": [ + { + "type": "text", + "id": "KAMsb", + "name": "Title", + "fill": "$foreground/foreground", + "content": "Minecraft (Java Edition)", + "fontFamily": "$typography/font-sans", + "fontSize": 13, + "fontWeight": "500" + }, + { + "type": "text", + "id": "BzBnC", + "name": "Subtitle", + "fill": "$muted", + "content": "Template: minecraft-java", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "g88dd", + "name": "CodeBlockWrap", + "width": "fill_container", + "fill": "#0a0a0a", + "layout": "vertical", + "gap": 2, + "padding": [ + 12, + 14 + ], + "children": [ + { + "type": "text", + "id": "Owk4f", + "name": "L0", + "fill": "$muted", + "content": "apiVersion: gameplane.local/v1alpha1", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "G6GfW", + "name": "L1", + "fill": "$muted", + "content": "kind: GameServer", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "F12pr", + "name": "L2", + "fill": "$muted", + "content": "metadata:", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "fFgyz", + "name": "L3", + "fill": "$muted", + "content": " name: mc-survival", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "WyHg3", + "name": "L4", + "fill": "$muted", + "content": "spec:", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "D3vnEw", + "name": "L5", + "fill": "$muted", + "content": " template: minecraft-java", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "Mhjne", + "name": "L6", + "fill": "$muted", + "content": " version: fabric-1.21.4", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "K2E9Zu", + "name": "L7", + "fill": "$muted", + "content": " resources:", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "szBfM", + "name": "L8", + "fill": "$muted", + "content": " cpu: 2000m", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "GCfWV", + "name": "L9", + "fill": "$muted", + "content": " memory: 6Gi", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "QIYPA", + "name": "L10", + "fill": "$muted", + "content": " storage: 50Gi", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "YYT4O", + "name": "L11", + "fill": "$muted", + "content": " config:", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "EVNoR", + "name": "L12", + "fill": "$muted", + "content": " motd: \"A Gameplane server\"", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "kcWFZ", + "name": "L13", + "fill": "$muted", + "content": " maxPlayers: 20", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "uVshr", + "name": "L14", + "fill": "$muted", + "content": " difficulty: hard", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "gS7l4", + "name": "L15", + "fill": "$muted", + "content": " gamemode: survival", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "R0WN43", + "name": "L16", + "fill": "$muted", + "content": " onlineMode: true", + "fontFamily": "$typography/font-mono", + "fontSize": 10, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "hpdCH", + "name": "MemoryTip", + "width": "fill_container", + "stroke": "$border/border", + "strokeWidth": { + "top": 1 + }, + "strokeAlignment": "inner", + "strokeLinejoin": "round", + "padding": [ + 12, + 14 + ], + "children": [ + { + "type": "text", + "id": "nNx5n", + "name": "TipText", + "fill": "$muted", + "textGrowth": "fixed-width", + "width": "fill_container", + "content": "Memory tip — factor in mod/plugin overhead; Minecraft Vanilla uses ~1.5 GB at 4 players.", + "lineHeight": 1.4, + "fontFamily": "$typography/font-sans", + "fontSize": 11, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "id": "Or9ff", + "type": "ref", + "ref": "IoDHI", + "name": "Footer", + "width": "fill_container" + } + ] + } + ] + } + ] } ], "themes": { diff --git a/i18n/locales/en/app.json b/i18n/locales/en/app.json index eeaeda262..560ed3397 100644 --- a/i18n/locales/en/app.json +++ b/i18n/locales/en/app.json @@ -575,6 +575,10 @@ "defaultMessage": "{provider, select, none {The tunnel is enabled but the server has no tunnel configuration.} other {The ''{provider}'' tunnel provider is selected but its configuration is missing.}}", "description": "TunnelReady condition for an incomplete tunnel setup. provider is the selected provider, or none." }, + "condition.tunnelready.invalidtailscaletags": { + "defaultMessage": "Correct the Tailscale tags: use an optional tag: prefix, a letter first, then only letters, digits or hyphens.", + "description": "TunnelReady condition when Tailscale access-control tags have invalid syntax." + }, "condition.tunnelready.nocredentials": { "defaultMessage": "No credentials secret was provided, so the ''{provider}'' tunnel cannot authenticate.", "description": "TunnelReady condition when a tunnel provider needs credentials that are missing." @@ -599,10 +603,6 @@ "defaultMessage": "Tailscale forwards only advertised TCP and UDP ports. Remove unsupported protocols from the advertised ports.", "description": "TunnelReady condition when a Tailscale template advertises a protocol other than TCP or UDP." }, - "condition.tunnelready.invalidtailscaletags": { - "defaultMessage": "Correct the Tailscale tags: use an optional tag: prefix, a letter first, then only letters, digits or hyphens.", - "description": "TunnelReady condition when Tailscale access-control tags have invalid syntax." - }, "condition.unquiesced.failed": { "defaultMessage": "The server could not be resumed after the backup. Its world may still have auto-save turned off.", "description": "Unquiesced condition when the unfreeze failed." @@ -6235,6 +6235,10 @@ "defaultMessage": "One CIDR per line. Restricts which clients reach the LoadBalancer; empty allows all.", "description": "Create server: hint under the IP allow-list box saying one CIDR goes on each line and an empty list allows all clients to reach the LoadBalancer" }, + "web.routes.create_server.only_the_reserved_amount_counts_when": { + "defaultMessage": "Only the reserved amount counts when the server is placed on a node, so more servers fit; it can use more, up to the limit, when the node has room (Burstable QoS).", + "description": "Create server: hint under the reserve-less switch; Burstable QoS is a Kubernetes technical term" + }, "web.routes.create_server.or_use_existing_secret_gitops": { "defaultMessage": "Or use existing Secret (GitOps)", "description": "Create server: field label for an option to reuse an existing Kubernetes Secret for the tunnel credentials instead of entering them" @@ -6307,6 +6311,18 @@ "defaultMessage": "203.0.113.50", "description": "Create server: placeholder of the requested load balancer address input, an example IP" }, + "web.routes.create_server.reserve_less_than_the_limit": { + "defaultMessage": "Reserve less than the limit", + "description": "Create server: label next to the switch that lets the CPU and memory reserved for the server (Kubernetes requests) be lower than its limits; the same text is the switch's accessible name" + }, + "web.routes.create_server.reserved_cpu": { + "defaultMessage": "Reserved CPU", + "description": "Create server: field label for the CPU reserved for the server (the Kubernetes request), shown when the reserve-less switch is on; the same text is the accessible name of its slider; CPU is a technical term, usually left untranslated" + }, + "web.routes.create_server.reserved_memory": { + "defaultMessage": "Reserved memory", + "description": "Create server: field label for the memory reserved for the server (the Kubernetes request), shown when the reserve-less switch is on; the same text is the accessible name of its slider" + }, "web.routes.create_server.review_all_sources": { "defaultMessage": "all", "description": "Create server: review value for the IP allow-list when no source range is set, meaning every address is allowed" @@ -6319,6 +6335,10 @@ "defaultMessage": "CPU", "description": "Create server: review row label for the CPU limit" }, + "web.routes.create_server.review_cpu_reserved": { + "defaultMessage": "Reserved CPU", + "description": "Create server: review row label for the CPU reserved for the server (the Kubernetes request), shown when the reserve-less switch is on" + }, "web.routes.create_server.review_credentials_configured": { "defaultMessage": "✓ Configured", "description": "Create server: review value shown when tunnel credentials were entered" @@ -6355,6 +6375,10 @@ "defaultMessage": "Memory", "description": "Create server: review row label for the memory limit" }, + "web.routes.create_server.review_memory_reserved": { + "defaultMessage": "Reserved memory", + "description": "Create server: review row label for the memory reserved for the server (the Kubernetes request), shown when the reserve-less switch is on" + }, "web.routes.create_server.review_name": { "defaultMessage": "Name", "description": "Create server: review row label for the server name" @@ -9543,6 +9567,10 @@ "defaultMessage": "allowVolumeExpansion: true", "description": "Resources: Kubernetes StorageClass field name shown in the storage size hint" }, + "web.routes.tabs.settings.resources.counted_when_the_server_is_placed_on_a": { + "defaultMessage": "Counted when the server is placed on a node. 0 reserves nothing.", + "description": "Resources: hint under the reserved CPU field saying only this amount counts when the server is placed on a node, and that 0 reserves nothing" + }, "web.routes.tabs.settings.resources.cpu_cores": { "defaultMessage": "CPU cores", "description": "Resources: field label for the number of CPU cores reserved for the server; CPU is a technical term, usually left untranslated" @@ -9563,10 +9591,34 @@ "defaultMessage": "Leave blank to use the cluster default.", "description": "Resources: hint under the StorageClass field saying a blank value uses the cluster default storage class" }, + "web.routes.tabs.settings.resources.limit_the_most_cpu_this_server_can_use": { + "defaultMessage": "Limit: the most CPU this server can use.", + "description": "Resources: hint under the CPU cores field when the reservation may be lower, saying the value is the limit, the most CPU the server can use; CPU is a technical term" + }, + "web.routes.tabs.settings.resources.limit_the_most_memory_this_server_can_use": { + "defaultMessage": "Limit: the most memory this server can use.", + "description": "Resources: hint under the memory field when the reservation may be lower, saying the value is the limit, the most memory the server can use" + }, "web.routes.tabs.settings.resources.memory_gib": { "defaultMessage": "Memory (GiB)", "description": "Resources: field label for the memory amount in GiB reserved for the server" }, + "web.routes.tabs.settings.resources.off_the_server_reserves_its_full_cpu": { + "defaultMessage": "Off: the server reserves its full CPU and memory (Guaranteed QoS). On: it reserves less, so more servers fit on a node, and uses more up to its limit when the node has room (Burstable QoS).", + "description": "Resources: hint under the reserve-less switch explaining what off and on do; Guaranteed QoS and Burstable QoS are Kubernetes technical terms, CPU is a technical term" + }, + "web.routes.tabs.settings.resources.reserve_less_than_the_limit": { + "defaultMessage": "Reserve less than the limit", + "description": "Resources: field label for the switch that lets the CPU and memory reserved for the server (Kubernetes requests) be lower than its limits; the same text is the switch's accessible name" + }, + "web.routes.tabs.settings.resources.reserved_cpu": { + "defaultMessage": "Reserved CPU", + "description": "Resources: field label for the CPU reserved for the server (the Kubernetes request), shown when the reservation may be lower than the limit; the same text is the accessible name of its slider; CPU is a technical term, usually left untranslated" + }, + "web.routes.tabs.settings.resources.reserved_memory_gib": { + "defaultMessage": "Reserved memory (GiB)", + "description": "Resources: field label for the memory in GiB reserved for the server (the Kubernetes request), shown when the reservation may be lower than the limit; the same text is the accessible name of its slider" + }, "web.routes.tabs.settings.resources.resizing_requires_a_storageclass_with": { "defaultMessage": "Resizing requires a StorageClass with {setting}.", "description": "Resources: hint under the storage size field saying resizing needs a StorageClass that allows volume expansion; {setting} is the allowVolumeExpansion: true field, shown in monospace" @@ -9587,6 +9639,10 @@ "defaultMessage": "StorageClass", "description": "Resources: field label for the optional storage class that provides the server's storage; the hint below says blank uses the cluster default" }, + "web.routes.tabs.settings.resources.when_a_node_runs_out_of_memory_servers": { + "defaultMessage": "When a node runs out of memory, servers above their reservation are stopped first. Keep it close to real use.", + "description": "Resources: hint under the reserved memory field warning that servers using more memory than they reserved are stopped first when a node runs out of memory" + }, "web.routes.tabs.settings.retry_reload": { "defaultMessage": "Retry reload", "description": "Settings tab: inline button shown after a failed reload that tries the reload again; keep it short" diff --git a/i18n/locales/fr/app.json b/i18n/locales/fr/app.json index 862caab82..854bd6fe9 100644 --- a/i18n/locales/fr/app.json +++ b/i18n/locales/fr/app.json @@ -7784,6 +7784,11 @@ "description": "Create server: hint under the IP allow-list box saying one CIDR goes on each line and an empty list allows all clients to reach the LoadBalancer", "sourceHash": "6aa166ea622b" }, + "web.routes.create_server.only_the_reserved_amount_counts_when": { + "defaultMessage": "Seule la réservation compte pour placer le serveur sur un nœud, donc plus de serveurs tiennent ; il peut consommer davantage, jusqu'à la limite, quand le nœud a de la marge (QoS Burstable).", + "description": "Create server: hint under the reserve-less switch; Burstable QoS is a Kubernetes technical term", + "sourceHash": "31fa0fbfa524" + }, "web.routes.create_server.or_use_existing_secret_gitops": { "defaultMessage": "Ou utiliser un Secret existant (GitOps)", "description": "Create server: field label for an option to reuse an existing Kubernetes Secret for the tunnel credentials instead of entering them", @@ -7874,6 +7879,21 @@ "description": "Create server: placeholder of the requested load balancer address input, an example IP", "sourceHash": "3b5cd36857cb" }, + "web.routes.create_server.reserve_less_than_the_limit": { + "defaultMessage": "Réserver moins que la limite", + "description": "Create server: label next to the switch that lets the CPU and memory reserved for the server (Kubernetes requests) be lower than its limits; the same text is the switch's accessible name", + "sourceHash": "acd9f6270468" + }, + "web.routes.create_server.reserved_cpu": { + "defaultMessage": "CPU réservé", + "description": "Create server: field label for the CPU reserved for the server (the Kubernetes request), shown when the reserve-less switch is on; the same text is the accessible name of its slider; CPU is a technical term, usually left untranslated", + "sourceHash": "db9d058d6d15" + }, + "web.routes.create_server.reserved_memory": { + "defaultMessage": "Mémoire réservée", + "description": "Create server: field label for the memory reserved for the server (the Kubernetes request), shown when the reserve-less switch is on; the same text is the accessible name of its slider", + "sourceHash": "31197fb9cc11" + }, "web.routes.create_server.review_all_sources": { "defaultMessage": "tout", "description": "Create server: review value for the IP allow-list when no source range is set, meaning every address is allowed", @@ -7889,6 +7909,11 @@ "description": "Create server: review row label for the CPU limit", "sourceHash": "db9a4c7d4c19" }, + "web.routes.create_server.review_cpu_reserved": { + "defaultMessage": "CPU réservé", + "description": "Create server: review row label for the CPU reserved for the server (the Kubernetes request), shown when the reserve-less switch is on", + "sourceHash": "db9d058d6d15" + }, "web.routes.create_server.review_credentials_configured": { "defaultMessage": "✓ Configurés", "description": "Create server: review value shown when tunnel credentials were entered", @@ -7934,6 +7959,11 @@ "description": "Create server: review row label for the memory limit", "sourceHash": "c3963aedaac6" }, + "web.routes.create_server.review_memory_reserved": { + "defaultMessage": "Mémoire réservée", + "description": "Create server: review row label for the memory reserved for the server (the Kubernetes request), shown when the reserve-less switch is on", + "sourceHash": "31197fb9cc11" + }, "web.routes.create_server.review_name": { "defaultMessage": "Nom", "description": "Create server: review row label for the server name", @@ -11919,6 +11949,11 @@ "description": "Resources: Kubernetes StorageClass field name shown in the storage size hint", "sourceHash": "1980baabd43d" }, + "web.routes.tabs.settings.resources.counted_when_the_server_is_placed_on_a": { + "defaultMessage": "Compté pour placer le serveur sur un nœud. 0 ne réserve rien.", + "description": "Resources: hint under the reserved CPU field saying only this amount counts when the server is placed on a node, and that 0 reserves nothing", + "sourceHash": "31e44a99748f" + }, "web.routes.tabs.settings.resources.cpu_cores": { "defaultMessage": "Cœurs CPU", "description": "Resources: field label for the number of CPU cores reserved for the server; CPU is a technical term, usually left untranslated", @@ -11944,11 +11979,41 @@ "description": "Resources: hint under the StorageClass field saying a blank value uses the cluster default storage class", "sourceHash": "9b1e131c4d2c" }, + "web.routes.tabs.settings.resources.limit_the_most_cpu_this_server_can_use": { + "defaultMessage": "Limite : le maximum de CPU que ce serveur peut utiliser.", + "description": "Resources: hint under the CPU cores field when the reservation may be lower, saying the value is the limit, the most CPU the server can use; CPU is a technical term", + "sourceHash": "ff804454a007" + }, + "web.routes.tabs.settings.resources.limit_the_most_memory_this_server_can_use": { + "defaultMessage": "Limite : le maximum de mémoire que ce serveur peut utiliser.", + "description": "Resources: hint under the memory field when the reservation may be lower, saying the value is the limit, the most memory the server can use", + "sourceHash": "a0472520e73f" + }, "web.routes.tabs.settings.resources.memory_gib": { "defaultMessage": "Mémoire (GiB)", "description": "Resources: field label for the memory amount in GiB reserved for the server", "sourceHash": "e360625ba4be" }, + "web.routes.tabs.settings.resources.off_the_server_reserves_its_full_cpu": { + "defaultMessage": "Désactivé : le serveur réserve tout son CPU et sa mémoire (QoS Guaranteed). Activé : il réserve moins, donc plus de serveurs tiennent sur un nœud, et consomme davantage jusqu'à sa limite quand le nœud a de la marge (QoS Burstable).", + "description": "Resources: hint under the reserve-less switch explaining what off and on do; Guaranteed QoS and Burstable QoS are Kubernetes technical terms, CPU is a technical term", + "sourceHash": "3b08ceae443b" + }, + "web.routes.tabs.settings.resources.reserve_less_than_the_limit": { + "defaultMessage": "Réserver moins que la limite", + "description": "Resources: field label for the switch that lets the CPU and memory reserved for the server (Kubernetes requests) be lower than its limits; the same text is the switch's accessible name", + "sourceHash": "acd9f6270468" + }, + "web.routes.tabs.settings.resources.reserved_cpu": { + "defaultMessage": "CPU réservé", + "description": "Resources: field label for the CPU reserved for the server (the Kubernetes request), shown when the reservation may be lower than the limit; the same text is the accessible name of its slider; CPU is a technical term, usually left untranslated", + "sourceHash": "db9d058d6d15" + }, + "web.routes.tabs.settings.resources.reserved_memory_gib": { + "defaultMessage": "Mémoire réservée (GiB)", + "description": "Resources: field label for the memory in GiB reserved for the server (the Kubernetes request), shown when the reservation may be lower than the limit; the same text is the accessible name of its slider", + "sourceHash": "0d4bcbc433ec" + }, "web.routes.tabs.settings.resources.resizing_requires_a_storageclass_with": { "defaultMessage": "Le redimensionnement nécessite une StorageClass avec {setting}.", "description": "Resources: hint under the storage size field saying resizing needs a StorageClass that allows volume expansion; {setting} is the allowVolumeExpansion: true field, shown in monospace", @@ -11974,6 +12039,11 @@ "description": "Resources: field label for the optional storage class that provides the server's storage; the hint below says blank uses the cluster default", "sourceHash": "ca92c8a29fb8" }, + "web.routes.tabs.settings.resources.when_a_node_runs_out_of_memory_servers": { + "defaultMessage": "Quand un nœud manque de mémoire, les serveurs au-delà de leur réservation sont arrêtés en premier. Gardez-la proche de la consommation réelle.", + "description": "Resources: hint under the reserved memory field warning that servers using more memory than they reserved are stopped first when a node runs out of memory", + "sourceHash": "3092e671b2fe" + }, "web.routes.tabs.settings.retry_reload": { "defaultMessage": "Réessayer le rechargement", "description": "Settings tab: inline button shown after a failed reload that tries the reload again; keep it short", diff --git a/web/specs.md b/web/specs.md index d24b3f964..a3b88115a 100644 --- a/web/specs.md +++ b/web/specs.md @@ -541,7 +541,8 @@ Every file in slice 2b imports **only** from `@heroui/react` and `@/components/u - Input types: Text, Number, Select (enum), Checkbox (boolean), Slider (range) - Each field carries its schema-declared label and description (helper text) - Client-side validation against declared constraints (min/max, enum values, string patterns) -- Validation: all required fields present; values within declared ranges +- CPU and memory (`ResourceInput`, defaults 2 cores / 4Gi, capped at the largest node's capacity when node data is available) are written as both requests and limits (Guaranteed QoS). The "Reserve less than the limit" switch (Burstable QoS) adds "Reserved CPU" / "Reserved memory" fields, which become the requests, from 0 (reserve nothing) up to the limit; turning the switch on starts them at the limits, and lowering a limit below its reservation lowers the reservation with it +- Validation: all required fields present; values within declared ranges; CPU and memory (and, with the switch on, the reservations) are valid Kubernetes quantities, and CPU and memory do not exceed the largest node **Step 4 — Networking:** - Service type picker (ClusterIP/NodePort/LoadBalancer) via Select/ListBox @@ -552,7 +553,7 @@ Every file in slice 2b imports **only** from `@heroui/react` and `@/components/u - Validation (`validateStep`): tunnel credentials required if tunnel enabled; frp requires a server address, at least one complete port mapping, and port numbers in 1–65535 (server port too, when set); no client-side check on the LoadBalancer address/CIDR fields themselves **Step 5 — Review & Confirmation:** -- Display-only summary of all prior steps' selections (name, template, version, config values, networking, tunnel). Values of password-type template config fields are shown as `********` (`maskPasswordConfig`, `web/src/lib/validation.ts`), never as typed text; the create payload is unchanged. The API never returns stored GameServer passwords (it sends the marker `__gameplane_redacted__`; see `api/specs.md`), so no screen may render a password-type `spec.config` value or that marker. The Settings tab's merged PUT (`mergeDraftOntoLatest`) preserves unchanged markers from the latest server: the API keeps the stored value for them. +- Display-only summary of all prior steps' selections (name, template, version, config values, CPU and memory, plus the "Reserved CPU" / "Reserved memory" rows when the reserve-less switch is on, networking, tunnel). Values of password-type template config fields are shown as `********` (`maskPasswordConfig`, `web/src/lib/validation.ts`), never as typed text; the create payload is unchanged. The API never returns stored GameServer passwords (it sends the marker `__gameplane_redacted__`; see `api/specs.md`), so no screen may render a password-type `spec.config` value or that marker. The Settings tab's merged PUT (`mergeDraftOntoLatest`) preserves unchanged markers from the latest server: the API keeps the stored value for them. - "Create server" button triggers POST to `/servers` endpoint - Error display if creation fails (network error, validation error, server name conflict) - On success, redirect to ServerDetail page for new server @@ -981,7 +982,7 @@ Save fetches the latest server and applies only the changes from the original ba 1. **General** — Server name, description, game-icon image field with placeholder from template's image or "(template image)" when no template 2. **Version** — Template version selector (triggers container restart) 2a. **Game configuration** — `GameConfigSection` (`web/src/routes/tabs/settings/GameConfig.tsx`), key `config`, shown only when `template.spec.configSchema` has entries (or the server still carries `spec.config` keys, so stray keys can be removed). Edits `draft.spec.config` through the tab's shared draft/Save footer; renders fields with the shared `ConfigFields` (`web/src/components/server/ConfigFields.tsx`, variant `settings`). An unset key means the template default (or the auto value); a value equal to the schema default removes the key, and an emptied map is written as `undefined` (never `{}`). Client validation (`validateConfig`, `web/src/lib/validation.ts`): required, enum, int (whole number, min/max), bool, string/password length; failures show inline and disable Save through `onValidityChange` (the operator stays authoritative; the API does not validate against the schema). Password fields are write-only: the API returns the marker `__gameplane_redacted__` for a stored password, the input is empty with the placeholder "Unchanged — type to replace" and a "Write-only" note, the marker is never rendered, stays in the draft until the user types (the PUT then keeps the stored value) and typing replaces it. A stored OPTIONAL password also shows a "Remove password" button (spec 021 OD-2): pressing it drops the key from the draft (the API deletes an optional password whose key is absent) and shows "Password will be removed" with an Undo button; the state is derived from `storedConfig` (the saved `gs.spec.config` marker) so it survives section switches, Undo restores the key in its saved position, required passwords and read-only sessions get no button, and emptying a typed value still returns to "unchanged". `target: file` fields carry a "Written to file" note. Keys present in `spec.config` but absent from the schema are listed with a "Remove" button ("No longer in the template") because the operator rejects unknown keys. An info alert states that saving restarts the server (always; there is no apply-later mode). When `status.conditions[type=Ready].message` starts with `invalid config:` a danger alert shows it. Without `servers:write` (`access.canWrite`) the fields are disabled, the alert is hidden and the footer shows "You need permission to change this server's settings." instead of Discard/Save. -3. **Resources** — CPU request/limit, memory request/limit (Kubernetes resource specs) +3. **Resources** — `ResourcesSection` (`web/src/routes/tabs/settings/Resources.tsx`): CPU and memory (`spec.resources`), storage size and StorageClass. The CPU and memory fields show the saved limit (falling back to the request, then the 2 cores / 4Gi dashboard default). By default each field writes the same value to the request and the limit (Guaranteed QoS). The "Reserve less than the limit" switch (Burstable QoS) is not stored: it is on when a saved request differs from its limit, or once the user turns it on. While it is on, the CPU and memory fields are the limits and "Reserved CPU" / "Reserved memory (GiB)" (`ResourceInput` with its `label` prop) are the requests, from 0 (reserve nothing) up to the saved limit (uncapped when there is no saved limit). Lowering a limit below a saved request lowers that request to the new limit, since Kubernetes rejects a request above its limit. Each control writes only its own value, so an absent limit or request stays absent: the displayed fallbacks are never written back. Switching off copies each saved limit onto its request; a resource without a saved limit keeps its request. 4. **Networking** — Service type (ClusterIP/NodePort/LoadBalancer), LoadBalancer hostname, address pool / explicit address request, port overrides. Tunnel validation (`tunnel.enabled`, provider-specific config, credentials) is computed during render and reported via `onValidityChange` callback in an effect; local field state for `addressPool` and `address` is held in `useState` — so consecutive edits within one render are cumulative rather than each recomputing against the same stale `net` snapshot — and is re-seeded from props by two complementary mechanisms: on identity change, via a parent `key` remount; and in-render, whenever the incoming values differ by value from the last synced pair (so a save round-trip or a reload that returns changed values for the *same* server is picked up). 5. **Environment** — Custom env var key=value pairs 6. **Lifecycle** — Pre/post-start/stop scripts, quiesce grace period diff --git a/web/src/components/ui/ResourceInput.test.tsx b/web/src/components/ui/ResourceInput.test.tsx index c77324af3..2df57f659 100644 --- a/web/src/components/ui/ResourceInput.test.tsx +++ b/web/src/components/ui/ResourceInput.test.tsx @@ -187,6 +187,13 @@ describe("ResourceInput", () => { expect(screen.getByLabelText("Memory (GiB) unit")).toBeInTheDocument(); }); + it("uses the label prop as the accessible name when given", () => { + render( {}} label="Reserved CPU" />); + expect(screen.getByRole("slider")).toHaveAttribute("aria-label", "Reserved CPU"); + expect(screen.getByLabelText("Reserved CPU value")).toBeInTheDocument(); + expect(screen.getByLabelText("Reserved CPU unit")).toBeInTheDocument(); + }); + it("disables all controls when disabled prop is true", () => { render( {}} disabled />); expect(screen.getByRole("slider")).toBeDisabled(); diff --git a/web/src/components/ui/ResourceInput.tsx b/web/src/components/ui/ResourceInput.tsx index 33fb53739..f53555c4f 100644 --- a/web/src/components/ui/ResourceInput.tsx +++ b/web/src/components/ui/ResourceInput.tsx @@ -26,6 +26,10 @@ export interface ResourceInputProps { step?: number; // slider step in base units disabled?: boolean; id?: string; + // Accessible name of the slider and its fields; defaults to the kind's name + // ("CPU cores" / "Memory (GiB)"). Set it when one form has two inputs of the + // same kind (e.g. a limit and a reservation) so each keeps a unique name. + label?: string; } interface SelectOption { @@ -88,6 +92,7 @@ export function ResourceInput({ step: userStep, disabled = false, id, + label, }: ResourceInputProps) { const defaults = kind === "cpu" ? CPU_DEFAULTS : MEM_DEFAULTS; const minBase = userMin ?? defaults.min; @@ -171,9 +176,10 @@ export function ResourceInput({ const intl = useOptionalIntl(); const ariaLabel = - kind === "cpu" + label ?? + (kind === "cpu" ? intl.formatMessage({ id: "web.components.ui.resource_input.cpu_cores", defaultMessage: "CPU cores", description: "Resource input: accessible name of the CPU slider and its fields" }) - : intl.formatMessage({ id: "web.components.ui.resource_input.memory_gib", defaultMessage: "Memory (GiB)", description: "Resource input: accessible name of the memory slider and its fields" }); + : intl.formatMessage({ id: "web.components.ui.resource_input.memory_gib", defaultMessage: "Memory (GiB)", description: "Resource input: accessible name of the memory slider and its fields" })); const unitOptions = kind === "cpu" ? CPU_UNIT_OPTIONS : MEM_UNIT_OPTIONS; return ( diff --git a/web/src/lib/quantity.test.ts b/web/src/lib/quantity.test.ts index 8c0d65b83..e0aea31ed 100644 --- a/web/src/lib/quantity.test.ts +++ b/web/src/lib/quantity.test.ts @@ -5,9 +5,11 @@ import { convertCpu, convertMem, cpuCores, + cpuQuantityCores, formatCpuQuantity, formatMemQuantity, memBytes, + memQuantityBytes, parseCpuQuantity, parseMemQuantity, } from "./quantity"; @@ -230,6 +232,34 @@ describe("memBytes", () => { }); }); +describe("cpuQuantityCores", () => { + it("compares whole cores and millicores by amount", () => { + expect(cpuQuantityCores("2")).toBe(2); + expect(cpuQuantityCores("2000m")).toBe(2); + expect(cpuQuantityCores("500m")).toBe(0.5); + expect(cpuQuantityCores("0")).toBe(0); + }); + + it("counts empty or invalid input as 0", () => { + expect(cpuQuantityCores(undefined)).toBe(0); + expect(cpuQuantityCores("")).toBe(0); + expect(cpuQuantityCores("two")).toBe(0); + }); +}); + +describe("memQuantityBytes", () => { + it("compares memory written in different units by amount", () => { + expect(memQuantityBytes("4Gi")).toBe(4 * 1024 ** 3); + expect(memQuantityBytes("4096Mi")).toBe(4 * 1024 ** 3); + }); + + it("counts empty, invalid or zero input as 0", () => { + expect(memQuantityBytes(undefined)).toBe(0); + expect(memQuantityBytes("lots")).toBe(0); + expect(memQuantityBytes("0Gi")).toBe(0); + }); +}); + describe("round-trip preservation", () => { it("preserves CPU cores in round-trip", () => { const original = "2"; diff --git a/web/src/lib/quantity.ts b/web/src/lib/quantity.ts index 7716b3fd9..bab43cc9f 100644 --- a/web/src/lib/quantity.ts +++ b/web/src/lib/quantity.ts @@ -202,3 +202,22 @@ export function memBytes(a: MemAmount): number { return a.value * magnitudes[a.unit]; } + +/** + * Get the core count of a canonical CPU quantity, so amounts written in + * different forms compare equal ("2" and "2000m" are both 2). Empty or + * invalid input counts as 0. + */ +export function cpuQuantityCores(qty: string | undefined): number { + const amount = parseCpuQuantity(qty); + return amount ? cpuCores(amount) : 0; +} + +/** + * Get the byte count of a canonical memory quantity ("4Gi" and "4096Mi" are + * equal). Empty, invalid or zero input counts as 0. + */ +export function memQuantityBytes(qty: string | undefined): number { + const amount = parseMemQuantity(qty); + return amount ? memBytes(amount) : 0; +} diff --git a/web/src/routes/CreateServer.test.tsx b/web/src/routes/CreateServer.test.tsx index 0b3b6401d..00e1e13db 100644 --- a/web/src/routes/CreateServer.test.tsx +++ b/web/src/routes/CreateServer.test.tsx @@ -1,5 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { screen, fireEvent, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { renderWithQuery } from "@/test/render"; import type { GameTemplate } from "@/types"; @@ -203,6 +204,74 @@ describe("CreateServerWizard", () => { expect(body.spec.resources.limits).toEqual({ cpu: "2", memory: "4Gi" }); }); + it("sends requests below the limits when reserving less (Burstable QoS)", async () => { + routeFetch(); + + renderWithQuery(); + await pickTemplate(template()); + + fireEvent.change(screen.getByPlaceholderText("mc-hardcore"), { + target: { value: "mc-test" }, + }); + expect(screen.queryByLabelText("Reserved CPU value")).not.toBeInTheDocument(); + await userEvent.click(screen.getByRole("switch", { name: "Reserve less than the limit" })); + // The reservation starts at the limits. + expect(screen.getByLabelText("Reserved CPU value")).toHaveValue(2); + expect(screen.getByLabelText("Reserved memory value")).toHaveValue(4); + + const cpu = screen.getByLabelText("Reserved CPU value"); + fireEvent.change(cpu, { target: { value: "0.5" } }); + fireEvent.blur(cpu); + const memory = screen.getByLabelText("Reserved memory value"); + fireEvent.change(memory, { target: { value: "1" } }); + fireEvent.blur(memory); + + fireEvent.click(screen.getByRole("button", { name: /Continue to Network/i })); + fireEvent.click(screen.getByRole("button", { name: /Continue to Review/i })); + expect(screen.getByText("Reserved CPU")).toBeInTheDocument(); + expect(screen.getByText("500m")).toBeInTheDocument(); + expect(screen.getByText("Reserved memory")).toBeInTheDocument(); + expect(screen.getByText("1Gi")).toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: /Create server/i })); + + await waitFor(() => expect(navigate).toHaveBeenCalled()); + const postCall = fetchMock.mock.calls.find((c) => (c[1] as FetchInit).method === "POST")!; + const body = JSON.parse((postCall[1] as FetchInit).body as string); + expect(body.spec.resources.requests).toEqual({ cpu: "500m", memory: "1Gi" }); + expect(body.spec.resources.limits).toEqual({ cpu: "2", memory: "4Gi" }); + }); + + it("lowering a limit below the reservation lowers the reservation too", async () => { + routeFetch(); + + renderWithQuery(); + await pickTemplate(template()); + + fireEvent.change(screen.getByPlaceholderText("mc-hardcore"), { + target: { value: "mc-test" }, + }); + await userEvent.click(screen.getByRole("switch", { name: "Reserve less than the limit" })); + + const cpuLimit = screen.getByLabelText("CPU cores value"); + fireEvent.change(cpuLimit, { target: { value: "1" } }); + fireEvent.blur(cpuLimit); + const memoryLimit = screen.getByLabelText("Memory (GiB) value"); + fireEvent.change(memoryLimit, { target: { value: "2" } }); + fireEvent.blur(memoryLimit); + expect(screen.getByLabelText("Reserved CPU value")).toHaveValue(1); + expect(screen.getByLabelText("Reserved memory value")).toHaveValue(2); + + fireEvent.click(screen.getByRole("button", { name: /Continue to Network/i })); + fireEvent.click(screen.getByRole("button", { name: /Continue to Review/i })); + fireEvent.click(screen.getByRole("button", { name: /Create server/i })); + + await waitFor(() => expect(navigate).toHaveBeenCalled()); + const postCall = fetchMock.mock.calls.find((c) => (c[1] as FetchInit).method === "POST")!; + const body = JSON.parse((postCall[1] as FetchInit).body as string); + expect(body.spec.resources.requests).toEqual({ cpu: "1", memory: "2Gi" }); + expect(body.spec.resources.limits).toEqual({ cpu: "1", memory: "2Gi" }); + }); + it("keeps the 4-step flow when the template declares no versions", async () => { fetchMock.mockResolvedValueOnce(jsonRes(200, { items: [template()] })); renderWithQuery(); diff --git a/web/src/routes/CreateServer.tsx b/web/src/routes/CreateServer.tsx index 074dea08e..aaeebea70 100644 --- a/web/src/routes/CreateServer.tsx +++ b/web/src/routes/CreateServer.tsx @@ -2,7 +2,7 @@ import { useMemo, useState } from "react"; import { useNavigate, useSearch } from "@tanstack/react-router"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { AlertCircle, ArrowLeft, ArrowRight, Check, ExternalLink, Loader2, Megaphone, X } from "lucide-react"; -import { Button, Input, Alert, Select, Label, ListBox, ListBoxItem } from "@heroui/react"; +import { Button, Input, Alert, Select, Label, ListBox, ListBoxItem, Switch } from "@heroui/react"; import { GameIcon } from "@/components/ui/GameIcon"; import { ResourceInput } from "@/components/ui/ResourceInput"; import { PortOverridesEditor } from "@/components/server/PortOverridesEditor"; @@ -23,7 +23,7 @@ import { maskPasswordConfig, validateConfig, } from "@/lib/validation"; -import { parseCpuQuantity, cpuCores, parseMemQuantity, memBytes } from "@/lib/quantity"; +import { parseCpuQuantity, cpuCores, parseMemQuantity, memBytes, cpuQuantityCores, memQuantityBytes } from "@/lib/quantity"; import { cn, ignoreRejection } from "@/lib/utils"; import { categoryFilters, categoryLabel, matchesCategory, resolveCategories } from "@/lib/games"; import type { GameTemplate, PortOverride, GameServerTunnel } from "@/types"; @@ -60,6 +60,10 @@ interface WizardState { config: Record; cpuLimit: string; memoryLimit: string; + // Burstable QoS opt-in: reserve (request) less than the limits. + reserveLess: boolean; + cpuRequest: string; + memoryRequest: string; storageSize: string; nodePlacement: "auto" | "pin" | "gpu"; expose: "ClusterIP" | "NodePort" | "LoadBalancer"; @@ -84,6 +88,7 @@ const initial: WizardState = { name: "", description: "", template: null, version: "", config: {}, cpuLimit: "2", memoryLimit: "4Gi", + reserveLess: false, cpuRequest: "2", memoryRequest: "4Gi", storageSize: "20Gi", nodePlacement: "auto", expose: "NodePort", hostname: "", sourceRanges: "", portOverrides: [], @@ -132,6 +137,11 @@ export function nonEmptyTunnelPortMappings( return mappings.filter((m) => m.name.trim() && m.remotePort && m.remotePort > 0); } +/** + * Turns the wizard state into the GameServer create payload: template and + * version, config, networking or tunnel, resources (requests equal to limits + * unless the user reserves less) and node placement. + */ function buildCreateBody(state: WizardState): ServerCreate { let nodeSelector: Record | undefined; if (state.nodePlacement === "pin") nodeSelector = { "gameplane.local/pinned": "true" }; @@ -210,11 +220,15 @@ function buildCreateBody(state: WizardState): ServerCreate { ...(tunnel ? { tunnel } : {}), }, resources: { - // The create form uses Guaranteed QoS: requests == limits, so the pod - // is scheduled onto (and kept on) a node that can always give it the - // full amount, and never gets OOM-killed or CPU-throttled below what - // was configured. - requests: { cpu: state.cpuLimit, memory: state.memoryLimit }, + // The create form uses Guaranteed QoS by default: requests == limits, so + // the pod is scheduled onto (and kept on) a node that can always give it + // the full amount, and never gets OOM-killed or CPU-throttled below what + // was configured. "Reserve less than the limit" opts into Burstable QoS + // instead: lower requests fit more servers per node, in exchange for + // throttling or eviction when the node runs short. + requests: state.reserveLess + ? { cpu: state.cpuRequest, memory: state.memoryRequest } + : { cpu: state.cpuLimit, memory: state.memoryLimit }, limits: { cpu: state.cpuLimit, memory: state.memoryLimit }, }, ...(nodeSelector ? { nodeSelector } : {}), @@ -253,7 +267,11 @@ function validateStep( if (!isValidQuantity(state.storageSize)) { return { ok: false, reason: intl.formatMessage({ id: "web.routes.create_server.validation_storage_quantity", defaultMessage: "Storage must be a Kubernetes quantity (e.g. 50Gi)", description: "Create server: validation message shown when the storage size is not a valid Kubernetes quantity" }) }; } - if (!isValidQuantity(state.cpuLimit) || !isValidQuantity(state.memoryLimit)) { + if ( + !isValidQuantity(state.cpuLimit) || + !isValidQuantity(state.memoryLimit) || + (state.reserveLess && (!isValidQuantity(state.cpuRequest) || !isValidQuantity(state.memoryRequest))) + ) { return { ok: false, reason: intl.formatMessage({ id: "web.routes.create_server.validation_cpu_memory_quantity", defaultMessage: "CPU and memory must be valid quantities", description: "Create server: validation message shown when the CPU or memory limit is not a valid Kubernetes quantity" }) }; } const cpuParsed = parseCpuQuantity(state.cpuLimit); @@ -731,6 +749,11 @@ function PickVersion({ state, setState, cluster }: { state: WizardState; setStat ); } +/** + * Configure step: server name and description, the template's config fields, + * CPU and memory (capped at the largest node) with the optional reservation + * below the limit, storage size and node placement. + */ function Configure({ state, setState, caps, cluster }: { state: WizardState; setState: (s: WizardState) => void; caps: { maxCpu: number; maxMemGi: number }; cluster: string }) { const intl = useOptionalIntl(); const text = useModuleText(state.template?.metadata.name, cluster); @@ -739,6 +762,9 @@ function Configure({ state, setState, caps, cluster }: { state: WizardState; set // can never place a pod that requests more than one node provides, so // let the user know the ceiling and clamp their input to it. const { maxCpu, maxMemGi } = caps; + const reserveLessLabel = intl.formatMessage({ id: "web.routes.create_server.reserve_less_than_the_limit", defaultMessage: "Reserve less than the limit", description: "Create server: label next to the switch that lets the CPU and memory reserved for the server (Kubernetes requests) be lower than its limits; the same text is the switch's accessible name" }); + const reservedCpuLabel = intl.formatMessage({ id: "web.routes.create_server.reserved_cpu", defaultMessage: "Reserved CPU", description: "Create server: field label for the CPU reserved for the server (the Kubernetes request), shown when the reserve-less switch is on; the same text is the accessible name of its slider; CPU is a technical term, usually left untranslated" }); + const reservedMemoryLabel = intl.formatMessage({ id: "web.routes.create_server.reserved_memory", defaultMessage: "Reserved memory", description: "Create server: field label for the memory reserved for the server (the Kubernetes request), shown when the reserve-less switch is on; the same text is the accessible name of its slider" }); return (
@@ -767,7 +793,13 @@ function Configure({ state, setState, caps, cluster }: { state: WizardState; set setState({ ...state, cpuLimit: q })} + // Kubernetes rejects a request above its limit, so a lower limit + // pulls the reservation down with it. + onChange={(q) => setState({ + ...state, + cpuLimit: q, + cpuRequest: cpuQuantityCores(state.cpuRequest) > cpuQuantityCores(q) ? q : state.cpuRequest, + })} max={maxCpu > 0 ? maxCpu : undefined} /> {maxCpu > 0 && ( @@ -779,7 +811,11 @@ function Configure({ state, setState, caps, cluster }: { state: WizardState; set setState({ ...state, memoryLimit: q })} + onChange={(q) => setState({ + ...state, + memoryLimit: q, + memoryRequest: memQuantityBytes(state.memoryRequest) > memQuantityBytes(q) ? q : state.memoryRequest, + })} max={maxMemGi > 0 ? maxMemGi : undefined} /> {maxMemGi > 0 && ( @@ -788,6 +824,53 @@ function Configure({ state, setState, caps, cluster }: { state: WizardState; set
+
+
+ setState({ ...state, reserveLess: on, cpuRequest: state.cpuLimit, memoryRequest: state.memoryLimit })} + aria-label={reserveLessLabel} + > + + + + + + + {reserveLessLabel} +
+ {intl.formatMessage({ id: "web.routes.create_server.only_the_reserved_amount_counts_when", defaultMessage: "Only the reserved amount counts when the server is placed on a node, so more servers fit; it can use more, up to the limit, when the node has room (Burstable QoS).", description: "Create server: hint under the reserve-less switch; Burstable QoS is a Kubernetes technical term" })} +
+ + {state.reserveLess && ( +
+
+ + setState({ ...state, cpuRequest: q })} + min={0} + max={cpuQuantityCores(state.cpuLimit)} + /> +
+
+ + setState({ ...state, memoryRequest: q })} + min={0} + max={memQuantityBytes(state.memoryLimit) / 1024 ** 3} + /> +
+
+ )} +
void; location: string; cluster: string }) { const intl = useOptionalIntl(); const text = useModuleText(state.template?.metadata.name, cluster); @@ -1205,6 +1295,12 @@ function Review({ state, onEdit, location, cluster }: { state: WizardState; onEd [intl.formatMessage(REVIEW_LABELS.location), location], [intl.formatMessage(REVIEW_LABELS.cpu), state.cpuLimit], [intl.formatMessage(REVIEW_LABELS.memory), state.memoryLimit], + ...(state.reserveLess + ? [ + [intl.formatMessage(REVIEW_LABELS.cpu_reserved), state.cpuRequest], + [intl.formatMessage(REVIEW_LABELS.memory_reserved), state.memoryRequest], + ] + : []), [intl.formatMessage(REVIEW_LABELS.storage), state.storageSize], [intl.formatMessage(REVIEW_LABELS.placement), intl.formatMessage(PLACEMENT_LABELS[state.nodePlacement])], ] as Array<[string, string]>, diff --git a/web/src/routes/tabs/settings/Resources.test.tsx b/web/src/routes/tabs/settings/Resources.test.tsx index c3134ffb6..c860f347d 100644 --- a/web/src/routes/tabs/settings/Resources.test.tsx +++ b/web/src/routes/tabs/settings/Resources.test.tsx @@ -1,7 +1,30 @@ import { describe, it, expect, vi } from "vitest"; import { render, screen, fireEvent } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { ResourcesSection } from "./Resources"; import { makeServer } from "@/test/factories"; +import type { ResourceRequirements } from "@/types"; + +function withResources(resources: ResourceRequirements) { + const server = makeServer(); + return { ...server, spec: { ...server.spec, resources } }; +} + +const guaranteed: ResourceRequirements = { + requests: { cpu: "2", memory: "4Gi" }, + limits: { cpu: "2", memory: "4Gi" }, +}; + +const burstable: ResourceRequirements = { + requests: { cpu: "500m", memory: "2Gi" }, + limits: { cpu: "2", memory: "4Gi" }, +}; + +function commit(label: string, value: string) { + const input = screen.getByLabelText(label); + fireEvent.change(input, { target: { value } }); + fireEvent.blur(input); +} describe("ResourcesSection", () => { it("renders default CPU and memory values when no resources set", () => { @@ -187,4 +210,149 @@ describe("ResourcesSection", () => { expect(lastCall.spec.storage.size).toBe("10Gi"); expect(lastCall.spec.resources.requests.cpu).toBe("8"); }); + + describe("reserve less than the limit", () => { + const switchName = { name: "Reserve less than the limit" }; + + it("is off by default and hides the reserved fields", () => { + render( {}} />); + expect(screen.getByRole("switch", switchName)).not.toBeChecked(); + expect(screen.queryByLabelText("Reserved CPU value")).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Reserved memory (GiB) value")).not.toBeInTheDocument(); + }); + + it("switching on shows the reserved fields at the limits without changing the draft", async () => { + const onChange = vi.fn(); + render(); + await userEvent.click(screen.getByRole("switch", switchName)); + expect(screen.getByRole("switch", switchName)).toBeChecked(); + expect(screen.getByLabelText("Reserved CPU value")).toHaveValue(2); + expect(screen.getByLabelText("Reserved memory (GiB) value")).toHaveValue(4); + expect(screen.getByText("Limit: the most CPU this server can use.")).toBeInTheDocument(); + expect(screen.getByText("Limit: the most memory this server can use.")).toBeInTheDocument(); + expect(onChange).not.toHaveBeenCalled(); + }); + + it("lowering the reserved CPU writes requests below the limits", async () => { + const onChange = vi.fn(); + render(); + await userEvent.click(screen.getByRole("switch", switchName)); + commit("Reserved CPU value", "0.5"); + expect(onChange.mock.calls.at(-1)![0].spec.resources).toEqual({ + requests: { cpu: "500m", memory: "4Gi" }, + limits: { cpu: "2", memory: "4Gi" }, + }); + }); + + it("a reserved memory of 0 reserves nothing", async () => { + const onChange = vi.fn(); + render(); + await userEvent.click(screen.getByRole("switch", switchName)); + commit("Reserved memory (GiB) value", "0"); + expect(onChange.mock.calls.at(-1)![0].spec.resources).toEqual({ + requests: { cpu: "2", memory: "0Gi" }, + limits: { cpu: "2", memory: "4Gi" }, + }); + }); + + it("opens switched on when the saved requests are below the limits", () => { + render( {}} />); + expect(screen.getByRole("switch", switchName)).toBeChecked(); + // 500m shows in its natural mCPU unit. + expect(screen.getByLabelText("Reserved CPU value")).toHaveValue(500); + expect(screen.getByLabelText("Reserved memory (GiB) value")).toHaveValue(2); + expect(screen.getByLabelText("CPU cores value")).toHaveValue(2); + expect(screen.getByLabelText("Memory (GiB) value")).toHaveValue(4); + }); + + it("lowering a limit below its reservation lowers the reservation too", () => { + const onChange = vi.fn(); + const draft = withResources({ + requests: { cpu: "1500m", memory: "3Gi" }, + limits: { cpu: "4", memory: "4Gi" }, + }); + const { unmount } = render(); + commit("CPU cores value", "1"); + expect(onChange.mock.calls.at(-1)![0].spec.resources).toEqual({ + requests: { cpu: "1", memory: "3Gi" }, + limits: { cpu: "1", memory: "4Gi" }, + }); + unmount(); + + render(); + commit("Memory (GiB) value", "2"); + expect(onChange.mock.calls.at(-1)![0].spec.resources).toEqual({ + requests: { cpu: "1500m", memory: "2Gi" }, + limits: { cpu: "4", memory: "2Gi" }, + }); + }); + + it("raising a limit keeps the reservation", () => { + const onChange = vi.fn(); + const { unmount } = render(); + commit("CPU cores value", "4"); + expect(onChange.mock.calls.at(-1)![0].spec.resources).toEqual({ + requests: { cpu: "500m", memory: "2Gi" }, + limits: { cpu: "4", memory: "4Gi" }, + }); + unmount(); + + render(); + commit("Memory (GiB) value", "8"); + expect(onChange.mock.calls.at(-1)![0].spec.resources).toEqual({ + requests: { cpu: "500m", memory: "2Gi" }, + limits: { cpu: "2", memory: "8Gi" }, + }); + }); + + it("switching off puts the requests back on the limits", async () => { + const onChange = vi.fn(); + render(); + await userEvent.click(screen.getByRole("switch", switchName)); + expect(onChange.mock.calls.at(-1)![0].spec.resources).toEqual(guaranteed); + }); + + it("switching off before lowering anything leaves the draft alone", async () => { + const onChange = vi.fn(); + render(); + await userEvent.click(screen.getByRole("switch", switchName)); + await userEvent.click(screen.getByRole("switch", switchName)); + expect(screen.getByRole("switch", switchName)).not.toBeChecked(); + expect(screen.queryByLabelText("Reserved CPU value")).not.toBeInTheDocument(); + expect(onChange).not.toHaveBeenCalled(); + }); + + // CPU has a reservation but no limit (e.g. edited with kubectl); memory + // reserves less than its limit, so the switch opens on. + const cpuWithoutLimit: ResourceRequirements = { + requests: { cpu: "500m", memory: "1Gi" }, + limits: { memory: "2Gi" }, + }; + + it("editing a reservation never adds a limit the server did not have", () => { + const onChange = vi.fn(); + render(); + commit("Reserved memory (GiB) value", "0.5"); + expect(onChange.mock.calls.at(-1)![0].spec.resources).toEqual({ + requests: { cpu: "500m", memory: "512Mi" }, + limits: { memory: "2Gi" }, + }); + // 500m shows in mCPU, so the typed value is in millicores. + commit("Reserved CPU value", "250"); + expect(onChange.mock.calls.at(-1)![0].spec.resources).toEqual({ + requests: { cpu: "250m", memory: "1Gi" }, + limits: { memory: "2Gi" }, + }); + }); + + it("switching off copies only the saved limits onto the requests", async () => { + const onChange = vi.fn(); + render(); + await userEvent.click(screen.getByRole("switch", switchName)); + expect(onChange.mock.calls.at(-1)![0].spec.resources).toEqual({ + requests: { cpu: "500m", memory: "2Gi" }, + limits: { memory: "2Gi" }, + }); + }); + }); }); diff --git a/web/src/routes/tabs/settings/Resources.tsx b/web/src/routes/tabs/settings/Resources.tsx index 0d331105e..e5df18fc6 100644 --- a/web/src/routes/tabs/settings/Resources.tsx +++ b/web/src/routes/tabs/settings/Resources.tsx @@ -1,12 +1,19 @@ -import { Input } from "@heroui/react"; +import { useState } from "react"; +import { Input, Switch } from "@heroui/react"; import type { ResourceRequirements } from "@/types"; import { isValidQuantity } from "@/lib/validation"; +import { cpuQuantityCores, memQuantityBytes } from "@/lib/quantity"; import { ResourceInput } from "@/components/ui/ResourceInput"; import { Field } from "./Field"; import type { SectionProps } from "./types"; import { useOptionalIntl } from "@/lib/i18n/format"; +/** + * Server settings, Resources section: the CPU and memory given to the server, + * with an optional reservation below the limit (Burstable QoS), plus its + * storage size and StorageClass. + */ export function ResourcesSection({ draft, onChange }: SectionProps) { const intl = useOptionalIntl(); const res = draft.spec.resources ?? {}; @@ -37,7 +44,56 @@ export function ResourcesSection({ draft, onChange }: SectionProps) { const sizeValid = !storage.size || isValidQuantity(storage.size); + // What the fields show: the limit (falling back to the request, then the + // dashboard default) and the request (falling back to the limit, as + // Kubernetes defaults a missing request to its limit). + const cpuLimit = res.limits?.cpu ?? res.requests?.cpu ?? "2"; + const memoryLimit = res.limits?.memory ?? res.requests?.memory ?? "4Gi"; + const cpuRequest = res.requests?.cpu ?? cpuLimit; + const memoryRequest = res.requests?.memory ?? memoryLimit; + + // Reserving less than the limit (Burstable QoS) is not a stored field: it is + // on whenever the saved requests sit below the limits, or once the user + // switches it on to start lowering them. + const requestsBelowLimits = + cpuQuantityCores(cpuRequest) !== cpuQuantityCores(cpuLimit) || + memQuantityBytes(memoryRequest) !== memQuantityBytes(memoryLimit); + const [reserveLessChosen, setReserveLessChosen] = useState(false); + const reserveLess = requestsBelowLimits || reserveLessChosen; + + // Each control below writes only the values it owns. The fields display + // fallbacks (a missing limit shows the request, a missing request shows the + // limit), and writing those back would add a limit or request the server + // never had: an absent CPU limit would suddenly cap the CPU. + + /** + * Turns the reserve-less switch on or off. Switching off copies each saved + * limit onto its request (Guaranteed QoS); a resource with no saved limit + * keeps its request rather than gaining a limit nobody set. + */ + const handleReserveLessChange = (on: boolean) => { + setReserveLessChosen(on); + if (!on && requestsBelowLimits) { + setResources({ ...res, requests: { ...res.requests, ...res.limits } }); + } + }; + + /** + * Sets the CPU limit. With the switch off the request follows it (Guaranteed + * QoS). With it on, only a saved reservation above the new limit comes down + * with it, since Kubernetes rejects a request above its limit. + */ const handleCpuChange = (qty: string) => { + if (reserveLess) { + const lowerRequest = + res.requests?.cpu !== undefined && cpuQuantityCores(res.requests.cpu) > cpuQuantityCores(qty); + setResources({ + ...res, + requests: lowerRequest ? { ...res.requests, cpu: qty } : res.requests, + limits: { ...res.limits, cpu: qty }, + }); + return; + } setResources({ ...res, requests: { ...res.requests, cpu: qty }, @@ -45,7 +101,18 @@ export function ResourcesSection({ draft, onChange }: SectionProps) { }); }; + /** Sets the memory limit; same rules as {@link handleCpuChange}. */ const handleMemoryChange = (qty: string) => { + if (reserveLess) { + const lowerRequest = + res.requests?.memory !== undefined && memQuantityBytes(res.requests.memory) > memQuantityBytes(qty); + setResources({ + ...res, + requests: lowerRequest ? { ...res.requests, memory: qty } : res.requests, + limits: { ...res.limits, memory: qty }, + }); + return; + } setResources({ ...res, requests: { ...res.requests, memory: qty }, @@ -53,27 +120,100 @@ export function ResourcesSection({ draft, onChange }: SectionProps) { }); }; + /** Sets the CPU reservation (request) alone; the limits stay as saved. */ + const handleCpuRequestChange = (qty: string) => { + setResources({ ...res, requests: { ...res.requests, cpu: qty } }); + }; + + /** Sets the memory reservation (request) alone; the limits stay as saved. */ + const handleMemoryRequestChange = (qty: string) => { + setResources({ ...res, requests: { ...res.requests, memory: qty } }); + }; + + const reserveLessLabel = intl.formatMessage({ id: "web.routes.tabs.settings.resources.reserve_less_than_the_limit", defaultMessage: "Reserve less than the limit", description: "Resources: field label for the switch that lets the CPU and memory reserved for the server (Kubernetes requests) be lower than its limits; the same text is the switch's accessible name" }); + const reservedCpuLabel = intl.formatMessage({ id: "web.routes.tabs.settings.resources.reserved_cpu", defaultMessage: "Reserved CPU", description: "Resources: field label for the CPU reserved for the server (the Kubernetes request), shown when the reservation may be lower than the limit; the same text is the accessible name of its slider; CPU is a technical term, usually left untranslated" }); + const reservedMemoryLabel = intl.formatMessage({ id: "web.routes.tabs.settings.resources.reserved_memory_gib", defaultMessage: "Reserved memory (GiB)", description: "Resources: field label for the memory in GiB reserved for the server (the Kubernetes request), shown when the reservation may be lower than the limit; the same text is the accessible name of its slider" }); + return (
- + + +
+ + + + + + + +
+
+ + {reserveLess && ( + <> + + + + + + + + + )} +