key table
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import GlobalProviders from "@/components/GlobalProviders.vue";
|
import GlobalProviders from "@/components/GlobalProviders.vue";
|
||||||
|
import GlobalTaskProgressBar from "@/components/GlobalTaskProgressBar.vue";
|
||||||
import Layout from "@/components/Layout.vue";
|
import Layout from "@/components/Layout.vue";
|
||||||
import { useAuthKey } from "@/services/auth";
|
import { useAuthKey } from "@/services/auth";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
@@ -15,7 +16,7 @@ const isLoggedIn = computed(() => !!authKey.value);
|
|||||||
<router-view v-else key="auth" />
|
<router-view v-else key="auth" />
|
||||||
|
|
||||||
<!-- 全局任务进度条 -->
|
<!-- 全局任务进度条 -->
|
||||||
<!-- <global-task-progress-bar /> -->
|
<global-task-progress-bar />
|
||||||
</div>
|
</div>
|
||||||
</global-providers>
|
</global-providers>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -118,17 +118,31 @@ export const keysApi = {
|
|||||||
|
|
||||||
// 清空所有无效密钥
|
// 清空所有无效密钥
|
||||||
clearAllInvalidKeys(group_id: number): Promise<void> {
|
clearAllInvalidKeys(group_id: number): Promise<void> {
|
||||||
return http.post("/keys/clear-all-invalid", { group_id });
|
return http.post(
|
||||||
|
"/keys/clear-all-invalid",
|
||||||
|
{ group_id },
|
||||||
|
{
|
||||||
|
hideMessage: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
// 导出密钥
|
// 导出密钥
|
||||||
async exportKeys(
|
exportKeys(groupId: number, status: "all" | "active" | "inactive" = "all") {
|
||||||
groupId: number,
|
let url = `${http.defaults.baseURL}/groups/${groupId}/keys/export`;
|
||||||
filter: "all" | "valid" | "invalid" = "all"
|
if (status !== "all") {
|
||||||
): Promise<{ keys: string[] }> {
|
url += `?status=${status}`;
|
||||||
const params: any = { filter };
|
}
|
||||||
const res = await http.get(`/groups/${groupId}/keys/export`, { params });
|
|
||||||
return res.data;
|
// 创建隐藏的 a 标签实现下载
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = url;
|
||||||
|
link.download = `group-${groupId}-keys-${status}.txt`;
|
||||||
|
link.style.display = "none";
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
},
|
},
|
||||||
|
|
||||||
// 验证分组密钥
|
// 验证分组密钥
|
||||||
|
@@ -1,12 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { keysApi } from "@/api/keys";
|
import { keysApi } from "@/api/keys";
|
||||||
import type { TaskInfo } from "@/types/models";
|
import type { TaskInfo } from "@/types/models";
|
||||||
import { NButton, NCard, NProgress, NText } from "naive-ui";
|
import { NButton, NCard, NProgress, NText, useMessage } from "naive-ui";
|
||||||
import { onBeforeUnmount, onMounted, ref } from "vue";
|
import { onBeforeUnmount, onMounted, ref } from "vue";
|
||||||
|
|
||||||
const taskInfo = ref<TaskInfo>({ is_running: false });
|
const taskInfo = ref<TaskInfo>({ is_running: false });
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
let pollTimer: number | null = null;
|
let pollTimer: number | null = null;
|
||||||
|
let isPolling = false; // 添加标志位
|
||||||
|
const message = useMessage();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
startPolling();
|
startPolling();
|
||||||
@@ -18,18 +20,49 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
function startPolling() {
|
function startPolling() {
|
||||||
stopPolling();
|
stopPolling();
|
||||||
pollTimer = setInterval(async () => {
|
isPolling = true;
|
||||||
|
pollOnce();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pollOnce() {
|
||||||
|
if (!isPolling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const task = await keysApi.getTaskStatus();
|
const task = await keysApi.getTaskStatus();
|
||||||
taskInfo.value = task;
|
taskInfo.value = task;
|
||||||
visible.value = task.is_running;
|
visible.value = task.is_running;
|
||||||
|
if (!task.is_running) {
|
||||||
|
stopPolling();
|
||||||
|
if (task.result) {
|
||||||
|
const lastTask = localStorage.getItem("last_closed_task");
|
||||||
|
if (lastTask !== task.finished_at) {
|
||||||
|
const { total_keys, valid_keys, invalid_keys } = task.result;
|
||||||
|
const msg = `任务已完成,处理了 ${total_keys} 个密钥,其中 ${valid_keys} 个有效密钥,${invalid_keys} 个无效密钥。`;
|
||||||
|
message.info(msg, {
|
||||||
|
closable: true,
|
||||||
|
duration: 0,
|
||||||
|
onClose: () => {
|
||||||
|
localStorage.setItem("last_closed_task", task.finished_at || "");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
// 错误已记录
|
// 错误已记录
|
||||||
}
|
}
|
||||||
}, 1000);
|
|
||||||
|
// 如果仍在轮询状态,1秒后发起下一次请求
|
||||||
|
if (isPolling) {
|
||||||
|
pollTimer = setTimeout(pollOnce, 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopPolling() {
|
function stopPolling() {
|
||||||
|
isPolling = false;
|
||||||
if (pollTimer) {
|
if (pollTimer) {
|
||||||
clearInterval(pollTimer);
|
clearInterval(pollTimer);
|
||||||
pollTimer = null;
|
pollTimer = null;
|
||||||
@@ -61,7 +94,7 @@ function handleClose() {
|
|||||||
<span class="progress-icon">⚡</span>
|
<span class="progress-icon">⚡</span>
|
||||||
<div class="progress-details">
|
<div class="progress-details">
|
||||||
<n-text strong class="progress-title">
|
<n-text strong class="progress-title">
|
||||||
{{ taskInfo.task_name || "正在处理任务" }}
|
正在处理分组 {{ taskInfo.group_name }} 的任务
|
||||||
</n-text>
|
</n-text>
|
||||||
<n-text depth="3" class="progress-subtitle">
|
<n-text depth="3" class="progress-subtitle">
|
||||||
{{ getProgressText() }} ({{ getProgressPercentage() }}%)
|
{{ getProgressText() }} ({{ getProgressPercentage() }}%)
|
||||||
@@ -156,6 +189,8 @@ function handleClose() {
|
|||||||
|
|
||||||
.progress-details {
|
.progress-details {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-title {
|
.progress-title {
|
||||||
|
@@ -279,125 +279,104 @@ function getStatusClass(status: KeyStatus): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function copyAllKeys() {
|
async function copyAllKeys() {
|
||||||
if (!props.selectedGroup) {
|
if (!props.selectedGroup?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
keysApi.exportKeys(props.selectedGroup.id, "all");
|
||||||
const result = await keysApi.exportKeys(props.selectedGroup.id, "all");
|
|
||||||
const keysText = result.keys.join("\n");
|
|
||||||
navigator.clipboard
|
|
||||||
.writeText(keysText)
|
|
||||||
.then(() => {
|
|
||||||
window.$message.success(`已复制${result.keys.length}个密钥到剪贴板`);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
window.$message.error("复制失败");
|
|
||||||
});
|
|
||||||
} catch (_error) {
|
|
||||||
// 错误已记录
|
|
||||||
window.$message.error("导出失败");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyValidKeys() {
|
async function copyValidKeys() {
|
||||||
if (!props.selectedGroup) {
|
if (!props.selectedGroup?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
keysApi.exportKeys(props.selectedGroup.id, "active");
|
||||||
const result = await keysApi.exportKeys(props.selectedGroup.id, "valid");
|
|
||||||
const keysText = result.keys.join("\n");
|
|
||||||
navigator.clipboard
|
|
||||||
.writeText(keysText)
|
|
||||||
.then(() => {
|
|
||||||
window.$message.success(`已复制${result.keys.length}个有效密钥到剪贴板`);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
window.$message.error("复制失败");
|
|
||||||
});
|
|
||||||
} catch (_error) {
|
|
||||||
// 错误已记录
|
|
||||||
window.$message.error("导出失败");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyInvalidKeys() {
|
async function copyInvalidKeys() {
|
||||||
if (!props.selectedGroup) {
|
if (!props.selectedGroup?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
keysApi.exportKeys(props.selectedGroup.id, "inactive");
|
||||||
const result = await keysApi.exportKeys(props.selectedGroup.id, "invalid");
|
|
||||||
const keysText = result.keys.join("\n");
|
|
||||||
navigator.clipboard
|
|
||||||
.writeText(keysText)
|
|
||||||
.then(() => {
|
|
||||||
window.$message.success(`已复制${result.keys.length}个无效密钥到剪贴板`);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
window.$message.error("复制失败");
|
|
||||||
});
|
|
||||||
} catch (_error) {
|
|
||||||
// 错误已记录
|
|
||||||
window.$message.error("导出失败");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restoreAllInvalid() {
|
async function restoreAllInvalid() {
|
||||||
if (!props.selectedGroup) {
|
if (!props.selectedGroup?.id || restoreMsg) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.warning({
|
dialog.warning({
|
||||||
// title: "恢复密钥",
|
title: "恢复密钥",
|
||||||
content: "确定要恢复所有无效密钥吗?",
|
content: "确定要恢复所有无效密钥吗?",
|
||||||
positiveText: "确定",
|
positiveText: "确定",
|
||||||
negativeText: "取消",
|
negativeText: "取消",
|
||||||
onPositiveClick: async () => {
|
onPositiveClick: async () => {
|
||||||
|
restoreMsg = window.$message.info("正在恢复密钥...", {
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
window.$message.success("所有无效密钥已恢复");
|
await keysApi.restoreAllInvalidKeys(props.selectedGroup.id);
|
||||||
await loadKeys();
|
await loadKeys();
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
// 错误已记录
|
console.error("恢复失败");
|
||||||
window.$message.error("恢复失败");
|
} finally {
|
||||||
|
restoreMsg?.destroy();
|
||||||
|
restoreMsg = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validateAllKeys() {
|
async function validateAllKeys() {
|
||||||
if (!props.selectedGroup) {
|
if (!props.selectedGroup?.id || testingMsg) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testingMsg = window.$message.info("正在验证密钥...", {
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await keysApi.validateGroupKeys(props.selectedGroup.id);
|
await keysApi.validateGroupKeys(props.selectedGroup.id);
|
||||||
window.$message.success(`验证完成: 有效${result.valid_count}个,无效${result.invalid_count}个`);
|
localStorage.removeItem("last_closed_task");
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
// 错误已记录
|
console.error("测试失败");
|
||||||
window.$message.error("验证失败");
|
} finally {
|
||||||
|
testingMsg?.destroy();
|
||||||
|
testingMsg = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearAllInvalid() {
|
async function clearAllInvalid() {
|
||||||
if (!props.selectedGroup) {
|
if (!props.selectedGroup?.id || deleteMsg) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-alert
|
dialog.warning({
|
||||||
const confirmed = window.confirm("确定要清除所有无效密钥吗?此操作不可恢复!");
|
title: "清除密钥",
|
||||||
if (!confirmed) {
|
content: "确定要清除所有无效密钥吗?此操作不可恢复!",
|
||||||
return;
|
positiveText: "确定",
|
||||||
}
|
negativeText: "取消",
|
||||||
|
onPositiveClick: async () => {
|
||||||
|
deleteMsg = window.$message.info("正在清除密钥...", {
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
window.$message.success("所有无效密钥已清除");
|
const { data } = await keysApi.clearAllInvalidKeys(props.selectedGroup.id);
|
||||||
|
window.$message.success(data?.message || "清除成功");
|
||||||
await loadKeys();
|
await loadKeys();
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
// 错误已记录
|
console.error("删除失败");
|
||||||
window.$message.error("清除失败");
|
} finally {
|
||||||
|
deleteMsg?.destroy();
|
||||||
|
deleteMsg = null;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function changePage(page: number) {
|
function changePage(page: number) {
|
||||||
|
@@ -57,13 +57,16 @@ export interface GroupStats {
|
|||||||
|
|
||||||
export interface TaskInfo {
|
export interface TaskInfo {
|
||||||
is_running: boolean;
|
is_running: boolean;
|
||||||
task_name?: string;
|
|
||||||
group_id?: number;
|
|
||||||
group_name?: string;
|
group_name?: string;
|
||||||
processed?: number;
|
processed?: number;
|
||||||
total?: number;
|
total?: number;
|
||||||
started_at?: string;
|
started_at?: string;
|
||||||
message?: string;
|
finished_at?: string;
|
||||||
|
result?: {
|
||||||
|
invalid_keys: number;
|
||||||
|
total_keys: number;
|
||||||
|
valid_keys: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestLog {
|
export interface RequestLog {
|
||||||
|
Reference in New Issue
Block a user