213 lines
6.8 KiB
Vue
213 lines
6.8 KiB
Vue
<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 px-0 sm:px-8">
|
|
<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">
|
|
|
|
<div class="card bg-base-100 shadow-xs overflow-x-auto dark:bg-base-200" v-if="user">
|
|
<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</th>
|
|
<th class="px-2 py-3">Status</th>
|
|
<!-- <th class="px-2 py-3">Key</th> -->
|
|
<th class="px-2 py-3">Expired</th>
|
|
<th class="px-2 py-3">Quota</th>
|
|
<th class="px-2 py-3">Used</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-1 py-3">
|
|
<div class="flex items-center gap-2.5">
|
|
<div class="lg:tooltip lg:tooltip-top lg:tooltip-open pt-1" data-tip="预览">
|
|
<button class="btn btn-ghost btn-xs btn-square" onclick="" @click="viewToken(token)">
|
|
<EyeIcon class="w-5 h-5 dark:text-white" />
|
|
</button>
|
|
</div>
|
|
|
|
<div class="md:tooltip" data-tip="clean used">
|
|
<button class="btn btn-ghost btn-xs btn-square text-sky-300 mt-1" @click="cleanUsedToken(token)"
|
|
aria-label="Revoke token">
|
|
<Eraser class="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
|
|
<button v-if="token.name !== 'default'"
|
|
class="btn btn-ghost btn-xs btn-square text-error items-center" @click="confirmRevokeToken(token)"
|
|
aria-label="Revoke token">
|
|
<TrashIcon class="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<dialog id="myToken" class="modal" ref="tokenRef">
|
|
<div class="modal-box px-0 sm:px-8">
|
|
<form method="dialog">
|
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
|
</form>
|
|
<QRCodeCard :value="qrCodeValue" :size="120" />
|
|
</div>
|
|
<form method="dialog" class="modal-backdrop">
|
|
<button>关闭</button>
|
|
</form>
|
|
</dialog>
|
|
</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 QRCodeCard from '@/components/QRCodeCard.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 showTokenModel = ref(false);
|
|
const tokenRef = ref(null);
|
|
const viewToken = (token) => {
|
|
const dialog = tokenRef.value;
|
|
if (dialog) {
|
|
if (!dialog.hasAttribute('open')) {
|
|
qrCodeValue.value = token.key;
|
|
dialog.showModal();
|
|
} else {
|
|
if (dialog.hasAttribute('open')) {
|
|
dialog.close();
|
|
}
|
|
}
|
|
}
|
|
showTokenModel.value = !showTokenModel.value
|
|
}
|
|
|
|
const qrCodeValue = ref('');
|
|
|
|
|
|
// 关闭模态框
|
|
const modalRef = ref(null);
|
|
const closeModal = async () => {
|
|
if (modalRef.value) {
|
|
modalRef.value.close();
|
|
}
|
|
await authStore.refreshProfile();
|
|
};
|
|
</script>
|