Add basic API key creation/deletion page. Clean up Update Modal, fill out Admin Event page, Fix user avatar formatting.

This commit is contained in:
Trenton Zimmer
2025-04-18 00:24:03 -04:00
parent c18f587829
commit 2366dc74b5
11 changed files with 372 additions and 56 deletions

View File

@@ -1,4 +1,4 @@
VITE_APP_VERSION="3.0.2"
VITE_APP_VERSION="3.0.3"
VITE_API_URL="http://10.5.7.5:8000/"
VITE_API_KEY="your_api_key_should_be_here"
VITE_ASSET_PATH="/assets"

View File

@@ -1,4 +1,4 @@
VITE_APP_VERSION="3.0.2"
VITE_APP_VERSION="3.0.3"
VITE_API_URL="https://restfulsleep.phaseii.network"
VITE_API_KEY="your_api_key_should_be_here"
VITE_ASSET_PATH="https://cdn.phaseii.network/file/PhaseII/web-assets"

View File

@@ -1,4 +1,5 @@
{
"3.0.1": ["- (Feature) Added update popup", "- (Optimization) Disabled ParticlesJS for the time being. This will be returned as an option in the future.", "- (Optimization) Fill in more score table data for GFDM.", "- (soon™) I've started rival support, but it's not ready just yet. Stay tuned!"],
"3.0.2": ["- (Feature) Added new greetings", "- (Feature) Added new user background", "- (Feature) Added toggle for disabling/enabling this popup", "- (Feature) Added version info to footer", "- (Feature) Fill in more profile data on personal profile view", "- (Fix) DDR stats now work again", "- (Fix) DDR player weight can now be set (kinda)"]
"3.0.2": ["- (Feature) Added new greetings", "- (Feature) Added new user background", "- (Feature) Added toggle for disabling/enabling this popup", "- (Feature) Added version info to footer", "- (Feature) Fill in more profile data on personal profile view", "- (Fix) DDR stats now work again", "- (Fix) DDR player weight can now be set (kinda)"],
"3.0.3": ["- (Feature) Discord avatars are now reactive!", "- (Feature) New profile badges! I'll be working on a backend for these soon™", "- (Optimization) Fix this box's formatting on mobile."]
}

View File

@@ -85,8 +85,10 @@ async function updateUserData(disable = false) {
w="w-20"
:size="45"
/>
<h1 class="text-2xl font-bold">Since you've been gone...</h1>
<h1 class="text-lg md:text-xl">
<h1 class="text-xl xl:text-2xl font-bold">
While you were away...
</h1>
<h1 class="text-lg xl:text-xl">
We've updated the site! These updates include feature updates and
bugfixes.
</h1>
@@ -96,7 +98,7 @@ async function updateUserData(disable = false) {
<div
v-if="changelog"
class="text-lg md:text-xl space-y-2 text-left text-pink-500 bg-slate-950 p-2"
class="text-sm md:text-md space-y-2 text-left text-pink-500 bg-slate-950 p-2 max-h-[120px] overflow-scroll"
>
<h1>Changelog:</h1>
<h2

View File

@@ -2,9 +2,9 @@
import { useMainStore } from "@/stores/main";
import {
mdiSecurity,
// mdiTestTube,
// mdiAccountStar,
// mdiCodeBraces,
mdiTestTube,
mdiAccountStar,
mdiCodeBraces,
mdiAccountOff,
// mdiAccountCheck,
} from "@mdi/js";
@@ -50,7 +50,9 @@ function getCardStyle() {
type="justify-around lg:justify-center md:space-x-4 lg:space-x-0"
class="bg-white dark:bg-slate-900/90 rounded-2xl p-3"
>
<UserAvatarCurrentUser class="w-28 md:w-32 lg:w-44 lg:mx-12" />
<UserAvatarCurrentUser
class="w-28 md:w-30 lg:w-[128px] lg:mx-12 lg:m-2"
/>
<div class="space-y-3 text-center md:text-left lg:mx-12">
<div class="space-y-2 md:space-y-0">
<h1
@@ -69,18 +71,42 @@ function getCardStyle() {
</div>
<div
v-if="!evenSmaller"
class="md:grid md:grid-flow-col md:auto-cols-max md:grid-rows-1 flex flex-wrap gap-4 md:place-content-start place-content-center"
class="flex flex-wrap gap-2 md:place-content-start place-content-center px-5 sm:px-0 py-2 sm:py-0 md:max-w-[400px]"
>
<PillTag
v-if="mainStore.userAdmin"
label="System Admin"
color="danger"
:icon="mdiSecurity"
small
/>
<PillTag
v-if="mainStore.userData.beta"
label="Beta Tester"
color="warning"
:icon="mdiTestTube"
small
/>
<PillTag
v-if="mainStore.userData.vet"
label="Veteran"
color="success"
:icon="mdiAccountStar"
small
/>
<PillTag
v-if="mainStore.userData.dev"
label="Active Dev"
color="info"
:icon="mdiCodeBraces"
small
/>
<PillTag
label="Private Profile"
color="info"
:icon="mdiAccountOff"
small
/>
<!-- <PillTag label="Beta Tester" color="warning" :icon="mdiTestTube" />
<PillTag label="Veteran" color="success" :icon="mdiAccountStar" />
<PillTag label="Active Dev" color="info" :icon="mdiCodeBraces" /> -->
<PillTag label="Private Profile" color="info" :icon="mdiAccountOff" />
<!-- <PillTag
label="Public Profile"
color="success"

View File

@@ -65,3 +65,23 @@ export async function APIAdminCreateMaintenancePeriod(newPeriod) {
throw error;
}
}
export async function APIAdminClients() {
try {
const data = await mainStore.callApi(`/admin/client`);
return data.data;
} catch (error) {
console.log("Error loading client:", error);
throw error;
}
}
export async function APIAdminCreateClient(newClient) {
try {
const data = await mainStore.callApi(`/admin/client`, "POST", newClient);
return data;
} catch (error) {
console.log("Error posting client:", error);
throw error;
}
}

View File

@@ -1,30 +1,140 @@
<script setup>
import { mdiFlagCheckered, mdiSecurity } from "@mdi/js";
import { ref, reactive, onMounted } from "vue";
import { mdiCloudKeyOutline, mdiKeyPlus } from "@mdi/js";
import SectionMain from "@/components/SectionMain.vue";
import CardBox from "@/components/CardBox.vue";
import CardBoxWidget from "@/components/CardBoxWidget.vue";
import GeneralTable from "@/components/GeneralTable.vue";
import FormField from "@/components/FormField.vue";
import FormControl from "@/components/FormControl.vue";
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
import SectionTitleLine from "@/components/SectionTitleLine.vue";
import BaseButton from "@/components/BaseButton.vue";
import { APIAdminClients, APIAdminCreateClient } from "@/stores/api/admin";
const clientData = ref([]);
const clientHeaders = [
{
text: "Create Timestamp",
value: "timestamp",
width: 120,
},
{
text: "Name",
value: "name",
width: 120,
},
];
const initClient = {
name: null,
};
const newClient = reactive({
...initClient,
});
onMounted(async () => {
loadData();
});
async function loadData() {
try {
const data = await APIAdminClients();
var formattedItems = [];
for (var item of data) {
if (item.timestamp) {
const date = new Date(item.timestamp * 1000);
item.timestamp = date.toLocaleString();
}
formattedItems.push(item);
}
clientData.value = formattedItems;
} catch (error) {
console.error("Failed to fetch client data:", error);
}
}
async function createClient() {
try {
const data = await APIAdminCreateClient(newClient);
clientData.value = data;
} catch (error) {
console.error("Failed to post client:", error);
}
loadData();
}
const copyToClipboard = (item) => {
navigator.clipboard
.writeText(item.token)
.then(() => {
alert("Copied to clipboard!");
})
.catch(() => {
alert("Failed to copy to clipboard!");
});
};
</script>
<template>
<LayoutAuthenticated>
<SectionMain>
<CardBox class="mb-6 p-1">
<h1 class="text-3xl">Data API</h1>
<p class="text-sm text-gray-400">Used for RestfulSleep and BEMAPI</p>
</CardBox>
<SectionTitleLine
:icon="mdiSecurity"
title="Network Administration"
color="text-red-600"
:icon="mdiCloudKeyOutline"
title="All Active Clients"
color="text-blue-400"
main
/>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 mb-6">
<CardBoxWidget :number="1" label="User Account(s)" />
<CardBoxWidget :number="2" label="Registered Arcades" />
<CardBoxWidget :number="3" label="Published Scores" />
</div>
<CardBox has-table class="mb-6">
<div
class="bg-white dark:bg-slate-900/95 rounded-2xl lg:flex lg:justify-between"
>
<div class="w-full">
<GeneralTable
:headers="clientHeaders"
:items="clientData"
@row-clicked="copyToClipboard"
/>
<h1 class="mx-4 mt-1 mb-2 text-md lg:text-lg">
Click row to copy token
</h1>
</div>
</div>
</CardBox>
<SectionTitleLine :icon="mdiFlagCheckered" title="Recent Errors" />
<CardBox> </CardBox>
<SectionTitleLine
:icon="mdiKeyPlus"
title="Create New Client"
color="text-emerald-600"
main
/>
<CardBox is-form class="row-span-1" @submit.prevent="createClient()">
<FormField
label="Name"
help="Name of the client you'd like to onboard."
>
<FormControl v-model="newClient.name" required />
</FormField>
<div>
<BaseButton
type="submit"
color="success"
label="Create"
:small="false"
/>
</div>
</CardBox>
</SectionMain>
</LayoutAuthenticated>
</template>

View File

@@ -25,7 +25,7 @@ onMounted(async () => {
const eventHeaders = [
{
text: "Timestamp",
value: "timestamp",
value: "date",
width: 120,
},
{
@@ -58,7 +58,7 @@ const eventHeaders = [
const transactionHeaders = [
{
text: "Timestamp",
value: "timestamp",
value: "date",
width: 120,
},
{
@@ -89,7 +89,7 @@ function formatTransactions(events) {
if (item.type == "paseli_transaction") {
if (item.timestamp) {
const date = new Date(item.timestamp * 1000);
item.timestamp = date.toLocaleString();
item.date = date.toLocaleString();
}
formattedItems.push(item);
@@ -105,7 +105,7 @@ function formatEvents(events) {
if (item.type == "pcbevent") {
if (item.timestamp) {
const date = new Date(item.timestamp * 1000);
item.timestamp = date.toLocaleString();
item.date = date.toLocaleString();
}
formattedItems.push(item);

View File

@@ -1,30 +1,166 @@
<script setup>
import { mdiFlagCheckered, mdiSecurity } from "@mdi/js";
import { ref, onMounted } from "vue";
import { mdiFlagCheckered, mdiCashEdit } from "@mdi/js";
import SectionMain from "@/components/SectionMain.vue";
import CardBox from "@/components/CardBox.vue";
import CardBoxWidget from "@/components/CardBoxWidget.vue";
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
import SectionTitleLine from "@/components/SectionTitleLine.vue";
import GeneralTable from "@/components/GeneralTable.vue";
import { APIAdminDashboard } from "@/stores/api/admin";
const dashboardData = ref({});
const loading = ref(true);
onMounted(async () => {
try {
const data = await APIAdminDashboard();
dashboardData.value = data;
loading.value = false;
} catch (error) {
console.log("Failed to fetch admin data:", error);
}
});
const eventHeaders = [
{
text: "Timestamp",
value: "date",
width: 120,
},
{
text: "IP Address",
value: "data.ip",
width: 120,
},
{
text: "Game",
value: "data.model",
width: 120,
},
{
text: "PCBID",
value: "data.pcbid",
width: 120,
},
{
text: "Name",
value: "data.name",
width: 120,
},
{
text: "Value",
value: "data.value",
width: 120,
},
];
const transactionHeaders = [
{
text: "Timestamp",
value: "date",
width: 120,
},
{
text: "PCBID",
value: "data.pcbid",
width: 120,
},
{
text: "Delta",
value: "data.delta",
width: 120,
},
{
text: "Remaining Balance",
value: "data.balance",
width: 120,
},
{
text: "Reason",
value: "data.reason",
width: 120,
},
];
function formatTransactions(events) {
var formattedItems = [];
for (var item of events) {
if (item.type == "paseli_transaction") {
if (item.timestamp) {
const date = new Date(item.timestamp * 1000);
item.date = date.toLocaleString();
}
formattedItems.push(item);
}
}
return formattedItems;
}
function formatEvents(events) {
var formattedItems = [];
for (var item of events) {
if (item.type == "pcbevent") {
if (item.timestamp) {
const date = new Date(item.timestamp * 1000);
item.date = date.toLocaleString();
}
formattedItems.push(item);
}
}
return formattedItems;
}
</script>
<template>
<LayoutAuthenticated>
<SectionMain>
<SectionTitleLine
:icon="mdiSecurity"
title="Network Administration"
color="text-red-600"
main
/>
<template v-if="!loading">
<CardBox class="mb-6 p-1">
<h1 class="text-3xl">Service Logs</h1>
</CardBox>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 mb-6">
<CardBoxWidget :number="1" label="User Account(s)" />
<CardBoxWidget :number="2" label="Registered Arcades" />
<CardBoxWidget :number="3" label="Published Scores" />
</div>
<SectionTitleLine
:icon="mdiFlagCheckered"
title="Recent PCB Events"
color="text-amber-400"
main
/>
<CardBox has-table class="mb-4">
<div
class="bg-white dark:bg-slate-900/95 rounded-2xl lg:flex lg:justify-between"
>
<div class="w-full">
<GeneralTable
:headers="eventHeaders"
:items="formatEvents(dashboardData?.audit)"
/>
</div>
</div>
</CardBox>
<SectionTitleLine :icon="mdiFlagCheckered" title="Recent Errors" />
<CardBox> </CardBox>
<SectionTitleLine
:icon="mdiCashEdit"
title="Recent PASELI Transactions"
color="text-emerald-400"
main
/>
<CardBox has-table>
<div
class="bg-white dark:bg-slate-900/95 rounded-2xl lg:flex lg:justify-between"
>
<div class="w-full">
<GeneralTable
:headers="transactionHeaders"
:items="formatTransactions(dashboardData?.audit)"
/>
</div>
</div>
</CardBox>
</template>
</SectionMain>
</LayoutAuthenticated>
</template>

View File

@@ -49,27 +49,48 @@ onMounted(async () => {
async function loadMaintenance() {
try {
const data = await APIAdminMaintenancePeriods();
maintData.value = data;
var formattedItems = [];
for (var item of data) {
if (item.timestamp) {
const date = new Date(item.timestamp * 1000);
item.timestamp = date.toLocaleString();
}
if (item.data.endTimestamp) {
const date = new Date(item.data.endTimestamp * 1000);
item.data.endTimestamp = date.toLocaleString();
}
formattedItems.push(item);
}
maintData.value = formattedItems;
} catch (error) {
console.error("Failed to fetch maint data:", error);
}
}
async function enterMaintenance() {
try {
const data = await APIAdminCreateMaintenancePeriod(newMaint);
maintData.value = data;
} catch (error) {
console.error("Failed to post maint:", error);
const confirmed = window.confirm("Are you really?");
if (confirmed) {
try {
const data = await APIAdminCreateMaintenancePeriod(newMaint);
maintData.value = data;
loadMaintenance();
} catch (error) {
console.error("Failed to post maint:", error);
}
}
loadMaintenance();
}
</script>
<template>
<LayoutAuthenticated>
<SectionMain>
<CardBox class="mb-6 p-1">
<h1 class="text-3xl">Network Maintenance</h1>
</CardBox>
<SectionTitleLine
:icon="mdiWrenchCogOutline"
title="Recent Maintenance Periods"
@@ -93,7 +114,7 @@ async function enterMaintenance() {
color="text-yellow-600"
main
/>
<CardBox is-form class="row-span-1" @submit="enterMaintenance()">
<CardBox is-form class="row-span-1" @submit.prevent="enterMaintenance()">
<FormField label="Reason" help="Why are we going under maintenance?">
<FormControl v-model="newMaint.reason" required />
</FormField>

View File

@@ -16,7 +16,7 @@ import GeneralTable from "@/components/GeneralTable.vue";
import { APIGetAllProfiles } from "@/stores/api/profile";
import { getGameInfo } from "@/constants";
import { getVideoSource, getCardStyle } from "@/constants/sources";
import { getCardStyle } from "@/constants/sources";
import { dashCode } from "@/constants/userData";
import { getIIDXDan } from "@/constants/danClass";