Files
hotline-planner/dev/backend/HotlinePlanner profile pic and bg worker/Views/Account/Tokens.cshtml
2026-02-24 13:32:01 +01:00

158 lines
5.8 KiB
Plaintext

@{
ViewData["Title"] = "Token Store";
}
<div class="animate__animated animate__fadeIn">
<div class="row items-center q-mb-lg">
<div class="col">
<h1 class="text-h4 text-weight-bold text-primary q-ma-none">Identity Store</h1>
<div class="text-subtitle2 text-grey-6 uppercase q-mt-xs" style="letter-spacing: 1px">Refresh Token Management</div>
</div>
<div class="col-auto">
<q-btn flat round color="primary" icon="refresh" v-on:click="onRequest({ pagination })">
<q-tooltip>Refresh Data</q-tooltip>
</q-btn>
</div>
</div>
<q-card flat class="clean-card overflow-hidden">
<q-table
:rows="rows"
:columns="columns"
row-key="id"
v-model:pagination="pagination"
:loading="loading"
:filter="filter"
binary-state-sort
v-on:request="onRequest"
flat
bordered
class="no-shadow"
:rows-per-page-options="[10, 20, 50]"
>
<template v-slot:top-right>
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search tokens...">
<template v-slot:append>
<q-icon name="search"></q-icon>
</template>
</q-input>
</template>
<template v-slot:body-cell-provider="props">
<q-td :props="props">
<div class="row items-center">
<q-icon :name="getProviderIcon(props.value)"
:color="getProviderColor(props.value)"
size="20px"
class="q-mr-sm"></q-icon>
{{ props.value }}
</div>
</q-td>
</template>
<template v-slot:body-cell-enabled="props">
<q-td :props="props" class="text-center">
<q-icon :name="props.value ? 'check_circle' : 'cancel'"
:color="props.value ? 'positive' : 'grey-5'"
size="20px"></q-icon>
</q-td>
</template>
<template v-slot:body-cell-lastUpdated="props">
<q-td :props="props">
{{ formatDate(props.value) }}
</q-td>
</template>
</q-table>
</q-card>
</div>
<style>
.uppercase { text-transform: uppercase; font-size: 0.7rem; }
.clean-card { border-radius: 12px; }
</style>
@section Scripts {
<script>
app.mixin({
data() {
return {
filter: '',
loading: false,
pagination: {
sortBy: 'lastUpdated',
descending: true,
page: 1,
rowsPerPage: 10,
rowsNumber: 0
},
columns: [
{ name: 'userId', label: 'User ID (GUID)', field: 'userId', align: 'left', sortable: true },
{ name: 'userEmail', label: 'Email / Account ID', field: 'userEmail', align: 'left', sortable: true },
{ name: 'tenant', label: 'Tenant ID / Domain', field: 'tenant', align: 'left', sortable: true },
{ name: 'provider', label: 'Provider', field: 'provider', align: 'left', sortable: true },
{ name: 'lastUpdated', label: 'Last Updated (UTC)', field: 'lastUpdated', align: 'left', sortable: true },
{ name: 'enabled', label: 'Active', field: 'enabled', align: 'center', sortable: true }
],
rows: []
}
},
mounted() {
this.onRequest({
pagination: this.pagination,
filter: this.filter
});
},
methods: {
async onRequest(props) {
const { page, rowsPerPage, sortBy, descending } = props.pagination;
const filter = props.filter;
this.loading = true;
try {
const url = `/Account/GetTokensData?page=${page}&rowsPerPage=${rowsPerPage}&filter=${filter || ''}`;
const response = await fetch(url);
const data = await response.json();
this.rows = data.items;
this.pagination.rowsNumber = data.totalItems;
this.pagination.page = page;
this.pagination.rowsPerPage = rowsPerPage;
this.pagination.sortBy = sortBy;
this.pagination.descending = descending;
} catch (error) {
console.error('Fetch error:', error);
this.$q.notify({
color: 'negative',
message: 'Failed to load tokens',
icon: 'report_problem'
});
} finally {
this.loading = false;
}
},
getProviderIcon(provider) {
switch (provider?.toLowerCase()) {
case 'microsoft': return 'widgets';
case 'google': return 'login';
default: return 'help';
}
},
getProviderColor(provider) {
switch (provider?.toLowerCase()) {
case 'microsoft': return 'blue-7';
case 'google': return 'positive';
default: return 'grey-7';
}
},
formatDate(val) {
if (!val) return '—';
const date = new Date(val);
return date.toLocaleString();
}
}
});
</script>
}