Expanding admin interface to include invite logs, deletion
This commit is contained in:
parent
a4f2a930b9
commit
c02896b595
|
@ -1,8 +1,16 @@
|
||||||
<slot></slot>
|
<slot />
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="/datacollection">Data Collection</a>
|
||||||
|
</footer>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:global(body) {
|
:global(body) {
|
||||||
font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif
|
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue,
|
||||||
}
|
helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
22
src/routes/datacollection.svelte
Normal file
22
src/routes/datacollection.svelte
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<h1>Data Collection</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
In order to operate, this service collects a small amount of user data. Should you want to
|
||||||
|
exercise your
|
||||||
|
<a href="https://gdpr.eu/right-to-be-forgotten/">right to be forgotten</a> or any other inquiries,
|
||||||
|
please reach out to me at mc-invites@gmem.ca.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
User data collected includes your Minecraft username at time of signup, your unique Minecraft
|
||||||
|
UUID, and Microsoft OAuth tokens required to speak to Mojang's API. IP addresses or other
|
||||||
|
personally identifiable data that may be sent is not permenantly logged. Other data includes the
|
||||||
|
server-related configuration when adding a new server to create invites, which includes the
|
||||||
|
address of the server, the address of the RCON server, and the RCON password, in addition to a
|
||||||
|
custom server name.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
p {
|
||||||
|
max-width: 50vw;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,31 +1,32 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
import { onMount } from "svelte";
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
let loggedIn = false
|
let loggedIn = false;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const request = await fetch("/api/v1/me")
|
const request = await fetch('/api/v1/me');
|
||||||
if (request.ok) {
|
if (request.ok) {
|
||||||
const currentUser = await request.text()
|
const currentUser = await request.text();
|
||||||
if (currentUser != "") {
|
if (currentUser != '') {
|
||||||
loggedIn = true
|
loggedIn = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !loggedIn}
|
{#if !loggedIn}
|
||||||
<a href="/api/v1/auth/redirect">Login</a>
|
<a href="/api/v1/auth/redirect">Login</a>
|
||||||
{:else}
|
{:else}
|
||||||
<a href="/servers">Manage Servers</a>
|
<a href="/servers">Manage Servers</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<slot></slot>
|
<slot />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:global(body) {
|
:global(body) {
|
||||||
font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,segoe ui,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif
|
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue,
|
||||||
}
|
helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,118 +1,117 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { onMount } from "svelte";
|
import { onMount } from 'svelte';
|
||||||
import mslogin from "$lib/images/mslogin.png"
|
import mslogin from '$lib/images/mslogin.png';
|
||||||
|
|
||||||
const { code } = $page.params;
|
const { code } = $page.params;
|
||||||
|
|
||||||
let invite;
|
let invite;
|
||||||
let success = "";
|
let success = '';
|
||||||
let login = true;
|
let login = true;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const loggedIn = await fetch('/api/v1/me')
|
const loggedIn = await fetch('/api/v1/me');
|
||||||
if (loggedIn.ok) {
|
if (loggedIn.ok) {
|
||||||
login = false
|
login = false;
|
||||||
}
|
}
|
||||||
const request = await fetch(`/api/v1/invite/${code}`)
|
const request = await fetch(`/api/v1/invite/${code}`);
|
||||||
if (request.ok) {
|
if (request.ok) {
|
||||||
invite = await request.json()
|
invite = await request.json();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
const acceptInvite = async () => {
|
|
||||||
const response = await fetch(`/api/v1/invite/${code}/accept`, {
|
|
||||||
method: "POST",
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
success = await response.text
|
|
||||||
} else {
|
|
||||||
console.log("not ok")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const acceptInvite = async () => {
|
||||||
|
const response = await fetch(`/api/v1/invite/${code}/accept`, {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
success = await response.text;
|
||||||
|
} else {
|
||||||
|
console.log('not ok');
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
{#if invite}
|
{#if invite}
|
||||||
<title>Accept Invite to {invite.server.name}</title>
|
<title>Accept Invite to {invite.server.name}</title>
|
||||||
{:else}
|
{:else}
|
||||||
<title>Accept Invite</title>
|
<title>Accept Invite</title>
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{#if invite}
|
{#if invite}
|
||||||
{#if success == ""}
|
{#if success == ''}
|
||||||
<p>Invite {code} from {invite.creator.display_name}</p>
|
<p>Invite {code} from {invite.creator.display_name}</p>
|
||||||
<p>Join {invite.server.name}?</p>
|
<p>Join {invite.server.name}?</p>
|
||||||
{#if login }
|
{#if login}
|
||||||
<a href="/api/v1/auth/redirect"><img src="{mslogin}" alt="Login with Microsoft"></a>
|
<a href="/api/v1/auth/redirect"><img src={mslogin} alt="Login with Microsoft" /></a>
|
||||||
{:else}
|
{:else}
|
||||||
<button on:click="{acceptInvite}">Yes!</button>
|
<button on:click={acceptInvite}>Yes!</button>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<p>You have been whitelisted! Connect at: <code>{invite.server.address}</code></p>
|
<p>You have been whitelisted! Connect at: <code>{invite.server.address}</code></p>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@import "https://fontlibrary.org/face/minecraftia";
|
@import 'https://fontlibrary.org/face/minecraftia';
|
||||||
div {
|
div {
|
||||||
background-color: #444a52f6;
|
background-color: #444a52f6;
|
||||||
color: white;
|
color: white;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 25vw;
|
width: 25vw;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 30%;
|
top: 30%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
div {
|
div {
|
||||||
width: 90vw;
|
width: 90vw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
color:white;
|
color: white;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
height: 6vh;
|
height: 6vh;
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMQAAAAPCAAAAACe4j/AAAAEuElEQVRIx1WWS5Ibuw5EtddrdRRJJE4iSKrb4c2/AUuy30iK+jEBHCTwAICMCgAKiGhKVQUwKzJHQo4mQGBy7AIvk1E2AQKhFt6bUGD1gILy0DkD4dmzACkGyzCScgFgQmLZr4LpQpC56QB2wgSYE8i1fd6xd1yPHIAKwAs14Undx2LIHK0X6vdFbzTa6CNBLQE8Y5rapn7oAl3KSDKC6cLfL6sLnIVG7ffnyUgvQ6rwLjUZdIVqWz0iEXXCZ+TJsV8feUdoPHDk+a/IeAbq0ldLT59jtHweHwmKS6gFSUYCqKcVsPami1pEnqolkCMBiAS87FXTICp03geYUr8TV8un3KuyC0BfTUzihOCdwjUya1bkT9VWe+AaFOoJzHdtERXRe/eulyEDhC6pjYwr+JQuIDU9/0wQBnJEAkd+9i6IRTJrV7YWnGfwLA6ESphkBoCu88M0YH1CTTOiRUpDdwpGkkP5yL9phhwn+QVxDarAXq513y9bLKgxBGR/CjDyMl4ECd6lO82CaMLbEHGHp1R8ePDERj392ssaCOigNjJ+dXXl6OLUFfVx6dA1BFJLlHpkUHPam095C3QJLIjn6OLIOB3xe0JS85Zd+govg1/z/UhtG3RdVW98yxNp5I0Vtf/U+6y/dJ/yHtSK4q7G9ARscC3/Df/kXTwiT3d6ovjEISLJIVEvBwWeCXlALZTbpy85xqWuf3sNiBBUmYjI+BqN6aNQqDWpipSYf3W1nOV/WALoykjIiCPreSn97UigsjZdPA5s3AmCDIxaVO0Fa1fV/qkaiijPPz/bEeRyLSBDI8K8j37TO8aH7vmOqUUQ1DboYhvqd+FlFGEo+2T75MbLGnjvPeu7Pl521Ap14dcR3h7p75qv4xieqnNDoyCPLt/yKkf5gOUXl+6S3d9Syq/982fz+fhpsm28C5EhJcxsWWQke6KoPMA7tqm7kKcrn6FLhWcoqCNCxDPyQ8wEKR73nIlEX8GpARSJX7/rzeNtWPD/Dl2IwtOYMwV7osGsHYJanzeU7KqIZVCTcrT0IEbyD4d1e1dkBtW2qLhLXK7a1ogDqsjI1DR4PY7nHEqfF9tn8HUJL+eQ/lGOXdwI1++57CqgmDaReM9CZYLAk4D4ymqti4KZI9Htz9OnrcuOhCQEqJfXaxHPAFG/l/ee1GFJPXmPryYUiUGPutnRGXkagmnjHk5RNpm7xxiclm6CjJEQ4+MD4xPmuT9Ef4b3YT3B51a7d4Np76PF0cavuC+SoaH6PlilipEakoJBJETWT3k7Hf+1REPj2fIx95t5ct6u9S5v3KM2fv2Ve29ax9yC98zT2IZot61MHyYtdPF92nguZ29xfcVxSu9Nj5Gl8G3wcVoW8n1Q/f6BHFEAyy5R2aRIdYH8XfV4453vHKcnqarXPD6mdrD0rlVV5xBPF2xuJM5Oo0SdaaqGMjJva7ii6bYv74IzPMo5pGebpu72ghypwTR+WWDuhQOkFijGyAzF3GXh11lEH0hkPINZivpTqLEtVm2j1hVcn/S0//q0es5dbyxOErxKivQ+bHotvzcT3/XztPf20XqG6fJZMePT2X6ts+nlm9LoSYHMiHuJjGXniB7v2fo/5qT4nuxYg6AAAAAASUVORK5CYII=);
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMQAAAAPCAAAAACe4j/AAAAEuElEQVRIx1WWS5Ibuw5EtddrdRRJJE4iSKrb4c2/AUuy30iK+jEBHCTwAICMCgAKiGhKVQUwKzJHQo4mQGBy7AIvk1E2AQKhFt6bUGD1gILy0DkD4dmzACkGyzCScgFgQmLZr4LpQpC56QB2wgSYE8i1fd6xd1yPHIAKwAs14Undx2LIHK0X6vdFbzTa6CNBLQE8Y5rapn7oAl3KSDKC6cLfL6sLnIVG7ffnyUgvQ6rwLjUZdIVqWz0iEXXCZ+TJsV8feUdoPHDk+a/IeAbq0ldLT59jtHweHwmKS6gFSUYCqKcVsPami1pEnqolkCMBiAS87FXTICp03geYUr8TV8un3KuyC0BfTUzihOCdwjUya1bkT9VWe+AaFOoJzHdtERXRe/eulyEDhC6pjYwr+JQuIDU9/0wQBnJEAkd+9i6IRTJrV7YWnGfwLA6ESphkBoCu88M0YH1CTTOiRUpDdwpGkkP5yL9phhwn+QVxDarAXq513y9bLKgxBGR/CjDyMl4ECd6lO82CaMLbEHGHp1R8ePDERj392ssaCOigNjJ+dXXl6OLUFfVx6dA1BFJLlHpkUHPam095C3QJLIjn6OLIOB3xe0JS85Zd+govg1/z/UhtG3RdVW98yxNp5I0Vtf/U+6y/dJ/yHtSK4q7G9ARscC3/Df/kXTwiT3d6ovjEISLJIVEvBwWeCXlALZTbpy85xqWuf3sNiBBUmYjI+BqN6aNQqDWpipSYf3W1nOV/WALoykjIiCPreSn97UigsjZdPA5s3AmCDIxaVO0Fa1fV/qkaiijPPz/bEeRyLSBDI8K8j37TO8aH7vmOqUUQ1DboYhvqd+FlFGEo+2T75MbLGnjvPeu7Pl521Ap14dcR3h7p75qv4xieqnNDoyCPLt/yKkf5gOUXl+6S3d9Syq/982fz+fhpsm28C5EhJcxsWWQke6KoPMA7tqm7kKcrn6FLhWcoqCNCxDPyQ8wEKR73nIlEX8GpARSJX7/rzeNtWPD/Dl2IwtOYMwV7osGsHYJanzeU7KqIZVCTcrT0IEbyD4d1e1dkBtW2qLhLXK7a1ogDqsjI1DR4PY7nHEqfF9tn8HUJL+eQ/lGOXdwI1++57CqgmDaReM9CZYLAk4D4ymqti4KZI9Htz9OnrcuOhCQEqJfXaxHPAFG/l/ee1GFJPXmPryYUiUGPutnRGXkagmnjHk5RNpm7xxiclm6CjJEQ4+MD4xPmuT9Ef4b3YT3B51a7d4Np76PF0cavuC+SoaH6PlilipEakoJBJETWT3k7Hf+1REPj2fIx95t5ct6u9S5v3KM2fv2Ve29ax9yC98zT2IZot61MHyYtdPF92nguZ29xfcVxSu9Nj5Gl8G3wcVoW8n1Q/f6BHFEAyy5R2aRIdYH8XfV4453vHKcnqarXPD6mdrD0rlVV5xBPF2xuJM5Oo0SdaaqGMjJva7ii6bYv74IzPMo5pGebpu72ghypwTR+WWDuhQOkFijGyAzF3GXh11lEH0hkPINZivpTqLEtVm2j1hVcn/S0//q0es5dbyxOErxKivQ+bHotvzcT3/XztPf20XqG6fJZMePT2X6ts+nlm9LoSYHMiHuJjGXniB7v2fo/5qT4nuxYg6AAAAAASUVORK5CYII=);
|
||||||
font-family: 'MinecraftiaRegular';
|
font-family: 'MinecraftiaRegular';
|
||||||
border-color: #AAA #565656 #565656 #AAA;
|
border-color: #aaa #565656 #565656 #aaa;
|
||||||
text-shadow: 3px 3px #4C4C4C;
|
text-shadow: 3px 3px #4c4c4c;
|
||||||
outline: 2px solid #000;
|
outline: 2px solid #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMQAAAAPCAMAAACMV5AuAAAAVFBMVEVtd7ZteLZverdwerdwe7hxfLhyfbl0frl1f7p2gLt4grx6hLx7hb18hr5+iL+AisCBi8GCjMGEjsKGkMOHkcSIksSKk8WLlMWLlcaMlcaNlsePmMdwBHmVAAAEjElEQVRIx1WW27YbKw5F3Ul2GYTWFAJX+pyd///PfgDsTg0/mSqQ1k08AEAeDkAA7kXyDAeyu2QCWREgCGSvAAbIE/D9U2m8Ji4Drw0CEhP7EMiqBFxmDMBEkHvd5AziDsjoCKQXddUl1nuZgMZrf0PMdj1kgNY+Ay+CJPg8kj1rR3X/GS9kxaoJVFaB6QkxIX5TBX65mpAZGQH3HaoCFMj6/GxuYgDyTsyup1hfxwxVN+F08ANGANyf8jqAPaBpranJvhxV+VV08JGPVScmcL8W4EIuAFWFHMZrUkUfNC3WtIsEaFogMWJRl839MES61w1cjMUsI1fb7GKaoENMOdFNykzXPz2mlwekkXjV4ioO9b15rZWZd4AaCL9cxdSutl8buIOUkd8JTgAy01IKoFoFPhAZr65SbDHgxFIGKi5IpLaKdlafAHxaVWDt2SSZ24LQhEx6aLGyj7RtELDLiA7EiD72ehBiQJo5oPq1nOQMYGAIYoY2AwIrImaA+25P8vZRa0KgqrjnQIaACiom/1G9Sla3GUHVLl/mMl/mxeUPGT0T5seAeeAQtF9WV2F7cX4niJ4DYmBd12oh7jymyRkBuq6M81mQuExHVv313beo9VH3ojfM2zZ8AkTG8X7EeDs6OZw+mrY7E5l/gqQJmZx+YySQAi1/dqQZy0as4FL1d7nrcfd1kjWTX1ZIYESTUCnuPZE7+anrqcxlz3diUX2HowHCf15S3AvxrnhRxeMwtQACGYGKZ7wGjNl7vH73MHdP8s/vSXM0FiRqMnPowfbjRsfe6n7nZ/FGIyfgF6+A/t1XTrcWB3Q42AxkMOfMfuJotRPgqGqFMfB8KO6eN+BEpO8F2eLZ18YO0GUdmEDcXMcNey+XuOfvP5P35guSVxAzWRYUpIoSuZiJrJ/p4zPouw0pwX+6XwpIcyNWEcJ/uf5Svuyx+ncTfhmotEhIBPe/8f961F/jI5aQfSs3WFOwChnZZxPk+HQjZu/WBuDFVwobZnoLISBOdkmNLFPE9iadiImsgTcdTDKA8dj1qBT518Xcg6+6L0Yl/nqCLeH+b46InkCSETTBzEQBRoPEwS9lKVUkpEx426M3oIMHYQLhK/B6jHvgXw6if4+YM4mlJdWDpLw4MhHgjwBZgIt+ZJdAVENOEkiv6mY4yy5nFPjnQmGfGVy0IqP+cmbsWF7Se/rT+5ZBzHPdKfajHW2ouWldPJaoTDJ3GUYTNMV/IyaiPYqQuX0VPXK+ra3Mc8c5Wlkp4D/e5S5htjXU3HnPPJsBXraRM/rSpOMX99zMoVradRl9AMxJdVPIYgf8Rhu0DvLM739A5rmjJ51QcW9SFXjcvR85oYOhSKTsd65pqdKWIV59ZO/7kKTDJLbvYw9XVRIyTXKJdfm4rOjE18yT8IHM/euZa0Z0fLvBSIh7h925QLoXR80WNfnqOHHP7Qk5aj+dTLX4E3hhIkafoGd15/IDT/lPTVSVr233dy6OkJtiLm0yBu+byeE2Yc59pVrDdKyPjxgD4h6r6vcFwpYNHMxVBaE2ApmfyTTjf5jQnWRymG7iAAAAAElFTkSuQmCC);
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMQAAAAPCAMAAACMV5AuAAAAVFBMVEVtd7ZteLZverdwerdwe7hxfLhyfbl0frl1f7p2gLt4grx6hLx7hb18hr5+iL+AisCBi8GCjMGEjsKGkMOHkcSIksSKk8WLlMWLlcaMlcaNlsePmMdwBHmVAAAEjElEQVRIx1WW27YbKw5F3Ul2GYTWFAJX+pyd///PfgDsTg0/mSqQ1k08AEAeDkAA7kXyDAeyu2QCWREgCGSvAAbIE/D9U2m8Ji4Drw0CEhP7EMiqBFxmDMBEkHvd5AziDsjoCKQXddUl1nuZgMZrf0PMdj1kgNY+Ay+CJPg8kj1rR3X/GS9kxaoJVFaB6QkxIX5TBX65mpAZGQH3HaoCFMj6/GxuYgDyTsyup1hfxwxVN+F08ANGANyf8jqAPaBpranJvhxV+VV08JGPVScmcL8W4EIuAFWFHMZrUkUfNC3WtIsEaFogMWJRl839MES61w1cjMUsI1fb7GKaoENMOdFNykzXPz2mlwekkXjV4ioO9b15rZWZd4AaCL9cxdSutl8buIOUkd8JTgAy01IKoFoFPhAZr65SbDHgxFIGKi5IpLaKdlafAHxaVWDt2SSZ24LQhEx6aLGyj7RtELDLiA7EiD72ehBiQJo5oPq1nOQMYGAIYoY2AwIrImaA+25P8vZRa0KgqrjnQIaACiom/1G9Sla3GUHVLl/mMl/mxeUPGT0T5seAeeAQtF9WV2F7cX4niJ4DYmBd12oh7jymyRkBuq6M81mQuExHVv313beo9VH3ojfM2zZ8AkTG8X7EeDs6OZw+mrY7E5l/gqQJmZx+YySQAi1/dqQZy0as4FL1d7nrcfd1kjWTX1ZIYESTUCnuPZE7+anrqcxlz3diUX2HowHCf15S3AvxrnhRxeMwtQACGYGKZ7wGjNl7vH73MHdP8s/vSXM0FiRqMnPowfbjRsfe6n7nZ/FGIyfgF6+A/t1XTrcWB3Q42AxkMOfMfuJotRPgqGqFMfB8KO6eN+BEpO8F2eLZ18YO0GUdmEDcXMcNey+XuOfvP5P35guSVxAzWRYUpIoSuZiJrJ/p4zPouw0pwX+6XwpIcyNWEcJ/uf5Svuyx+ncTfhmotEhIBPe/8f961F/jI5aQfSs3WFOwChnZZxPk+HQjZu/WBuDFVwobZnoLISBOdkmNLFPE9iadiImsgTcdTDKA8dj1qBT518Xcg6+6L0Yl/nqCLeH+b46InkCSETTBzEQBRoPEwS9lKVUkpEx426M3oIMHYQLhK/B6jHvgXw6if4+YM4mlJdWDpLw4MhHgjwBZgIt+ZJdAVENOEkiv6mY4yy5nFPjnQmGfGVy0IqP+cmbsWF7Se/rT+5ZBzHPdKfajHW2ouWldPJaoTDJ3GUYTNMV/IyaiPYqQuX0VPXK+ra3Mc8c5Wlkp4D/e5S5htjXU3HnPPJsBXraRM/rSpOMX99zMoVradRl9AMxJdVPIYgf8Rhu0DvLM739A5rmjJ51QcW9SFXjcvR85oYOhSKTsd65pqdKWIV59ZO/7kKTDJLbvYw9XVRIyTXKJdfm4rOjE18yT8IHM/euZa0Z0fLvBSIh7h925QLoXR80WNfnqOHHP7Qk5aj+dTLX4E3hhIkafoGd15/IDT/lPTVSVr233dy6OkJtiLm0yBu+byeE2Yc59pVrDdKyPjxgD4h6r6vcFwpYNHMxVBaE2ApmfyTTjf5jQnWRymG7iAAAAAElFTkSuQmCC);
|
||||||
border-color: #BDC6FF #59639A #59639A #BDC6FF;
|
border-color: #bdc6ff #59639a #59639a #bdc6ff;
|
||||||
}
|
}
|
||||||
code {
|
code {
|
||||||
font-family: 'MinecraftiaRegular';
|
font-family: 'MinecraftiaRegular';
|
||||||
color: white;
|
color: white;
|
||||||
padding: 20px 5px;
|
padding: 20px 5px;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: larger;
|
font-size: larger;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
border: 3px inset grey;
|
border: 3px inset grey;
|
||||||
}
|
}
|
||||||
:global(body) {
|
:global(body) {
|
||||||
font-family: 'MinecraftiaRegular';
|
font-family: 'MinecraftiaRegular';
|
||||||
background-color: #202225;
|
background-color: #202225;
|
||||||
background-image: url("/dirt.jpg");
|
background-image: url('/dirt.jpg');
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,58 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { goto } from "$app/navigation";
|
|
||||||
import { page } from '$app/stores';
|
|
||||||
|
|
||||||
const { id } = $page.params;
|
|
||||||
let inviteCode = "";
|
|
||||||
|
|
||||||
const createInvite = async (e: Event) => {
|
|
||||||
const form: HTMLFormElement = e.currentTarget;
|
|
||||||
const url = form.action;
|
|
||||||
|
|
||||||
let formFields = new FormData(form);
|
|
||||||
let formData = Object.fromEntries(formFields.entries());
|
|
||||||
const payload = {
|
|
||||||
"server": id,
|
|
||||||
"uses": Number(formData.uses),
|
|
||||||
"unlimited": formData.unlimited == "on"
|
|
||||||
};
|
|
||||||
if (payload.uses === 0) { payload.uses = 5 }
|
|
||||||
console.log(payload)
|
|
||||||
const pl = JSON.stringify(payload);
|
|
||||||
|
|
||||||
const response = await fetch("/api/v1/invites", {
|
|
||||||
method: "POST",
|
|
||||||
body: pl
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
inviteCode = await response.text()
|
|
||||||
} else {
|
|
||||||
console.log("not ok")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if inviteCode }
|
|
||||||
<code>{$page.url.hostname}/invite/{inviteCode}</code>
|
|
||||||
{:else}
|
|
||||||
<h2>Create Invite</h2>
|
|
||||||
<form on:submit|preventDefault="{createInvite}" action="/api/v1/invites" method="POST">
|
|
||||||
<label>Number of uses
|
|
||||||
<input type="number" name="uses" placeholder="5"></label>
|
|
||||||
<label>Unlimited?
|
|
||||||
<input type="checkbox" name="unlimited"></label>
|
|
||||||
<input type="submit" value="Save">
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
label {
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
border: 1px solid black;
|
|
||||||
padding: 5px;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: large;
|
|
||||||
}
|
|
||||||
</style>
|
|
92
src/routes/servers/[id]/index.svelte
Normal file
92
src/routes/servers/[id]/index.svelte
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
const { id } = $page.params;
|
||||||
|
|
||||||
|
let server;
|
||||||
|
let invites;
|
||||||
|
let loaded = false;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const request = await fetch(`/api/v1/server/${id}`);
|
||||||
|
if (request.ok) {
|
||||||
|
server = await request.json();
|
||||||
|
} else {
|
||||||
|
console.log('not ok');
|
||||||
|
}
|
||||||
|
|
||||||
|
const r2 = await fetch(`/api/v1/server/${id}/invites`);
|
||||||
|
if (request.ok) {
|
||||||
|
invites = await r2.json();
|
||||||
|
for (let invite in invites) {
|
||||||
|
const logsReq = await fetch(`/api/v1/invite/${invites[invite].token}/log`)
|
||||||
|
if (request.ok) {
|
||||||
|
invites[invite]["log"] = await logsReq.json()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loaded = true;
|
||||||
|
} else {
|
||||||
|
console.log('not ok');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteServer = async (e: Event) => {
|
||||||
|
const element = e.target;
|
||||||
|
const serverId = element.dataset.serverid;
|
||||||
|
const request = await fetch(`/api/v1/server/${serverId}`, {
|
||||||
|
method: "DELETE"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (request.ok) {
|
||||||
|
servers = servers.filter((server) => server.id != serverId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteInvite = async (e: Event) => {
|
||||||
|
const element = e.target;
|
||||||
|
const token = element.dataset.invitetoken;
|
||||||
|
const request = await fetch(`/api/v1/invite/${token}`, {
|
||||||
|
method: "DELETE"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (request.ok) {
|
||||||
|
invites = invites.filter((invite) => invite.token != token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if loaded}
|
||||||
|
<h2>{server.name}</h2>
|
||||||
|
<div>
|
||||||
|
<p>Server Address: {server.address}</p>
|
||||||
|
<p>Server RCON Address: {server.rcon.address}</p>
|
||||||
|
<div>
|
||||||
|
<h4>Invites <a href="/servers/{server.id}/new">create new</a></h4>
|
||||||
|
{#if invites.length > 0}
|
||||||
|
{#each invites as invite}
|
||||||
|
<details>
|
||||||
|
<summary>{invite.token} | Uses: {!invite.unlimited ? invite.uses : "unlimited"} <button on:click={deleteInvite} data-invitetoken="{invite.token}">delete</button></summary>
|
||||||
|
{#if invite.log}
|
||||||
|
{#each invite.log as log}
|
||||||
|
<p>{log.user.display_name} ({log.user.id})</p>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</details>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<p>No invites</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<button on:click={deleteServer} data-serverid="{server.id}">delete</button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p>Loading</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
59
src/routes/servers/[id]/new.svelte
Normal file
59
src/routes/servers/[id]/new.svelte
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
const { id } = $page.params;
|
||||||
|
|
||||||
|
const createInvite = async (e: Event) => {
|
||||||
|
const form: HTMLFormElement = e.currentTarget;
|
||||||
|
const url = form.action;
|
||||||
|
|
||||||
|
let formFields = new FormData(form);
|
||||||
|
let formData = Object.fromEntries(formFields.entries());
|
||||||
|
const payload = {
|
||||||
|
server: id,
|
||||||
|
uses: Number(formData.uses),
|
||||||
|
unlimited: formData.unlimited == 'on'
|
||||||
|
};
|
||||||
|
if (payload.uses === 0) {
|
||||||
|
payload.uses = 5;
|
||||||
|
}
|
||||||
|
console.log(payload);
|
||||||
|
const pl = JSON.stringify(payload);
|
||||||
|
|
||||||
|
const response = await fetch('/api/v1/invites', {
|
||||||
|
method: 'POST',
|
||||||
|
body: pl
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
goto(`/servers/${id}`)
|
||||||
|
} else {
|
||||||
|
console.log('not ok');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h2>Create Invite</h2>
|
||||||
|
<form on:submit|preventDefault={createInvite} action="/api/v1/invites" method="POST">
|
||||||
|
<label
|
||||||
|
>Number of uses
|
||||||
|
<input type="number" name="uses" placeholder="5" /></label
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
>Unlimited?
|
||||||
|
<input type="checkbox" name="unlimited" /></label
|
||||||
|
>
|
||||||
|
<input type="submit" value="Save" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
label {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,22 +1,22 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
import { onMount } from "svelte";
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
let loggedIn = false
|
let loggedIn = false;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const request = await fetch("/api/v1/me")
|
const request = await fetch('/api/v1/me');
|
||||||
if (request.ok) {
|
if (request.ok) {
|
||||||
loggedIn = true
|
loggedIn = true;
|
||||||
} else {
|
} else {
|
||||||
goto("/")
|
goto('/');
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !loggedIn}
|
{#if !loggedIn}
|
||||||
<a href="/api/v1/auth/redirect">Login</a>
|
<a href="/api/v1/auth/redirect">Login</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<slot></slot>
|
<slot />
|
||||||
|
|
|
@ -1,39 +1,52 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { server } from "$app/env";
|
import { server } from '$app/env';
|
||||||
import { onMount } from "svelte";
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
let servers = []
|
let servers = [];
|
||||||
let loaded = false
|
let loaded = false;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const request = await fetch("/api/v1/servers")
|
const request = await fetch('/api/v1/servers');
|
||||||
if (request.ok) {
|
if (request.ok) {
|
||||||
loaded = true
|
loaded = true;
|
||||||
servers = await request.json()
|
servers = await request.json();
|
||||||
} else {
|
} else {
|
||||||
console.log("not ok")
|
console.log('not ok');
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const deleteServer = async (e: Event) => {
|
||||||
|
const element = e.target;
|
||||||
|
const serverId = element.dataset.serverid;
|
||||||
|
const request = await fetch(`/api/v1/server/${serverId}`, {
|
||||||
|
method: "DELETE"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (request.ok) {
|
||||||
|
servers = servers.filter((server) => server.id != serverId);
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loaded}
|
{#if loaded}
|
||||||
<h2>Your servers <small><a href="/servers/new">new</a></small></h2>
|
<h2>Your servers <small><a href="/servers/new">new</a></small></h2>
|
||||||
{#if servers.length > 0}
|
{#if servers.length > 0}
|
||||||
{#each servers as server}
|
{#each servers as server}
|
||||||
<div>
|
<div>
|
||||||
<h3>{server.name}</h3>
|
<h3>{server.name}</h3>
|
||||||
<p>Server Address: {server.address}</p>
|
<p>Server Address: {server.address}</p>
|
||||||
<p>Server RCON Address: {server.rcon.address}</p>
|
<p>Server RCON Address: {server.rcon.address}</p>
|
||||||
<a href="/servers/{server.id}">new invite</a> <button>delete</button>
|
<a href="/servers/{server.id}">view</a>
|
||||||
</div>
|
<button on:click={deleteServer} data-serverid="{server.id}">delete</button>
|
||||||
{/each}
|
</div>
|
||||||
{/if}
|
{/each}
|
||||||
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<p>Loading</p>
|
<p>Loading</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
div {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,57 +1,64 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
const createServer = async (e: Event) => {
|
||||||
|
const form: HTMLFormElement = e.currentTarget;
|
||||||
|
const url = form.action;
|
||||||
|
|
||||||
const createServer = async (e: Event) => {
|
let formFields = new FormData(form);
|
||||||
const form: HTMLFormElement = e.currentTarget;
|
let formData = Object.fromEntries(formFields.entries());
|
||||||
const url = form.action;
|
const payload = {
|
||||||
|
address: formData.address,
|
||||||
|
name: formData.name,
|
||||||
|
rcon: {
|
||||||
|
address: formData.rcon_address,
|
||||||
|
password: formData.rcon_password
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const pl = JSON.stringify(payload);
|
||||||
|
|
||||||
let formFields = new FormData(form);
|
const response = await fetch('/api/v1/servers', {
|
||||||
let formData = Object.fromEntries(formFields.entries());
|
method: 'POST',
|
||||||
const payload = {
|
body: pl
|
||||||
"address": formData.address,
|
});
|
||||||
"name": formData.name,
|
if (response.ok) {
|
||||||
"rcon": {
|
goto('/servers');
|
||||||
"address": formData.rcon_address,
|
} else {
|
||||||
"password": formData.rcon_password
|
console.log('not ok');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const pl = JSON.stringify(payload);
|
|
||||||
|
|
||||||
const response = await fetch("/api/v1/servers", {
|
|
||||||
method: "POST",
|
|
||||||
body: pl
|
|
||||||
});
|
|
||||||
if (response.ok) {
|
|
||||||
goto("/servers")
|
|
||||||
} else {
|
|
||||||
console.log("not ok")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h2>Add Server</h2>
|
<h2>Add Server</h2>
|
||||||
<form on:submit|preventDefault="{createServer}" action="/api/v1/servers" method="POST">
|
<form on:submit|preventDefault={createServer} action="/api/v1/servers" method="POST">
|
||||||
<label>Server Name
|
<label
|
||||||
<input type="text" name="name" placeholder="A Minecraft Server"></label>
|
>Server Name
|
||||||
<label>Server Address
|
<input type="text" name="name" placeholder="A Minecraft Server" /></label
|
||||||
<input type="text" name="address" placeholder="127.0.0.1"></label>
|
>
|
||||||
<label>RCON Address
|
<label
|
||||||
<input type="text" name="rcon_address" placeholder="127.0.0.1:25575"></label>
|
>Server Address
|
||||||
<label>RCON Password
|
<input type="text" name="address" placeholder="127.0.0.1" /></label
|
||||||
<input type="password" name="rcon_password" placeholder="rcon password"></label>
|
>
|
||||||
<input type="submit" value="Save">
|
<label
|
||||||
|
>RCON Address
|
||||||
|
<input type="text" name="rcon_address" placeholder="127.0.0.1:25575" /></label
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
>RCON Password
|
||||||
|
<input type="password" name="rcon_password" placeholder="rcon password" /></label
|
||||||
|
>
|
||||||
|
<input type="submit" value="Save" />
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
input {
|
input {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-size: large;
|
font-size: large;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
Loading…
Reference in a new issue