From 59e5c5ff3896b539afc678b6dc1796cdcde70398 Mon Sep 17 00:00:00 2001 From: chris bell Date: Tue, 9 Jun 2026 21:55:37 -0500 Subject: [PATCH] Added a create instance and more error handling and some style changes --- flake.lock | 61 ++++++++++ vssm_web/vssm_web/src/api.ts | 24 +++- vssm_web/vssm_web/src/assets/Goudament.ttf | Bin 0 -> 40536 bytes .../vssm_web/src/components/InstanceTable.tsx | 75 ++++++++++-- vssm_web/vssm_web/src/index.css | 113 ++++++++++++++---- 5 files changed, 239 insertions(+), 34 deletions(-) create mode 100644 flake.lock create mode 100644 vssm_web/vssm_web/src/assets/Goudament.ttf diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..9140dbc --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1780749050, + "narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "a799d3e3886da994fa307f817a6bc705ae538eeb", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/vssm_web/vssm_web/src/api.ts b/vssm_web/vssm_web/src/api.ts index d302643..86e5aa7 100644 --- a/vssm_web/vssm_web/src/api.ts +++ b/vssm_web/vssm_web/src/api.ts @@ -14,18 +14,36 @@ class api { export async function fetchInstanceList(): Promise { const res = await fetch(`${ADDRESS}/instances/list`); - if (!res.ok) throw new Error(`Could not fetch instance list: ${res.status}`); + if (!res.ok) { + const message = await res.text(); + throw new Error(`Could not fetch instance list: ${message.trim()}`); + } return res.json(); } export async function sendStartInstanceRequest(name: string) { const res = await fetch(`${ADDRESS}/instances/start?name=${name}`, {method: "POST"}); - if (!res.ok) throw new Error(`Start Server Request failed: ${res.status}`); + if (!res.ok) { + const message = await res.text(); + throw new Error(`Start Instance Request failed: ${message.trim()}`); + } return res.json; } export async function sendStopInstanceRequest(name: string) { const res = await fetch(`${ADDRESS}/instances/stop?name=${name}`, {method: "POST"}); - if (!res.ok) throw new Error(`Stop Server Request failed: ${res.status}`); + if (!res.ok) { + const message = await res.text(); + throw new Error(`Stop Instance Request failed: ${message.trim()}`); + } + return res.json; +} + +export async function sendCreateInstanceRequest(name: string, version: string, port: number) { + const res = await fetch(`${ADDRESS}/instances/create?name=${name}&version=${version}&port=${port}`, {method: "POST"}); + if (!res.ok) { + const message = await res.text(); + throw new Error(`Create instance request failed: ${message.trim()}`); + } return res.json; } \ No newline at end of file diff --git a/vssm_web/vssm_web/src/assets/Goudament.ttf b/vssm_web/vssm_web/src/assets/Goudament.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f50427049ccd1f86918a2cb7534afb8849da0f39 GIT binary patch literal 40536 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 const [actionLoading, setActionLoading] = useState>({}); + const [createModalOpen, setCreateModalOpen] = useState(false); + const [createForm, setCreateForm] = useState({ name: "", version: "", port: "" }); + const [createError, setCreateError] = useState(null); + const [createLoading, setCreateLoading] = useState(false); + async function refreshInstanceList() { setLoading(true); setError(null); @@ -72,13 +77,37 @@ export default function InstancesTable() { } } + function openCreateModal() { + setCreateForm({ name: "", version: "", port: "" }); + setCreateError(null); + setCreateModalOpen(true); + } + + function handleFormChange(e: React.ChangeEvent) { + setCreateForm(prev => ({ ...prev, [e.target.name]: e.target.value })); + } + + async function handleCreate() { + setCreateError(null); + setCreateLoading(true); + try { + await sendCreateInstanceRequest(createForm.name, createForm.version, parseInt(createForm.port)); + setCreateModalOpen(false); + await refreshInstanceList(); + } catch (e: any) { + setCreateError(e.message ?? String(e)); + } finally { + setCreateLoading(false); + } + } + useEffect(() => { refreshInstanceList(); }, []); return (
-

Instances

+

Instances

{error &&
Error: {error}
} @@ -94,7 +123,7 @@ export default function InstancesTable() { {!instances && !loading && ( - No data + No instances found, create one below! )} @@ -108,19 +137,49 @@ export default function InstancesTable() { {inst.port} - + > + {actionLoading[inst.name] ? "..." : inst.status === "RUNNING" ? "\u25FC Stop" : "\u25B6 Start"} + + +
))} + + + +
+ Create new instance + +
+ + + + + + + {createModalOpen && ( +
setCreateModalOpen(false)}> +
e.stopPropagation()}> +

Create New Instance

+ {createError &&
Error: {createError}
} + + + + +
+
+ )} ); -} +} \ No newline at end of file diff --git a/vssm_web/vssm_web/src/index.css b/vssm_web/vssm_web/src/index.css index a227e35..705549d 100644 --- a/vssm_web/vssm_web/src/index.css +++ b/vssm_web/vssm_web/src/index.css @@ -12,7 +12,7 @@ rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; --sans: system-ui, 'Segoe UI', Roboto, sans-serif; - --heading: system-ui, 'Segoe UI', Roboto, sans-serif; + --heading: Goudament, system-ui, 'Segoe UI', Roboto, sans-serif; --mono: ui-monospace, Consolas, monospace; font: 18px/145% var(--sans); @@ -30,6 +30,11 @@ } } +@font-face { + font-family: Goudament; + src: url(assets/Goudament.ttf); +} + /* @media (prefers-color-scheme: dark) { :root { --text: #9ca3af; @@ -85,22 +90,27 @@ body { h1, h2 { - font-family: var(--heading); font-weight: 500; - color: var(--text-h); + color: #91a357; + text-shadow: 3px 3px 5px #2c0105 ; + font-family: var(--heading); +} +h3 { + color: var(--text); } h1 { - font-size: 56px; + font-size: 4.5em; letter-spacing: -1.68px; margin: 32px 0; @media (max-width: 1024px) { font-size: 36px; margin: 20px 0; } + font-family: var(--heading); } h2 { - font-size: 24px; + font-size: 4em; line-height: 118%; letter-spacing: -0.24px; margin: 0 0 8px; @@ -108,6 +118,7 @@ h2 { font-size: 20px; } } + p { margin: 0; } @@ -128,33 +139,91 @@ code { } .contentBox { - background-color: rgba(255,255,255,0.6); - width: 1200; - max-width: 95%; + background-color: var(--accent-bg); + /* opacity: 0.8; */ + width: 1000; + max-width: 85%; min-height: 200px; margin: auto; padding: 20px 0; } header { - width: 100%; - background-color: rgba(103, 183, 7, 0.27); + max-width: 100%; + background-color: rgba(0, 0, 0, 0.2); color: #333; height: auto; - padding: 5px 25px; + padding: 5px 20px; margin-bottom: 25px; - display: flex; } header h1 { - font-size: xx-large; - font-family: 'Times New Roman', Times, serif; + font-size: 4em; } +/* Layout stuff */ +.flexboxHorizontal { + display: flex; + gap: 10px; +} + +.modalBackdrop { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; +} +.modalCard { + background: var(--accent-bg); + padding: 50px; + border-radius: 8px; + display: flex; + flex-direction: column; + gap: 12px; + min-width: 300px; +} + +/* Normal components styling */ +button { + transition: transform 0.15s ease-in-out 0s; + padding: 5px 15px; + border-radius: 6px; + color: #f4f3ec; + background-color: var(--accent); + border: transparent 0px solid; + min-height: 3px; + font-size: medium; +} button:hover { + transform: scale(1.05); +} + +input { + transition: transform 0.15s ease-in-out 0s; + padding: 5px 15px; + border-radius: 6px; + color: #f4f3ec; + background-color: #333; + border: transparent 0px solid; + min-height: 25px; +} input:focus { + transform: scale(1.05); +} + +.normalButton { + padding: 5px 15px; + border-radius: 6px; + color: #f4f3ec; + background-color: #455487; + border: transparent 0px solid; +} + /* -- INSTANCE TABLE */ .instancesBlock { - background-color: var(--accent-bg); - max-width: 95%; + background-color: transparent; + max-width: 80%; margin: auto; padding: 15px; } @@ -169,8 +238,8 @@ header h1 { border: var(--accent-border) 0px solid; } .instancesBlock #startStopButton.stop{ - background-color: red; - border: #a40000 0px solid; + background-color: rgb(206, 62, 62); + border: transparent 0px solid; } .instancesBlock table { @@ -179,14 +248,12 @@ header h1 { border-collapse: collapse; text-align: left; } - .instancesBlock th { padding: 10px 12px; font-weight: 600; border-bottom: 2px solid var(--border); color: var(--text-h); } - .instancesBlock td { padding: 10px 12px; border-bottom: 1px solid var(--border); @@ -195,9 +262,6 @@ header h1 { #statusIndicator { padding: 5px 15px; border-radius: 10px; - - /* background-color: var(--accent); */ - /* border: var(--accent-border) 3px solid; */ } #statusIndicator.running { color: var(--accent-border); @@ -205,3 +269,6 @@ header h1 { #statusIndicator.stopped { color: #a40000; } + + +