Optimizations
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m5s

This commit is contained in:
2026-01-19 20:55:47 -07:00
parent d4a2c5853b
commit 8a3932a013
7 changed files with 200 additions and 106 deletions

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref } from 'vue';
import { Icon } from '@iconify/vue';
import { ref, onMounted } from "vue";
import { Icon } from "@iconify/vue";
interface ApiToken {
id: string;
@@ -16,12 +16,17 @@ const props = defineProps<{
const tokens = ref<ApiToken[]>(props.initialTokens);
const createModalOpen = ref(false);
const showTokenModalOpen = ref(false);
const newTokenName = ref('');
const newTokenValue = ref('');
const newTokenName = ref("");
const newTokenValue = ref("");
const loading = ref(false);
const isMounted = ref(false);
onMounted(() => {
isMounted.value = true;
});
function formatDate(dateString: string | null) {
if (!dateString) return 'Never';
if (!dateString) return "Never";
return new Date(dateString).toLocaleDateString();
}
@@ -30,10 +35,10 @@ async function createToken() {
loading.value = true;
try {
const response = await fetch('/api/user/tokens', {
method: 'POST',
const response = await fetch("/api/user/tokens", {
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify({ name: newTokenName.value }),
});
@@ -48,42 +53,46 @@ async function createToken() {
id: tokenMeta.id,
name: tokenMeta.name,
lastUsedAt: tokenMeta.lastUsedAt,
createdAt: tokenMeta.createdAt
createdAt: tokenMeta.createdAt,
});
newTokenValue.value = token;
createModalOpen.value = false;
showTokenModalOpen.value = true;
newTokenName.value = '';
newTokenName.value = "";
} else {
alert('Failed to create token');
alert("Failed to create token");
}
} catch (error) {
console.error('Error creating token:', error);
alert('An error occurred');
console.error("Error creating token:", error);
alert("An error occurred");
} finally {
loading.value = false;
}
}
async function deleteToken(id: string) {
if (!confirm('Are you sure you want to revoke this token? Any applications using it will stop working.')) {
if (
!confirm(
"Are you sure you want to revoke this token? Any applications using it will stop working.",
)
) {
return;
}
try {
const response = await fetch(`/api/user/tokens/${id}`, {
method: 'DELETE'
method: "DELETE",
});
if (response.ok) {
tokens.value = tokens.value.filter(t => t.id !== id);
tokens.value = tokens.value.filter((t) => t.id !== id);
} else {
alert('Failed to delete token');
alert("Failed to delete token");
}
} catch (error) {
console.error('Error deleting token:', error);
alert('An error occurred');
console.error("Error deleting token:", error);
alert("An error occurred");
}
}
@@ -93,7 +102,7 @@ function copyToken() {
function closeShowTokenModal() {
showTokenModalOpen.value = false;
newTokenValue.value = '';
newTokenValue.value = "";
}
</script>
@@ -103,10 +112,16 @@ function closeShowTokenModal() {
<div class="card-body p-4 sm:p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="card-title text-lg sm:text-xl">
<Icon icon="heroicons:code-bracket-square" class="w-5 h-5 sm:w-6 sm:h-6" />
<Icon
icon="heroicons:code-bracket-square"
class="w-5 h-5 sm:w-6 sm:h-6"
/>
API Tokens
</h2>
<button class="btn btn-primary btn-sm" @click="createModalOpen = true">
<button
class="btn btn-primary btn-sm"
@click="createModalOpen = true"
>
<Icon icon="heroicons:plus" class="w-4 h-4" />
Create Token
</button>
@@ -131,10 +146,16 @@ function closeShowTokenModal() {
<tr v-else v-for="token in tokens" :key="token.id">
<td class="font-medium">{{ token.name }}</td>
<td class="text-sm">
{{ formatDate(token.lastUsedAt) }}
<span v-if="isMounted">{{
formatDate(token.lastUsedAt)
}}</span>
<span v-else>{{ token.lastUsedAt || "Never" }}</span>
</td>
<td class="text-sm">
{{ formatDate(token.createdAt) }}
<span v-if="isMounted">{{
formatDate(token.createdAt)
}}</span>
<span v-else>{{ token.createdAt }}</span>
</td>
<td>
<button
@@ -175,15 +196,24 @@ function closeShowTokenModal() {
</div>
<div class="modal-action">
<button type="button" class="btn" @click="createModalOpen = false">Cancel</button>
<button type="button" class="btn" @click="createModalOpen = false">
Cancel
</button>
<button type="submit" class="btn btn-primary" :disabled="loading">
<span v-if="loading" class="loading loading-spinner loading-sm"></span>
<span
v-if="loading"
class="loading loading-spinner loading-sm"
></span>
Generate Token
</button>
</div>
</form>
</div>
<form method="dialog" class="modal-backdrop" @click="createModalOpen = false">
<form
method="dialog"
class="modal-backdrop"
@click="createModalOpen = false"
>
<button>close</button>
</form>
</dialog>
@@ -196,10 +226,13 @@ function closeShowTokenModal() {
Token Created
</h3>
<p class="py-4">
Make sure to copy your personal access token now. You won't be able to see it again!
Make sure to copy your personal access token now. You won't be able to
see it again!
</p>
<div class="bg-base-200 p-4 rounded-lg break-all font-mono text-sm relative group">
<div
class="bg-base-200 p-4 rounded-lg break-all font-mono text-sm relative group"
>
<span>{{ newTokenValue }}</span>
<button
class="absolute top-2 right-2 btn btn-xs btn-ghost opacity-0 group-hover:opacity-100 transition-opacity"
@@ -211,11 +244,13 @@ function closeShowTokenModal() {
</div>
<div class="modal-action">
<button class="btn btn-primary" @click="closeShowTokenModal">Done</button>
<button class="btn btn-primary" @click="closeShowTokenModal">
Done
</button>
</div>
</div>
<form method="dialog" class="modal-backdrop" @click="closeShowTokenModal">
<button>close</button>
<button>close</button>
</form>
</dialog>
</div>