This commit is contained in:
Baobhan Sith
2025-04-25 21:54:13 +08:00
parent 5b3c6f325a
commit aa665d6609
3 changed files with 81 additions and 69 deletions
@@ -4,25 +4,28 @@
{{ $t('settings.notifications.title') }} {{ $t('settings.notifications.title') }}
</h2> </h2>
<!-- Loading state (Only show if loading AND no settings are displayed yet) --> <!-- Error state (Show first if error exists) -->
<div v-if="store.isLoading && settings.length === 0" class="p-4 text-center text-text-secondary italic"> <div v-if="store.error" class="p-4 mb-4 border-l-4 border-error bg-error/10 text-error rounded">
{{ $t('common.loading') }}
</div>
<div v-if="store.error" class="p-4 mb-4 border-l-4 border-error bg-error/10 text-error rounded"> <!-- Error state using error color -->
{{ store.error }} {{ store.error }}
</div> </div>
<div v-if="!store.isLoading && !store.error"> <!-- Add Button (Show if no error) -->
<button @click="showAddForm = true" class="px-4 py-2 bg-button text-button-text rounded hover:bg-button-hover mb-4 inline-flex items-center text-sm font-medium"> <!-- Add button --> <button v-if="!store.error" @click="showAddForm = true" class="px-4 py-2 bg-button text-button-text rounded hover:bg-button-hover mb-4 inline-flex items-center text-sm font-medium">
{{ $t('settings.notifications.addChannel') }} {{ $t('settings.notifications.addChannel') }}
</button> </button>
<div v-if="settings.length === 0" class="p-4 mb-4 border-l-4 border-blue-400 bg-blue-100 text-blue-700 rounded"> <!-- Info state (using blue for now) --> <!-- Loading state (Show only if loading AND settings empty AND no error) -->
{{ $t('settings.notifications.noChannels') }} <div v-if="store.isLoading && settings.length === 0 && !store.error" class="p-4 text-center text-text-secondary italic">
</div> {{ $t('common.loading') }}
</div>
<!-- Notification List --> <!-- Empty state (Show only if not loading, no error, and settings empty) -->
<div v-else class="grid gap-4 mt-4"> <div v-else-if="!store.isLoading && !store.error && settings.length === 0" class="p-4 mb-4 border-l-4 border-blue-400 bg-blue-100 text-blue-700 rounded">
{{ $t('settings.notifications.noChannels') }}
</div>
<!-- Notification List (Show if not loading, no error, and has settings) -->
<div v-else-if="!store.isLoading && !store.error && settings.length > 0" class="grid gap-4 mt-4">
<div v-for="setting in settings" :key="setting.id" class="bg-background border border-border rounded-lg p-4 flex justify-between items-start gap-4 shadow-sm hover:shadow-md transition-shadow duration-200"> <!-- List item card --> <div v-for="setting in settings" :key="setting.id" class="bg-background border border-border rounded-lg p-4 flex justify-between items-start gap-4 shadow-sm hover:shadow-md transition-shadow duration-200"> <!-- List item card -->
<div class="flex-grow"> <!-- Details section --> <div class="flex-grow"> <!-- Details section -->
<strong class="block font-semibold text-base mb-1 text-foreground">{{ setting.name }}</strong> <strong class="block font-semibold text-base mb-1 text-foreground">{{ setting.name }}</strong>
@@ -51,7 +54,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Add/Edit Form Section --> <!-- Add/Edit Form Section -->
<div v-if="showAddForm || editingSetting" class="mt-6 p-6 border border-border bg-background rounded-lg shadow-sm"> <!-- Form container --> <div v-if="showAddForm || editingSetting" class="mt-6 p-6 border border-border bg-background rounded-lg shadow-sm"> <!-- Form container -->
+14 -10
View File
@@ -29,19 +29,23 @@
</div> </div>
<!-- End Filtering Controls --> <!-- End Filtering Controls -->
<!-- Loading state (Only show if loading AND no logs are displayed yet) --> <!-- Error state -->
<div v-if="store.isLoading && logs.length === 0" class="p-4 text-center text-text-secondary italic"> <div v-if="store.error" class="p-4 mb-4 border-l-4 border-error bg-error/10 text-error rounded">
{{ $t('common.loading') }}
</div>
<div v-if="store.error" class="p-4 mb-4 border-l-4 border-error bg-error/10 text-error rounded"> <!-- Error state -->
{{ store.error }} {{ store.error }}
</div> </div>
<div v-if="!store.isLoading && !store.error"> <!-- Loading state (Only show if loading AND logs empty) -->
<div v-if="logs.length === 0" class="p-4 mb-4 border-l-4 border-blue-400 bg-blue-100 text-blue-700 rounded"> <!-- No logs state --> <div v-else-if="store.isLoading && logs.length === 0" class="p-4 text-center text-text-secondary italic">
{{ $t('auditLog.noLogs') }} {{ $t('common.loading') }}
</div> </div>
<div v-else>
<!-- No logs state (Show only if not loading, no error, and logs empty) -->
<div v-else-if="!store.isLoading && !store.error && logs.length === 0" class="p-4 mb-4 border-l-4 border-blue-400 bg-blue-100 text-blue-700 rounded">
{{ $t('auditLog.noLogs') }}
</div>
<!-- Table and Pagination (Show if not loading, no error, and logs exist) -->
<div v-else-if="!store.isLoading && !store.error && logs.length > 0">
<div class="border border-border rounded-lg overflow-hidden shadow-sm mt-4 bg-background"> <!-- Table container --> <div class="border border-border rounded-lg overflow-hidden shadow-sm mt-4 bg-background"> <!-- Table container -->
<div class="overflow-x-auto"> <!-- Allow horizontal scroll --> <div class="overflow-x-auto"> <!-- Allow horizontal scroll -->
<table class="min-w-full divide-y divide-border text-sm"> <!-- Table styling --> <table class="min-w-full divide-y divide-border text-sm"> <!-- Table styling -->
+50 -44
View File
@@ -6,19 +6,23 @@
{{ $t('settings.title') }} {{ $t('settings.title') }}
</h1> </h1>
<!-- General Settings Loading/Error --> <!-- Error state (Show first if error exists) -->
<div v-if="settingsLoading" class="p-4 text-center text-text-secondary italic"> <!-- Loading state --> <div v-if="settingsError" class="p-4 mb-4 border-l-4 border-error bg-error/10 text-error rounded">
{{ $t('common.loading') }}
</div>
<div v-if="settingsError" class="p-4 mb-4 border-l-4 border-error bg-error/10 text-error rounded"> <!-- Error state -->
{{ settingsError }} {{ settingsError }}
</div> </div>
<!-- Settings Sections Grid --> <!-- Settings Sections Grid (Render grid structure always if no error) -->
<div v-if="!settingsLoading && !settingsError" class="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div v-else class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Column 1: Security --> <!-- Loading state (Show inside grid area when loading) -->
<div class="lg:col-span-2 space-y-6"> <!-- Security takes 2 columns on large screens --> <div v-if="settingsLoading" class="lg:col-span-3 p-8 text-center text-text-secondary italic">
{{ $t('common.loading') }}
</div>
<!-- Actual Settings Content (Show only when not loading) -->
<template v-else>
<!-- Column 1: Security -->
<div class="lg:col-span-2 space-y-6"> <!-- Security takes 2 columns on large screens -->
<div class="bg-background border border-border rounded-lg shadow-sm overflow-hidden"> <div class="bg-background border border-border rounded-lg shadow-sm overflow-hidden">
<h2 class="text-lg font-semibold text-foreground px-6 py-4 border-b border-border bg-header/50">{{ $t('settings.category.security') }}</h2> <h2 class="text-lg font-semibold text-foreground px-6 py-4 border-b border-border bg-header/50">{{ $t('settings.category.security') }}</h2>
<div class="p-6 space-y-6"> <div class="p-6 space-y-6">
@@ -242,43 +246,45 @@
<hr class="border-border/50"> <hr class="border-border/50">
<!-- Blacklist table --> <!-- Blacklist table -->
<h3 class="text-base font-semibold text-foreground">{{ $t('settings.ipBlacklist.currentBannedTitle') }}</h3> <h3 class="text-base font-semibold text-foreground">{{ $t('settings.ipBlacklist.currentBannedTitle') }}</h3>
<!-- Loading state (Only show if loading AND no entries are displayed yet) --> <!-- Error state -->
<div v-if="ipBlacklist.loading && ipBlacklist.entries.length === 0" class="p-4 text-center text-text-secondary italic">{{ $t('settings.ipBlacklist.loadingList') }}</div>
<div v-if="ipBlacklist.error" class="p-3 border-l-4 border-error bg-error/10 text-error text-sm rounded">{{ ipBlacklist.error }}</div> <div v-if="ipBlacklist.error" class="p-3 border-l-4 border-error bg-error/10 text-error text-sm rounded">{{ ipBlacklist.error }}</div>
<div v-if="!ipBlacklist.loading && !ipBlacklist.error"> <!-- Loading state (Only show if loading AND no entries are displayed yet) -->
<div v-if="ipBlacklist.entries.length > 0" class="overflow-x-auto border border-border rounded-lg shadow-sm bg-background"> <div v-else-if="ipBlacklist.loading && ipBlacklist.entries.length === 0" class="p-4 text-center text-text-secondary italic">{{ $t('settings.ipBlacklist.loadingList') }}</div>
<table class="min-w-full divide-y divide-border text-sm"> <!-- Empty state (Show only if not loading, no error, and entries empty) -->
<thead class="bg-header"> <p v-else-if="!ipBlacklist.loading && !ipBlacklist.error && ipBlacklist.entries.length === 0" class="p-4 text-center text-text-secondary italic">{{ $t('settings.ipBlacklist.noBannedIps') }}</p>
<tr> <!-- Table (Show if not loading, no error, and has entries) -->
<th scope="col" class="px-4 py-2 text-left font-medium text-text-secondary tracking-wider whitespace-nowrap">{{ $t('settings.ipBlacklist.table.ipAddress') }}</th> <div v-else-if="!ipBlacklist.loading && !ipBlacklist.error && ipBlacklist.entries.length > 0" class="overflow-x-auto border border-border rounded-lg shadow-sm bg-background">
<th scope="col" class="px-4 py-2 text-left font-medium text-text-secondary tracking-wider whitespace-nowrap">{{ $t('settings.ipBlacklist.table.attempts') }}</th> <table class="min-w-full divide-y divide-border text-sm">
<th scope="col" class="px-4 py-2 text-left font-medium text-text-secondary tracking-wider whitespace-nowrap">{{ $t('settings.ipBlacklist.table.lastAttempt') }}</th> <thead class="bg-header">
<th scope="col" class="px-4 py-2 text-left font-medium text-text-secondary tracking-wider whitespace-nowrap">{{ $t('settings.ipBlacklist.table.bannedUntil') }}</th> <tr>
<th scope="col" class="px-4 py-2 text-left font-medium text-text-secondary tracking-wider whitespace-nowrap">{{ $t('settings.ipBlacklist.table.actions') }}</th> <th scope="col" class="px-4 py-2 text-left font-medium text-text-secondary tracking-wider whitespace-nowrap">{{ $t('settings.ipBlacklist.table.ipAddress') }}</th>
</tr> <th scope="col" class="px-4 py-2 text-left font-medium text-text-secondary tracking-wider whitespace-nowrap">{{ $t('settings.ipBlacklist.table.attempts') }}</th>
</thead> <th scope="col" class="px-4 py-2 text-left font-medium text-text-secondary tracking-wider whitespace-nowrap">{{ $t('settings.ipBlacklist.table.lastAttempt') }}</th>
<tbody class="divide-y divide-border"> <th scope="col" class="px-4 py-2 text-left font-medium text-text-secondary tracking-wider whitespace-nowrap">{{ $t('settings.ipBlacklist.table.bannedUntil') }}</th>
<tr v-for="entry in ipBlacklist.entries" :key="entry.ip" class="hover:bg-header/50"> <th scope="col" class="px-4 py-2 text-left font-medium text-text-secondary tracking-wider whitespace-nowrap">{{ $t('settings.ipBlacklist.table.actions') }}</th>
<td class="px-4 py-2 whitespace-nowrap">{{ entry.ip }}</td> </tr>
<td class="px-4 py-2 whitespace-nowrap">{{ entry.attempts }}</td> </thead>
<td class="px-4 py-2 whitespace-nowrap">{{ new Date(entry.last_attempt_at * 1000).toLocaleString() }}</td> <tbody class="divide-y divide-border">
<td class="px-4 py-2 whitespace-nowrap">{{ entry.blocked_until ? new Date(entry.blocked_until * 1000).toLocaleString() : $t('statusMonitor.notAvailable') }}</td> <tr v-for="entry in ipBlacklist.entries" :key="entry.ip" class="hover:bg-header/50">
<td class="px-4 py-2 whitespace-nowrap"> <td class="px-4 py-2 whitespace-nowrap">{{ entry.ip }}</td>
<button <td class="px-4 py-2 whitespace-nowrap">{{ entry.attempts }}</td>
@click="handleDeleteIp(entry.ip)" <td class="px-4 py-2 whitespace-nowrap">{{ new Date(entry.last_attempt_at * 1000).toLocaleString() }}</td>
:disabled="blacklistDeleteLoading && blacklistToDeleteIp === entry.ip" <td class="px-4 py-2 whitespace-nowrap">{{ entry.blocked_until ? new Date(entry.blocked_until * 1000).toLocaleString() : $t('statusMonitor.notAvailable') }}</td>
class="px-2 py-1 bg-error text-error-text rounded text-xs font-medium hover:bg-error/80 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-error disabled:opacity-50 disabled:cursor-not-allowed transition duration-150 ease-in-out" <td class="px-4 py-2 whitespace-nowrap">
> <button
{{ (blacklistDeleteLoading && blacklistToDeleteIp === entry.ip) ? $t('settings.ipBlacklist.table.deleting') : $t('settings.ipBlacklist.table.removeButton') }} @click="handleDeleteIp(entry.ip)"
</button> :disabled="blacklistDeleteLoading && blacklistToDeleteIp === entry.ip"
</td> class="px-2 py-1 bg-error text-error-text rounded text-xs font-medium hover:bg-error/80 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-error disabled:opacity-50 disabled:cursor-not-allowed transition duration-150 ease-in-out"
</tr> >
</tbody> {{ (blacklistDeleteLoading && blacklistToDeleteIp === entry.ip) ? $t('settings.ipBlacklist.table.deleting') : $t('settings.ipBlacklist.table.removeButton') }}
</table> </button>
</div> </td>
<p v-else class="p-4 text-center text-text-secondary italic">{{ $t('settings.ipBlacklist.noBannedIps') }}</p> </tr>
<p v-if="blacklistDeleteError" class="mt-3 text-sm text-error">{{ blacklistDeleteError }}</p> </tbody>
</table>
</div> </div>
<!-- Delete Error (Show regardless of loading state if present) -->
<p v-if="blacklistDeleteError" class="mt-3 text-sm text-error">{{ blacklistDeleteError }}</p>
</div> </div>
</div> </div>
</div> </div>