This commit is contained in:
Baobhan Sith
2025-04-23 16:51:02 +08:00
parent 36618099d4
commit b5b0e7348d
7 changed files with 501 additions and 666 deletions
+6
View File
@@ -434,6 +434,12 @@
},
"settings": {
"title": "Settings",
"category": {
"security": "Security Settings",
"appearance": "Appearance Settings",
"workspace": "Workspace Settings",
"system": "System Settings"
},
"changePassword": {
"title": "Change Password",
"currentPassword": "Current Password:",
+6
View File
@@ -434,6 +434,12 @@
},
"settings": {
"title": "设置",
"category": {
"security": "安全设置",
"appearance": "外观设置",
"workspace": "工作区设置",
"system": "系统设置"
},
"changePassword": {
"title": "修改密码",
"currentPassword": "当前密码:",
+10 -4
View File
@@ -11,7 +11,12 @@ export const useAuditLogStore = defineStore('auditLog', () => {
const currentPage = ref(1);
const logsPerPage = ref(50); // Default page size
const fetchLogs = async (page: number = 1, filters: { actionType?: AuditLogActionType, startDate?: number, endDate?: number } = {}) => {
// Updated fetchLogs to accept searchTerm and actionType directly
const fetchLogs = async (
page: number = 1,
searchTerm?: string,
actionType?: AuditLogActionType | '' // Allow empty string from select
) => {
isLoading.value = true;
error.value = null;
currentPage.value = page;
@@ -21,10 +26,11 @@ export const useAuditLogStore = defineStore('auditLog', () => {
const params: Record<string, any> = {
limit: logsPerPage.value,
offset: offset,
...filters // Spread filter parameters
// Add new filter parameters if they have values
...(searchTerm && { search: searchTerm }),
...(actionType && { action_type: actionType }),
};
// Remove undefined filter values
Object.keys(params).forEach(key => params[key] === undefined && delete params[key]);
// No need to remove undefined keys here as we conditionally add them
const response = await apiClient.get<AuditLogApiResponse>('/audit-logs', { params }); // 使用 apiClient
logs.value = response.data.logs;
+58 -2
View File
@@ -5,7 +5,29 @@
{{ $t('auditLog.title') }}
</h1>
<!-- TODO: Add filtering options (Action Type, Date Range) -->
<!-- Filtering Controls -->
<div class="flex flex-wrap items-center gap-4 mb-4 p-4 border border-border rounded-lg bg-header/50">
<div class="flex-grow min-w-[200px]">
<label for="search-term" class="block text-sm font-medium text-text-secondary mb-1">{{ $t('common.search') }}</label>
<input type="text" id="search-term" v-model="searchTerm" :placeholder="$t('auditLog.searchPlaceholder')"
class="w-full px-3 py-2 border border-border rounded-md shadow-sm bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary text-sm">
</div>
<div class="flex-grow min-w-[200px]">
<label for="action-type" class="block text-sm font-medium text-text-secondary mb-1">{{ $t('auditLog.table.actionType') }}</label>
<select id="action-type" v-model="selectedActionType"
class="w-full px-3 py-2 border border-border rounded-md shadow-sm bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary appearance-none bg-no-repeat bg-right pr-8 text-sm"
style="background-image: url('data:image/svg+xml,%3csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 16 16\'%3e%3cpath fill=\'none\' stroke=\'%236c757d\' stroke-linecap=\'round\' stroke-linejoin=\'round\' stroke-width=\'2\' d=\'M2 5l6 6 6-6\'/%3e%3c/svg%3e'); background-position: right 0.75rem center; background-size: 16px 12px;">
<option value="">{{ $t('common.all') }}</option>
<option v-for="type in allActionTypes" :key="type" :value="type">{{ translateActionType(type) }}</option>
</select>
</div>
<div class="self-end">
<button @click="applyFilters" class="px-4 py-2 bg-button text-button-text rounded hover:bg-button-hover text-sm font-medium">
{{ $t('common.filter') }}
</button>
</div>
</div>
<!-- End Filtering Controls -->
<div v-if="store.isLoading" class="p-4 text-center text-text-secondary italic"> <!-- Loading state -->
{{ $t('common.loading') }}
@@ -76,14 +98,35 @@
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { ref, onMounted, computed } from 'vue'; // Removed watch
import { useAuditLogStore } from '../stores/audit.store';
import { AuditLogEntry, AuditLogActionType } from '../types/server.types';
import { useI18n } from 'vue-i18n';
// Removed lodash-es import
const store = useAuditLogStore();
const { t } = useI18n();
// --- Filtering State ---
const searchTerm = ref('');
const selectedActionType = ref<AuditLogActionType | ''>(''); // Allow empty string for 'All'
// Define all possible action types for the dropdown
const allActionTypes: AuditLogActionType[] = [
'LOGIN_SUCCESS', 'LOGIN_FAILURE', 'LOGOUT', 'PASSWORD_CHANGED',
'2FA_ENABLED', '2FA_DISABLED', 'PASSKEY_REGISTERED', 'PASSKEY_DELETED',
'CONNECTION_CREATED', 'CONNECTION_UPDATED', 'CONNECTION_DELETED', 'CONNECTION_TESTED',
'CONNECTIONS_IMPORTED', 'CONNECTIONS_EXPORTED',
'PROXY_CREATED', 'PROXY_UPDATED', 'PROXY_DELETED',
'TAG_CREATED', 'TAG_UPDATED', 'TAG_DELETED',
'SETTINGS_UPDATED', 'IP_WHITELIST_UPDATED',
'NOTIFICATION_SETTING_CREATED', 'NOTIFICATION_SETTING_UPDATED', 'NOTIFICATION_SETTING_DELETED',
'API_KEY_CREATED', 'API_KEY_DELETED',
'SFTP_ACTION',
'SERVER_STARTED', 'SERVER_ERROR', 'DATABASE_MIGRATION'
];
// --- End Filtering State ---
const logs = computed(() => store.logs);
const totalLogs = computed(() => store.totalLogs);
const currentPage = computed(() => store.currentPage);
@@ -91,7 +134,20 @@ const logsPerPage = computed(() => store.logsPerPage);
const totalPages = computed(() => Math.ceil(totalLogs.value / logsPerPage.value));
// Function to apply filters and fetch logs
const applyFilters = () => {
// Pass undefined if filter is empty, otherwise pass the value
store.fetchLogs(
1, // Reset to page 1 when applying filters
searchTerm.value || undefined,
selectedActionType.value || undefined
);
};
// Removed watch for filters
onMounted(() => {
// Fetch initial logs without filters
store.fetchLogs();
});
File diff suppressed because it is too large Load Diff