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.
93 lines
3.1 KiB
Svelte
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>
|