Populated config screen with a basic editor
This commit is contained in:
@@ -12,6 +12,9 @@ class api {
|
||||
|
||||
} export default api;
|
||||
|
||||
|
||||
// --- GET CALLS ---
|
||||
|
||||
export async function fetchInstanceList(): Promise<Instance[]> {
|
||||
const res = await fetch(`${ADDRESS}/instances/list`);
|
||||
if (!res.ok) {
|
||||
@@ -30,13 +33,24 @@ export async function fetchInstanceLogs(name: string): Promise<string[]> {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function fetchInstanceConfig(name: string): Promise<string> {
|
||||
const res = await fetch(`${ADDRESS}/instances/getConfig?name=${name}`);
|
||||
if (!res.ok) {
|
||||
const message = await res.text();
|
||||
throw new Error(`Could not fetch instance config: ${message.trim()}`);
|
||||
}
|
||||
return res.text();
|
||||
}
|
||||
|
||||
// --- POST CALLS ---
|
||||
|
||||
export async function sendStartInstanceRequest(name: string) {
|
||||
const res = await fetch(`${ADDRESS}/instances/start?name=${name}`, { method: "POST" });
|
||||
if (!res.ok) {
|
||||
const message = await res.text();
|
||||
throw new Error(`Start Instance Request failed: ${message.trim()}`);
|
||||
}
|
||||
return res.json;
|
||||
return res.text();
|
||||
}
|
||||
|
||||
export async function sendStopInstanceRequest(name: string) {
|
||||
@@ -45,7 +59,7 @@ export async function sendStopInstanceRequest(name: string) {
|
||||
const message = await res.text();
|
||||
throw new Error(`Stop Instance Request failed: ${message.trim()}`);
|
||||
}
|
||||
return res.json;
|
||||
return res.text();
|
||||
}
|
||||
|
||||
export async function sendCreateInstanceRequest(name: string, version: string, port: number) {
|
||||
@@ -54,7 +68,7 @@ export async function sendCreateInstanceRequest(name: string, version: string, p
|
||||
const message = await res.text();
|
||||
throw new Error(`Create instance request failed: ${message.trim()}`);
|
||||
}
|
||||
return res.json;
|
||||
return res.text();
|
||||
}
|
||||
|
||||
export async function sendDeleteInstanceRequest(name: string, purge: boolean) {
|
||||
@@ -63,7 +77,7 @@ export async function sendDeleteInstanceRequest(name: string, purge: boolean) {
|
||||
const message = await res.text();
|
||||
throw new Error(`Delete instance request failed: ${message.trim()}`);
|
||||
}
|
||||
return res.json;
|
||||
return res.text();
|
||||
}
|
||||
|
||||
export async function sendModifyInstanceRequest(name: string, newName: string, newPort: number) {
|
||||
@@ -72,7 +86,7 @@ export async function sendModifyInstanceRequest(name: string, newName: string, n
|
||||
const message = await res.text();
|
||||
throw new Error(`Modify instance request failed: ${message.trim()}`);
|
||||
}
|
||||
return res.json;
|
||||
return res.text();
|
||||
}
|
||||
|
||||
export async function sendCommandRequest(name: string, payload: string) {
|
||||
@@ -85,5 +99,18 @@ export async function sendCommandRequest(name: string, payload: string) {
|
||||
const message = await res.text();
|
||||
throw new Error(`Send Instance Command failed: ${message.trim()}`);
|
||||
}
|
||||
return res.json;
|
||||
return res.text();
|
||||
}
|
||||
|
||||
export async function sendSetInstanceConfigRequest(name: string, config: string) {
|
||||
const res = await fetch(`${ADDRESS}/instances/setConfig?name=${name}`, {
|
||||
method: "POST",
|
||||
body: config,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
if (!res.ok) {
|
||||
const message = await res.text();
|
||||
throw new Error(`Set instance config request failed: ${message.trim()}`);
|
||||
}
|
||||
return res.text();
|
||||
}
|
||||
50
vssm_web/vssm_web/src/components/InstanceConfig.tsx
Normal file
50
vssm_web/vssm_web/src/components/InstanceConfig.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import type { Instance } from "../types";
|
||||
import { fetchInstanceConfig, sendSetInstanceConfigRequest } from "../api";
|
||||
|
||||
type Props = {
|
||||
instance: Instance;
|
||||
};
|
||||
|
||||
export default function InstanceConfig({ instance }: Props) {
|
||||
const [config, setConfig] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchInstanceConfig(instance.name)
|
||||
.then(setConfig)
|
||||
.catch(e => setError(e.message ?? String(e)))
|
||||
.finally(() => setLoading(false));
|
||||
}, [instance.name]);
|
||||
|
||||
async function handleSave() {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
await sendSetInstanceConfigRequest(instance.name, config);
|
||||
} catch (e: any) {
|
||||
setError(e.message ?? String(e));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) return <p>Loading config...</p>;
|
||||
|
||||
return (
|
||||
<div className="configEditor">
|
||||
{error && <div className="error">Error: {error}</div>}
|
||||
<textarea
|
||||
value={config}
|
||||
onChange={e => setConfig(e.target.value)}
|
||||
rows={25}
|
||||
spellCheck={false}
|
||||
/>
|
||||
<button disabled={saving} onClick={handleSave}>
|
||||
{saving ? "Saving..." : "Save"}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useState } from "react";
|
||||
import type { Instance } from "../types";
|
||||
import { sendDeleteInstanceRequest, sendModifyInstanceRequest } from "../api";
|
||||
import InstanceConsole from "./InstanceConsole";
|
||||
import InstanceConfig from "./InstanceConfig";
|
||||
|
||||
type Props = {
|
||||
instance: Instance;
|
||||
@@ -58,8 +59,8 @@ export default function InstanceDetail({ instance, onClose, onDeleted, onSaved }
|
||||
|
||||
<div className="tabBar">
|
||||
<button onClick={() => setActiveTab("general")}>General</button>
|
||||
<button onClick={() => setActiveTab("config")}>Configuration</button>
|
||||
<button onClick={() => setActiveTab("console")}>Console</button>
|
||||
<button onClick={() => setActiveTab("config")}>Configuration</button>
|
||||
<button onClick={() => setActiveTab("danger")}>Danger Zone</button>
|
||||
</div>
|
||||
|
||||
@@ -83,10 +84,10 @@ export default function InstanceDetail({ instance, onClose, onDeleted, onSaved }
|
||||
</div>
|
||||
}
|
||||
|
||||
{activeTab === "config" && <p>Config (coming soon)</p>}
|
||||
|
||||
{activeTab === "console" && <InstanceConsole instance={instance} />}
|
||||
|
||||
{activeTab === "config" && <InstanceConfig instance={instance}/>}
|
||||
|
||||
{activeTab === "danger" &&
|
||||
<div className="dangerZone">
|
||||
<h3>Danger Zone</h3>
|
||||
|
||||
@@ -442,4 +442,26 @@ input[type="checkbox"] {
|
||||
.consoleLine i {
|
||||
color: #a0a0c0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Config editor styles */
|
||||
.configEditor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.configEditor textarea {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.85em;
|
||||
background-color: #1a1a1a;
|
||||
color: #d4d4d4;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
border: transparent 0px solid;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.configEditor button {
|
||||
max-width: fit-content;
|
||||
}
|
||||
Reference in New Issue
Block a user