frontend
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-base-100 p-4">
|
||||
<!-- Breadcrumb and Title -->
|
||||
<BreadcrumbHeader />
|
||||
|
||||
<div class="flex flex-wrap gap-2 mb-4 justify-end items-center">
|
||||
|
||||
<button class="btn btn-outline btn-success btn-sm gap-1" onclick="myModal.showModal()">
|
||||
<PlusIcon class="w-3.5 h-3.5" />New
|
||||
</button>
|
||||
<dialog id="myModal" class="modal" ref="modalRef">
|
||||
<div class="modal-box w-11/12 max-w-5xl h-screen">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
</form>
|
||||
<TokenNew @closeModal="closeModal" />
|
||||
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>关闭</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="card card-bordered bg-base-100 shadow-sm mt-6" v-if="user">
|
||||
|
||||
<div class="card-body">
|
||||
<h3 class="card-title text-lg">Tokens</h3>
|
||||
<div v-if="user.tokens && user.tokens.length" class="overflow-x-auto -mx-6">
|
||||
<table class="table table-sm w-full">
|
||||
<thead>
|
||||
<tr class="text-xs text-base-content/70 uppercase bg-base-200">
|
||||
<th class="px-2 py-3">Token Name</th>
|
||||
<th class="px-2 py-3">Status</th>
|
||||
<th class="px-2 py-3">Key</th>
|
||||
<th class="px-2 py-3">Expired At</th>
|
||||
<th class="px-2 py-3">Quota</th>
|
||||
<th class="px-2 py-3">Used Quota</th>
|
||||
<th class="text-right px-2 py-3"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="token in user.tokens" :key="token.id" class="hover">
|
||||
<td class="font-mono text-xs px-2 py-3">{{ token.name }}</td>
|
||||
<td>
|
||||
<input type="checkbox" class="toggle toggle-xs"
|
||||
:class="token.active ? 'toggle-success' : 'toggle-error'" v-model="token.active"
|
||||
@change="updateStatus(token)" />
|
||||
</td>
|
||||
<td class="font-mono text-xs px-2 py-3">{{ token.key }}</td>
|
||||
<td class="px-2 py-3">{{ token.expired_at == 0 ? 'Never' : unixToDate(token.expired_at) }}</td>
|
||||
<td class="px-2 py-3">
|
||||
<template v-if="token.unlimited_quota">
|
||||
<Infinity />
|
||||
</template>
|
||||
<template v-else>{{ token.quota }}</template>
|
||||
</td>
|
||||
<td class="px-2 py-3">{{ token.used_quota }}</td>
|
||||
<td class="text-right px-2 py-3 flex justify-between items-center gap-1">
|
||||
<div class="md:tooltip" data-tip="clean usedquota">
|
||||
<button class="btn btn-ghost btn-xs btn-square text-sky-300" @click="cleanUsedToken(token)"
|
||||
aria-label="Revoke token">
|
||||
<Eraser class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button v-if="token.name !== 'default'" class="btn btn-ghost btn-xs btn-square text-error"
|
||||
@click="confirmRevokeToken(token)" aria-label="Revoke token">
|
||||
<TrashIcon class="w-4 h-4" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p v-else class="text-center text-base-content/70 py-4">No tokens found</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, inject, computed,watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import BreadcrumbHeader from '@/components/dashboard/BreadcrumbHeader.vue';
|
||||
import TokenNew from '@/views/dashboard/TokenNew.vue';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import {
|
||||
EyeIcon, PlusIcon, TrashIcon, Infinity, Eraser
|
||||
} from 'lucide-vue-next';
|
||||
import { unixToDate } from '@/utils/format-date';
|
||||
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
const user = computed(() => authStore.user);
|
||||
const { setToast } = inject('toast');
|
||||
|
||||
onMounted(async () => {
|
||||
await authStore.refreshProfile();
|
||||
})
|
||||
|
||||
watch(() => authStore.user, async (newUser) => {
|
||||
if (newUser.expired_at>0) {
|
||||
newUser.format_expired_at = unixToDate(newUser.expired_at);
|
||||
}
|
||||
})
|
||||
|
||||
const updateStatus = async (token) => {
|
||||
console.log(token);
|
||||
try {
|
||||
const res = await authStore.updateToken({ userid: token.userid, id: token.id, name: token.name, active: token.active });
|
||||
if (res.data?.code == 200) {
|
||||
setToast(`Token ${token.name} updated`, 'success');
|
||||
}
|
||||
} catch (error) {
|
||||
token.active = !token.active
|
||||
console.log(error.response.data.error);
|
||||
setToast(error.response.data.error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
const confirmRevokeToken = async (token) => {
|
||||
if (confirm(`确认删除 ${token.name}?`)) {
|
||||
await revokeToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
const revokeToken = async (token) => {
|
||||
try {
|
||||
const res = await authStore.deleteToken(token.id);
|
||||
if (res.data?.code == 200) {
|
||||
setToast(`Token ${token.name} revoked`, 'success');
|
||||
}
|
||||
await authStore.refreshProfile();
|
||||
|
||||
} catch (error) {
|
||||
setToast(error.response.data.error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
const cleanUsedToken = async (token) => {
|
||||
|
||||
if (token.used_quota == 0 || token.used_quota == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await authStore.resetToken(token.id);
|
||||
console.log('cleanUsedToken', res);
|
||||
if (res.data?.code == 200) {
|
||||
setToast(`Token ${token.name} used quota reset`, 'success');
|
||||
}
|
||||
await authStore.refreshProfile();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
setToast(error, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 关闭模态框
|
||||
const modalRef = ref(null);
|
||||
const closeModal = async () => {
|
||||
if (modalRef.value) {
|
||||
modalRef.value.close();
|
||||
}
|
||||
await authStore.refreshProfile();
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user