diff --git a/frontend/src/views/Dashboard/Dashboard.css b/frontend/src/views/Dashboard/Dashboard.css index a413e7c..e5a4523 100644 --- a/frontend/src/views/Dashboard/Dashboard.css +++ b/frontend/src/views/Dashboard/Dashboard.css @@ -891,4 +891,133 @@ code { .settings-modal { width: min(100%, 980px); } +} +.page-shell { + max-width: 1380px; +} + +.task-details-modal, +.certificate-versions-modal { + width: min(100%, 860px); +} + +.task-detail-summary { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; + margin-bottom: 18px; +} + +.task-detail-summary > div { + display: grid; + gap: 7px; + min-width: 0; + padding: 13px 14px; + border: 1px solid #e0e8f2; + border-radius: 12px; + background: #f8fbff; +} + +.task-detail-summary span, +.task-detail-message > span { + color: #718198; + font-size: 12px; + font-weight: 700; +} + +.task-detail-summary strong { + min-width: 0; + color: #243653; + font-size: 13px; + overflow-wrap: anywhere; +} + +.task-detail-summary strong.tag { + width: fit-content; + color: inherit; +} + +.task-detail-message { + margin-bottom: 20px; + padding: 14px 16px; + border: 1px solid #e0e8f2; + border-radius: 12px; + background: #fbfdff; +} + +.task-detail-message p { + margin: 8px 0 0; + color: #3f506b; + line-height: 1.65; + overflow-wrap: anywhere; +} + +.modal-section-title { + margin: 0 0 10px; + color: #32405a; + font-size: 15px; +} + +.attempt-list, +.version-list { + display: grid; + gap: 9px; +} + +.attempt-row, +.version-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 14px; + padding: 13px 14px; + border: 1px solid #e1e8f1; + border-radius: 12px; + background: #ffffff; +} + +.attempt-row > div, +.version-row > div { + display: grid; + gap: 5px; + min-width: 0; +} + +.attempt-row strong, +.version-row strong { + color: #2d4568; + font-size: 13px; + overflow-wrap: anywhere; +} + +.attempt-error { + color: #b14e4e; + font-size: 12px; + line-height: 1.5; + overflow-wrap: anywhere; +} + +.version-row .muted { + overflow-wrap: anywhere; +} + +@media (max-width: 900px) { + .task-detail-summary { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 640px) { + .page-shell { + padding: 28px 16px 52px; + } + + .task-detail-summary { + grid-template-columns: minmax(0, 1fr); + } + + .attempt-row, + .version-row { + flex-direction: column; + } } \ No newline at end of file diff --git a/frontend/src/views/Dashboard/Dashboard.js b/frontend/src/views/Dashboard/Dashboard.js index e9f048e..89896a1 100644 --- a/frontend/src/views/Dashboard/Dashboard.js +++ b/frontend/src/views/Dashboard/Dashboard.js @@ -45,6 +45,16 @@ export function useDashboard() { private_key: null, issuer_bundle: null, }); + const showTaskDetails = ref(false); + const taskDetails = ref(null); + const taskAttempts = ref([]); + const taskDetailsLoading = ref(false); + const taskDetailsError = ref(""); + const showCertificateVersions = ref(false); + const certificateVersions = ref([]); + const certificateVersionsDomain = ref(null); + const certificateVersionsLoading = ref(false); + const certificateVersionsError = ref(""); const hasOpenModal = computed( () => showForm.value || @@ -100,6 +110,49 @@ export function useDashboard() { failed: "失败", cancelled: "已取消", }; + const taskTypeLabels = { + certificate_issue: "申请证书", + certificate_import: "导入证书", + certificate_deploy: "部署证书", + certificate_validate: "校验证书", + certificate_restore: "恢复证书版本", + renewal_run: "自动续期", + dns_test: "DNS 在线测试", + }; + const auditActionLabels = { + certificate_issue: "申请证书", + certificate_import: "导入证书", + certificate_deploy: "部署证书", + certificate_validate: "校验证书", + certificate_version_restore: "恢复证书版本", + task_cancel: "取消任务", + settings_update: "更新系统设置", + domain_create: "创建域名", + domain_update: "更新域名", + domain_delete: "删除域名", + server_create: "创建服务器", + server_update: "更新服务器", + server_delete: "删除服务器", + ssh_key_create: "添加 SSH 私钥", + ssh_key_delete: "删除 SSH 私钥", + renewal_run: "执行自动续期", + }; + const taskStageLabels = { + queued: "排队中", + certificate_issue: "申请证书", + certificate_validate: "校验证书", + certificate_deploy: "部署证书", + deployment: "远程部署", + verification: "部署后校验", + rollback: "回滚处理", + completed: "已完成", + }; + const attemptStatusLabels = { + running: "执行中", + success: "已完成", + failed: "失败", + cancelled: "已取消", + }; const domainStatusLabels = { pending: "等待申请", active: "正常", @@ -159,6 +212,22 @@ export function useDashboard() { return fallback; } + function taskTypeLabel(value) { + return displayLabel(taskTypeLabels, value); + } + + function auditActionLabel(value) { + return displayLabel(auditActionLabels, value); + } + + function taskStageLabel(value) { + return displayLabel(taskStageLabels, value); + } + + function attemptStatusLabel(value) { + return displayLabel(attemptStatusLabels, value); + } + function createEmptyForm() { return { name: "", @@ -523,6 +592,65 @@ export function useDashboard() { return runDomainAction(item, "deploy", `已提交 ${item.name} 的证书部署`); } + async function openTaskDetails(item) { + showTaskDetails.value = true; + taskDetails.value = null; + taskAttempts.value = []; + taskDetailsError.value = ""; + taskDetailsLoading.value = true; + try { + const [taskResponse, attemptsResponse] = await Promise.all([ + fetchAdmin(`/api/v1/tasks/${item.id}`), + fetchAdmin(`/api/v1/tasks/${item.id}/attempts`), + ]); + if (!taskResponse.ok) { + throw new Error(await responseError(taskResponse, "读取任务详情失败")); + } + if (!attemptsResponse.ok) { + throw new Error(await responseError(attemptsResponse, "读取任务尝试记录失败")); + } + taskDetails.value = await taskResponse.json(); + taskAttempts.value = (await attemptsResponse.json()).items || []; + } catch (err) { + taskDetailsError.value = err.message || "读取任务详情失败"; + } finally { + taskDetailsLoading.value = false; + } + } + + function closeTaskDetails() { + showTaskDetails.value = false; + taskDetails.value = null; + taskAttempts.value = []; + taskDetailsError.value = ""; + } + + async function openCertificateVersions(item) { + showCertificateVersions.value = true; + certificateVersionsDomain.value = item; + certificateVersions.value = []; + certificateVersionsError.value = ""; + certificateVersionsLoading.value = true; + try { + const response = await fetchAdmin(`/api/v1/domains/${item.id}/certificate/versions`); + if (!response.ok) { + throw new Error(await responseError(response, "查询证书版本失败")); + } + certificateVersions.value = (await response.json()).items || []; + } catch (err) { + certificateVersionsError.value = err.message || "查询证书版本失败"; + } finally { + certificateVersionsLoading.value = false; + } + } + + function closeCertificateVersions() { + showCertificateVersions.value = false; + certificateVersions.value = []; + certificateVersionsDomain.value = null; + certificateVersionsError.value = ""; + } + async function cancelTask(item) { if (!window.confirm("确定取消这项正在执行的证书任务吗?")) return; error.value = ""; @@ -851,6 +979,10 @@ export function useDashboard() { certificateTypeLabels, domainStatusLabels, taskStatusLabels, + taskTypeLabels, + auditActionLabels, + taskStageLabels, + attemptStatusLabels, httpsModeLabels, environmentLabels, webServerLabels, @@ -904,6 +1036,24 @@ export function useDashboard() { validateDomain, deployDomain, cancelTask, + openTaskDetails, + closeTaskDetails, + showTaskDetails, + taskDetails, + taskAttempts, + taskDetailsLoading, + taskDetailsError, + openCertificateVersions, + closeCertificateVersions, + showCertificateVersions, + certificateVersions, + certificateVersionsDomain, + certificateVersionsLoading, + certificateVersionsError, + taskTypeLabel, + auditActionLabel, + taskStageLabel, + attemptStatusLabel, runRenewalNow, openSettings, closeSettings, diff --git a/frontend/src/views/Dashboard/Dashboard.vue b/frontend/src/views/Dashboard/Dashboard.vue index ccd3252..5a4c834 100644 --- a/frontend/src/views/Dashboard/Dashboard.vue +++ b/frontend/src/views/Dashboard/Dashboard.vue @@ -31,7 +31,12 @@ const { certificateTypeLabels, domainStatusLabels, taskStatusLabels, + taskTypeLabel, + auditActionLabel, + taskStageLabel, + attemptStatusLabel, httpsModeLabels, + dnsProviders, environmentLabels, webServerLabels, isEditing, @@ -80,6 +85,20 @@ const { deployDomain, runRenewalNow, cancelTask, + openTaskDetails, + closeTaskDetails, + showTaskDetails, + taskDetails, + taskAttempts, + taskDetailsLoading, + taskDetailsError, + openCertificateVersions, + closeCertificateVersions, + showCertificateVersions, + certificateVersions, + certificateVersionsDomain, + certificateVersionsLoading, + certificateVersionsError, displayLabel, formatDate, daysRemaining, @@ -198,11 +217,14 @@ const {
- {{ item.task_type }} + {{ taskTypeLabel(item.task_type) }} {{ item.message || "无附加信息" }}
{{ displayLabel(taskStatusLabels, item.status) }} +
@@ -293,6 +315,9 @@ const { + + +
正在读取任务详情……
+

{{ taskDetailsError }}

+ + + + +