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.
This commit is contained in:
commit
a1f19e86c6
74 files changed
+2108
-95
No files matched your search
@@ -11,6 +11,7 @@
|
||||
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';
|
||||
@@ -64,6 +65,8 @@
|
||||
<Servers />
|
||||
{:else if route.name === 'capes'}
|
||||
<Capes />
|
||||
{:else if route.name === 'activity'}
|
||||
<Activity />
|
||||
{:else if route.name === 'users'}
|
||||
<Users />
|
||||
{:else if route.name === 'branding'}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
{ id: 'instances', label: 'Instances', icon: Boxes },
|
||||
{ id: 'users', label: 'Players', icon: Users },
|
||||
{ id: 'servers', label: 'Servers', icon: Server },
|
||||
{ id: 'activity', label: 'Activity', icon: LayoutDashboard },
|
||||
{ id: 'capes', label: 'Capes', icon: Flag },
|
||||
{ id: 'branding', label: 'Launcher design', icon: Palette },
|
||||
{ id: 'settings', label: 'Settings', icon: Settings },
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<ol class="steps">
|
||||
<li>
|
||||
<strong>Install the SCOPENET integration</strong>
|
||||
<span class="muted small">The plugin for Paper, Purpur or Spigot, or the mod for Fabric and Forge servers.</span>
|
||||
<span class="muted small">Use scopenet-paper for Paper, Purpur or Spigot. Fabric and Forge need the JAR for the exact Minecraft version: 1.20.1, 1.21.1, 26.1.2, 26.2 or 26.3.</span>
|
||||
<a class="dl" href="https://github.com/scopeddlol/SCOPENET-MC/releases/latest" target="_blank" rel="noreferrer"><Download size={14} /> Download from Releases</a>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { get } from '../lib/api';
|
||||
type Entry = { id: number; source: string; server: string | null; uuid: string | null; name: string | null; kind: string; detail: string | null; created_at: string };
|
||||
let rows = $state<Entry[]>([]);
|
||||
let source = $state('');
|
||||
let player = $state('');
|
||||
let offset = $state(0);
|
||||
let busy = $state(false);
|
||||
let error = $state('');
|
||||
async function load(reset = false) {
|
||||
if (reset) offset = 0;
|
||||
busy = true; error = '';
|
||||
try { rows = await get<Entry[]>('/api/admin/activity?' + new URLSearchParams({ source, player: player.trim(), offset: String(offset) })); }
|
||||
catch (e) { error = String(e); }
|
||||
finally { busy = false; }
|
||||
}
|
||||
onMount(() => { void load(); });
|
||||
</script>
|
||||
|
||||
<div class="page">
|
||||
<header><h1>Activity</h1><p class="muted">Server gameplay, launcher activity and account changes. Players keep the same UUID when renamed.</p></header>
|
||||
<form class="filters" onsubmit={(e) => { e.preventDefault(); load(true); }}>
|
||||
<label>Source<select bind:value={source}><option value="">All sources</option><option value="server">Servers</option><option value="launcher">Launcher</option><option value="auth">Sign-ins</option><option value="panel">Account & admin changes</option></select></label>
|
||||
<label>Player<input bind:value={player} placeholder="Exact username or permanent UUID" /></label>
|
||||
<button disabled={busy}>Refresh</button>
|
||||
</form>
|
||||
<p class="muted small">Chat counts only. Command arguments and passwords are never included. Repeated gameplay actions are grouped by reporting interval.</p>
|
||||
{#if error}<p role="alert">{error}</p>{/if}
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>Time</th><th>Source</th><th>Player</th><th>Action</th><th>Details</th></tr></thead>
|
||||
<tbody>
|
||||
{#each rows as row (row.source + ':' + row.id)}
|
||||
<tr><td>{new Date(row.created_at).toLocaleString()}</td><td>{row.server ?? row.source}</td><td title={row.uuid ?? ''}>{row.name ?? 'Server'}</td><td>{row.kind.replaceAll('_', ' ')}</td><td>{row.detail ?? '—'}</td></tr>
|
||||
{:else}<tr><td colspan="5">{busy ? 'Loading…' : 'No matching activity yet.'}</td></tr>{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="filters"><button class="ghost" disabled={busy || offset === 0} onclick={() => { offset = Math.max(0, offset - 100); load(); }}>Newer</button><span class="muted">Page {offset / 100 + 1}</span><button class="ghost" disabled={busy || rows.length < 100} onclick={() => { offset += 100; load(); }}>Older</button></div>
|
||||
</div>
|
||||
<style>
|
||||
.page { padding: 32px; max-width: 1500px; margin: auto; }
|
||||
header { margin-bottom: 24px; }
|
||||
.filters { display: flex; gap: 16px; align-items: end; margin: 20px 0; flex-wrap: wrap; }
|
||||
label { display: flex; flex-direction: column; gap: 6px; }
|
||||
input { min-width: 300px; }
|
||||
.table-wrap { overflow-x: auto; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 12px; border-bottom: 1px solid var(--line); text-align: left; }
|
||||
td:last-child { overflow-wrap: anywhere; }
|
||||
</style>
|
||||
Reference in new issue
Block a user