feat(report): add drilldown dialog for slow API details

This commit is contained in:
2026-02-07 12:24:44 +08:00
parent 592d48dde0
commit e93db6fbf1
21 changed files with 168 additions and 52 deletions

View File

@@ -38,6 +38,7 @@ const dockerStats = ref(null)
const browserPoolStats = ref(null)
const systemConfig = ref(null)
const requestMetrics = ref(null)
const requestDetailsOpen = ref(false)
const queueTab = ref('running')
function recordUpdatedAt() {
@@ -85,6 +86,16 @@ function formatUnixTime(value) {
}
}
function formatUnixDateTime(value) {
const ts = Number(value)
if (!Number.isFinite(ts) || ts <= 0) return '-'
try {
return new Date(ts * 1000).toLocaleString('zh-CN', { hour12: false, timeZone: 'Asia/Shanghai' })
} catch {
return '-'
}
}
function sourceLabel(source) {
const raw = String(source ?? '').trim()
if (!raw) return '手动'
@@ -335,9 +346,47 @@ const requestModuleDesc = computed(() => {
const avgMs = formatMs(requestMetrics.value?.avg_duration_ms)
const maxMs = formatMs(requestMetrics.value?.max_duration_ms)
const lastAt = formatUnixTime(requestMetrics.value?.last_request_ts)
return `均值 ${avgMs} · 峰值 ${maxMs} · 最近 ${lastAt}`
const slowThreshold = formatMs(requestMetrics.value?.slow_threshold_ms)
return `均值 ${avgMs} · 峰值 ${maxMs} · 慢阈 ${slowThreshold} · 最近 ${lastAt}`
})
const requestDetailTopPaths = computed(() => {
const rows = Array.isArray(requestMetrics.value?.top_paths) ? requestMetrics.value.top_paths : []
return rows.map((row, index) => ({
rank: index + 1,
path: String(row?.path || '-'),
count: normalizeCount(row?.count),
avg_ms: formatMs(row?.avg_ms),
max_ms: formatMs(row?.max_ms),
status_5xx: normalizeCount(row?.status_5xx),
}))
})
const requestRecentSlowRows = computed(() => {
const rows = Array.isArray(requestMetrics.value?.recent_slow) ? requestMetrics.value.recent_slow : []
return [...rows]
.sort((a, b) => Number(b?.time || 0) - Number(a?.time || 0))
.map((row) => ({
time_text: formatUnixDateTime(row?.time),
method: String(row?.method || '-').toUpperCase(),
path: String(row?.path || '-'),
status: normalizeCount(row?.status),
duration_ms: formatMs(row?.duration_ms),
}))
})
function requestStatusTagType(status) {
const code = normalizeCount(status)
if (code >= 500) return 'danger'
if (code >= 400) return 'warning'
if (code >= 300) return 'info'
return 'success'
}
function openRequestDetails() {
requestDetailsOpen.value = true
}
const configModuleItems = computed(() => [
{ label: '定时任务', value: scheduleEnabled.value ? '启用' : '关闭' },
{ label: '执行时间', value: scheduleTime.value || '-' },
@@ -550,8 +599,51 @@ onUnmounted(() => {
<div class="mobile-metric-value">{{ item.value }}</div>
</div>
</div>
<div v-if="module.key === 'request'" class="module-extra-actions">
<el-button size="small" type="primary" plain @click="openRequestDetails">查看慢接口详情</el-button>
</div>
</el-card>
</section>
<el-dialog v-model="requestDetailsOpen" title="慢接口详情" width="min(1080px, 96vw)">
<div class="request-dialog-summary app-muted">
<span>总请求{{ normalizeCount(requestMetrics?.total_requests) }}</span>
<span>API请求{{ normalizeCount(requestMetrics?.api_requests) }}</span>
<span>慢请求{{ normalizeCount(requestMetrics?.slow_requests) }}</span>
<span>错误请求{{ normalizeCount(requestMetrics?.error_requests) }}</span>
</div>
<div class="request-dialog-block">
<div class="request-dialog-title">慢接口排行榜</div>
<div class="table-wrap">
<el-table :data="requestDetailTopPaths" size="small" max-height="280">
<el-table-column prop="rank" label="#" width="60" />
<el-table-column prop="path" label="接口路径" min-width="340" show-overflow-tooltip />
<el-table-column prop="count" label="请求数" width="100" />
<el-table-column prop="avg_ms" label="平均耗时" width="120" />
<el-table-column prop="max_ms" label="峰值耗时" width="120" />
<el-table-column prop="status_5xx" label="5xx" width="90" />
</el-table>
</div>
</div>
<div class="request-dialog-block">
<div class="request-dialog-title">最近慢请求</div>
<div class="table-wrap">
<el-table :data="requestRecentSlowRows" size="small" max-height="320">
<el-table-column prop="time_text" label="时间" width="180" />
<el-table-column prop="method" label="方法" width="90" />
<el-table-column prop="path" label="接口路径" min-width="320" show-overflow-tooltip />
<el-table-column label="状态" width="100">
<template #default="scope">
<el-tag size="small" :type="requestStatusTagType(scope.row.status)">{{ scope.row.status || '-' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="duration_ms" label="耗时" width="110" />
</el-table>
</div>
</div>
</el-dialog>
</div>
</template>
@@ -710,6 +802,30 @@ onUnmounted(() => {
word-break: break-word;
}
.module-extra-actions {
margin-top: 10px;
display: flex;
justify-content: flex-end;
}
.request-dialog-summary {
display: flex;
flex-wrap: wrap;
gap: 10px 16px;
font-size: 12px;
margin-bottom: 12px;
}
.request-dialog-block + .request-dialog-block {
margin-top: 14px;
}
.request-dialog-title {
font-size: 13px;
font-weight: 800;
margin-bottom: 8px;
}
.hero-overview-grid {
display: none;
}

View File

@@ -1,6 +1,6 @@
{
"_MetricGrid-CPdC57ab.js": {
"file": "assets/MetricGrid-CPdC57ab.js",
"_MetricGrid-DBgW22G4.js": {
"file": "assets/MetricGrid-DBgW22G4.js",
"name": "MetricGrid",
"imports": [
"index.html",
@@ -14,29 +14,29 @@
"file": "assets/MetricGrid-yP_dkP6X.css",
"src": "_MetricGrid-yP_dkP6X.css"
},
"_email-DgztAWKJ.js": {
"file": "assets/email-DgztAWKJ.js",
"_email-D8pS1Pbx.js": {
"file": "assets/email-D8pS1Pbx.js",
"name": "email",
"imports": [
"index.html"
]
},
"_system-1p1K-j-4.js": {
"file": "assets/system-1p1K-j-4.js",
"_system-DO-Wv0rM.js": {
"file": "assets/system-DO-Wv0rM.js",
"name": "system",
"imports": [
"index.html"
]
},
"_tasks-Y3hOqi3L.js": {
"file": "assets/tasks-Y3hOqi3L.js",
"_tasks-BrCJEcYb.js": {
"file": "assets/tasks-BrCJEcYb.js",
"name": "tasks",
"imports": [
"index.html"
]
},
"_users-kt1Evatk.js": {
"file": "assets/users-kt1Evatk.js",
"_users-Czwk10aS.js": {
"file": "assets/users-Czwk10aS.js",
"name": "users",
"imports": [
"index.html"
@@ -74,7 +74,7 @@
]
},
"index.html": {
"file": "assets/index-bXG7gEdM.js",
"file": "assets/index-ZQnpYe51.js",
"name": "index",
"src": "index.html",
"isEntry": true,
@@ -100,7 +100,7 @@
]
},
"src/pages/AnnouncementsPage.vue": {
"file": "assets/AnnouncementsPage-fRPrWBwG.js",
"file": "assets/AnnouncementsPage-BZKzU8qv.js",
"name": "AnnouncementsPage",
"src": "src/pages/AnnouncementsPage.vue",
"isDynamicEntry": true,
@@ -116,14 +116,14 @@
]
},
"src/pages/EmailPage.vue": {
"file": "assets/EmailPage-CFpw2E0J.js",
"file": "assets/EmailPage-BW-iqQTt.js",
"name": "EmailPage",
"src": "src/pages/EmailPage.vue",
"isDynamicEntry": true,
"imports": [
"_email-DgztAWKJ.js",
"_email-D8pS1Pbx.js",
"index.html",
"_MetricGrid-CPdC57ab.js",
"_MetricGrid-DBgW22G4.js",
"_vendor-element-CJoVtPsD.js",
"_vendor-sLgkZK1v.js",
"_vendor-vue-CWkOjFoA.js",
@@ -134,13 +134,13 @@
]
},
"src/pages/FeedbacksPage.vue": {
"file": "assets/FeedbacksPage-BXNEKJpt.js",
"file": "assets/FeedbacksPage-C-dXtI0j.js",
"name": "FeedbacksPage",
"src": "src/pages/FeedbacksPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_MetricGrid-CPdC57ab.js",
"_MetricGrid-DBgW22G4.js",
"_vendor-element-CJoVtPsD.js",
"_vendor-sLgkZK1v.js",
"_vendor-vue-CWkOjFoA.js",
@@ -151,13 +151,13 @@
]
},
"src/pages/LogsPage.vue": {
"file": "assets/LogsPage-xSURzHTt.js",
"file": "assets/LogsPage-BzvZ_jRl.js",
"name": "LogsPage",
"src": "src/pages/LogsPage.vue",
"isDynamicEntry": true,
"imports": [
"_users-kt1Evatk.js",
"_tasks-Y3hOqi3L.js",
"_users-Czwk10aS.js",
"_tasks-BrCJEcYb.js",
"index.html",
"_vendor-element-CJoVtPsD.js",
"_vendor-sLgkZK1v.js",
@@ -169,33 +169,33 @@
]
},
"src/pages/ReportPage.vue": {
"file": "assets/ReportPage-BITaX3hh.js",
"file": "assets/ReportPage-JOjMl_cz.js",
"name": "ReportPage",
"src": "src/pages/ReportPage.vue",
"isDynamicEntry": true,
"imports": [
"_vendor-element-CJoVtPsD.js",
"index.html",
"_email-DgztAWKJ.js",
"_tasks-Y3hOqi3L.js",
"_system-1p1K-j-4.js",
"_MetricGrid-CPdC57ab.js",
"_email-D8pS1Pbx.js",
"_tasks-BrCJEcYb.js",
"_system-DO-Wv0rM.js",
"_MetricGrid-DBgW22G4.js",
"_vendor-sLgkZK1v.js",
"_vendor-vue-CWkOjFoA.js",
"_vendor-axios-B9ygI19o.js"
],
"css": [
"assets/ReportPage-21i0jfUn.css"
"assets/ReportPage-DlF2eaTa.css"
]
},
"src/pages/SecurityPage.vue": {
"file": "assets/SecurityPage-CQuWzZ_p.js",
"file": "assets/SecurityPage-DlbTJ4QF.js",
"name": "SecurityPage",
"src": "src/pages/SecurityPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_MetricGrid-CPdC57ab.js",
"_MetricGrid-DBgW22G4.js",
"_vendor-element-CJoVtPsD.js",
"_vendor-sLgkZK1v.js",
"_vendor-vue-CWkOjFoA.js",
@@ -206,7 +206,7 @@
]
},
"src/pages/SettingsPage.vue": {
"file": "assets/SettingsPage-Ct3Jh1K3.js",
"file": "assets/SettingsPage-cWvS8Esv.js",
"name": "SettingsPage",
"src": "src/pages/SettingsPage.vue",
"isDynamicEntry": true,
@@ -222,12 +222,12 @@
]
},
"src/pages/SystemPage.vue": {
"file": "assets/SystemPage-DE9Z1xG-.js",
"file": "assets/SystemPage-BdmQOog3.js",
"name": "SystemPage",
"src": "src/pages/SystemPage.vue",
"isDynamicEntry": true,
"imports": [
"_system-1p1K-j-4.js",
"_system-DO-Wv0rM.js",
"index.html",
"_vendor-element-CJoVtPsD.js",
"_vendor-sLgkZK1v.js",
@@ -239,12 +239,12 @@
]
},
"src/pages/UsersPage.vue": {
"file": "assets/UsersPage-BqAhpZky.js",
"file": "assets/UsersPage-CZk3ZLBk.js",
"name": "UsersPage",
"src": "src/pages/UsersPage.vue",
"isDynamicEntry": true,
"imports": [
"_users-kt1Evatk.js",
"_users-Czwk10aS.js",
"index.html",
"_vendor-element-CJoVtPsD.js",
"_vendor-sLgkZK1v.js",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{_}from"./index-bXG7gEdM.js";import{aW as c,z as s,A as t,R as r,ak as u,E as p,B as o,N as l,S as y,L as h,K as i,O as k,Q as n,P as v,D as f}from"./vendor-sLgkZK1v.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},B={class:"metric-label"},N={class:"metric-value"},g={key:0,class:"metric-hint app-muted"},C={__name:"MetricGrid",props:{items:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},minWidth:{type:Number,default:180}},setup(a){return(V,z)=>{const d=c("el-icon"),m=c("el-skeleton");return t(),s("div",{class:"metric-grid",style:f({"--metric-min":`${a.minWidth}px`})},[(t(!0),s(r,null,u(a.items,e=>(t(),s("div",{key:e?.key||e?.label,class:p(["metric-card",`metric-tone--${e?.tone||"blue"}`])},[o("div",b,[e?.icon?(t(),s("div",x,[y(d,null,{default:h(()=>[(t(),i(k(e.icon)))]),_:2},1024)])):l("",!0),o("div",B,n(e?.label||"-"),1)]),o("div",N,[a.loading?(t(),i(m,{key:0,rows:1,animated:""})):(t(),s(r,{key:1},[v(n(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",g,n(e?.hint||e?.sub),1)):l("",!0)],2))),128))],4)}}},S=_(C,[["__scopeId","data-v-00e217d4"]]);export{S as M};
import{_}from"./index-ZQnpYe51.js";import{aW as c,z as s,A as t,R as r,ak as u,E as p,B as o,N as l,S as y,L as h,K as i,O as k,Q as n,P as v,D as f}from"./vendor-sLgkZK1v.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},B={class:"metric-label"},N={class:"metric-value"},g={key:0,class:"metric-hint app-muted"},C={__name:"MetricGrid",props:{items:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},minWidth:{type:Number,default:180}},setup(a){return(V,z)=>{const d=c("el-icon"),m=c("el-skeleton");return t(),s("div",{class:"metric-grid",style:f({"--metric-min":`${a.minWidth}px`})},[(t(!0),s(r,null,u(a.items,e=>(t(),s("div",{key:e?.key||e?.label,class:p(["metric-card",`metric-tone--${e?.tone||"blue"}`])},[o("div",b,[e?.icon?(t(),s("div",x,[y(d,null,{default:h(()=>[(t(),i(k(e.icon)))]),_:2},1024)])):l("",!0),o("div",B,n(e?.label||"-"),1)]),o("div",N,[a.loading?(t(),i(m,{key:0,rows:1,animated:""})):(t(),s(r,{key:1},[v(n(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",g,n(e?.hint||e?.sub),1)):l("",!0)],2))),128))],4)}}},S=_(C,[["__scopeId","data-v-00e217d4"]]);export{S as M};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{a as m,_ as h}from"./index-bXG7gEdM.js";import{f as u,E as x}from"./vendor-element-CJoVtPsD.js";import{r as p,aW as i,z as T,A as P,B as r,S as a,L as o,P as b}from"./vendor-sLgkZK1v.js";import"./vendor-vue-CWkOjFoA.js";import"./vendor-axios-B9ygI19o.js";async function S(l){const{data:s}=await m.put("/admin/username",{new_username:l});return s}async function A(l){const{data:s}=await m.put("/admin/password",{new_password:l});return s}async function C(){const{data:l}=await m.post("/logout");return l}const E={class:"page-stack"},U={__name:"SettingsPage",setup(l){const s=p(""),d=p(""),n=p(!1);function k(t){const e=String(t||"");return e.length<8?{ok:!1,message:"密码长度至少8位"}:e.length>128?{ok:!1,message:"密码长度不能超过128个字符"}:!/[a-zA-Z]/.test(e)||!/\d/.test(e)?{ok:!1,message:"密码必须包含字母和数字"}:{ok:!0,message:""}}async function f(){try{await C()}catch{}finally{window.location.href="/yuyx"}}async function B(){const t=s.value.trim();if(!t){u.error("请输入新用户名");return}try{await x.confirm(`确定将管理员用户名修改为「${t}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await S(t),u.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function V(){const t=d.value;if(!t){u.error("请输入新密码");return}const e=k(t);if(!e.ok){u.error(e.message);return}try{await x.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await A(t),u.success("密码修改成功,请重新登录"),d.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}return(t,e)=>{const g=i("el-input"),v=i("el-form-item"),w=i("el-form"),y=i("el-button"),_=i("el-card");return P(),T("div",E,[e[7]||(e[7]=r("div",{class:"app-page-title"},[r("h2",null,"设置"),r("span",{class:"app-muted"},"管理员账号设置")],-1)),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:o(()=>[e[3]||(e[3]=r("h3",{class:"section-title"},"修改管理员用户名",-1)),a(w,{"label-width":"120px"},{default:o(()=>[a(v,{label:"新用户名"},{default:o(()=>[a(g,{modelValue:s.value,"onUpdate:modelValue":e[0]||(e[0]=c=>s.value=c),placeholder:"输入新用户名",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(y,{type:"primary",loading:n.value,onClick:B},{default:o(()=>[...e[2]||(e[2]=[b("保存用户名",-1)])]),_:1},8,["loading"])]),_:1}),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:o(()=>[e[5]||(e[5]=r("h3",{class:"section-title"},"修改管理员密码",-1)),a(w,{"label-width":"120px"},{default:o(()=>[a(v,{label:"新密码"},{default:o(()=>[a(g,{modelValue:d.value,"onUpdate:modelValue":e[1]||(e[1]=c=>d.value=c),type:"password","show-password":"",placeholder:"输入新密码",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(y,{type:"primary",loading:n.value,onClick:V},{default:o(()=>[...e[4]||(e[4]=[b("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码至少8位且包含字母与数字。",-1))]),_:1})])}}},W=h(U,[["__scopeId","data-v-83d3840a"]]);export{W as default};
import{a as m,_ as h}from"./index-ZQnpYe51.js";import{f as u,E as x}from"./vendor-element-CJoVtPsD.js";import{r as p,aW as i,z as T,A as P,B as r,S as a,L as o,P as b}from"./vendor-sLgkZK1v.js";import"./vendor-vue-CWkOjFoA.js";import"./vendor-axios-B9ygI19o.js";async function S(l){const{data:s}=await m.put("/admin/username",{new_username:l});return s}async function A(l){const{data:s}=await m.put("/admin/password",{new_password:l});return s}async function C(){const{data:l}=await m.post("/logout");return l}const E={class:"page-stack"},U={__name:"SettingsPage",setup(l){const s=p(""),d=p(""),n=p(!1);function k(t){const e=String(t||"");return e.length<8?{ok:!1,message:"密码长度至少8位"}:e.length>128?{ok:!1,message:"密码长度不能超过128个字符"}:!/[a-zA-Z]/.test(e)||!/\d/.test(e)?{ok:!1,message:"密码必须包含字母和数字"}:{ok:!0,message:""}}async function f(){try{await C()}catch{}finally{window.location.href="/yuyx"}}async function B(){const t=s.value.trim();if(!t){u.error("请输入新用户名");return}try{await x.confirm(`确定将管理员用户名修改为「${t}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await S(t),u.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function V(){const t=d.value;if(!t){u.error("请输入新密码");return}const e=k(t);if(!e.ok){u.error(e.message);return}try{await x.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await A(t),u.success("密码修改成功,请重新登录"),d.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}return(t,e)=>{const g=i("el-input"),v=i("el-form-item"),w=i("el-form"),y=i("el-button"),_=i("el-card");return P(),T("div",E,[e[7]||(e[7]=r("div",{class:"app-page-title"},[r("h2",null,"设置"),r("span",{class:"app-muted"},"管理员账号设置")],-1)),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:o(()=>[e[3]||(e[3]=r("h3",{class:"section-title"},"修改管理员用户名",-1)),a(w,{"label-width":"120px"},{default:o(()=>[a(v,{label:"新用户名"},{default:o(()=>[a(g,{modelValue:s.value,"onUpdate:modelValue":e[0]||(e[0]=c=>s.value=c),placeholder:"输入新用户名",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(y,{type:"primary",loading:n.value,onClick:B},{default:o(()=>[...e[2]||(e[2]=[b("保存用户名",-1)])]),_:1},8,["loading"])]),_:1}),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:o(()=>[e[5]||(e[5]=r("h3",{class:"section-title"},"修改管理员密码",-1)),a(w,{"label-width":"120px"},{default:o(()=>[a(v,{label:"新密码"},{default:o(()=>[a(g,{modelValue:d.value,"onUpdate:modelValue":e[1]||(e[1]=c=>d.value=c),type:"password","show-password":"",placeholder:"输入新密码",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(y,{type:"primary",loading:n.value,onClick:V},{default:o(()=>[...e[4]||(e[4]=[b("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码至少8位且包含字母与数字。",-1))]),_:1})])}}},W=h(U,[["__scopeId","data-v-83d3840a"]]);export{W as default};

View File

@@ -1,4 +1,4 @@
import{f as Ve,u as Y}from"./system-1p1K-j-4.js";import{a as P,_ as ge}from"./index-bXG7gEdM.js";import{E as ne,f as m}from"./vendor-element-CJoVtPsD.js";import{r as n,c as ke,w as xe,a1 as be,v as we,aW as v,b7 as Ue,M as Ce,A as V,z as g,B as s,S as l,L as t,P as k,N as Z,Q as ee}from"./vendor-sLgkZK1v.js";import"./vendor-vue-CWkOjFoA.js";import"./vendor-axios-B9ygI19o.js";async function le(r={}){const{data:c}=await P.get("/kdocs/status",{params:r});return c}async function Pe(r={}){const c={force:!0,...r},{data:x}=await P.post("/kdocs/qr",c);return x}async function Ie(){const{data:r}=await P.post("/kdocs/clear-login",{});return r}async function Ae(){const{data:r}=await P.get("/proxy/config");return r}async function Ne(r){const{data:c}=await P.post("/proxy/config",r);return c}async function Se(r){const{data:c}=await P.post("/proxy/test",r);return c}const De={class:"page-stack"},Ke={class:"config-grid"},Ee={class:"row-actions"},Be={class:"row-actions"},Te={class:"row-actions"},Le={class:"section-head"},Qe={class:"status-inline app-muted"},$e={key:0},he={key:1},qe={key:2},Me={class:"kdocs-inline"},Re={class:"kdocs-range"},ze={class:"row-actions"},Fe={key:0,class:"help"},He={key:1,class:"help"},Oe={class:"kdocs-qr"},Ge=["src"],We={__name:"SystemPage",setup(r){const c=n(!1),x=n(2),A=n(1),N=n(3),I=n(!1),f=n(""),S=n(3),D=n(!1),K=n(10),E=n(7),B=n(!1),T=n(""),L=n(""),Q=n(""),$=n(0),h=n("A"),q=n("D"),M=n(0),R=n(0),z=n(!1),F=n(""),p=n({}),b=n(!1),w=n(""),ae=n(!1),H=n(!1),U=n(!1),C=n(!1),O=n("");let G=null;const oe=ke(()=>H.value||U.value||C.value);function d(a){if(!a){O.value="";return}const e=new Date().toLocaleTimeString("zh-CN",{hour12:!1});O.value=`${a} (${e})`}async function ue(){c.value=!0;try{const[a,e,i]=await Promise.all([Ve(),Ae(),le().catch(()=>({}))]);x.value=a.max_concurrent_global??2,A.value=a.max_concurrent_per_account??1,N.value=a.max_screenshot_concurrent??3,D.value=(a.auto_approve_enabled??0)===1,K.value=a.auto_approve_hourly_limit??10,E.value=a.auto_approve_vip_days??7,I.value=(e.proxy_enabled??0)===1,f.value=e.proxy_api_url||"",S.value=e.proxy_expire_minutes??3,B.value=(a.kdocs_enabled??0)===1,T.value=a.kdocs_doc_url||"",L.value=a.kdocs_default_unit||"",Q.value=a.kdocs_sheet_name||"",$.value=a.kdocs_sheet_index??0,h.value=(a.kdocs_unit_column||"A").toUpperCase(),q.value=(a.kdocs_image_column||"D").toUpperCase(),M.value=a.kdocs_row_start??0,R.value=a.kdocs_row_end??0,z.value=(a.kdocs_admin_notify_enabled??0)===1,F.value=a.kdocs_admin_notify_email||"",p.value=i||{}}catch{}finally{c.value=!1}}async function de(){const a={max_concurrent_global:Number(x.value),max_concurrent_per_account:Number(A.value),max_screenshot_concurrent:Number(N.value)};try{await ne.confirm(`确定更新并发配置吗?
import{f as Ve,u as Y}from"./system-DO-Wv0rM.js";import{a as P,_ as ge}from"./index-ZQnpYe51.js";import{E as ne,f as m}from"./vendor-element-CJoVtPsD.js";import{r as n,c as ke,w as xe,a1 as be,v as we,aW as v,b7 as Ue,M as Ce,A as V,z as g,B as s,S as l,L as t,P as k,N as Z,Q as ee}from"./vendor-sLgkZK1v.js";import"./vendor-vue-CWkOjFoA.js";import"./vendor-axios-B9ygI19o.js";async function le(r={}){const{data:c}=await P.get("/kdocs/status",{params:r});return c}async function Pe(r={}){const c={force:!0,...r},{data:x}=await P.post("/kdocs/qr",c);return x}async function Ie(){const{data:r}=await P.post("/kdocs/clear-login",{});return r}async function Ae(){const{data:r}=await P.get("/proxy/config");return r}async function Ne(r){const{data:c}=await P.post("/proxy/config",r);return c}async function Se(r){const{data:c}=await P.post("/proxy/test",r);return c}const De={class:"page-stack"},Ke={class:"config-grid"},Ee={class:"row-actions"},Be={class:"row-actions"},Te={class:"row-actions"},Le={class:"section-head"},Qe={class:"status-inline app-muted"},$e={key:0},he={key:1},qe={key:2},Me={class:"kdocs-inline"},Re={class:"kdocs-range"},ze={class:"row-actions"},Fe={key:0,class:"help"},He={key:1,class:"help"},Oe={class:"kdocs-qr"},Ge=["src"],We={__name:"SystemPage",setup(r){const c=n(!1),x=n(2),A=n(1),N=n(3),I=n(!1),f=n(""),S=n(3),D=n(!1),K=n(10),E=n(7),B=n(!1),T=n(""),L=n(""),Q=n(""),$=n(0),h=n("A"),q=n("D"),M=n(0),R=n(0),z=n(!1),F=n(""),p=n({}),b=n(!1),w=n(""),ae=n(!1),H=n(!1),U=n(!1),C=n(!1),O=n("");let G=null;const oe=ke(()=>H.value||U.value||C.value);function d(a){if(!a){O.value="";return}const e=new Date().toLocaleTimeString("zh-CN",{hour12:!1});O.value=`${a} (${e})`}async function ue(){c.value=!0;try{const[a,e,i]=await Promise.all([Ve(),Ae(),le().catch(()=>({}))]);x.value=a.max_concurrent_global??2,A.value=a.max_concurrent_per_account??1,N.value=a.max_screenshot_concurrent??3,D.value=(a.auto_approve_enabled??0)===1,K.value=a.auto_approve_hourly_limit??10,E.value=a.auto_approve_vip_days??7,I.value=(e.proxy_enabled??0)===1,f.value=e.proxy_api_url||"",S.value=e.proxy_expire_minutes??3,B.value=(a.kdocs_enabled??0)===1,T.value=a.kdocs_doc_url||"",L.value=a.kdocs_default_unit||"",Q.value=a.kdocs_sheet_name||"",$.value=a.kdocs_sheet_index??0,h.value=(a.kdocs_unit_column||"A").toUpperCase(),q.value=(a.kdocs_image_column||"D").toUpperCase(),M.value=a.kdocs_row_start??0,R.value=a.kdocs_row_end??0,z.value=(a.kdocs_admin_notify_enabled??0)===1,F.value=a.kdocs_admin_notify_email||"",p.value=i||{}}catch{}finally{c.value=!1}}async function de(){const a={max_concurrent_global:Number(x.value),max_concurrent_per_account:Number(A.value),max_screenshot_concurrent:Number(N.value)};try{await ne.confirm(`确定更新并发配置吗?
全局并发数: ${a.max_concurrent_global}
单账号并发数: ${a.max_concurrent_per_account}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{a as n}from"./index-bXG7gEdM.js";async function i(){const{data:a}=await n.get("/email/settings");return a}async function e(a){const{data:t}=await n.post("/email/settings",a);return t}async function c(){const{data:a}=await n.get("/email/stats");return a}async function o(a){const{data:t}=await n.get("/email/logs",{params:a});return t}async function l(a){const{data:t}=await n.post("/email/logs/cleanup",{days:a});return t}export{o as a,i as b,l as c,c as f,e as u};
import{a as n}from"./index-ZQnpYe51.js";async function i(){const{data:a}=await n.get("/email/settings");return a}async function e(a){const{data:t}=await n.post("/email/settings",a);return t}async function c(){const{data:a}=await n.get("/email/stats");return a}async function o(a){const{data:t}=await n.get("/email/logs",{params:a});return t}async function l(a){const{data:t}=await n.post("/email/logs/cleanup",{days:a});return t}export{o as a,i as b,l as c,c as f,e as u};

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{a}from"./index-bXG7gEdM.js";async function o(){const{data:t}=await a.get("/system/config");return t}async function e(t){const{data:n}=await a.post("/system/config",t);return n}export{o as f,e as u};
import{a}from"./index-ZQnpYe51.js";async function o(){const{data:t}=await a.get("/system/config");return t}async function e(t){const{data:n}=await a.post("/system/config",t);return n}export{o as f,e as u};

View File

@@ -1 +1 @@
import{a}from"./index-bXG7gEdM.js";async function e(){const{data:t}=await a.get("/server/info");return t}async function c(){const{data:t}=await a.get("/docker_stats");return t}async function r(){const{data:t}=await a.get("/request_metrics");return t}async function o(){const{data:t}=await a.get("/task/stats");return t}async function i(){const{data:t}=await a.get("/task/running");return t}async function u(t){const{data:s}=await a.get("/task/logs",{params:t});return s}async function f(t){const{data:s}=await a.post("/task/logs/clear",{days:t});return s}export{i as a,e as b,c,r as d,u as e,o as f,f as g};
import{a}from"./index-ZQnpYe51.js";async function e(){const{data:t}=await a.get("/server/info");return t}async function c(){const{data:t}=await a.get("/docker_stats");return t}async function r(){const{data:t}=await a.get("/request_metrics");return t}async function o(){const{data:t}=await a.get("/task/stats");return t}async function i(){const{data:t}=await a.get("/task/running");return t}async function u(t){const{data:s}=await a.get("/task/logs",{params:t});return s}async function f(t){const{data:s}=await a.post("/task/logs/clear",{days:t});return s}export{i as a,e as b,c,r as d,u as e,o as f,f as g};

View File

@@ -1 +1 @@
import{a as t}from"./index-bXG7gEdM.js";async function n(){const{data:s}=await t.get("/users");return s}async function o(s){const{data:a}=await t.post(`/users/${s}/approve`);return a}async function c(s){const{data:a}=await t.post(`/users/${s}/reject`);return a}async function i(s){const{data:a}=await t.delete(`/users/${s}`);return a}async function u(s,a){const{data:e}=await t.post(`/users/${s}/vip`,{days:a});return e}async function p(s){const{data:a}=await t.delete(`/users/${s}/vip`);return a}async function d(s,a){const{data:e}=await t.post(`/users/${s}/reset_password`,{new_password:a});return e}export{o as a,p as b,d as c,i as d,n as f,c as r,u as s};
import{a as t}from"./index-ZQnpYe51.js";async function n(){const{data:s}=await t.get("/users");return s}async function o(s){const{data:a}=await t.post(`/users/${s}/approve`);return a}async function c(s){const{data:a}=await t.post(`/users/${s}/reject`);return a}async function i(s){const{data:a}=await t.delete(`/users/${s}`);return a}async function u(s,a){const{data:e}=await t.post(`/users/${s}/vip`,{days:a});return e}async function p(s){const{data:a}=await t.delete(`/users/${s}/vip`);return a}async function d(s,a){const{data:e}=await t.post(`/users/${s}/reset_password`,{new_password:a});return e}export{o as a,p as b,d as c,i as d,n as f,c as r,u as s};

View File

@@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>后台管理 - 知识管理平台</title>
<script type="module" crossorigin src="./assets/index-bXG7gEdM.js"></script>
<script type="module" crossorigin src="./assets/index-ZQnpYe51.js"></script>
<link rel="modulepreload" crossorigin href="./assets/vendor-sLgkZK1v.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-element-CJoVtPsD.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-vue-CWkOjFoA.js">