Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin Dashboard #17

Merged
merged 10 commits into from
Dec 8, 2024
137 changes: 117 additions & 20 deletions src/routes/admin/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
<script lang="ts">
import { io } from 'socket.io-client';
import type { TeamMatch } from '$lib/types';
import { io, Socket } from 'socket.io-client';

const socket = io({
let scout_queue: string[] = $state([]);
let robot_queue: string[] = $state([]);
let submitted_team_matches: TeamMatch[] = $state([]);

let socket: Socket = io({
auth: {
token: 'celary'
token: 'celary',
username: 'admin'
}
});

socket.on('robot_joined_queue', (robots: string[]) => {
robots.forEach((robot) => robot_queue.push(robot));
});

socket.on('robot_left_queue', (robot: string) => {
const index = robot_queue.indexOf(robot);
if (index === -1) return;

robot_queue.splice(index, 1);
});

socket.on('scout_joined_queue', (scout: string) => {
scout_queue.push(scout);
});

socket.on('scout_left_queue', (scout: string) => {
const index = scout_queue.indexOf(scout);
if (index === -1) return;

scout_queue.splice(index, 1);
});

socket.on('new_team_match', (team_match: TeamMatch) => {
submitted_team_matches.push(team_match);
});

let match_key: string = $state('');
const colors = ['red', 'red', 'red', 'blue', 'blue', 'blue'] as const;
let teams: string[] = $state(['', '', '', '', '', '']);
Expand All @@ -15,31 +47,96 @@
);

const queue_match = () => {
teams = teams.filter((team) => team != '');
socket.emit('send_match', [match_key, team_color]);
match_key = '';
teams = ['', '', '', '', '', ''];
};

const remove_scout = (scout_id: string) => {
const index = scout_queue.indexOf(scout_id);
if (index === -1) return;

scout_queue.splice(index, 1);

socket.emit('leave_scout_queue', scout_id);
};

const remove_robot = (robot: string) => {
const index = robot_queue.indexOf(robot);
if (index === -1) return;

robot_queue.splice(index, 1);

socket.emit('leave_robot_queue', robot);
};

const remove_submission = (team_match: TeamMatch) => {
const index = submitted_team_matches.indexOf(team_match);
if (index === -1) return;

submitted_team_matches.splice(index, 1);

socket.emit('remove_team_match', team_match);
};
</script>

<div class="m-4 grid place-items-center gap-4 text-white">
<input
bind:value={match_key}
placeholder="Match Key"
type="text"
class="rounded p-2 text-black"
/>

<div class="grid grid-flow-col grid-rows-4 gap-2">
<div class="grid grid-cols-2 grid-rows-1 gap-4 text-xl">
<h1>Red</h1>
<h1>Blue</h1>
<div class="grid grid-cols-2 grid-rows-2 gap-6">
<div class="m-4 grid place-items-center gap-4 text-white">
<input
bind:value={match_key}
placeholder="Match Key"
type="text"
class="rounded p-2 text-black"
/>

<div class="grid grid-flow-col grid-rows-4 gap-2">
<div class="grid grid-cols-2 grid-rows-1 gap-4 text-xl">
<h1>Red</h1>
<h1>Blue</h1>
</div>
<div class="row-span-3 grid grid-flow-col grid-cols-2 grid-rows-3 gap-4 text-black">
{#each teams as _, i}
<input bind:value={teams[i]} type="text" class="rounded p-1" />
{/each}
</div>
</div>
<div class="row-span-3 grid grid-flow-col grid-cols-2 grid-rows-3 gap-4 text-black">
{#each teams as _, i}
<input bind:value={teams[i]} type="text" class="rounded p-1" />

<button onclick={queue_match} class="rounded border p-2">Queue Match</button>
</div>
<div class="mt-4 grid grid-cols-1 grid-rows-10 place-items-center gap-4 text-xl">
<h1 class="row-span-2">Queued Scouts</h1>
<div class="row-span-8 grid grid-cols-1 grid-rows-8 gap-2">
{#each scout_queue as scout}
<button onclick={() => remove_scout(scout)} class="rounded border p-2 text-center">
{scout}
</button>
{/each}
</div>
</div>
<div class="mt-4 grid grid-cols-1 grid-rows-10 place-items-center gap-4 text-xl">
<h1 class="row-span-2">Queued Robots</h1>
<div class="row-span-8 grid grid-cols-1 grid-rows-8 gap-2">
{#each robot_queue as robot}
<button onclick={() => remove_robot(robot)} class="rounded border p-2 text-center">
{robot}
</button>
{/each}
</div>
</div>
<div class="mt-4 grid grid-cols-1 grid-rows-10 place-items-center gap-4 text-xl">
<h1 class="row-span-2">Team-Match Submissions</h1>
<div class="row-span-8 grid grid-cols-1 grid-rows-8 gap-2">
{#each submitted_team_matches as team_match}
<button
onclick={() => remove_submission(team_match)}
class="rounded border p-2 text-center"
>
<div class="grid grid-cols-4 grid-rows-4">
{team_match.match_key}:{team_match.team_key}
</div>
</button>
{/each}
</div>
</div>

<button onclick={queue_match} class="rounded border p-2">Queue Match</button>
</div>
19 changes: 16 additions & 3 deletions src/routes/queue/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
<script lang="ts">
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
import { io } from 'socket.io-client';
import { io, Socket } from 'socket.io-client';
const username = 'test_scout';
let socket: Socket;

const socket = io();
socket = io({
auth: {
username
}
});

socket.on('connect', () => {
socket.emit('join_queue');
Expand All @@ -12,8 +19,14 @@
goto(`/scout/${match_key}-${team_key}-${color}`);
});

socket.on('scout_left_queue', (scout: string) => {
if (scout === username) {
goto('/');
}
});

const leave = () => {
socket.emit('leave_queue');
socket.emit('leave_scout_queue', 'test_scout');
goto('/');
};
</script>
Expand Down
12 changes: 10 additions & 2 deletions src/routes/scout/[team_data]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
import { ArrowRight, ArrowLeft } from 'lucide-svelte';
import { browser } from '$app/environment';
import Postmatch from './Postmatch.svelte';
import { io } from 'socket.io-client';

const { data }: { data: PageData } = $props();

const scout_id = browser ? (localStorage?.getItem('scout_id') ?? '') : '';
const username = browser ? (localStorage?.getItem('username') ?? '') : '';

let actions: AutoActionData[] = $state([]);
let held: AutoHeldItems = $state({
Expand Down Expand Up @@ -38,12 +39,18 @@
gamePhase = gamePhase === 'Post' ? 'Tele' : gamePhase === 'Tele' ? 'Auto' : 'Auto'; // Last case should never happen
}

const socket = io({
auth: {
username
}
});

function submit() {
const auto_actions = actions.slice(0, furthest_auto_index + 1);
const tele_actions = actions.slice(furthest_auto_index + 1) as TeleActionData[]; // TODO: Add verification function to ensure that this always works
const match: TeamMatch = {
id: 0,
scout_id,
scout_id: username,
team_key: data.team_key,
match_key: data.match_key,
speed,
Expand All @@ -55,6 +62,7 @@
tele_actions
};

socket.emit('submit_team_match', match);
console.log(match);
}
</script>
Expand Down
File renamed without changes.
113 changes: 93 additions & 20 deletions ws.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { TeamMatch } from '$lib/types';
import { Server } from 'socket.io';
import { type ViteDevServer } from 'vite';
const info = (s: string) => console.log(`\x1b[32m ${s} \x1b[0m`);

const robotQueue: [string, 'red' | 'blue'][] = [];
const sid_to_username: Map<string, string> = new Map();
const robot_queue: [string, 'red' | 'blue'][] = [];
let curr_match_key: string = '';

const webSocketServer = {
Expand All @@ -10,49 +13,119 @@ const webSocketServer = {
if (!server.httpServer) return;
const io = new Server(server.httpServer);

io.use((socket, next) => {
const username = socket.handshake.auth.username;
if (!username) {
return next(new Error('invalid username'));
}

let old_entries = sid_to_username.entries().find(([_key, value]) => value == username);
if (old_entries) {
old_entries
.map(([key, _value]) => key)
.forEach((key) => sid_to_username.delete(key));
}

sid_to_username.set(socket.id, username);

next();
});

io.on('connect', (socket) => {
if (socket.handshake.auth.token === 'celary') socket.join('admin_room');
if (socket.handshake.auth.token === 'celary') {
info('Admin Aquired');
socket.join('admin_room');
}

socket.on('join_queue', (_) => {
const team_data = robotQueue.pop();
socket.emit('session', {
session_id: socket.id
});

socket.on('join_queue', () => {
const username = sid_to_username.get(socket.id);

const team_data = robot_queue.pop();
if (!team_data) {
io.to('admin_room').emit('scout_joined_queue', username);
socket.join('scout_queue');
return;
}
io.to('admin_room').emit('robot_left_queue', team_data);
socket.emit('time_to_scout', [curr_match_key, ...team_data]);
});

socket.on('leave_queue', (_) => {
socket.leave('scout_queue');
socket.on('leave_scout_queue', (scout_id: string) => {
const scout_sid = sid_to_username
.entries()
.filter(([_sid, scout]) => scout === scout_id)
.map(([sid, _]) => sid)
.toArray()[0];
console.log(scout_sid);
// This event exist in the cast that the scout removed itself from the queue
io.emit('scout_left_queue', scout_id);
// This event exists in the case that the admin removed the scout from the queue
// io.to(scout_sid).emit('you_left_queue');
io.sockets.sockets.get(scout_sid)?.leave('scout_queue');
});

socket.on('send_match', ([match_key, teams]: [string, [string, 'red' | 'blue'][]]) => {
if (!socket.rooms.has('admin_room')) return;
socket.on('leave_robot_queue', (robot: string) => {
const index = robot_queue.findIndex(([id, _color]) => id === robot);
if (index === -1) return;

robot_queue.splice(index, 1);
});

const scout_queue: Set<string> | undefined = io
.of('/')
.adapter.rooms.get('scout_queue');
if (scout_queue) {
for (const sid of scout_queue.values()) {
socket.on(
'send_match',
async ([match_key, teams]: [string, [string, 'red' | 'blue'][]]) => {
if (!socket.rooms.has('admin_room')) return;

const scout_queue = await io.in('scout_queue').fetchSockets();
for (const socket of scout_queue) {
const team_data = teams.pop();
if (!team_data) break;

const username = sid_to_username.get(socket.id);
if (!username) {
console.error('Scout in queue not in map');
continue;
}

socket.leave('scout_queue');
io.to(sid).emit('time_to_scout', [match_key, ...team_data]);
socket.emit('time_to_scout', [match_key, ...team_data]);
io.to('admin_room').emit('scout_left_queue', username);
}
}

robotQueue.push(...teams);
socket.emit(
'robot_joined_queue',
teams.map(([team, _color]) => team)
);
robot_queue.push(...teams);

// Update all connected sockets with new match info (for cosmetic purposes)
io.emit('new_match', match_key);
curr_match_key = match_key;
});
// Update all connected sockets with new match info (for cosmetic purposes)
io.emit('new_match', match_key);
curr_match_key = match_key;
}
);

// Event-listener sockets that were offline to sync back up with the current match key
socket.on('request_curr_match', () => {
socket.emit('new_match', curr_match_key);
});

socket.on('remove_team_match', (team_match: TeamMatch) => {
if (!socket.rooms.has('admin_room')) return;

info(
`TeamMatch: ${team_match.match_key}_${team_match.team_key} was removed by the admin`
);
// TODO: Emit message from the db to remove team_match
});

socket.on('submit_team_match', (team_match: TeamMatch) => {
console.log('log match');
io.to('admin_room').emit('new_team_match', team_match);
});
});
}
};
Expand Down
Loading