Change theme

This commit is contained in:
2026-06-05 23:13:58 -05:00
parent 8d211da476
commit 52670731d9
3 changed files with 56 additions and 64 deletions

View File

@@ -8,7 +8,6 @@
let connectionError = null;
let processingTargets = {};
// Selection router state tracker
let activeFocusedInstance = null;
async function syncTelemetry() {
@@ -16,7 +15,6 @@
instances = await fetchInstances();
connectionError = null;
// Keep our detailed view object synced with fresh status polling adjustments
if (activeFocusedInstance) {
const freshSnapshot = instances.find(i => i.name === activeFocusedInstance.name);
if (freshSnapshot) activeFocusedInstance = freshSnapshot;
@@ -47,24 +45,25 @@
});
</script>
<main class="min-h-screen bg-neutral-950 text-neutral-100 font-mono p-6 select-none">
<header class="border-b border-neutral-800 pb-4 mb-6 flex justify-between items-center">
<main class="min-h-screen bg-stone-900 bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-emerald-950 to-stone-950 text-stone-100 p-6 select-none">
<header class="border-b border-stone-700/60 pb-4 mb-6 flex justify-between items-center">
<div>
<h1 class="text-lg font-bold tracking-tight text-amber-500">-- VSSM DASHBOARD --</h1>
<p class="text-xs text-neutral-400 mt-1">VintageStory Server Manager</p>
<h1 class="text-xl font-serif font-bold tracking-wide text-amber-100/90">VSSM Dashboard</h1>
<p class="text-xs text-stone-400 mt-1">VintageStory Server Manager</p>
</div>
{#if !activeFocusedInstance}
<button
on:click={syncTelemetry}
class="border border-neutral-700 hover:border-neutral-500 hover:text-amber-400 px-3 py-1.5 text-xs uppercase tracking-wider transition-colors cursor-pointer">
Force Sync
class="border border-stone-600 bg-stone-800/60 hover:bg-stone-700 hover:text-amber-100 px-4 py-1.5 text-xs uppercase tracking-wider transition-colors cursor-pointer rounded shadow-sm">
Refresh Status
</button>
{/if}
</header>
{#if connectionError}
<div class="bg-red-950/40 border border-red-900/60 text-red-400 p-3 text-xs mb-6 rounded flex items-center gap-2">
<span class="w-1.5 h-1.5 rounded-full bg-red-500 animate-pulse"></span>
<div class="bg-red-900/20 border border-red-700/40 text-red-200 p-3 text-xs mb-6 rounded flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-red-500"></span>
{connectionError}
</div>
{/if}

View File

@@ -10,7 +10,6 @@
let isSending = false;
let terminalContainer;
function logsAreEqual(arr1, arr2) {
if (arr1.length !== arr2.length) return false;
for (let i = 0; i < arr1.length; i++) {
@@ -19,11 +18,9 @@
return true;
}
async function syncLogs() {
try {
const logs = await fetchServerLogs(instance.name);
if (!logsAreEqual(logs, terminalLogs)) {
terminalLogs = logs;
autoScrollToBottom();
@@ -33,7 +30,6 @@
}
}
function autoScrollToBottom() {
if (terminalContainer) {
setTimeout(() => {
@@ -62,9 +58,7 @@
onMount(() => {
syncLogs();
// Check for new logs every second
const logInterval = setInterval(syncLogs, 1000);
return () => clearInterval(logInterval);
});
</script>
@@ -73,70 +67,70 @@
<div class="flex items-center gap-4">
<button
on:click={onBack}
class="border border-neutral-700 hover:border-neutral-500 text-neutral-300 px-3 py-1 text-xs uppercase transition-colors cursor-pointer">
class="border border-stone-600 bg-stone-800/60 hover:bg-stone-700 text-stone-200 px-3 py-1 text-xs uppercase transition-colors cursor-pointer rounded shadow-sm">
&larr; Back to Server List
</button>
<div class="h-4 w-px bg-neutral-800"></div>
<div class="text-sm">
Server: <span class="text-amber-500 font-bold">{instance.name}</span>
<span class="text-xs text-neutral-500">({instance.version})</span>
<div class="h-4 w-px bg-stone-700"></div>
<div class="text-sm text-stone-300">
Active Server: <span class="text-amber-200 font-bold">{instance.name}</span>
<span class="text-xs text-stone-400">({instance.version})</span>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2 flex flex-col h-[450px] border border-neutral-800 bg-neutral-900 rounded overflow-hidden">
<div class="bg-neutral-950 border-b border-neutral-800 px-4 py-2 text-xs flex justify-between items-center select-none">
<span class="text-neutral-400 font-bold">Console Output</span>
<span class="text-[10px] {instance.status === 'RUNNING' ? 'text-emerald-400' : 'text-neutral-500'}">
{instance.status}
<div class="lg:col-span-2 flex flex-col h-[450px] border border-stone-700/60 bg-stone-900/60 rounded shadow-md overflow-hidden">
<div class="bg-stone-800/80 border-b border-stone-700/60 px-4 py-2 text-xs flex justify-between items-center select-none text-stone-300">
<span class="font-medium">Live Server Console</span>
<span class="text-xs font-medium px-1.5 py-0.5 rounded {instance.status === 'RUNNING' ? 'text-emerald-300 bg-emerald-900/30' : 'text-stone-400 bg-stone-800'}">
{instance.status}
</span>
</div>
<div
bind:this={terminalContainer}
class="flex-1 p-4 overflow-y-auto text-xs space-y-1 text-neutral-300 font-mono select-text selection:bg-neutral-800">
class="flex-1 p-4 overflow-y-auto text-xs space-y-1 bg-stone-950/40 text-stone-200 font-mono select-text selection:bg-stone-700">
{#if terminalLogs.length === 0}
<div class="text-neutral-600 italic">[No console logs available.]</div>
<div class="text-stone-500 italic">[Waiting for log output...]</div>
{:else}
{#each terminalLogs as line}
<div class="whitespace-pre-wrap break-all leading-relaxed">{line}</div>
<div class="whitespace-pre-wrap break-all leading-relaxed opacity-90">{line}</div>
{/each}
{/if}
</div>
<form
on:submit|preventDefault={executeConsoleSubmit}
class="border-t border-neutral-800 bg-neutral-950 flex items-center">
<span class="pl-4 text-neutral-600 text-xs select-none">&gt;</span>
class="border-t border-stone-700/60 bg-stone-800/40 flex items-center">
<span class="pl-4 text-stone-400 text-xs select-none">&gt;</span>
<input
type="text"
bind:value={textCommandBuffer}
disabled={instance.status !== 'RUNNING' || isSending}
placeholder={instance.status === 'RUNNING' ? "Type a server command..." : "Server is offline."}
class="w-full bg-transparent px-2 py-3 text-xs text-neutral-200 placeholder-neutral-600 focus:outline-none disabled:cursor-not-allowed" />
placeholder={instance.status === 'RUNNING' ? "Type a command and press Enter..." : "Console is read-only while server is offline."}
class="w-full bg-transparent px-3 py-3 text-xs text-stone-100 placeholder-stone-500 focus:outline-none disabled:cursor-not-allowed" />
</form>
</div>
<div class="border border-neutral-800 bg-neutral-900/10 rounded p-4 space-y-4">
<h3 class="text-xs font-bold uppercase tracking-wider text-neutral-400 border-b border-neutral-800 pb-2">
Server Operations
<div class="border border-stone-700/60 bg-stone-900/40 rounded p-4 space-y-4 shadow-md backdrop-blur-sm">
<h3 class="text-xs font-bold uppercase tracking-wider text-stone-300 border-b border-stone-700/60 pb-2">
Operations
</h3>
<div class="opacity-50 space-y-3 pointer-events-none">
<button class="w-full text-left border border-neutral-800 bg-neutral-900/50 text-xs p-3 hover:border-neutral-700 transition-all rounded">
<div class="font-bold text-neutral-200">Configure</div>
<div class="text-[10px] text-neutral-400 mt-0.5">Edit instance configuration.</div>
<div class="space-y-3">
<button class="w-full text-left border border-stone-600 bg-stone-800/50 text-xs p-3 hover:bg-stone-800 transition-all rounded shadow-sm group">
<div class="font-bold text-stone-200 group-hover:text-amber-200">Configure Server</div>
<div class="text-[10px] text-stone-400 mt-0.5">Edit operational settings and ports.</div>
</button>
<button class="w-full text-left border border-neutral-800 bg-neutral-900/50 text-xs p-3 hover:border-neutral-700 transition-all rounded">
<div class="font-bold text-neutral-200">Trigger Backup</div>
<div class="text-[10px] text-neutral-400 mt-0.5">Create a backup of the instance.</div>
<button class="w-full text-left border border-stone-600 bg-stone-800/50 text-xs p-3 hover:bg-stone-800 transition-all rounded shadow-sm group">
<div class="font-bold text-stone-200 group-hover:text-amber-200">Trigger Backup</div>
<div class="text-[10px] text-stone-400 mt-0.5">Save a zip archive of current world data.</div>
</button>
<button class="w-full text-left border border-red-950 bg-red-950/10 text-xs p-3 rounded">
<div class="font-bold text-red-400">Delete Instance</div>
<div class="text-[10px] text-red-500/70 mt-0.5">Remove server instance and optionally delete its files.</div>
<button class="w-full text-left border border-amber-900/40 bg-amber-950/10 text-xs p-3 rounded shadow-sm hover:bg-amber-950/20 group">
<div class="font-bold text-amber-300">Delete Instance</div>
<div class="text-[10px] text-amber-400/70 mt-0.5">Remove files and configs permanently from disk.</div>
</button>
</div>
</div>

View File

@@ -1,41 +1,40 @@
<script>
// Props received from parent context
export let instances = [];
export let processingTargets = {};
export let onPowerToggle;
export let onSelectInstance;
</script>
<div class="border border-neutral-800 bg-neutral-900/20 rounded overflow-hidden">
<div class="border border-stone-700/60 bg-stone-900/60 rounded backdrop-blur-sm shadow-md overflow-hidden">
<table class="w-full text-left border-collapse">
<thead>
<tr class="border-b border-neutral-800 bg-neutral-900/80 text-xs text-neutral-400 uppercase">
<tr class="border-b border-stone-700/60 bg-stone-800/50 text-xs text-stone-300 uppercase tracking-wider">
<th class="p-4 font-medium">Instance ID</th>
<th class="p-4 font-medium">Engine Version</th>
<th class="p-4 font-medium">Network Port</th>
<th class="p-4 font-medium">Runtime Status</th>
<th class="p-4 font-medium text-right">Quick Controls</th>
<th class="p-4 font-medium text-right">Controls</th>
</tr>
</thead>
<tbody class="divide-y divide-neutral-800/40 text-sm">
<tbody class="divide-y divide-stone-800/60 text-sm">
{#if instances.length === 0}
<tr>
<td colspan="5" class="p-8 text-center text-neutral-500 text-xs">
No managed target nodes registered with daemon configuration profile.
<td colspan="5" class="p-8 text-center text-stone-400 text-xs italic">
No server instances found in your configuration.
</td>
</tr>
{:else}
{#each instances as inst}
<tr
on:click={() => onSelectInstance(inst)}
class="hover:bg-neutral-900/40 transition-colors cursor-pointer">
<td class="p-4 font-bold tracking-wide text-amber-500 hover:underline">{inst.name}</td>
<td class="p-4 text-neutral-400">{inst.version}</td>
<td class="p-4 text-neutral-400 font-mono text-xs">{inst.port}</td>
class="hover:bg-stone-800/40 transition-colors cursor-pointer">
<td class="p-4 font-bold text-amber-200 hover:underline">{inst.name}</td>
<td class="p-4 text-stone-300">{inst.version}</td>
<td class="p-4 text-stone-300 font-mono text-xs">{inst.port}</td>
<td class="p-4">
<span class="inline-flex items-center gap-1.5 text-xs font-semibold px-2 py-0.5 rounded-sm
{inst.status === 'RUNNING' ? 'bg-emerald-950/40 text-emerald-400 border border-emerald-900/30' : 'bg-neutral-800 text-neutral-400'}">
<span class="w-1.5 h-1.5 rounded-full {inst.status === 'RUNNING' ? 'bg-emerald-400 animate-pulse' : 'bg-neutral-500'}"></span>
<span class="inline-flex items-center gap-1.5 text-xs font-medium px-2.5 py-0.5 rounded
{inst.status === 'RUNNING' ? 'bg-emerald-900/40 text-emerald-200 border border-emerald-800/40' : 'bg-stone-800 text-stone-400 border border-stone-700/40'}">
<span class="w-1.5 h-1.5 rounded-full {inst.status === 'RUNNING' ? 'bg-emerald-400' : 'bg-stone-500'}"></span>
{inst.status}
</span>
</td>
@@ -43,11 +42,11 @@
<button
disabled={processingTargets[inst.name]}
on:click={() => onPowerToggle(inst.name, inst.status)}
class="w-24 text-center border text-xs uppercase font-bold py-1 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed
class="w-24 text-center border text-xs font-bold py-1 rounded transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed shadow-sm
{inst.status === 'RUNNING'
? 'border-red-900/60 bg-red-950/20 text-red-400 hover:bg-red-900/40'
: 'border-emerald-900/60 bg-emerald-950/20 text-emerald-400 hover:bg-emerald-900/40'}">
{processingTargets[inst.name] ? 'PENDING...' : inst.status === 'RUNNING' ? 'STOP' : 'START'}
? 'border-amber-900/50 bg-amber-950/20 text-amber-300 hover:bg-amber-900/30'
: 'border-emerald-800/60 bg-emerald-900/20 text-emerald-200 hover:bg-emerald-800/40'}">
{processingTargets[inst.name] ? '...' : inst.status === 'RUNNING' ? 'STOP' : 'START'}
</button>
</td>
</tr>