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 connectionError = null;
let processingTargets = {}; let processingTargets = {};
// Selection router state tracker
let activeFocusedInstance = null; let activeFocusedInstance = null;
async function syncTelemetry() { async function syncTelemetry() {
@@ -16,7 +15,6 @@
instances = await fetchInstances(); instances = await fetchInstances();
connectionError = null; connectionError = null;
// Keep our detailed view object synced with fresh status polling adjustments
if (activeFocusedInstance) { if (activeFocusedInstance) {
const freshSnapshot = instances.find(i => i.name === activeFocusedInstance.name); const freshSnapshot = instances.find(i => i.name === activeFocusedInstance.name);
if (freshSnapshot) activeFocusedInstance = freshSnapshot; if (freshSnapshot) activeFocusedInstance = freshSnapshot;
@@ -47,24 +45,25 @@
}); });
</script> </script>
<main class="min-h-screen bg-neutral-950 text-neutral-100 font-mono p-6 select-none"> <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-neutral-800 pb-4 mb-6 flex justify-between items-center">
<header class="border-b border-stone-700/60 pb-4 mb-6 flex justify-between items-center">
<div> <div>
<h1 class="text-lg font-bold tracking-tight text-amber-500">-- VSSM DASHBOARD --</h1> <h1 class="text-xl font-serif font-bold tracking-wide text-amber-100/90">VSSM Dashboard</h1>
<p class="text-xs text-neutral-400 mt-1">VintageStory Server Manager</p> <p class="text-xs text-stone-400 mt-1">VintageStory Server Manager</p>
</div> </div>
{#if !activeFocusedInstance} {#if !activeFocusedInstance}
<button <button
on:click={syncTelemetry} 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"> 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">
Force Sync Refresh Status
</button> </button>
{/if} {/if}
</header> </header>
{#if connectionError} {#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"> <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-1.5 h-1.5 rounded-full bg-red-500 animate-pulse"></span> <span class="w-2 h-2 rounded-full bg-red-500"></span>
{connectionError} {connectionError}
</div> </div>
{/if} {/if}

View File

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

View File

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