✨ Add key management to admin settings
This commit is contained in:
@ -1,71 +1,112 @@
|
||||
<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 } from "lucide-vue-next";
|
||||
import { PlusIcon, SaveIcon, XIcon, RefreshCwIcon } from "lucide-vue-next";
|
||||
import { ref } from "vue";
|
||||
|
||||
const timetables = ref([]);
|
||||
async function fetchTimetables() {
|
||||
const response = await fetch(baseUrl + "/admin/timetable");
|
||||
if (response.status != 200) return;
|
||||
timetables.value = await response.json();
|
||||
function confirm(message) {
|
||||
return window.confirm(message);
|
||||
}
|
||||
|
||||
async function deleteTimetable(id) {
|
||||
/* 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/timetable?id=" + encodeURIComponent(id),
|
||||
baseUrl + `/admin/${type}?id=${encodeURIComponent(id)}`,
|
||||
{ method: "delete" }
|
||||
);
|
||||
if (response.status != 200) alert("Delete failed!");
|
||||
fetchTimetables();
|
||||
updateData();
|
||||
}
|
||||
|
||||
const editId = ref(-1);
|
||||
const timetableName = ref();
|
||||
const timetableClass = ref();
|
||||
const timetableSource = ref();
|
||||
const timetableTrusted = ref(true);
|
||||
|
||||
async function createTimetable() {
|
||||
const response = await fetch(baseUrl + "/admin/timetable", {
|
||||
async function createObject(type, data) {
|
||||
const response = await fetch(baseUrl + "/admin/" + type, {
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: timetableName.value,
|
||||
class: timetableClass.value,
|
||||
source: timetableSource.value,
|
||||
trusted: timetableTrusted.value,
|
||||
data: [],
|
||||
}),
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (response.status != 201) alert("Post failed!");
|
||||
fetchTimetables();
|
||||
updateData();
|
||||
}
|
||||
|
||||
async function updateTimetable() {
|
||||
async function updateObject(type, id, data) {
|
||||
const response = await fetch(
|
||||
baseUrl + "/admin/timetable?id=" + encodeURIComponent(editId.value),
|
||||
baseUrl + `/admin/${type}?id=${encodeURIComponent(id)}`,
|
||||
{
|
||||
method: "put",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
title: timetableName.value,
|
||||
class: timetableClass.value,
|
||||
source: timetableSource.value,
|
||||
trusted: timetableTrusted.value,
|
||||
}),
|
||||
body: JSON.stringify(data),
|
||||
}
|
||||
);
|
||||
if (response.status != 201) alert("Post failed!");
|
||||
fetchTimetables();
|
||||
updateData();
|
||||
}
|
||||
|
||||
fetchTimetables();
|
||||
/* 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>
|
||||
@ -80,15 +121,27 @@ fetchTimetables();
|
||||
<input type="checkbox" v-model="timetableTrusted" />
|
||||
<span>Trusted</span>
|
||||
</div>
|
||||
<div class="button" v-if="editId == -1" @click="createTimetable()">
|
||||
<div
|
||||
class="button"
|
||||
v-if="timetableEditId == -1"
|
||||
@click="createTimetable()"
|
||||
>
|
||||
<PlusIcon />
|
||||
<span>Create Timetable</span>
|
||||
</div>
|
||||
<div class="button" v-if="editId != -1" @click="updateTimetable()">
|
||||
<div
|
||||
class="button"
|
||||
v-if="timetableEditId != -1"
|
||||
@click="updateTimetable()"
|
||||
>
|
||||
<SaveIcon />
|
||||
<span>Save Timetable</span>
|
||||
</div>
|
||||
<div class="button" v-if="editId != -1" @click="editId = -1">
|
||||
<div
|
||||
class="button"
|
||||
v-if="timetableEditId != -1"
|
||||
@click="timetableEditId = -1"
|
||||
>
|
||||
<XIcon />
|
||||
<span>Cancel edit</span>
|
||||
</div>
|
||||
@ -98,12 +151,12 @@ fetchTimetables();
|
||||
:key="timetable"
|
||||
:timetable="timetable"
|
||||
:editable="true"
|
||||
:selected="timetable.id == editId"
|
||||
:selected="timetable.id == timetableEditId"
|
||||
:admin="true"
|
||||
@delete="deleteTimetable(timetable.id)"
|
||||
@delete="deleteObject('timetable', timetable.id)"
|
||||
@edit="
|
||||
() => {
|
||||
editId = timetable.id;
|
||||
timetableEditId = timetable.id;
|
||||
timetableName = timetable.title;
|
||||
timetableClass = timetable.class;
|
||||
timetableSource = timetable.source;
|
||||
@ -113,6 +166,63 @@ fetchTimetables();
|
||||
/>
|
||||
</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>
|
||||
@ -138,11 +248,23 @@ h1 {
|
||||
|
||||
.create_options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
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>
|
||||
|
Reference in New Issue
Block a user