feat(report): add 24h slow-sql dashboard and metrics api
This commit is contained in:
@@ -27,6 +27,10 @@ DB_LOCK_RETRY_COUNT=3
|
||||
DB_LOCK_RETRY_BASE_MS=50
|
||||
DB_SLOW_QUERY_MS=120
|
||||
DB_SLOW_QUERY_SQL_MAX_LEN=240
|
||||
DB_SLOW_SQL_WINDOW_SECONDS=86400
|
||||
DB_SLOW_SQL_TOP_LIMIT=12
|
||||
DB_SLOW_SQL_RECENT_LIMIT=50
|
||||
DB_SLOW_SQL_MAX_EVENTS=20000
|
||||
DB_PRAGMA_OPTIMIZE_INTERVAL_SECONDS=21600
|
||||
DB_ANALYZE_INTERVAL_SECONDS=86400
|
||||
DB_WAL_CHECKPOINT_INTERVAL_SECONDS=43200
|
||||
|
||||
@@ -15,6 +15,11 @@ export async function fetchRequestMetrics() {
|
||||
return data
|
||||
}
|
||||
|
||||
export async function fetchSlowSqlMetrics() {
|
||||
const { data } = await api.get('/slow_sql_metrics')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function fetchTaskStats() {
|
||||
const { data } = await api.get('/task/stats')
|
||||
return data
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
|
||||
import { fetchFeedbackStats } from '../api/feedbacks'
|
||||
import { fetchEmailStats } from '../api/email'
|
||||
import { fetchDockerStats, fetchRequestMetrics, fetchRunningTasks, fetchServerInfo, fetchTaskStats } from '../api/tasks'
|
||||
import { fetchDockerStats, fetchRequestMetrics, fetchRunningTasks, fetchServerInfo, fetchSlowSqlMetrics, fetchTaskStats } from '../api/tasks'
|
||||
import { fetchBrowserPoolStats } from '../api/browser_pool'
|
||||
import { fetchSystemConfig } from '../api/system'
|
||||
import MetricGrid from '../components/MetricGrid.vue'
|
||||
@@ -38,7 +38,9 @@ const dockerStats = ref(null)
|
||||
const browserPoolStats = ref(null)
|
||||
const systemConfig = ref(null)
|
||||
const requestMetrics = ref(null)
|
||||
const slowSqlMetrics = ref(null)
|
||||
const requestDetailsOpen = ref(false)
|
||||
const slowSqlDetailsOpen = ref(false)
|
||||
const queueTab = ref('running')
|
||||
|
||||
function recordUpdatedAt() {
|
||||
@@ -350,6 +352,66 @@ const requestModuleDesc = computed(() => {
|
||||
return `均值 ${avgMs} · 峰值 ${maxMs} · 慢阈 ${slowThreshold} · 最近 ${lastAt}`
|
||||
})
|
||||
|
||||
const slowSqlTopRows = computed(() => {
|
||||
const rows = Array.isArray(slowSqlMetrics.value?.top_sql) ? slowSqlMetrics.value.top_sql : []
|
||||
return rows.slice(0, 3)
|
||||
})
|
||||
|
||||
const slowSqlWindowHours = computed(() => {
|
||||
const seconds = normalizeCount(slowSqlMetrics.value?.window_seconds)
|
||||
if (seconds <= 0) return 24
|
||||
return Math.max(1, Math.round(seconds / 3600))
|
||||
})
|
||||
|
||||
const slowSqlModuleItems = computed(() => {
|
||||
const items = [
|
||||
{ label: `慢SQL(${slowSqlWindowHours.value}h)`, value: normalizeCount(slowSqlMetrics.value?.total_slow_queries) },
|
||||
{ label: '去重SQL', value: normalizeCount(slowSqlMetrics.value?.unique_sql) },
|
||||
{ label: '平均耗时', value: formatMs(slowSqlMetrics.value?.avg_duration_ms) },
|
||||
{ label: '峰值耗时', value: formatMs(slowSqlMetrics.value?.max_duration_ms) },
|
||||
]
|
||||
|
||||
slowSqlTopRows.value.forEach((row, index) => {
|
||||
items.push({
|
||||
label: `慢SQL${index + 1}`,
|
||||
value: `${formatMs(row?.max_ms)} · ${String(row?.sql || '-')}`,
|
||||
})
|
||||
})
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
const slowSqlModuleDesc = computed(() => {
|
||||
const threshold = formatMs(slowSqlMetrics.value?.slow_threshold_ms)
|
||||
const lastAt = formatUnixTime(slowSqlMetrics.value?.last_slow_ts)
|
||||
return `窗口 ${slowSqlWindowHours.value}h · 慢阈 ${threshold} · 最近 ${lastAt}`
|
||||
})
|
||||
|
||||
const slowSqlDetailTopRows = computed(() => {
|
||||
const rows = Array.isArray(slowSqlMetrics.value?.top_sql) ? slowSqlMetrics.value.top_sql : []
|
||||
return rows.map((row, index) => ({
|
||||
rank: index + 1,
|
||||
sql: String(row?.sql || '-'),
|
||||
count: normalizeCount(row?.count),
|
||||
avg_ms: formatMs(row?.avg_ms),
|
||||
max_ms: formatMs(row?.max_ms),
|
||||
last_seen: formatUnixDateTime(row?.last_ts),
|
||||
sample_params: String(row?.sample_params || '-'),
|
||||
}))
|
||||
})
|
||||
|
||||
const slowSqlRecentRows = computed(() => {
|
||||
const rows = Array.isArray(slowSqlMetrics.value?.recent_slow_sql) ? slowSqlMetrics.value.recent_slow_sql : []
|
||||
return [...rows]
|
||||
.sort((a, b) => Number(b?.time || 0) - Number(a?.time || 0))
|
||||
.map((row) => ({
|
||||
time_text: formatUnixDateTime(row?.time),
|
||||
sql: String(row?.sql || '-'),
|
||||
duration_ms: formatMs(row?.duration_ms),
|
||||
params: String(row?.params || '-'),
|
||||
}))
|
||||
})
|
||||
|
||||
const requestDetailTopPaths = computed(() => {
|
||||
const rows = Array.isArray(requestMetrics.value?.top_paths) ? requestMetrics.value.top_paths : []
|
||||
return rows.map((row, index) => ({
|
||||
@@ -387,6 +449,10 @@ function openRequestDetails() {
|
||||
requestDetailsOpen.value = true
|
||||
}
|
||||
|
||||
function openSlowSqlDetails() {
|
||||
slowSqlDetailsOpen.value = true
|
||||
}
|
||||
|
||||
const configModuleItems = computed(() => [
|
||||
{ label: '定时任务', value: scheduleEnabled.value ? '启用' : '关闭' },
|
||||
{ label: '执行时间', value: scheduleTime.value || '-' },
|
||||
@@ -448,6 +514,13 @@ const mobileModules = computed(() => [
|
||||
tone: 'purple',
|
||||
items: requestModuleItems.value,
|
||||
},
|
||||
{
|
||||
key: 'slow_sql',
|
||||
title: '慢SQL监控',
|
||||
desc: slowSqlModuleDesc.value,
|
||||
tone: 'red',
|
||||
items: slowSqlModuleItems.value,
|
||||
},
|
||||
{
|
||||
key: 'worker',
|
||||
title: '截图线程池',
|
||||
@@ -481,6 +554,7 @@ async function refreshAll(options = {}) {
|
||||
dockerResult,
|
||||
browserPoolResult,
|
||||
requestMetricsResult,
|
||||
slowSqlResult,
|
||||
configResult,
|
||||
] = await Promise.allSettled([
|
||||
fetchTaskStats(),
|
||||
@@ -491,6 +565,7 @@ async function refreshAll(options = {}) {
|
||||
fetchDockerStats(),
|
||||
fetchBrowserPoolStats(),
|
||||
fetchRequestMetrics(),
|
||||
fetchSlowSqlMetrics(),
|
||||
fetchSystemConfig(),
|
||||
])
|
||||
|
||||
@@ -502,6 +577,7 @@ async function refreshAll(options = {}) {
|
||||
if (dockerResult.status === 'fulfilled') dockerStats.value = dockerResult.value
|
||||
if (browserPoolResult.status === 'fulfilled') browserPoolStats.value = browserPoolResult.value
|
||||
if (requestMetricsResult.status === 'fulfilled') requestMetrics.value = requestMetricsResult.value
|
||||
if (slowSqlResult.status === 'fulfilled') slowSqlMetrics.value = slowSqlResult.value
|
||||
if (configResult.status === 'fulfilled') systemConfig.value = configResult.value
|
||||
|
||||
await refreshStats?.()
|
||||
@@ -599,8 +675,15 @@ 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 v-if="module.key === 'request' || module.key === 'slow_sql'" class="module-extra-actions">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
plain
|
||||
@click="module.key === 'request' ? openRequestDetails() : openSlowSqlDetails()"
|
||||
>
|
||||
{{ module.key === 'request' ? '查看慢接口详情' : '查看慢SQL详情' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</section>
|
||||
@@ -644,6 +727,43 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="slowSqlDetailsOpen" title="慢SQL详情(近24小时)" width="min(1080px, 96vw)">
|
||||
<div class="request-dialog-summary app-muted">
|
||||
<span>慢SQL总数:{{ normalizeCount(slowSqlMetrics?.total_slow_queries) }}</span>
|
||||
<span>去重SQL:{{ normalizeCount(slowSqlMetrics?.unique_sql) }}</span>
|
||||
<span>平均耗时:{{ formatMs(slowSqlMetrics?.avg_duration_ms) }}</span>
|
||||
<span>峰值耗时:{{ formatMs(slowSqlMetrics?.max_duration_ms) }}</span>
|
||||
<span>慢阈值:{{ formatMs(slowSqlMetrics?.slow_threshold_ms) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="request-dialog-block">
|
||||
<div class="request-dialog-title">TOP 慢SQL(按出现次数)</div>
|
||||
<div class="table-wrap">
|
||||
<el-table :data="slowSqlDetailTopRows" size="small" max-height="320">
|
||||
<el-table-column prop="rank" label="#" width="60" />
|
||||
<el-table-column prop="sql" label="SQL" min-width="400" show-overflow-tooltip />
|
||||
<el-table-column prop="count" label="次数" width="90" />
|
||||
<el-table-column prop="avg_ms" label="平均耗时" width="120" />
|
||||
<el-table-column prop="max_ms" label="峰值耗时" width="120" />
|
||||
<el-table-column prop="last_seen" label="最近出现" width="180" />
|
||||
<el-table-column prop="sample_params" label="参数样本" min-width="140" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="request-dialog-block">
|
||||
<div class="request-dialog-title">最近慢SQL</div>
|
||||
<div class="table-wrap">
|
||||
<el-table :data="slowSqlRecentRows" size="small" max-height="320">
|
||||
<el-table-column prop="time_text" label="时间" width="180" />
|
||||
<el-table-column prop="sql" label="SQL" min-width="420" show-overflow-tooltip />
|
||||
<el-table-column prop="duration_ms" label="耗时" width="110" />
|
||||
<el-table-column prop="params" label="参数" min-width="130" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -135,6 +135,10 @@ class Config:
|
||||
DB_LOCK_RETRY_BASE_MS = int(os.environ.get("DB_LOCK_RETRY_BASE_MS", "50"))
|
||||
DB_SLOW_QUERY_MS = int(os.environ.get("DB_SLOW_QUERY_MS", "120"))
|
||||
DB_SLOW_QUERY_SQL_MAX_LEN = int(os.environ.get("DB_SLOW_QUERY_SQL_MAX_LEN", "240"))
|
||||
DB_SLOW_SQL_WINDOW_SECONDS = int(os.environ.get("DB_SLOW_SQL_WINDOW_SECONDS", "86400"))
|
||||
DB_SLOW_SQL_TOP_LIMIT = int(os.environ.get("DB_SLOW_SQL_TOP_LIMIT", "12"))
|
||||
DB_SLOW_SQL_RECENT_LIMIT = int(os.environ.get("DB_SLOW_SQL_RECENT_LIMIT", "50"))
|
||||
DB_SLOW_SQL_MAX_EVENTS = int(os.environ.get("DB_SLOW_SQL_MAX_EVENTS", "20000"))
|
||||
DB_PRAGMA_OPTIMIZE_INTERVAL_SECONDS = int(os.environ.get("DB_PRAGMA_OPTIMIZE_INTERVAL_SECONDS", "21600"))
|
||||
DB_ANALYZE_INTERVAL_SECONDS = int(os.environ.get("DB_ANALYZE_INTERVAL_SECONDS", "86400"))
|
||||
DB_WAL_CHECKPOINT_INTERVAL_SECONDS = int(os.environ.get("DB_WAL_CHECKPOINT_INTERVAL_SECONDS", "43200"))
|
||||
@@ -280,6 +284,14 @@ class Config:
|
||||
errors.append("DB_SLOW_QUERY_MS不能为负数")
|
||||
if cls.DB_SLOW_QUERY_SQL_MAX_LEN < 80:
|
||||
errors.append("DB_SLOW_QUERY_SQL_MAX_LEN建议至少80")
|
||||
if cls.DB_SLOW_SQL_WINDOW_SECONDS < 600:
|
||||
errors.append("DB_SLOW_SQL_WINDOW_SECONDS建议至少600")
|
||||
if cls.DB_SLOW_SQL_TOP_LIMIT < 5:
|
||||
errors.append("DB_SLOW_SQL_TOP_LIMIT建议至少5")
|
||||
if cls.DB_SLOW_SQL_RECENT_LIMIT < 10:
|
||||
errors.append("DB_SLOW_SQL_RECENT_LIMIT建议至少10")
|
||||
if cls.DB_SLOW_SQL_MAX_EVENTS < cls.DB_SLOW_SQL_RECENT_LIMIT:
|
||||
errors.append("DB_SLOW_SQL_MAX_EVENTS必须不小于DB_SLOW_SQL_RECENT_LIMIT")
|
||||
|
||||
# 验证日志配置
|
||||
if cls.LOG_LEVEL not in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
|
||||
|
||||
14
db_pool.py
14
db_pool.py
@@ -302,9 +302,17 @@ class PooledConnection:
|
||||
return
|
||||
if elapsed_ms < DB_SLOW_QUERY_MS:
|
||||
return
|
||||
logger.warning(
|
||||
f"[慢SQL] {elapsed_ms:.1f}ms sql=\"{_compact_sql(sql)}\" params={_describe_params(parameters)}"
|
||||
)
|
||||
|
||||
params_info = _describe_params(parameters)
|
||||
|
||||
try:
|
||||
from services.slow_sql_metrics import record_slow_sql
|
||||
|
||||
record_slow_sql(sql=sql, duration_ms=elapsed_ms, params_info=params_info)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.warning(f"[慢SQL] {elapsed_ms:.1f}ms sql=\"{_compact_sql(sql)}\" params={params_info}")
|
||||
|
||||
def cursor(self):
|
||||
"""获取游标"""
|
||||
|
||||
@@ -33,6 +33,12 @@ services:
|
||||
- DB_FILE=data/app_data.db
|
||||
- DB_POOL_SIZE=5
|
||||
- SYSTEM_CONFIG_CACHE_TTL_SECONDS=30
|
||||
- DB_SLOW_QUERY_MS=120
|
||||
- DB_SLOW_SQL_WINDOW_SECONDS=86400
|
||||
- DB_SLOW_SQL_TOP_LIMIT=12
|
||||
- DB_SLOW_SQL_RECENT_LIMIT=50
|
||||
- DB_SLOW_SQL_MAX_EVENTS=20000
|
||||
- ADMIN_SLOW_SQL_METRICS_CACHE_TTL_SECONDS=3
|
||||
# 并发控制配置
|
||||
- MAX_CONCURRENT_GLOBAL=2
|
||||
- MAX_CONCURRENT_PER_ACCOUNT=1
|
||||
|
||||
@@ -14,6 +14,7 @@ from flask import jsonify, session
|
||||
from routes.admin_api import admin_api_bp
|
||||
from routes.decorators import admin_required
|
||||
from services.request_metrics import get_request_metrics_snapshot
|
||||
from services.slow_sql_metrics import get_slow_sql_metrics_snapshot
|
||||
from services.time_utils import BEIJING_TZ, get_beijing_now
|
||||
|
||||
logger = get_logger("app")
|
||||
@@ -30,6 +31,10 @@ _REQUEST_METRICS_CACHE_TTL = max(1.0, float(os.environ.get("ADMIN_REQUEST_METRIC
|
||||
_request_metrics_cache: dict[str, object] = {"expires_at_monotonic": 0.0, "data": None}
|
||||
_request_metrics_cache_lock = threading.Lock()
|
||||
|
||||
_SLOW_SQL_METRICS_CACHE_TTL = max(1.0, float(os.environ.get("ADMIN_SLOW_SQL_METRICS_CACHE_TTL_SECONDS", "3")))
|
||||
_slow_sql_metrics_cache: dict[str, object] = {"expires_at_monotonic": 0.0, "data": None}
|
||||
_slow_sql_metrics_cache_lock = threading.Lock()
|
||||
|
||||
|
||||
def _get_system_stats_cached() -> dict:
|
||||
now = time.monotonic()
|
||||
@@ -65,6 +70,23 @@ def _get_request_metrics_cached() -> dict:
|
||||
return dict(fresh_data)
|
||||
|
||||
|
||||
def _get_slow_sql_metrics_cached() -> dict:
|
||||
now = time.monotonic()
|
||||
with _slow_sql_metrics_cache_lock:
|
||||
expires_at = float(_slow_sql_metrics_cache.get("expires_at_monotonic") or 0.0)
|
||||
cached_data = _slow_sql_metrics_cache.get("data")
|
||||
if isinstance(cached_data, dict) and now < expires_at:
|
||||
return dict(cached_data)
|
||||
|
||||
fresh_data = get_slow_sql_metrics_snapshot() or {}
|
||||
|
||||
with _slow_sql_metrics_cache_lock:
|
||||
_slow_sql_metrics_cache["data"] = dict(fresh_data)
|
||||
_slow_sql_metrics_cache["expires_at_monotonic"] = now + _SLOW_SQL_METRICS_CACHE_TTL
|
||||
|
||||
return dict(fresh_data)
|
||||
|
||||
|
||||
@admin_api_bp.route("/stats", methods=["GET"])
|
||||
@admin_required
|
||||
def get_system_stats():
|
||||
@@ -86,6 +108,18 @@ def get_request_metrics():
|
||||
return jsonify({"error": "获取请求级监控指标失败"}), 500
|
||||
|
||||
|
||||
@admin_api_bp.route("/slow_sql_metrics", methods=["GET"])
|
||||
@admin_required
|
||||
def get_slow_sql_metrics():
|
||||
"""获取慢 SQL 监控指标"""
|
||||
try:
|
||||
metrics = _get_slow_sql_metrics_cached()
|
||||
return jsonify(metrics)
|
||||
except Exception as e:
|
||||
logger.exception(f"获取慢 SQL 监控指标失败: {e}")
|
||||
return jsonify({"error": "获取慢 SQL 监控指标失败"}), 500
|
||||
|
||||
|
||||
@admin_api_bp.route("/browser_pool/stats", methods=["GET"])
|
||||
@admin_required
|
||||
def get_browser_pool_stats():
|
||||
|
||||
181
services/slow_sql_metrics.py
Normal file
181
services/slow_sql_metrics.py
Normal file
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
慢 SQL 指标(轻量内存版)
|
||||
- 记录超过阈值的 SQL 执行样本
|
||||
- 维护近窗口期(默认24小时)聚合统计
|
||||
- 输出 TOP SQL 与最近慢 SQL 列表
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from collections import deque
|
||||
|
||||
_SLOW_SQL_THRESHOLD_MS = max(0.0, float(os.environ.get("DB_SLOW_QUERY_MS", "120") or 120))
|
||||
_WINDOW_SECONDS = max(600, int(os.environ.get("DB_SLOW_SQL_WINDOW_SECONDS", "86400") or 86400))
|
||||
_TOP_LIMIT = max(5, int(os.environ.get("DB_SLOW_SQL_TOP_LIMIT", "12") or 12))
|
||||
_RECENT_LIMIT = max(10, int(os.environ.get("DB_SLOW_SQL_RECENT_LIMIT", "50") or 50))
|
||||
_MAX_EVENTS = max(_RECENT_LIMIT, int(os.environ.get("DB_SLOW_SQL_MAX_EVENTS", "20000") or 20000))
|
||||
_SQL_MAX_LEN = max(80, int(os.environ.get("DB_SLOW_QUERY_SQL_MAX_LEN", "240") or 240))
|
||||
|
||||
_lock = threading.Lock()
|
||||
|
||||
_state = {
|
||||
"start_ts": time.time(),
|
||||
"last_slow_ts": 0.0,
|
||||
"events": deque(),
|
||||
"recent": deque(maxlen=_RECENT_LIMIT),
|
||||
}
|
||||
|
||||
|
||||
def _compact_text(value: str, max_len: int) -> str:
|
||||
text = " ".join(str(value or "").split())
|
||||
if len(text) <= max_len:
|
||||
return text
|
||||
return f"{text[: max_len - 3]}..."
|
||||
|
||||
|
||||
def _compact_sql(sql: str) -> str:
|
||||
return _compact_text(str(sql or ""), _SQL_MAX_LEN)
|
||||
|
||||
|
||||
def _compact_params(params_info: str) -> str:
|
||||
return _compact_text(str(params_info or "none"), 64)
|
||||
|
||||
|
||||
def _prune_events_locked(now_ts: float) -> None:
|
||||
cutoff_ts = now_ts - float(_WINDOW_SECONDS)
|
||||
events = _state["events"]
|
||||
|
||||
while events and float(events[0].get("time", 0.0) or 0.0) < cutoff_ts:
|
||||
events.popleft()
|
||||
|
||||
overflow = len(events) - int(_MAX_EVENTS)
|
||||
while overflow > 0 and events:
|
||||
events.popleft()
|
||||
overflow -= 1
|
||||
|
||||
|
||||
def record_slow_sql(*, sql: str, duration_ms: float, params_info: str = "none") -> None:
|
||||
duration = max(0.0, float(duration_ms or 0.0))
|
||||
now = time.time()
|
||||
sql_text = _compact_sql(sql)
|
||||
params_text = _compact_params(params_info)
|
||||
|
||||
event = {
|
||||
"time": now,
|
||||
"sql": sql_text,
|
||||
"duration_ms": round(duration, 2),
|
||||
"params": params_text,
|
||||
}
|
||||
|
||||
with _lock:
|
||||
_prune_events_locked(now)
|
||||
_state["events"].append(event)
|
||||
_state["recent"].append(event)
|
||||
_state["last_slow_ts"] = now
|
||||
|
||||
|
||||
def get_slow_sql_metrics_snapshot() -> dict:
|
||||
now = time.time()
|
||||
|
||||
with _lock:
|
||||
_prune_events_locked(now)
|
||||
|
||||
events = list(_state["events"])
|
||||
recent_rows = list(_state["recent"])
|
||||
last_slow_ts = float(_state.get("last_slow_ts") or 0.0)
|
||||
|
||||
grouped: dict[str, dict] = {}
|
||||
total_duration_ms = 0.0
|
||||
max_duration_ms = 0.0
|
||||
|
||||
for item in events:
|
||||
sql_text = str(item.get("sql") or "-")
|
||||
duration = float(item.get("duration_ms") or 0.0)
|
||||
ts = float(item.get("time") or 0.0)
|
||||
params_text = str(item.get("params") or "none")
|
||||
|
||||
total_duration_ms += duration
|
||||
if duration > max_duration_ms:
|
||||
max_duration_ms = duration
|
||||
|
||||
bucket = grouped.get(sql_text)
|
||||
if bucket is None:
|
||||
bucket = {
|
||||
"sql": sql_text,
|
||||
"count": 0,
|
||||
"total_ms": 0.0,
|
||||
"max_ms": 0.0,
|
||||
"last_ts": 0.0,
|
||||
"params": params_text,
|
||||
}
|
||||
grouped[sql_text] = bucket
|
||||
|
||||
bucket["count"] = int(bucket["count"] or 0) + 1
|
||||
bucket["total_ms"] = float(bucket["total_ms"] or 0.0) + duration
|
||||
if duration > float(bucket["max_ms"] or 0.0):
|
||||
bucket["max_ms"] = duration
|
||||
bucket["params"] = params_text
|
||||
if ts >= float(bucket["last_ts"] or 0.0):
|
||||
bucket["last_ts"] = ts
|
||||
|
||||
top_sql_rows = sorted(
|
||||
grouped.values(),
|
||||
key=lambda row: (
|
||||
int(row.get("count", 0) or 0),
|
||||
float(row.get("max_ms", 0.0) or 0.0),
|
||||
float(row.get("total_ms", 0.0) or 0.0),
|
||||
),
|
||||
reverse=True,
|
||||
)[:_TOP_LIMIT]
|
||||
|
||||
top_sql = []
|
||||
for idx, row in enumerate(top_sql_rows, start=1):
|
||||
count = int(row.get("count", 0) or 0)
|
||||
total_ms = float(row.get("total_ms", 0.0) or 0.0)
|
||||
avg_ms = (total_ms / count) if count > 0 else 0.0
|
||||
top_sql.append(
|
||||
{
|
||||
"rank": idx,
|
||||
"sql": row.get("sql") or "-",
|
||||
"count": count,
|
||||
"avg_ms": round(avg_ms, 2),
|
||||
"max_ms": round(float(row.get("max_ms", 0.0) or 0.0), 2),
|
||||
"last_ts": int(float(row.get("last_ts", 0.0) or 0.0)),
|
||||
"sample_params": row.get("params") or "none",
|
||||
}
|
||||
)
|
||||
|
||||
cutoff_ts = now - float(_WINDOW_SECONDS)
|
||||
recent = [
|
||||
{
|
||||
"time": int(float(item.get("time") or 0.0)),
|
||||
"sql": str(item.get("sql") or "-"),
|
||||
"duration_ms": round(float(item.get("duration_ms") or 0.0), 2),
|
||||
"params": str(item.get("params") or "none"),
|
||||
}
|
||||
for item in recent_rows
|
||||
if float(item.get("time") or 0.0) >= cutoff_ts
|
||||
]
|
||||
|
||||
total_events = len(events)
|
||||
avg_duration_ms = round((total_duration_ms / total_events), 2) if total_events > 0 else 0.0
|
||||
|
||||
return {
|
||||
"since_ts": int(float(events[0].get("time") or 0.0)) if events else 0,
|
||||
"window_seconds": _WINDOW_SECONDS,
|
||||
"top_limit": _TOP_LIMIT,
|
||||
"recent_limit": _RECENT_LIMIT,
|
||||
"slow_threshold_ms": _SLOW_SQL_THRESHOLD_MS,
|
||||
"total_slow_queries": total_events,
|
||||
"unique_sql": len(grouped),
|
||||
"avg_duration_ms": avg_duration_ms,
|
||||
"max_duration_ms": round(max_duration_ms, 2),
|
||||
"last_slow_ts": int(last_slow_ts) if last_slow_ts > 0 else 0,
|
||||
"top_sql": top_sql,
|
||||
"recent_slow_sql": recent,
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"_MetricGrid-Db4tVG3V.js": {
|
||||
"file": "assets/MetricGrid-Db4tVG3V.js",
|
||||
"_MetricGrid-Cm-6uQPC.js": {
|
||||
"file": "assets/MetricGrid-Cm-6uQPC.js",
|
||||
"name": "MetricGrid",
|
||||
"imports": [
|
||||
"index.html",
|
||||
@@ -14,29 +14,29 @@
|
||||
"file": "assets/MetricGrid-yP_dkP6X.css",
|
||||
"src": "_MetricGrid-yP_dkP6X.css"
|
||||
},
|
||||
"_email-DUrghb1a.js": {
|
||||
"file": "assets/email-DUrghb1a.js",
|
||||
"_email-UCdCFkiw.js": {
|
||||
"file": "assets/email-UCdCFkiw.js",
|
||||
"name": "email",
|
||||
"imports": [
|
||||
"index.html"
|
||||
]
|
||||
},
|
||||
"_system-CKJgzNxM.js": {
|
||||
"file": "assets/system-CKJgzNxM.js",
|
||||
"_system-BqUnjCUC.js": {
|
||||
"file": "assets/system-BqUnjCUC.js",
|
||||
"name": "system",
|
||||
"imports": [
|
||||
"index.html"
|
||||
]
|
||||
},
|
||||
"_tasks-h6MdOVMy.js": {
|
||||
"file": "assets/tasks-h6MdOVMy.js",
|
||||
"_tasks-DW-oE78v.js": {
|
||||
"file": "assets/tasks-DW-oE78v.js",
|
||||
"name": "tasks",
|
||||
"imports": [
|
||||
"index.html"
|
||||
]
|
||||
},
|
||||
"_users-C4yl3Txu.js": {
|
||||
"file": "assets/users-C4yl3Txu.js",
|
||||
"_users-LqVgYOIa.js": {
|
||||
"file": "assets/users-LqVgYOIa.js",
|
||||
"name": "users",
|
||||
"imports": [
|
||||
"index.html"
|
||||
@@ -54,7 +54,7 @@
|
||||
"src": "_vendor-C68yOrAy.css"
|
||||
},
|
||||
"index.html": {
|
||||
"file": "assets/index-UGawl5hI.js",
|
||||
"file": "assets/index-C51y9xCM.js",
|
||||
"name": "index",
|
||||
"src": "index.html",
|
||||
"isEntry": true,
|
||||
@@ -77,7 +77,7 @@
|
||||
]
|
||||
},
|
||||
"src/pages/AnnouncementsPage.vue": {
|
||||
"file": "assets/AnnouncementsPage-C6-W6_wH.js",
|
||||
"file": "assets/AnnouncementsPage-D4vGLsnP.js",
|
||||
"name": "AnnouncementsPage",
|
||||
"src": "src/pages/AnnouncementsPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
@@ -90,14 +90,14 @@
|
||||
]
|
||||
},
|
||||
"src/pages/EmailPage.vue": {
|
||||
"file": "assets/EmailPage-CewNq_Xr.js",
|
||||
"file": "assets/EmailPage-BgnCtp6d.js",
|
||||
"name": "EmailPage",
|
||||
"src": "src/pages/EmailPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_email-DUrghb1a.js",
|
||||
"_email-UCdCFkiw.js",
|
||||
"index.html",
|
||||
"_MetricGrid-Db4tVG3V.js",
|
||||
"_MetricGrid-Cm-6uQPC.js",
|
||||
"_vendor-BczUEOE_.js"
|
||||
],
|
||||
"css": [
|
||||
@@ -105,13 +105,13 @@
|
||||
]
|
||||
},
|
||||
"src/pages/FeedbacksPage.vue": {
|
||||
"file": "assets/FeedbacksPage-ZRHvop0J.js",
|
||||
"file": "assets/FeedbacksPage-B-5AB1AS.js",
|
||||
"name": "FeedbacksPage",
|
||||
"src": "src/pages/FeedbacksPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"index.html",
|
||||
"_MetricGrid-Db4tVG3V.js",
|
||||
"_MetricGrid-Cm-6uQPC.js",
|
||||
"_vendor-BczUEOE_.js"
|
||||
],
|
||||
"css": [
|
||||
@@ -119,13 +119,13 @@
|
||||
]
|
||||
},
|
||||
"src/pages/LogsPage.vue": {
|
||||
"file": "assets/LogsPage-_MZy14g6.js",
|
||||
"file": "assets/LogsPage-DF5QqYRJ.js",
|
||||
"name": "LogsPage",
|
||||
"src": "src/pages/LogsPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_users-C4yl3Txu.js",
|
||||
"_tasks-h6MdOVMy.js",
|
||||
"_users-LqVgYOIa.js",
|
||||
"_tasks-DW-oE78v.js",
|
||||
"index.html",
|
||||
"_vendor-BczUEOE_.js"
|
||||
],
|
||||
@@ -134,30 +134,30 @@
|
||||
]
|
||||
},
|
||||
"src/pages/ReportPage.vue": {
|
||||
"file": "assets/ReportPage-CBBSz-4x.js",
|
||||
"file": "assets/ReportPage-CJumwOUP.js",
|
||||
"name": "ReportPage",
|
||||
"src": "src/pages/ReportPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_vendor-BczUEOE_.js",
|
||||
"index.html",
|
||||
"_email-DUrghb1a.js",
|
||||
"_tasks-h6MdOVMy.js",
|
||||
"_system-CKJgzNxM.js",
|
||||
"_MetricGrid-Db4tVG3V.js"
|
||||
"_email-UCdCFkiw.js",
|
||||
"_tasks-DW-oE78v.js",
|
||||
"_system-BqUnjCUC.js",
|
||||
"_MetricGrid-Cm-6uQPC.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/ReportPage-DlF2eaTa.css"
|
||||
"assets/ReportPage-Csv-Q6wC.css"
|
||||
]
|
||||
},
|
||||
"src/pages/SecurityPage.vue": {
|
||||
"file": "assets/SecurityPage-BYXzNSAi.js",
|
||||
"file": "assets/SecurityPage-rmlzXSVY.js",
|
||||
"name": "SecurityPage",
|
||||
"src": "src/pages/SecurityPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"index.html",
|
||||
"_MetricGrid-Db4tVG3V.js",
|
||||
"_MetricGrid-Cm-6uQPC.js",
|
||||
"_vendor-BczUEOE_.js"
|
||||
],
|
||||
"css": [
|
||||
@@ -165,7 +165,7 @@
|
||||
]
|
||||
},
|
||||
"src/pages/SettingsPage.vue": {
|
||||
"file": "assets/SettingsPage-DQr6tcde.js",
|
||||
"file": "assets/SettingsPage-Czik5vp6.js",
|
||||
"name": "SettingsPage",
|
||||
"src": "src/pages/SettingsPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
@@ -178,12 +178,12 @@
|
||||
]
|
||||
},
|
||||
"src/pages/SystemPage.vue": {
|
||||
"file": "assets/SystemPage-DSDI53G_.js",
|
||||
"file": "assets/SystemPage-CYpwmM8C.js",
|
||||
"name": "SystemPage",
|
||||
"src": "src/pages/SystemPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_system-CKJgzNxM.js",
|
||||
"_system-BqUnjCUC.js",
|
||||
"index.html",
|
||||
"_vendor-BczUEOE_.js"
|
||||
],
|
||||
@@ -192,12 +192,12 @@
|
||||
]
|
||||
},
|
||||
"src/pages/UsersPage.vue": {
|
||||
"file": "assets/UsersPage-cY1psjBR.js",
|
||||
"file": "assets/UsersPage-hjzizufJ.js",
|
||||
"name": "UsersPage",
|
||||
"src": "src/pages/UsersPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_users-C4yl3Txu.js",
|
||||
"_users-LqVgYOIa.js",
|
||||
"index.html",
|
||||
"_vendor-BczUEOE_.js"
|
||||
],
|
||||
|
||||
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
@@ -1 +1 @@
|
||||
import{_}from"./index-UGawl5hI.js";import{r as n,l as s,o as t,F as r,m as u,V as p,k as o,i as l,j as y,w as h,c as i,A as k,B as c,C as v,a0 as f}from"./vendor-BczUEOE_.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},B={class:"metric-label"},C={class:"metric-value"},g={key:0,class:"metric-hint app-muted"},N={__name:"MetricGrid",props:{items:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},minWidth:{type:Number,default:180}},setup(a){return(V,w)=>{const d=n("el-icon"),m=n("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,c(e?.label||"-"),1)]),o("div",C,[a.loading?(t(),i(m,{key:0,rows:1,animated:""})):(t(),s(r,{key:1},[v(c(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",g,c(e?.hint||e?.sub),1)):l("",!0)],2))),128))],4)}}},A=_(N,[["__scopeId","data-v-00e217d4"]]);export{A as M};
|
||||
import{_}from"./index-C51y9xCM.js";import{r as n,l as s,o as t,F as r,m as u,V as p,k as o,i as l,j as y,w as h,c as i,A as k,B as c,C as v,a0 as f}from"./vendor-BczUEOE_.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},B={class:"metric-label"},C={class:"metric-value"},g={key:0,class:"metric-hint app-muted"},N={__name:"MetricGrid",props:{items:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},minWidth:{type:Number,default:180}},setup(a){return(V,w)=>{const d=n("el-icon"),m=n("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,c(e?.label||"-"),1)]),o("div",C,[a.loading?(t(),i(m,{key:0,rows:1,animated:""})):(t(),s(r,{key:1},[v(c(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",g,c(e?.hint||e?.sub),1)):l("",!0)],2))),128))],4)}}},A=_(N,[["__scopeId","data-v-00e217d4"]]);export{A as M};
|
||||
File diff suppressed because one or more lines are too long
1
static/admin/assets/ReportPage-CJumwOUP.js
Normal file
1
static/admin/assets/ReportPage-CJumwOUP.js
Normal file
File diff suppressed because one or more lines are too long
1
static/admin/assets/ReportPage-Csv-Q6wC.css
Normal file
1
static/admin/assets/ReportPage-Csv-Q6wC.css
Normal file
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
@@ -1 +1 @@
|
||||
import{a as m,_ as h}from"./index-UGawl5hI.js";import{e as p,r as u,l as T,o as C,k as r,j as a,w as o,C as x,b as i,E as b}from"./vendor-BczUEOE_.js";async function P(l){const{data:s}=await m.put("/admin/username",{new_username:l});return s}async function E(l){const{data:s}=await m.put("/admin/password",{new_password:l});return s}async function S(){const{data:l}=await m.post("/logout");return l}const U={class:"page-stack"},A={__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 S()}catch{}finally{window.location.href="/yuyx"}}async function V(){const t=s.value.trim();if(!t){i.error("请输入新用户名");return}try{await b.confirm(`确定将管理员用户名修改为「${t}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await P(t),i.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function B(){const t=d.value;if(!t){i.error("请输入新密码");return}const e=k(t);if(!e.ok){i.error(e.message);return}try{await b.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await E(t),i.success("密码修改成功,请重新登录"),d.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}return(t,e)=>{const g=u("el-input"),w=u("el-form-item"),v=u("el-form"),y=u("el-button"),_=u("el-card");return C(),T("div",U,[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(v,{"label-width":"120px"},{default:o(()=>[a(w,{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:V},{default:o(()=>[...e[2]||(e[2]=[x("保存用户名",-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(v,{"label-width":"120px"},{default:o(()=>[a(w,{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:B},{default:o(()=>[...e[4]||(e[4]=[x("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码(至少8位且包含字母与数字)。",-1))]),_:1})])}}},j=h(A,[["__scopeId","data-v-83d3840a"]]);export{j as default};
|
||||
import{a as m,_ as h}from"./index-C51y9xCM.js";import{e as p,r as u,l as T,o as C,k as r,j as a,w as o,C as x,b as i,E as b}from"./vendor-BczUEOE_.js";async function P(l){const{data:s}=await m.put("/admin/username",{new_username:l});return s}async function E(l){const{data:s}=await m.put("/admin/password",{new_password:l});return s}async function S(){const{data:l}=await m.post("/logout");return l}const U={class:"page-stack"},A={__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 S()}catch{}finally{window.location.href="/yuyx"}}async function V(){const t=s.value.trim();if(!t){i.error("请输入新用户名");return}try{await b.confirm(`确定将管理员用户名修改为「${t}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await P(t),i.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function B(){const t=d.value;if(!t){i.error("请输入新密码");return}const e=k(t);if(!e.ok){i.error(e.message);return}try{await b.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await E(t),i.success("密码修改成功,请重新登录"),d.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}return(t,e)=>{const g=u("el-input"),w=u("el-form-item"),v=u("el-form"),y=u("el-button"),_=u("el-card");return C(),T("div",U,[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(v,{"label-width":"120px"},{default:o(()=>[a(w,{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:V},{default:o(()=>[...e[2]||(e[2]=[x("保存用户名",-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(v,{"label-width":"120px"},{default:o(()=>[a(w,{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:B},{default:o(()=>[...e[4]||(e[4]=[x("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码(至少8位且包含字母与数字)。",-1))]),_:1})])}}},j=h(A,[["__scopeId","data-v-83d3840a"]]);export{j as default};
|
||||
@@ -1,4 +1,4 @@
|
||||
import{f as ge,u as Y}from"./system-CKJgzNxM.js";import{a as P,_ as Ve}from"./index-UGawl5hI.js";import{e as n,f as ke,a1 as xe,h as be,g as we,r as v,W as Ue,X as Ce,o as g,l as V,k as s,j as l,w as t,C as k,i as Z,B as ee,E as ne,b as c}from"./vendor-BczUEOE_.js";async function le(r={}){const{data:m}=await P.get("/kdocs/status",{params:r});return m}async function Pe(r={}){const m={force:!0,...r},{data:x}=await P.post("/kdocs/qr",m);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:m}=await P.post("/proxy/config",r);return m}async function Se(r){const{data:m}=await P.post("/proxy/test",r);return m}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"},he={class:"status-inline app-muted"},Qe={key:0},$e={key:1},qe={key:2},Me={class:"kdocs-inline"},Re={class:"kdocs-range"},Fe={class:"row-actions"},He={key:0,class:"help"},ze={key:1,class:"help"},Oe={class:"kdocs-qr"},je=["src"],Ge={__name:"SystemPage",setup(r){const m=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(""),h=n(""),Q=n(0),$=n("A"),q=n("D"),M=n(0),R=n(0),F=n(!1),H=n(""),p=n({}),b=n(!1),w=n(""),ae=n(!1),z=n(!1),U=n(!1),C=n(!1),O=n("");let j=null;const oe=ke(()=>z.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(){m.value=!0;try{const[a,e,i]=await Promise.all([ge(),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||"",h.value=a.kdocs_sheet_name||"",Q.value=a.kdocs_sheet_index??0,$.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,F.value=(a.kdocs_admin_notify_enabled??0)===1,H.value=a.kdocs_admin_notify_email||"",p.value=i||{}}catch{}finally{m.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 ge,u as Y}from"./system-BqUnjCUC.js";import{a as P,_ as Ve}from"./index-C51y9xCM.js";import{e as n,f as ke,a1 as xe,h as be,g as we,r as v,W as Ue,X as Ce,o as g,l as V,k as s,j as l,w as t,C as k,i as Z,B as ee,E as ne,b as c}from"./vendor-BczUEOE_.js";async function le(r={}){const{data:m}=await P.get("/kdocs/status",{params:r});return m}async function Pe(r={}){const m={force:!0,...r},{data:x}=await P.post("/kdocs/qr",m);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:m}=await P.post("/proxy/config",r);return m}async function Se(r){const{data:m}=await P.post("/proxy/test",r);return m}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"},he={class:"status-inline app-muted"},Qe={key:0},$e={key:1},qe={key:2},Me={class:"kdocs-inline"},Re={class:"kdocs-range"},Fe={class:"row-actions"},He={key:0,class:"help"},ze={key:1,class:"help"},Oe={class:"kdocs-qr"},je=["src"],Ge={__name:"SystemPage",setup(r){const m=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(""),h=n(""),Q=n(0),$=n("A"),q=n("D"),M=n(0),R=n(0),F=n(!1),H=n(""),p=n({}),b=n(!1),w=n(""),ae=n(!1),z=n(!1),U=n(!1),C=n(!1),O=n("");let j=null;const oe=ke(()=>z.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(){m.value=!0;try{const[a,e,i]=await Promise.all([ge(),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||"",h.value=a.kdocs_sheet_name||"",Q.value=a.kdocs_sheet_index??0,$.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,F.value=(a.kdocs_admin_notify_enabled??0)===1,H.value=a.kdocs_admin_notify_email||"",p.value=i||{}}catch{}finally{m.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
@@ -1 +1 @@
|
||||
import{a as n}from"./index-UGawl5hI.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-C51y9xCM.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
@@ -1 +1 @@
|
||||
import{a}from"./index-UGawl5hI.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-C51y9xCM.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};
|
||||
1
static/admin/assets/tasks-DW-oE78v.js
Normal file
1
static/admin/assets/tasks-DW-oE78v.js
Normal file
@@ -0,0 +1 @@
|
||||
import{a}from"./index-C51y9xCM.js";async function c(){const{data:t}=await a.get("/server/info");return t}async function e(){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("/slow_sql_metrics");return t}async function i(){const{data:t}=await a.get("/task/stats");return t}async function u(){const{data:t}=await a.get("/task/running");return t}async function f(t){const{data:s}=await a.get("/task/logs",{params:t});return s}async function g(t){const{data:s}=await a.post("/task/logs/clear",{days:t});return s}export{u as a,c as b,e as c,r as d,o as e,i as f,f as g,g as h};
|
||||
@@ -1 +0,0 @@
|
||||
import{a}from"./index-UGawl5hI.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};
|
||||
@@ -1 +1 @@
|
||||
import{a as t}from"./index-UGawl5hI.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-C51y9xCM.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};
|
||||
@@ -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-UGawl5hI.js"></script>
|
||||
<script type="module" crossorigin src="./assets/index-C51y9xCM.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="./assets/vendor-BczUEOE_.js">
|
||||
<link rel="stylesheet" crossorigin href="./assets/vendor-C68yOrAy.css">
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-a3a11Ghn.css">
|
||||
|
||||
Reference in New Issue
Block a user