mirror of
https://github.com/PhaseII-eAmusement-Network/PhaseWeb3-Vue.git
synced 2026-09-28 03:36:36 -05:00
Add in network admin pages, vpn exporting
This commit is contained in:
@@ -18,10 +18,15 @@ const props = defineProps({
|
||||
type: [String, Number],
|
||||
default: null,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const spanClass = computed(
|
||||
() => `inline-flex justify-center items-center ${props.w} ${props.h}`
|
||||
() =>
|
||||
`inline-flex justify-center items-center ${props.w} ${props.h} ${props.color}`
|
||||
);
|
||||
|
||||
const iconSize = computed(() => props.size ?? 16);
|
||||
|
||||
@@ -20,6 +20,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
minlength: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null,
|
||||
@@ -149,6 +153,7 @@ if (props.ctrlKFocus) {
|
||||
:class="inputElClass"
|
||||
:name="name"
|
||||
:maxlength="maxlength"
|
||||
:minlength="minlength"
|
||||
:placeholder="placeholder"
|
||||
:required="required"
|
||||
/>
|
||||
@@ -159,6 +164,7 @@ if (props.ctrlKFocus) {
|
||||
v-model="computedValue"
|
||||
:name="name"
|
||||
:maxlength="maxlength"
|
||||
:minlength="minlength"
|
||||
:inputmode="inputmode"
|
||||
:autocomplete="autocomplete"
|
||||
:required="required"
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
<script setup>
|
||||
import { colorsText, colorsBgLight } from "@/colors.js";
|
||||
import { computed } from "vue";
|
||||
import BaseIcon from "@/components/BaseIcon.vue";
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: null,
|
||||
@@ -21,6 +25,8 @@ defineProps({
|
||||
},
|
||||
bg: Boolean,
|
||||
});
|
||||
|
||||
const spanColor = computed(() => props.iconColor);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -28,12 +34,8 @@ defineProps({
|
||||
:path="icon"
|
||||
:w="w"
|
||||
:h="h"
|
||||
:color="spanColor"
|
||||
size="24"
|
||||
class="rounded-full"
|
||||
:class="
|
||||
bg
|
||||
? colorsBgLight[color]
|
||||
: [colorsText[color], 'bg-gray-50 dark:bg-slate-800']
|
||||
"
|
||||
class="rounded-full bg-slate-900"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -8,6 +8,10 @@ defineProps({
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
@@ -27,11 +31,18 @@ const hasSlot = computed(() => useSlots().default);
|
||||
<IconRounded
|
||||
v-if="icon && main"
|
||||
:icon="icon"
|
||||
:icon-color="color"
|
||||
color="light"
|
||||
class="mr-3"
|
||||
bg
|
||||
/>
|
||||
<BaseIcon v-else-if="icon" :path="icon" class="mr-2" size="20" />
|
||||
<BaseIcon
|
||||
v-else-if="icon"
|
||||
:path="icon"
|
||||
:color="color"
|
||||
class="mr-2"
|
||||
size="20"
|
||||
/>
|
||||
<h1 :class="main ? 'text-3xl' : 'text-2xl'" class="leading-tight">
|
||||
{{ title }}
|
||||
</h1>
|
||||
|
||||
12
src/constants/pcbid.js
Normal file
12
src/constants/pcbid.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export function generatePCBID() {
|
||||
const prefix = "01201000000000";
|
||||
const hexChars = "0123456789ABCDEF";
|
||||
let randomPart = "";
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
randomPart += hexChars[Math.floor(Math.random() * hexChars.length)];
|
||||
}
|
||||
|
||||
const potentialPCBid = prefix + randomPart;
|
||||
return potentialPCBid;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
mdiMonitor,
|
||||
mdiStoreCog,
|
||||
mdiGamepad,
|
||||
mdiSecurity,
|
||||
} from "@mdi/js";
|
||||
import { ref, watch, onMounted, computed } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
@@ -133,11 +134,72 @@ const menuAside = computed(() => {
|
||||
// ],
|
||||
}));
|
||||
|
||||
return [
|
||||
{ to: "/", icon: mdiMonitor, label: "Dashboard" },
|
||||
{ label: "My Arcades", icon: mdiStoreCog, menu: sortedArcades },
|
||||
{ label: "Games", icon: mdiGamepad, menu: sortedGames },
|
||||
const adminMenu = [
|
||||
{
|
||||
label: "Dashboard",
|
||||
to: "/admin",
|
||||
},
|
||||
{
|
||||
label: "Onboarding",
|
||||
to: "/admin/onboarding",
|
||||
},
|
||||
{
|
||||
label: "Events",
|
||||
to: "/admin/events",
|
||||
},
|
||||
{
|
||||
label: "API",
|
||||
to: "/admin/api",
|
||||
},
|
||||
{
|
||||
label: "Arcades",
|
||||
to: "/admin/arcades",
|
||||
},
|
||||
{
|
||||
label: "Machines",
|
||||
to: "/admin/machines",
|
||||
},
|
||||
{
|
||||
label: "Cards",
|
||||
to: "/admin/cards",
|
||||
},
|
||||
{
|
||||
label: "Users",
|
||||
to: "/admin/users",
|
||||
},
|
||||
{
|
||||
label: "News",
|
||||
to: "/admin/news",
|
||||
},
|
||||
];
|
||||
|
||||
var sideMenu = [{ to: "/", icon: mdiMonitor, label: "Dashboard" }];
|
||||
|
||||
if (mainStore.userAdmin) {
|
||||
sideMenu.push({
|
||||
label: "Admin",
|
||||
icon: mdiSecurity,
|
||||
menu: adminMenu,
|
||||
});
|
||||
}
|
||||
|
||||
if (sortedGames.length) {
|
||||
sideMenu.push({
|
||||
label: "Games",
|
||||
icon: mdiGamepad,
|
||||
menu: sortedGames,
|
||||
});
|
||||
}
|
||||
|
||||
if (sortedArcades.length) {
|
||||
sideMenu.push({
|
||||
label: "My Arcades",
|
||||
icon: mdiStoreCog,
|
||||
menu: sortedArcades,
|
||||
});
|
||||
}
|
||||
|
||||
return sideMenu;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -93,6 +93,78 @@ const routes = [
|
||||
hotReload: true, // disables Hot Reload
|
||||
},
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: "Admin Dashboard",
|
||||
},
|
||||
path: "/admin",
|
||||
name: "admin_dashboard",
|
||||
component: () => import("@/views/Admin/DashboardView.vue"),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: "Onboarding",
|
||||
},
|
||||
path: "/admin/onboarding",
|
||||
name: "onboarding",
|
||||
component: () => import("@/views/Admin/OnboardingView.vue"),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: "Events",
|
||||
},
|
||||
path: "/admin/events",
|
||||
name: "admin_events",
|
||||
component: () => import("@/views/Admin/EventsView.vue"),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: "Data API",
|
||||
},
|
||||
path: "/admin/api",
|
||||
name: "data_api",
|
||||
component: () => import("@/views/Admin/APIView.vue"),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: "Arcades",
|
||||
},
|
||||
path: "/admin/arcades",
|
||||
name: "admin_arcades",
|
||||
component: () => import("@/views/Admin/ArcadesView.vue"),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: "Machines",
|
||||
},
|
||||
path: "/admin/machines",
|
||||
name: "admin_machines",
|
||||
component: () => import("@/views/Admin/MachinesView.vue"),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: "Cards",
|
||||
},
|
||||
path: "/admin/cards",
|
||||
name: "admin_cards",
|
||||
component: () => import("@/views/Admin/CardsView.vue"),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: "Users",
|
||||
},
|
||||
path: "/admin/users",
|
||||
name: "admin_users",
|
||||
component: () => import("@/views/Admin/UsersView.vue"),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
title: "News",
|
||||
},
|
||||
path: "/admin/news",
|
||||
name: "admin_news",
|
||||
component: () => import("@/views/Admin/NewsView.vue"),
|
||||
},
|
||||
// {
|
||||
// meta: {
|
||||
// title: "Public Arcades",
|
||||
|
||||
@@ -263,6 +263,16 @@ export const useMainStore = defineStore("main", {
|
||||
}
|
||||
},
|
||||
|
||||
async getArcadeVPN(arcadeId) {
|
||||
try {
|
||||
const data = await this.callApi(`/arcade/${arcadeId}/exportVPN`);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.log("Error fetching arcade VPN:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async getPaseliData(arcadeId) {
|
||||
try {
|
||||
const data = await this.callApi(`/arcade/${arcadeId}/paseli`);
|
||||
|
||||
30
src/views/Admin/APIView.vue
Normal file
30
src/views/Admin/APIView.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { mdiFlagCheckered, mdiSecurity } 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";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLine
|
||||
:icon="mdiSecurity"
|
||||
title="Network Administration"
|
||||
color="text-red-600"
|
||||
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>
|
||||
|
||||
<SectionTitleLine :icon="mdiFlagCheckered" title="Recent Errors" />
|
||||
<CardBox> </CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
30
src/views/Admin/ArcadesView.vue
Normal file
30
src/views/Admin/ArcadesView.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { mdiFlagCheckered, mdiSecurity } 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";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLine
|
||||
:icon="mdiSecurity"
|
||||
title="Network Administration"
|
||||
color="text-red-600"
|
||||
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>
|
||||
|
||||
<SectionTitleLine :icon="mdiFlagCheckered" title="Recent Errors" />
|
||||
<CardBox> </CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
30
src/views/Admin/CardsView.vue
Normal file
30
src/views/Admin/CardsView.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { mdiFlagCheckered, mdiSecurity } 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";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLine
|
||||
:icon="mdiSecurity"
|
||||
title="Network Administration"
|
||||
color="text-red-600"
|
||||
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>
|
||||
|
||||
<SectionTitleLine :icon="mdiFlagCheckered" title="Recent Errors" />
|
||||
<CardBox> </CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
30
src/views/Admin/DashboardView.vue
Normal file
30
src/views/Admin/DashboardView.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { mdiFlagCheckered, mdiSecurity } 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";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLine
|
||||
:icon="mdiSecurity"
|
||||
title="Network Administration"
|
||||
color="text-red-600"
|
||||
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>
|
||||
|
||||
<SectionTitleLine :icon="mdiFlagCheckered" title="Recent Errors" />
|
||||
<CardBox> </CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
30
src/views/Admin/EventsView.vue
Normal file
30
src/views/Admin/EventsView.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { mdiFlagCheckered, mdiSecurity } 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";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLine
|
||||
:icon="mdiSecurity"
|
||||
title="Network Administration"
|
||||
color="text-red-600"
|
||||
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>
|
||||
|
||||
<SectionTitleLine :icon="mdiFlagCheckered" title="Recent Errors" />
|
||||
<CardBox> </CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
30
src/views/Admin/MachinesView.vue
Normal file
30
src/views/Admin/MachinesView.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { mdiFlagCheckered, mdiSecurity } 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";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLine
|
||||
:icon="mdiSecurity"
|
||||
title="Network Administration"
|
||||
color="text-red-600"
|
||||
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>
|
||||
|
||||
<SectionTitleLine :icon="mdiFlagCheckered" title="Recent Errors" />
|
||||
<CardBox> </CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
30
src/views/Admin/NewsView.vue
Normal file
30
src/views/Admin/NewsView.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { mdiFlagCheckered, mdiSecurity } 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";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLine
|
||||
:icon="mdiSecurity"
|
||||
title="Network Administration"
|
||||
color="text-red-600"
|
||||
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>
|
||||
|
||||
<SectionTitleLine :icon="mdiFlagCheckered" title="Recent Errors" />
|
||||
<CardBox> </CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
297
src/views/Admin/OnboardingView.vue
Normal file
297
src/views/Admin/OnboardingView.vue
Normal file
@@ -0,0 +1,297 @@
|
||||
<script setup>
|
||||
import { reactive } from "vue";
|
||||
import { mdiInformationOutline, mdiCogOutline, mdiSailBoat } from "@mdi/js";
|
||||
import SectionMain from "@/components/SectionMain.vue";
|
||||
import CardBox from "@/components/CardBox.vue";
|
||||
import FormField from "@/components/FormField.vue";
|
||||
import FormCheckRadio from "@/components/FormCheckRadio.vue";
|
||||
import FormControl from "@/components/FormControl.vue";
|
||||
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
|
||||
import SectionTitleLine from "@/components/SectionTitleLine.vue";
|
||||
import PillTag from "@/components/PillTag.vue";
|
||||
import BaseButton from "@/components/BaseButton.vue";
|
||||
|
||||
import { generatePCBID } from "@/constants/pcbid.js";
|
||||
|
||||
const initArcade = {
|
||||
name: "",
|
||||
useDiscord: true,
|
||||
discordId: "",
|
||||
description: "",
|
||||
paseli: true,
|
||||
infinitePaseli: true,
|
||||
maintenance: false,
|
||||
betas: false,
|
||||
incognito: false,
|
||||
machineList: [],
|
||||
};
|
||||
|
||||
const initMachine = {
|
||||
name: "",
|
||||
cabinet: false,
|
||||
ota: false,
|
||||
generatePCBID: true,
|
||||
PCBID: "",
|
||||
};
|
||||
|
||||
const newArcade = reactive({
|
||||
...initArcade,
|
||||
});
|
||||
|
||||
const newMachine = reactive({
|
||||
...initMachine,
|
||||
});
|
||||
|
||||
function addMachine() {
|
||||
if (newMachine.generatePCBID) {
|
||||
newMachine.PCBID = generatePCBID();
|
||||
}
|
||||
|
||||
newArcade.machineList.push({
|
||||
name: newMachine.name,
|
||||
cabinet: newMachine.cabinet,
|
||||
ota: newMachine.ota,
|
||||
PCBID: newMachine.PCBID.toUpperCase(),
|
||||
});
|
||||
Object.assign(newMachine, initMachine);
|
||||
}
|
||||
|
||||
function removeMachine(machinePCBID) {
|
||||
newArcade.machineList = newArcade.machineList.filter(
|
||||
(machine) => machine.PCBID !== machinePCBID
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLine
|
||||
:icon="mdiSailBoat"
|
||||
title="Arcade Onboarding"
|
||||
color="text-green-400"
|
||||
main
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<CardBox is-form class="row-span-1">
|
||||
<PillTag
|
||||
color="info"
|
||||
label="General Information"
|
||||
:icon="mdiInformationOutline"
|
||||
class="mb-2"
|
||||
/>
|
||||
<FormField label="Arcade Name">
|
||||
<FormControl v-model="newArcade.name" name="arcadeName" required />
|
||||
</FormField>
|
||||
<FormField
|
||||
label="Use Discord"
|
||||
help="Automated provisioning rather than manual. BadManiac will DM the user via Discord."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="newArcade.useDiscord"
|
||||
name="useDiscord"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
v-if="newArcade.useDiscord"
|
||||
label="Discord ID"
|
||||
help="Owner's Discord User ID."
|
||||
>
|
||||
<FormControl
|
||||
v-model="newArcade.discordId"
|
||||
name="discordId"
|
||||
required
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
v-if="!newArcade.useDiscord"
|
||||
v-model="newArcade.description"
|
||||
label="Description"
|
||||
help="Manual description rather than automated."
|
||||
>
|
||||
<FormControl name="description" required />
|
||||
</FormField>
|
||||
</CardBox>
|
||||
|
||||
<CardBox is-form class="row-span-1">
|
||||
<PillTag
|
||||
color="info"
|
||||
label="Settings"
|
||||
:icon="mdiCogOutline"
|
||||
class="mb-2"
|
||||
/>
|
||||
<div
|
||||
class="grid grid-cols-1 w-full gap-2 md:gap-6 md:flex md:place-content-stretch"
|
||||
>
|
||||
<FormField
|
||||
label="PASELI Enabled"
|
||||
help="Enable or disable PASELI services."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="newArcade.paseli"
|
||||
name="paseli"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
label="Infinite PASELI"
|
||||
help="Enable or disable infinite PASELI."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="newArcade.infinitePaseli"
|
||||
name="infinitePaseli"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
label="Maintenance Mode"
|
||||
help="Place this arcade under Maintenance."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="newArcade.maintenance"
|
||||
name="maintenance"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
label="Incognito Mode"
|
||||
help="Hide the eAmusement network and ranking data."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="newArcade.incognito"
|
||||
name="incognito"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
label="Beta Testing"
|
||||
help="Enable or disable beta testing."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="newArcade.betas"
|
||||
name="betas"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox class="row-span-1">
|
||||
<PillTag
|
||||
color="info"
|
||||
label="Machines"
|
||||
:icon="mdiInformationOutline"
|
||||
class="mb-2"
|
||||
/>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<form class="h-full" @submit.prevent="addMachine()">
|
||||
<FormField
|
||||
label="Generate PCBID"
|
||||
help="Generate a PCBID rather than supply one."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="newMachine.generatePCBID"
|
||||
name="generatePCBID"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField v-if="!newMachine.generatePCBID" label="PCBID">
|
||||
<FormControl
|
||||
v-model="newMachine.PCBID"
|
||||
name="pcbid"
|
||||
required
|
||||
minlength="20"
|
||||
maxlength="20"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Name">
|
||||
<FormControl v-model="newMachine.name" name="name" required />
|
||||
</FormField>
|
||||
|
||||
<div
|
||||
class="grid grid-cols-1 w-full gap-2 md:gap-6 md:flex md:place-content-stretch pb-4 md:pb-0"
|
||||
>
|
||||
<FormField
|
||||
label="Arcade Cabinet"
|
||||
help="If this is a real cabinet, enable this."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="newMachine.cabinet"
|
||||
name="cabinet"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
label="Enable Updates"
|
||||
help="Enables or disables OTA updates."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="newMachine.ota"
|
||||
name="ota"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<BaseButton color="success" type="submit" label="Add Machine" />
|
||||
</form>
|
||||
|
||||
<div>
|
||||
<PillTag color="warning" label="Queue" class="mb-4" />
|
||||
<div class="grid gap-4">
|
||||
<CardBox
|
||||
v-for="machine of newArcade.machineList"
|
||||
:key="machine.name"
|
||||
color-prop="bg-slate-800 dark:bg-slate-800"
|
||||
>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="m-[-5px]">
|
||||
<h1 class="text-md font-bold">{{ machine.name }}</h1>
|
||||
<h2 class="text-sm">
|
||||
{{ machine.cabinet ? "Cabinet" : "Setup"
|
||||
}}{{ machine.ota ? " - OTA Enabled" : null }}
|
||||
</h2>
|
||||
<h2 class="text-sm font-mono">
|
||||
{{
|
||||
machine.PCBID != "" ? machine.PCBID : "not generated"
|
||||
}}
|
||||
</h2>
|
||||
</div>
|
||||
<BaseButton
|
||||
color="danger"
|
||||
label="Remove"
|
||||
@click="removeMachine(machine.PCBID)"
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardBox>
|
||||
<div class="grid md:flex gap-4">
|
||||
<BaseButton
|
||||
type="submit"
|
||||
color="success"
|
||||
label="Create Arcade with Discord"
|
||||
:small="false"
|
||||
/>
|
||||
<BaseButton
|
||||
type="submit"
|
||||
color="success"
|
||||
label="Create Arcade with VPN"
|
||||
:small="false"
|
||||
/>
|
||||
<BaseButton
|
||||
type="submit"
|
||||
color="info"
|
||||
label="Create Arcade"
|
||||
:small="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
30
src/views/Admin/UsersView.vue
Normal file
30
src/views/Admin/UsersView.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script setup>
|
||||
import { mdiFlagCheckered, mdiSecurity } 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";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LayoutAuthenticated>
|
||||
<SectionMain>
|
||||
<SectionTitleLine
|
||||
:icon="mdiSecurity"
|
||||
title="Network Administration"
|
||||
color="text-red-600"
|
||||
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>
|
||||
|
||||
<SectionTitleLine :icon="mdiFlagCheckered" title="Recent Errors" />
|
||||
<CardBox> </CardBox>
|
||||
</SectionMain>
|
||||
</LayoutAuthenticated>
|
||||
</template>
|
||||
@@ -1,13 +1,13 @@
|
||||
<script setup>
|
||||
import { useRoute } from "vue-router";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { mdiStore } from "@mdi/js";
|
||||
import { mdiStore, mdiCogOutline } from "@mdi/js";
|
||||
import SectionMain from "@/components/SectionMain.vue";
|
||||
import LayoutAuthenticated from "@/layouts/LayoutAuthenticated.vue";
|
||||
import SectionTitleLine from "@/components/SectionTitleLine.vue";
|
||||
import ArcadeCard from "@/components/ArcadeCard.vue";
|
||||
import CardBox from "@/components/CardBox.vue";
|
||||
// import FormField from "@/components/FormField.vue";
|
||||
import FormField from "@/components/FormField.vue";
|
||||
// import FormFilePicker from "@/components/FormFilePicker.vue";
|
||||
import FormCheckRadio from "@/components/FormCheckRadio.vue";
|
||||
// import FormControl from "@/components/FormControl.vue";
|
||||
@@ -33,6 +33,41 @@ onMounted(async () => {
|
||||
console.log("Failed to fetch arcade data:", error);
|
||||
}
|
||||
});
|
||||
|
||||
function formatName(inputString, replaceWith = "NA_") {
|
||||
var asciiFriendly = [...inputString]
|
||||
.map((c) => {
|
||||
return c.charCodeAt(0) < 128 ? c : replaceWith;
|
||||
})
|
||||
.join("");
|
||||
|
||||
asciiFriendly = asciiFriendly.replace(
|
||||
/[!"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]/g,
|
||||
""
|
||||
);
|
||||
|
||||
asciiFriendly = asciiFriendly.replace(/\s+/g, "_");
|
||||
return asciiFriendly;
|
||||
}
|
||||
|
||||
async function exportVPN() {
|
||||
try {
|
||||
const data = await mainStore.getArcadeVPN(arcadeId);
|
||||
const blob = new Blob([data], { type: "application/ovpn" });
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = `gradius-${formatName(
|
||||
arcadeData.value.name
|
||||
)}-phaseii-config.ovpn`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
URL.revokeObjectURL(link.href);
|
||||
document.body.removeChild(link);
|
||||
} catch (error) {
|
||||
console.log("Failed to fetch arcade data:", error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -41,50 +76,64 @@ onMounted(async () => {
|
||||
<template v-if="!loading">
|
||||
<ArcadeCard class="mb-6" :arcade="arcadeData" />
|
||||
|
||||
<SectionTitleLine :icon="mdiStore" title="Admin Overview" main />
|
||||
<SectionTitleLine :icon="mdiStore" title="Arcade Overview" main />
|
||||
|
||||
<CardBox is-form class="mb-6" @submit.prevent="">
|
||||
<PillTag color="info" label="General Settings" class="mb-4" />
|
||||
<div class="grid gap-4 grid-cols-2 md:grid-cols-4 mb-4">
|
||||
<!-- <FormCheckRadio
|
||||
:model-value="arcadeData.public"
|
||||
:input-value="arcadeData.public"
|
||||
name="public"
|
||||
type="switch"
|
||||
label="Public Arcade"
|
||||
class="outline outline-gray-400 rounded-xl outline-1 text-sm md:text-md p-2"
|
||||
/> -->
|
||||
<FormCheckRadio
|
||||
:model-value="arcadeData.data.paseli_enabled"
|
||||
:input-value="arcadeData.data.paseli_enabled"
|
||||
name="paseli_enabled"
|
||||
type="switch"
|
||||
label="PASELI"
|
||||
class="outline outline-gray-400 rounded-xl outline-1 text-sm md:text-md p-2"
|
||||
/>
|
||||
<FormCheckRadio
|
||||
:model-value="arcadeData.data.paseli_infinite"
|
||||
:input-value="arcadeData.data.paseli_infinite"
|
||||
name="paseli_infinite"
|
||||
type="switch"
|
||||
label="Infinite PASELI"
|
||||
class="outline outline-gray-400 rounded-xl outline-1 text-sm md:text-md p-2"
|
||||
/>
|
||||
<FormCheckRadio
|
||||
:model-value="arcadeData.data.maint"
|
||||
:input-value="arcadeData.data.maint"
|
||||
name="maint"
|
||||
type="switch"
|
||||
label="Maintenance Mode"
|
||||
class="outline outline-gray-400 rounded-xl outline-1 text-sm md:text-md p-2"
|
||||
/>
|
||||
</div>
|
||||
<BaseButton
|
||||
color="success"
|
||||
type="submit"
|
||||
label="Save"
|
||||
:small="false"
|
||||
<PillTag
|
||||
color="info"
|
||||
label="Settings"
|
||||
:icon="mdiCogOutline"
|
||||
class="mb-2"
|
||||
/>
|
||||
<div
|
||||
class="grid grid-cols-1 w-full gap-2 md:gap-6 md:flex md:place-content-stretch"
|
||||
>
|
||||
<FormField
|
||||
label="PASELI Enabled"
|
||||
help="Enable or disable PASELI services."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="arcadeData.data.paseli_enabled"
|
||||
:input-value="arcadeData.data.paseli_enabled"
|
||||
name="paseli"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
label="Infinite PASELI"
|
||||
help="Enable or disable infinite PASELI."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="arcadeData.data.paseli_infinite"
|
||||
:input-value="arcadeData.data.paseli_infinite"
|
||||
name="infinitePaseli"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
label="Maintenance Mode"
|
||||
help="Place this arcade under Maintenance."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="arcadeData.data.maint"
|
||||
:input-value="arcadeData.data.maint"
|
||||
name="maintenance"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField
|
||||
label="Incognito Mode"
|
||||
help="Hide the eAmusement network and ranking data."
|
||||
>
|
||||
<FormCheckRadio
|
||||
v-model="arcadeData.data.hide_network"
|
||||
:input-value="arcadeData.data.hide_network"
|
||||
name="incognito"
|
||||
type="switch"
|
||||
/>
|
||||
</FormField>
|
||||
</div>
|
||||
<BaseButton color="success" type="submit" label="Save" />
|
||||
</CardBox>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 mb-6">
|
||||
@@ -98,6 +147,7 @@ onMounted(async () => {
|
||||
color="success"
|
||||
class="my-2"
|
||||
label="Export"
|
||||
@click="exportVPN()"
|
||||
/>
|
||||
</CardBox>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user