Add in network admin pages, vpn exporting

This commit is contained in:
Trenton Zimmer
2024-09-13 23:19:45 -04:00
parent 8da696f857
commit d23c2e494f
18 changed files with 824 additions and 57 deletions

View File

@@ -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);

View File

@@ -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"

View File

@@ -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>

View File

@@ -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
View 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;
}

View File

@@ -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>

View File

@@ -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",

View File

@@ -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`);

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>