diff --git a/packages/frontend/src/locales/en-US.json b/packages/frontend/src/locales/en-US.json index 1637e5f..8628331 100644 --- a/packages/frontend/src/locales/en-US.json +++ b/packages/frontend/src/locales/en-US.json @@ -1039,7 +1039,11 @@ "type": "Type", "updated": "Updated", "created": "Created" - } + }, + "filterTags": { + "all": "All Tags" + }, + "noConnectionsWithTag": "No connections found with this tag" }, "terminalTabBar": { "selectServerTitle": "Select server to connect" diff --git a/packages/frontend/src/locales/ja-JP.json b/packages/frontend/src/locales/ja-JP.json index 4c45d4b..3936a79 100644 --- a/packages/frontend/src/locales/ja-JP.json +++ b/packages/frontend/src/locales/ja-JP.json @@ -195,6 +195,10 @@ "type": "タイプ", "updated": "更新日時" }, + "filterTags": { + "all": "すべてのタグ" + }, + "noConnectionsWithTag": "このタグには接続記録がありません", "viewAllConnections": "すべての接続を表示", "viewFullAuditLog": "完全な監査ログを表示" }, diff --git a/packages/frontend/src/locales/zh-CN.json b/packages/frontend/src/locales/zh-CN.json index e27cc62..110177d 100644 --- a/packages/frontend/src/locales/zh-CN.json +++ b/packages/frontend/src/locales/zh-CN.json @@ -1042,7 +1042,11 @@ "type": "类型", "updated": "修改时间", "created": "创建时间" - } + }, + "filterTags": { + "all": "所有标签" + }, + "noConnectionsWithTag": "该标签下没有连接记录" }, "terminalTabBar": { "selectServerTitle": "选择要连接的服务器" diff --git a/packages/frontend/src/views/DashboardView.vue b/packages/frontend/src/views/DashboardView.vue index fd7df1a..6db4fd8 100644 --- a/packages/frontend/src/views/DashboardView.vue +++ b/packages/frontend/src/views/DashboardView.vue @@ -3,6 +3,8 @@ import { ref, computed, onMounted, watch } from 'vue'; import { useConnectionsStore } from '../stores/connections.store'; import { useAuditLogStore } from '../stores/audit.store'; import { useSessionStore } from '../stores/session.store'; +import { useTagsStore } from '../stores/tags.store'; // +++ 添加标签 store +++ +import type { TagInfo } from '../stores/tags.store'; // +++ 修正标签类型导入 +++ // Removed settings store import for sorting import type { SortField, SortOrder } from '../stores/settings.store'; // Keep type import import { useI18n } from 'vue-i18n'; @@ -18,19 +20,29 @@ const router = useRouter(); const connectionsStore = useConnectionsStore(); const auditLogStore = useAuditLogStore(); const sessionStore = useSessionStore(); +const tagsStore = useTagsStore(); // +++ 实例化标签 store +++ // Removed settings store instantiation const { connections, isLoading: isLoadingConnections } = storeToRefs(connectionsStore); const { logs: auditLogs, isLoading: isLoadingLogs, totalLogs } = storeToRefs(auditLogStore); +const { tags, isLoading: isLoadingTags } = storeToRefs(tagsStore); // +++ 获取标签数据和加载状态 +++ // Removed refs from settings store // Local state for sorting with localStorage persistence const LS_SORT_BY_KEY = 'dashboard_connections_sort_by'; const LS_SORT_ORDER_KEY = 'dashboard_connections_sort_order'; +const LS_FILTER_TAG_KEY = 'dashboard_connections_filter_tag'; // +++ 添加标签筛选的 localStorage key +++ // Initialize with localStorage values or defaults const localSortBy = ref(localStorage.getItem(LS_SORT_BY_KEY) as SortField || 'last_connected_at'); const localSortOrder = ref(localStorage.getItem(LS_SORT_ORDER_KEY) as SortOrder || 'desc'); +// +++ 初始化标签筛选状态,从 localStorage 读取,注意类型转换 (修正 ref 初始化) +++ +const getInitialSelectedTagId = (): number | null => { + const storedValue = localStorage.getItem(LS_FILTER_TAG_KEY); + // 如果存储的值是 'null' 字符串或空,则返回 null,否则解析为数字 + return storedValue && storedValue !== 'null' ? parseInt(storedValue, 10) : null; +}; +const selectedTagId = ref(getInitialSelectedTagId()); const maxRecentLogs = 5; @@ -42,12 +54,20 @@ const sortOptions: { value: SortField; labelKey: string }[] = [ { value: 'created_at', labelKey: 'dashboard.sortOptions.created' }, ]; -const sortedConnections = computed(() => { - const sortBy = localSortBy.value; // Use local state - const sortOrderVal = localSortOrder.value; // Use local state +// +++ 修改计算属性,先筛选再排序 +++ +const filteredAndSortedConnections = computed(() => { + const sortBy = localSortBy.value; + const sortOrderVal = localSortOrder.value; const factor = sortOrderVal === 'desc' ? -1 : 1; + const filterTagId = selectedTagId.value; - return [...connections.value].sort((a, b) => { + // 1. Filter by selected tag + const filtered = filterTagId === null + ? [...connections.value] // No tag selected, show all + : connections.value.filter(conn => conn.tag_ids?.includes(filterTagId)); + + // 2. Sort the filtered connections + return filtered.sort((a, b) => { let valA: any; let valB: any; @@ -69,10 +89,8 @@ const sortedConnections = computed(() => { valB = b.updated_at ?? 0; return (valA - valB) * factor; case 'last_connected_at': - // Handle null/undefined last_connected_at based on sort order for consistent sorting valA = a.last_connected_at ?? (sortOrderVal === 'desc' ? -Infinity : Infinity); valB = b.last_connected_at ?? (sortOrderVal === 'desc' ? -Infinity : Infinity); - // Ensure consistent comparison for potentially infinite values if (valA === valB) return 0; if (valA < valB) return -1 * factor; return 1 * factor; @@ -87,8 +105,9 @@ const recentAuditLogs = computed(() => { }); onMounted(async () => { - // Load saved sort preferences from localStorage (already done during ref initialization) + // Load saved preferences from localStorage (already done during ref initialization) + // Fetch connections if not already loaded if (connections.value.length === 0) { try { await connectionsStore.fetchConnections(); @@ -96,6 +115,8 @@ onMounted(async () => { console.error("加载连接列表失败:", error); } } + + // Fetch recent audit logs try { await auditLogStore.fetchLogs({ page: 1, @@ -106,6 +127,13 @@ onMounted(async () => { } catch (error) { console.error("加载审计日志失败:", error); } + + // +++ Fetch tags for filtering +++ + try { + await tagsStore.fetchTags(); + } catch (error) { + console.error("加载标签列表失败:", error); + } }); const connectTo = (connection: ConnectionInfo) => { @@ -128,6 +156,12 @@ watch(localSortOrder, (newValue) => { localStorage.setItem(LS_SORT_ORDER_KEY, newValue); }); +// +++ Watch for changes in selected tag and save to localStorage +++ +watch(selectedTagId, (newValue) => { + // Store 'null' as a string or the number + localStorage.setItem(LS_FILTER_TAG_KEY, newValue === null ? 'null' : String(newValue)); +}); + const dateFnsLocales: Record = { 'en-US': enUS, 'zh-CN': zhCN, @@ -189,6 +223,20 @@ const isFailedAction = (actionType: string): boolean => { // 检查常见的失败关键词 return lowerCaseAction.includes('fail') || lowerCaseAction.includes('error') || lowerCaseAction.includes('denied'); }; + +// +++ 恢复:根据 tag_ids 获取标签名称数组 +++ +const getTagNames = (tagIds: number[] | undefined): string[] => { + if (!tagIds || tagIds.length === 0) { + return []; + } + const allTags = tags.value as TagInfo[]; + return tagIds + .map(id => allTags.find(tag => tag.id === id)?.name) + .filter((name): name is string => !!name); // 过滤掉未找到的标签并确保类型为 string +}; + +// --- 移除 selectTagFilter 函数 --- +