Release version 1.0.0 #4
@@ -12,6 +12,9 @@ class api {
|
|||||||
|
|
||||||
} export default api;
|
} export default api;
|
||||||
|
|
||||||
|
|
||||||
|
// --- GET CALLS ---
|
||||||
|
|
||||||
export async function fetchInstanceList(): Promise<Instance[]> {
|
export async function fetchInstanceList(): Promise<Instance[]> {
|
||||||
const res = await fetch(`${ADDRESS}/instances/list`);
|
const res = await fetch(`${ADDRESS}/instances/list`);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@@ -30,13 +33,24 @@ export async function fetchInstanceLogs(name: string): Promise<string[]> {
|
|||||||
return res.json();
|
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) {
|
export async function sendStartInstanceRequest(name: string) {
|
||||||
const res = await fetch(`${ADDRESS}/instances/start?name=${name}`, { method: "POST" });
|
const res = await fetch(`${ADDRESS}/instances/start?name=${name}`, { method: "POST" });
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const message = await res.text();
|
const message = await res.text();
|
||||||
throw new Error(`Start Instance Request failed: ${message.trim()}`);
|
throw new Error(`Start Instance Request failed: ${message.trim()}`);
|
||||||
}
|
}
|
||||||
return res.json;
|
return res.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendStopInstanceRequest(name: string) {
|
export async function sendStopInstanceRequest(name: string) {
|
||||||
@@ -45,7 +59,7 @@ export async function sendStopInstanceRequest(name: string) {
|
|||||||
const message = await res.text();
|
const message = await res.text();
|
||||||
throw new Error(`Stop Instance Request failed: ${message.trim()}`);
|
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) {
|
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();
|
const message = await res.text();
|
||||||
throw new Error(`Create instance request failed: ${message.trim()}`);
|
throw new Error(`Create instance request failed: ${message.trim()}`);
|
||||||
}
|
}
|
||||||
return res.json;
|
return res.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendDeleteInstanceRequest(name: string, purge: boolean) {
|
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();
|
const message = await res.text();
|
||||||
throw new Error(`Delete instance request failed: ${message.trim()}`);
|
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) {
|
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();
|
const message = await res.text();
|
||||||
throw new Error(`Modify instance request failed: ${message.trim()}`);
|
throw new Error(`Modify instance request failed: ${message.trim()}`);
|
||||||
}
|
}
|
||||||
return res.json;
|
return res.text();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendCommandRequest(name: string, payload: string) {
|
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();
|
const message = await res.text();
|
||||||
throw new Error(`Send Instance Command failed: ${message.trim()}`);
|
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 type { Instance } from "../types";
|
||||||
import { sendDeleteInstanceRequest, sendModifyInstanceRequest } from "../api";
|
import { sendDeleteInstanceRequest, sendModifyInstanceRequest } from "../api";
|
||||||
import InstanceConsole from "./InstanceConsole";
|
import InstanceConsole from "./InstanceConsole";
|
||||||
|
import InstanceConfig from "./InstanceConfig";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
instance: Instance;
|
instance: Instance;
|
||||||
@@ -58,8 +59,8 @@ export default function InstanceDetail({ instance, onClose, onDeleted, onSaved }
|
|||||||
|
|
||||||
<div className="tabBar">
|
<div className="tabBar">
|
||||||
<button onClick={() => setActiveTab("general")}>General</button>
|
<button onClick={() => setActiveTab("general")}>General</button>
|
||||||
<button onClick={() => setActiveTab("config")}>Configuration</button>
|
|
||||||
<button onClick={() => setActiveTab("console")}>Console</button>
|
<button onClick={() => setActiveTab("console")}>Console</button>
|
||||||
|
<button onClick={() => setActiveTab("config")}>Configuration</button>
|
||||||
<button onClick={() => setActiveTab("danger")}>Danger Zone</button>
|
<button onClick={() => setActiveTab("danger")}>Danger Zone</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -83,10 +84,10 @@ export default function InstanceDetail({ instance, onClose, onDeleted, onSaved }
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
{activeTab === "config" && <p>Config (coming soon)</p>}
|
|
||||||
|
|
||||||
{activeTab === "console" && <InstanceConsole instance={instance} />}
|
{activeTab === "console" && <InstanceConsole instance={instance} />}
|
||||||
|
|
||||||
|
{activeTab === "config" && <InstanceConfig instance={instance}/>}
|
||||||
|
|
||||||
{activeTab === "danger" &&
|
{activeTab === "danger" &&
|
||||||
<div className="dangerZone">
|
<div className="dangerZone">
|
||||||
<h3>Danger Zone</h3>
|
<h3>Danger Zone</h3>
|
||||||
|
|||||||
@@ -443,3 +443,25 @@ input[type="checkbox"] {
|
|||||||
color: #a0a0c0;
|
color: #a0a0c0;
|
||||||
font-style: italic;
|
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