Files
SCOPENET-MC/panel/web/src/App.svelte
T
SCOPEDD a1f19e86c6 Complete private authentication, server integrations and activity reporting
Add Fabric/Forge version builds and Paper integration, preserve permanent player identities across renames, harden session authorization, and surface privacy-conscious launcher/server activity in the panel.
2026-09-28 13:31:17 -04:00

93 lines
3.1 KiB
Svelte

<script lang="ts">
import Sidebar from './components/Sidebar.svelte';
import Toasts from './components/Toasts.svelte';
import Login from './pages/Login.svelte';
import Dashboard from './pages/Dashboard.svelte';
import Instances from './pages/Instances.svelte';
import InstanceEditor from './pages/InstanceEditor.svelte';
import Users from './pages/Users.svelte';
import Branding from './pages/Branding.svelte';
import Settings from './pages/Settings.svelte';
import Servers from './pages/Servers.svelte';
import ServerDetail from './pages/ServerDetail.svelte';
import Capes from './pages/Capes.svelte';
import Activity from './pages/Activity.svelte';
import { get } from './lib/api';
import { route } from './lib/router.svelte';
import { logout, session, type Me } from './lib/session.svelte';
import { toast } from './lib/toast.svelte';
let brand = $state<{ name: string; logo_url: string | null }>({ name: 'SCOPENET', logo_url: null });
let checking = $state(!!session.token);
$effect(() => {
if (!session.token) {
checking = false;
return;
}
get<Me>('/api/v1/auth/me')
.then((me) => {
if (me.role !== 'admin') {
toast('That account is not an admin.', 'error');
logout();
} else session.user = me;
})
.catch(() => logout())
.finally(() => (checking = false));
});
$effect(() => {
get('/api/v1/launcher/manifest')
.then((m) => {
brand = m.branding;
document.title = `${m.branding.name} · Admin`;
})
.catch(() => {});
});
</script>
{#if checking}
<div class="boot"><span class="pulse"></span></div>
{:else if !session.user}
<Login brandName={brand.name} logo={brand.logo_url} />
{:else}
<div class="shell">
<Sidebar brandName={brand.name} logo={brand.logo_url} />
<main>
{#key route.name + route.params.join('/')}
{#if route.name === 'instances' && route.params[0]}
<InstanceEditor id={route.params[0]} />
{:else if route.name === 'instances'}
<Instances />
{:else if route.name === 'servers' && route.params[0]}
<ServerDetail id={route.params[0]} />
{:else if route.name === 'servers'}
<Servers />
{:else if route.name === 'capes'}
<Capes />
{:else if route.name === 'activity'}
<Activity />
{:else if route.name === 'users'}
<Users />
{:else if route.name === 'branding'}
<Branding onsaved={(b) => (brand = b)} />
{:else if route.name === 'settings'}
<Settings />
{:else}
<Dashboard />
{/if}
{/key}
</main>
</div>
{/if}
<Toasts />
<style>
.shell { display: flex; min-height: 100vh; }
main { flex: 1; min-width: 0; }
.boot { height: 100vh; display: grid; place-items: center; }
.pulse { width: 28px; height: 28px; border-radius: 50%; border: 3px solid var(--surface-3); border-top-color: var(--accent); animation: spin 0.8s linear infinite; }
@keyframes pulse { from { transform: scale(0.85); opacity: 0.6; } to { transform: scale(1); opacity: 1; } }
</style>