update
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { AuditLogEntry, AuditLogApiResponse, AuditLogActionType } from '../types/server.types';
|
||||
|
||||
export const useAuditLogStore = defineStore('auditLog', () => {
|
||||
const logs = ref<AuditLogEntry[]>([]);
|
||||
const totalLogs = ref(0);
|
||||
const isLoading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const currentPage = ref(1);
|
||||
const logsPerPage = ref(50); // Default page size
|
||||
|
||||
const fetchLogs = async (page: number = 1, filters: { actionType?: AuditLogActionType, startDate?: number, endDate?: number } = {}) => {
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
currentPage.value = page;
|
||||
const offset = (page - 1) * logsPerPage.value;
|
||||
|
||||
try {
|
||||
const params: Record<string, any> = {
|
||||
limit: logsPerPage.value,
|
||||
offset: offset,
|
||||
...filters // Spread filter parameters
|
||||
};
|
||||
// Remove undefined filter values
|
||||
Object.keys(params).forEach(key => params[key] === undefined && delete params[key]);
|
||||
|
||||
const response = await axios.get<AuditLogApiResponse>('/api/v1/audit-logs', { params });
|
||||
logs.value = response.data.logs;
|
||||
totalLogs.value = response.data.total;
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching audit logs:', err);
|
||||
error.value = err.response?.data?.message || '获取审计日志失败';
|
||||
logs.value = [];
|
||||
totalLogs.value = 0;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Function to change page size and refetch
|
||||
const setLogsPerPage = (size: number) => {
|
||||
logsPerPage.value = size;
|
||||
fetchLogs(1); // Reset to first page when size changes
|
||||
};
|
||||
|
||||
return {
|
||||
logs,
|
||||
totalLogs,
|
||||
isLoading,
|
||||
error,
|
||||
currentPage,
|
||||
logsPerPage,
|
||||
fetchLogs,
|
||||
setLogsPerPage,
|
||||
};
|
||||
});
|
||||
@@ -9,6 +9,13 @@ interface UserInfo {
|
||||
isTwoFactorEnabled?: boolean; // 后端 /status 接口会返回这个
|
||||
}
|
||||
|
||||
// 新增:登录请求的载荷接口
|
||||
interface LoginPayload {
|
||||
username: string;
|
||||
password: string;
|
||||
rememberMe?: boolean; // 可选的“记住我”标志
|
||||
}
|
||||
|
||||
// Auth Store State 接口
|
||||
interface AuthState {
|
||||
isAuthenticated: boolean;
|
||||
@@ -16,6 +23,11 @@ interface AuthState {
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
loginRequires2FA: boolean; // 新增状态:标记登录是否需要 2FA
|
||||
// 新增:存储 IP 黑名单数据
|
||||
ipBlacklist: {
|
||||
entries: any[]; // TODO: Define a proper type for blacklist entries
|
||||
total: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
@@ -25,20 +37,22 @@ export const useAuthStore = defineStore('auth', {
|
||||
isLoading: false,
|
||||
error: null,
|
||||
loginRequires2FA: false, // 初始为不需要
|
||||
ipBlacklist: { entries: [], total: 0 }, // 初始化黑名单状态
|
||||
}),
|
||||
getters: {
|
||||
// 可以添加一些 getter,例如获取用户名
|
||||
loggedInUser: (state) => state.user?.username,
|
||||
},
|
||||
actions: {
|
||||
// 登录 Action
|
||||
async login(credentials: { username: string; password: string }) {
|
||||
// 登录 Action - 更新为接受 LoginPayload
|
||||
async login(payload: LoginPayload) {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
this.loginRequires2FA = false; // 重置 2FA 状态
|
||||
try {
|
||||
// 后端可能返回 user 或 requiresTwoFactor
|
||||
const response = await axios.post<{ message: string; user?: UserInfo; requiresTwoFactor?: boolean }>('/api/v1/auth/login', credentials);
|
||||
// 将完整的 payload (包含 rememberMe) 发送给后端
|
||||
const response = await axios.post<{ message: string; user?: UserInfo; requiresTwoFactor?: boolean }>('/api/v1/auth/login', payload);
|
||||
|
||||
if (response.data.requiresTwoFactor) {
|
||||
// 需要 2FA 验证
|
||||
@@ -198,6 +212,56 @@ export const useAuthStore = defineStore('auth', {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// --- IP 黑名单管理 Actions ---
|
||||
/**
|
||||
* 获取 IP 黑名单列表
|
||||
* @param limit 每页数量
|
||||
* @param offset 偏移量
|
||||
*/
|
||||
async fetchIpBlacklist(limit: number = 50, offset: number = 0) {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
try {
|
||||
const response = await axios.get('/api/v1/settings/ip-blacklist', {
|
||||
params: { limit, offset }
|
||||
});
|
||||
// 注意:这里需要将获取到的数据存储在 state 中,
|
||||
// 但当前 AuthState 没有定义相关字段。
|
||||
// 暂时只返回数据,需要在 state 中添加 ipBlacklist 字段。
|
||||
console.log('获取 IP 黑名单成功:', response.data);
|
||||
return response.data; // { entries: [], total: number }
|
||||
} catch (err: any) {
|
||||
console.error('获取 IP 黑名单失败:', err);
|
||||
this.error = err.response?.data?.message || err.message || '获取 IP 黑名单时发生未知错误。';
|
||||
// 确保抛出 Error 时提供字符串消息
|
||||
throw new Error(this.error ?? '获取 IP 黑名单时发生未知错误。');
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 从 IP 黑名单中删除一个 IP
|
||||
* @param ip 要删除的 IP 地址
|
||||
*/
|
||||
async deleteIpFromBlacklist(ip: string) {
|
||||
this.isLoading = true;
|
||||
this.error = null;
|
||||
try {
|
||||
await axios.delete(`/api/v1/settings/ip-blacklist/${encodeURIComponent(ip)}`);
|
||||
console.log(`IP ${ip} 已从黑名单删除`);
|
||||
// 成功后需要重新获取列表或从本地 state 中移除
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
console.error(`删除 IP ${ip} 失败:`, err);
|
||||
this.error = err.response?.data?.message || err.message || '删除 IP 时发生未知错误。';
|
||||
// 确保抛出 Error 时提供字符串消息
|
||||
throw new Error(this.error ?? '删除 IP 时发生未知错误。');
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
persist: true, // 使用默认持久化配置 (localStorage, 持久化所有 state)
|
||||
});
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import axios from 'axios'; // Assuming axios is globally available or installed
|
||||
import { NotificationSetting, NotificationSettingData } from '../types/server.types';
|
||||
|
||||
export const useNotificationsStore = defineStore('notifications', () => {
|
||||
const settings = ref<NotificationSetting[]>([]);
|
||||
const isLoading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
const fetchSettings = async () => {
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const response = await axios.get<NotificationSetting[]>('/api/v1/notifications');
|
||||
settings.value = response.data;
|
||||
} catch (err: any) {
|
||||
console.error('Error fetching notification settings:', err);
|
||||
error.value = err.response?.data?.message || '获取通知设置失败';
|
||||
settings.value = []; // Clear settings on error
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const addSetting = async (settingData: NotificationSettingData): Promise<NotificationSetting | null> => {
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const response = await axios.post<NotificationSetting>('/api/v1/notifications', settingData);
|
||||
settings.value.push(response.data);
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
console.error('Error adding notification setting:', err);
|
||||
error.value = err.response?.data?.message || '添加通知设置失败';
|
||||
return null;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const updateSetting = async (id: number, settingData: Partial<NotificationSettingData>): Promise<NotificationSetting | null> => {
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
const response = await axios.put<NotificationSetting>(`/api/v1/notifications/${id}`, settingData);
|
||||
const index = settings.value.findIndex(s => s.id === id);
|
||||
if (index !== -1) {
|
||||
settings.value[index] = response.data;
|
||||
} else {
|
||||
// If not found locally, maybe fetch again or just add it
|
||||
settings.value.push(response.data);
|
||||
}
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
console.error(`Error updating notification setting ${id}:`, err);
|
||||
error.value = err.response?.data?.message || '更新通知设置失败';
|
||||
return null;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteSetting = async (id: number): Promise<boolean> => {
|
||||
isLoading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
await axios.delete(`/api/v1/notifications/${id}`);
|
||||
settings.value = settings.value.filter(s => s.id !== id);
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
console.error(`Error deleting notification setting ${id}:`, err);
|
||||
error.value = err.response?.data?.message || '删除通知设置失败';
|
||||
return false;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const testSetting = async (id: number, config: any): Promise<{ success: boolean; message: string }> => {
|
||||
// Note: We don't set isLoading here as it might interfere with the main form submission state.
|
||||
// The component handles its own 'testingNotification' state.
|
||||
error.value = null; // Clear previous general errors
|
||||
try {
|
||||
// Send the config to test in the request body
|
||||
const response = await axios.post<{ message: string }>(`/api/v1/notifications/${id}/test`, { config });
|
||||
return { success: true, message: response.data.message || '测试成功' };
|
||||
} catch (err: any) {
|
||||
console.error(`Error testing notification setting ${id}:`, err);
|
||||
// Don't set the main 'error' ref here, let the component handle test-specific errors/results.
|
||||
// Throw the error so the component's catch block can handle it.
|
||||
throw err; // Re-throw the error to be caught in the component
|
||||
}
|
||||
// No finally block needed here as loading state is managed in the component
|
||||
};
|
||||
|
||||
|
||||
// Computed property to get settings by type (example)
|
||||
const webhookSettings = computed(() => settings.value.filter(s => s.channel_type === 'webhook'));
|
||||
const emailSettings = computed(() => settings.value.filter(s => s.channel_type === 'email'));
|
||||
const telegramSettings = computed(() => settings.value.filter(s => s.channel_type === 'telegram'));
|
||||
|
||||
return {
|
||||
settings,
|
||||
isLoading,
|
||||
error,
|
||||
fetchSettings,
|
||||
addSetting,
|
||||
updateSetting,
|
||||
deleteSetting,
|
||||
testSetting, // Add the new function here
|
||||
webhookSettings,
|
||||
emailSettings,
|
||||
telegramSettings,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user