271 lines
6.7 KiB
Vue
271 lines
6.7 KiB
Vue
<script setup>
|
|
import ExpandSection from "@/components/settings/expand-section.vue";
|
|
import KeyCard from "@/components/settings/key-card.vue";
|
|
import TimetableCard from "@/components/settings/timetable-card.vue";
|
|
import { baseUrl } from "@/store";
|
|
import { PlusIcon, SaveIcon, XIcon, RefreshCwIcon } from "lucide-vue-next";
|
|
import { ref } from "vue";
|
|
|
|
function confirm(message) {
|
|
return window.confirm(message);
|
|
}
|
|
|
|
/* General */
|
|
async function fetchObjects(type) {
|
|
const response = await fetch(baseUrl + "/admin/" + type);
|
|
if (response.status != 200) return;
|
|
return await response.json();
|
|
}
|
|
|
|
async function deleteObject(type, id) {
|
|
const response = await fetch(
|
|
baseUrl + `/admin/${type}?id=${encodeURIComponent(id)}`,
|
|
{ method: "delete" }
|
|
);
|
|
if (response.status != 200) alert("Delete failed!");
|
|
updateData();
|
|
}
|
|
|
|
async function createObject(type, data) {
|
|
const response = await fetch(baseUrl + "/admin/" + type, {
|
|
method: "post",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(data),
|
|
});
|
|
if (response.status != 201) alert("Post failed!");
|
|
updateData();
|
|
}
|
|
|
|
async function updateObject(type, id, data) {
|
|
const response = await fetch(
|
|
baseUrl + `/admin/${type}?id=${encodeURIComponent(id)}`,
|
|
{
|
|
method: "put",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(data),
|
|
}
|
|
);
|
|
if (response.status != 201) alert("Post failed!");
|
|
updateData();
|
|
}
|
|
|
|
/* Timetable */
|
|
const timetables = ref([]);
|
|
const timetableEditId = ref(-1);
|
|
const timetableName = ref();
|
|
const timetableClass = ref();
|
|
const timetableSource = ref();
|
|
const timetableTrusted = ref(true);
|
|
async function createTimetable() {
|
|
return await createObject("timetable", {
|
|
title: timetableName.value,
|
|
class: timetableClass.value,
|
|
source: timetableSource.value,
|
|
trusted: timetableTrusted.value,
|
|
data: [],
|
|
});
|
|
}
|
|
async function updateTimetable() {
|
|
return await updateObject("timetable", timetableEditId.value, {
|
|
title: timetableName.value,
|
|
class: timetableClass.value,
|
|
source: timetableSource.value,
|
|
trusted: timetableTrusted.value,
|
|
});
|
|
}
|
|
|
|
/* Key */
|
|
const keys = ref([]);
|
|
const keyEditId = ref(-1);
|
|
const keyId = ref();
|
|
const keyPermissions = ref();
|
|
const keyNotes = ref();
|
|
async function createKey() {
|
|
return await createObject("key", {
|
|
key: keyId.value,
|
|
permissions: keyPermissions.value,
|
|
notes: keyNotes.value,
|
|
});
|
|
}
|
|
async function updateKey() {
|
|
return await updateObject("key", keyEditId.value, {
|
|
key: keyId.value,
|
|
permissions: keyPermissions.value.split(","),
|
|
notes: keyNotes.value,
|
|
});
|
|
}
|
|
function regenerateId() {
|
|
keyId.value = Math.random().toString(36).slice(-8);
|
|
}
|
|
|
|
async function updateData() {
|
|
timetables.value = await fetchObjects("timetable");
|
|
keys.value = await fetchObjects("key");
|
|
}
|
|
updateData();
|
|
</script>
|
|
|
|
<template>
|
|
<h1>Admin Settings</h1>
|
|
<ExpandSection title="Timetables">
|
|
<div class="list">
|
|
<div class="create_options">
|
|
<input type="text" placeholder="Name" v-model="timetableName" />
|
|
<input type="text" placeholder="Class" v-model="timetableClass" />
|
|
<input type="text" placeholder="Source" v-model="timetableSource" />
|
|
<div>
|
|
<input type="checkbox" v-model="timetableTrusted" />
|
|
<span>Trusted</span>
|
|
</div>
|
|
<div
|
|
class="button"
|
|
v-if="timetableEditId == -1"
|
|
@click="createTimetable()"
|
|
>
|
|
<PlusIcon />
|
|
<span>Create Timetable</span>
|
|
</div>
|
|
<div
|
|
class="button"
|
|
v-if="timetableEditId != -1"
|
|
@click="updateTimetable()"
|
|
>
|
|
<SaveIcon />
|
|
<span>Save Timetable</span>
|
|
</div>
|
|
<div
|
|
class="button"
|
|
v-if="timetableEditId != -1"
|
|
@click="timetableEditId = -1"
|
|
>
|
|
<XIcon />
|
|
<span>Cancel edit</span>
|
|
</div>
|
|
</div>
|
|
<TimetableCard
|
|
v-for="timetable in timetables"
|
|
:key="timetable"
|
|
:timetable="timetable"
|
|
:editable="true"
|
|
:selected="timetable.id == timetableEditId"
|
|
:admin="true"
|
|
@delete="deleteObject('timetable', timetable.id)"
|
|
@edit="
|
|
() => {
|
|
timetableEditId = timetable.id;
|
|
timetableName = timetable.title;
|
|
timetableClass = timetable.class;
|
|
timetableSource = timetable.source;
|
|
timetableTrusted = timetable.trusted;
|
|
}
|
|
"
|
|
/>
|
|
</div>
|
|
</ExpandSection>
|
|
<ExpandSection title="Keys">
|
|
<div class="list">
|
|
<div class="create_options">
|
|
<div class="inline">
|
|
<input type="text" placeholder="Key" v-model="keyId" />
|
|
<RefreshCwIcon
|
|
:size="24"
|
|
class="button inline"
|
|
@click="regenerateId()"
|
|
/>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
placeholder="Permissions (eg. perm1,perm2)"
|
|
v-model="keyPermissions"
|
|
/>
|
|
<input type="text" placeholder="Notes" v-model="keyNotes" />
|
|
<div class="button" v-if="keyEditId == -1" @click="createKey()">
|
|
<PlusIcon />
|
|
<span>Create Key</span>
|
|
</div>
|
|
<div class="button" v-if="keyEditId != -1" @click="updateKey()">
|
|
<SaveIcon />
|
|
<span>Save Key</span>
|
|
</div>
|
|
<div class="button" v-if="keyEditId != -1" @click="keyEditId = -1">
|
|
<XIcon />
|
|
<span>Cancel edit</span>
|
|
</div>
|
|
</div>
|
|
<KeyCard
|
|
:edit="true"
|
|
:keyData="key"
|
|
v-for="key in keys"
|
|
:key="key"
|
|
@delete="
|
|
() => {
|
|
if (
|
|
key.permissions.includes('admin') &&
|
|
!confirm('Are you sure you want to delete an admin key?')
|
|
) {
|
|
return;
|
|
}
|
|
deleteObject('key', key.key);
|
|
}
|
|
"
|
|
@edit="
|
|
() => {
|
|
keyEditId = key.key;
|
|
keyId = key.key;
|
|
keyPermissions = key.permissions.join(',');
|
|
keyNotes = key.notes;
|
|
}
|
|
"
|
|
/>
|
|
</div>
|
|
</ExpandSection>
|
|
</template>
|
|
|
|
<style scoped>
|
|
h1 {
|
|
margin: 0px 0px 10px;
|
|
}
|
|
|
|
.list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
}
|
|
|
|
.button {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 10px;
|
|
background-color: var(--element-color);
|
|
padding: 5px 10px;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
.create_options {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 10px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.inline {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto;
|
|
box-sizing: border-box;
|
|
gap: 5px;
|
|
width: 100%;
|
|
}
|
|
|
|
.inline .button {
|
|
padding: 5px;
|
|
}
|
|
</style>
|