feat(config): add live slow-sql threshold setting
This commit is contained in:
@@ -11,6 +11,7 @@ const loading = ref(false)
|
||||
const maxConcurrentGlobal = ref(2)
|
||||
const maxConcurrentPerAccount = ref(1)
|
||||
const maxScreenshotConcurrent = ref(3)
|
||||
const dbSlowQueryMs = ref(120)
|
||||
|
||||
const proxyEnabled = ref(false)
|
||||
const proxyApiUrl = ref('')
|
||||
@@ -67,6 +68,7 @@ async function loadAll() {
|
||||
maxConcurrentGlobal.value = system.max_concurrent_global ?? 2
|
||||
maxConcurrentPerAccount.value = system.max_concurrent_per_account ?? 1
|
||||
maxScreenshotConcurrent.value = system.max_screenshot_concurrent ?? 3
|
||||
dbSlowQueryMs.value = system.db_slow_query_ms ?? 120
|
||||
|
||||
autoApproveEnabled.value = (system.auto_approve_enabled ?? 0) === 1
|
||||
autoApproveHourlyLimit.value = system.auto_approve_hourly_limit ?? 10
|
||||
@@ -100,11 +102,12 @@ async function saveConcurrency() {
|
||||
max_concurrent_global: Number(maxConcurrentGlobal.value),
|
||||
max_concurrent_per_account: Number(maxConcurrentPerAccount.value),
|
||||
max_screenshot_concurrent: Number(maxScreenshotConcurrent.value),
|
||||
db_slow_query_ms: Number(dbSlowQueryMs.value),
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定更新并发配置吗?\n\n全局并发数: ${payload.max_concurrent_global}\n单账号并发数: ${payload.max_concurrent_per_account}\n截图并发数: ${payload.max_screenshot_concurrent}`,
|
||||
`确定更新并发配置吗?\n\n全局并发数: ${payload.max_concurrent_global}\n单账号并发数: ${payload.max_concurrent_per_account}\n截图并发数: ${payload.max_screenshot_concurrent}\n慢 SQL 阈值: ${payload.db_slow_query_ms}ms`,
|
||||
'保存并发配置',
|
||||
{ confirmButtonText: '保存', cancelButtonText: '取消', type: 'warning' },
|
||||
)
|
||||
@@ -337,6 +340,11 @@ onMounted(loadAll)
|
||||
<el-input-number v-model="maxScreenshotConcurrent" :min="1" :max="50" />
|
||||
<div class="help">截图资源占用较低,可按机器性能逐步提高。</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="慢 SQL 阈值(ms)">
|
||||
<el-input-number v-model="dbSlowQueryMs" :min="0" :max="60000" />
|
||||
<div class="help">低于该阈值不会计入慢 SQL(0 表示关闭慢 SQL 采样)。</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="row-actions">
|
||||
|
||||
15
database.py
15
database.py
@@ -120,7 +120,7 @@ config = get_config()
|
||||
DB_FILE = config.DB_FILE
|
||||
|
||||
# 数据库版本 (用于迁移管理)
|
||||
DB_VERSION = 19
|
||||
DB_VERSION = 20
|
||||
|
||||
|
||||
# ==================== 系统配置缓存(P1 / O-03) ====================
|
||||
@@ -183,6 +183,12 @@ def init_database():
|
||||
|
||||
ensure_default_admin()
|
||||
|
||||
try:
|
||||
config_value = get_system_config()
|
||||
db_pool.configure_slow_query_runtime(threshold_ms=config_value.get("db_slow_query_ms"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def migrate_database():
|
||||
"""数据库迁移(对外保留接口)。"""
|
||||
@@ -240,6 +246,7 @@ def update_system_config(
|
||||
kdocs_admin_notify_email=None,
|
||||
kdocs_row_start=None,
|
||||
kdocs_row_end=None,
|
||||
db_slow_query_ms=None,
|
||||
):
|
||||
"""更新系统配置(写入后立即失效缓存)。"""
|
||||
ok = _update_system_config(
|
||||
@@ -268,7 +275,13 @@ def update_system_config(
|
||||
kdocs_admin_notify_email=kdocs_admin_notify_email,
|
||||
kdocs_row_start=kdocs_row_start,
|
||||
kdocs_row_end=kdocs_row_end,
|
||||
db_slow_query_ms=db_slow_query_ms,
|
||||
)
|
||||
if ok:
|
||||
invalidate_system_config_cache()
|
||||
try:
|
||||
latest_config = get_system_config()
|
||||
db_pool.configure_slow_query_runtime(threshold_ms=latest_config.get("db_slow_query_ms"))
|
||||
except Exception:
|
||||
pass
|
||||
return ok
|
||||
|
||||
@@ -17,6 +17,7 @@ _DEFAULT_SYSTEM_CONFIG = {
|
||||
"max_concurrent_global": 2,
|
||||
"max_concurrent_per_account": 1,
|
||||
"max_screenshot_concurrent": 3,
|
||||
"db_slow_query_ms": 120,
|
||||
"schedule_enabled": 0,
|
||||
"schedule_time": "02:00",
|
||||
"schedule_browse_type": "应读",
|
||||
@@ -49,6 +50,7 @@ _SYSTEM_CONFIG_UPDATERS = (
|
||||
("schedule_weekdays", "schedule_weekdays"),
|
||||
("max_concurrent_per_account", "max_concurrent_per_account"),
|
||||
("max_screenshot_concurrent", "max_screenshot_concurrent"),
|
||||
("db_slow_query_ms", "db_slow_query_ms"),
|
||||
("enable_screenshot", "enable_screenshot"),
|
||||
("proxy_enabled", "proxy_enabled"),
|
||||
("proxy_api_url", "proxy_api_url"),
|
||||
@@ -265,6 +267,7 @@ def update_system_config(
|
||||
kdocs_admin_notify_email=None,
|
||||
kdocs_row_start=None,
|
||||
kdocs_row_end=None,
|
||||
db_slow_query_ms=None,
|
||||
) -> bool:
|
||||
"""更新系统配置(仅更新DB,不做缓存处理)。"""
|
||||
arg_values = {
|
||||
@@ -293,6 +296,7 @@ def update_system_config(
|
||||
"kdocs_admin_notify_email": kdocs_admin_notify_email,
|
||||
"kdocs_row_start": kdocs_row_start,
|
||||
"kdocs_row_end": kdocs_row_end,
|
||||
"db_slow_query_ms": db_slow_query_ms,
|
||||
}
|
||||
|
||||
updates = []
|
||||
|
||||
@@ -74,6 +74,7 @@ def _get_migration_steps():
|
||||
(17, _migrate_to_v17),
|
||||
(18, _migrate_to_v18),
|
||||
(19, _migrate_to_v19),
|
||||
(20, _migrate_to_v20),
|
||||
]
|
||||
|
||||
|
||||
@@ -884,3 +885,21 @@ def _migrate_to_v19(conn):
|
||||
cursor.execute(statement)
|
||||
|
||||
conn.commit()
|
||||
|
||||
|
||||
|
||||
def _migrate_to_v20(conn):
|
||||
"""迁移到版本20 - 慢SQL阈值系统配置"""
|
||||
cursor = conn.cursor()
|
||||
|
||||
columns = _get_table_columns(cursor, "system_config")
|
||||
_add_column_if_missing(
|
||||
cursor,
|
||||
"system_config",
|
||||
columns,
|
||||
"db_slow_query_ms",
|
||||
"INTEGER DEFAULT 120",
|
||||
ok_message=" [OK] 添加 system_config.db_slow_query_ms 字段",
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
|
||||
@@ -210,6 +210,7 @@ def ensure_schema(conn) -> None:
|
||||
proxy_expire_minutes INTEGER DEFAULT 3,
|
||||
max_screenshot_concurrent INTEGER DEFAULT 3,
|
||||
max_concurrent_per_account INTEGER DEFAULT 1,
|
||||
db_slow_query_ms INTEGER DEFAULT 120,
|
||||
schedule_weekdays TEXT DEFAULT '1,2,3,4,5,6,7',
|
||||
enable_screenshot INTEGER DEFAULT 1,
|
||||
auto_approve_enabled INTEGER DEFAULT 0,
|
||||
|
||||
49
db_pool.py
49
db_pool.py
@@ -27,6 +27,45 @@ DB_LOCK_RETRY_BASE_MS = max(10, int(getattr(config, "DB_LOCK_RETRY_BASE_MS", 50)
|
||||
DB_SLOW_QUERY_MS = max(0, int(getattr(config, "DB_SLOW_QUERY_MS", 120)))
|
||||
DB_SLOW_QUERY_SQL_MAX_LEN = max(80, int(getattr(config, "DB_SLOW_QUERY_SQL_MAX_LEN", 240)))
|
||||
|
||||
_slow_query_runtime_lock = threading.Lock()
|
||||
_slow_query_runtime_threshold_ms = DB_SLOW_QUERY_MS
|
||||
_slow_query_runtime_sql_max_len = DB_SLOW_QUERY_SQL_MAX_LEN
|
||||
|
||||
|
||||
def _get_slow_query_runtime_values() -> tuple[int, int]:
|
||||
with _slow_query_runtime_lock:
|
||||
return int(_slow_query_runtime_threshold_ms), int(_slow_query_runtime_sql_max_len)
|
||||
|
||||
|
||||
def get_slow_query_runtime() -> dict:
|
||||
threshold_ms, sql_max_len = _get_slow_query_runtime_values()
|
||||
return {"threshold_ms": threshold_ms, "sql_max_len": sql_max_len}
|
||||
|
||||
|
||||
def configure_slow_query_runtime(*, threshold_ms=None, sql_max_len=None) -> dict:
|
||||
global _slow_query_runtime_threshold_ms, _slow_query_runtime_sql_max_len
|
||||
|
||||
with _slow_query_runtime_lock:
|
||||
if threshold_ms is not None:
|
||||
_slow_query_runtime_threshold_ms = max(0, int(threshold_ms))
|
||||
if sql_max_len is not None:
|
||||
_slow_query_runtime_sql_max_len = max(80, int(sql_max_len))
|
||||
|
||||
runtime_threshold_ms = int(_slow_query_runtime_threshold_ms)
|
||||
runtime_sql_max_len = int(_slow_query_runtime_sql_max_len)
|
||||
|
||||
try:
|
||||
from services.slow_sql_metrics import configure_slow_sql_runtime
|
||||
|
||||
configure_slow_sql_runtime(
|
||||
threshold_ms=runtime_threshold_ms,
|
||||
sql_max_len=runtime_sql_max_len,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {"threshold_ms": runtime_threshold_ms, "sql_max_len": runtime_sql_max_len}
|
||||
|
||||
|
||||
def _is_lock_conflict_error(error: sqlite3.OperationalError) -> bool:
|
||||
message = str(error or "").lower()
|
||||
@@ -34,10 +73,11 @@ def _is_lock_conflict_error(error: sqlite3.OperationalError) -> bool:
|
||||
|
||||
|
||||
def _compact_sql(sql: str) -> str:
|
||||
_, sql_max_len = _get_slow_query_runtime_values()
|
||||
statement = " ".join(str(sql or "").split())
|
||||
if len(statement) <= DB_SLOW_QUERY_SQL_MAX_LEN:
|
||||
if len(statement) <= sql_max_len:
|
||||
return statement
|
||||
return statement[: DB_SLOW_QUERY_SQL_MAX_LEN - 3] + "..."
|
||||
return statement[: sql_max_len - 3] + "..."
|
||||
|
||||
|
||||
def _describe_params(parameters) -> str:
|
||||
@@ -298,9 +338,10 @@ class PooledConnection:
|
||||
return False
|
||||
|
||||
def _on_query_executed(self, sql: str, parameters, elapsed_ms: float) -> None:
|
||||
if DB_SLOW_QUERY_MS <= 0:
|
||||
slow_query_ms, _ = _get_slow_query_runtime_values()
|
||||
if slow_query_ms <= 0:
|
||||
return
|
||||
if elapsed_ms < DB_SLOW_QUERY_MS:
|
||||
if elapsed_ms < slow_query_ms:
|
||||
return
|
||||
|
||||
params_info = _describe_params(parameters)
|
||||
|
||||
@@ -34,6 +34,7 @@ def update_system_config_api():
|
||||
schedule_weekdays = data.get("schedule_weekdays")
|
||||
new_max_concurrent_per_account = data.get("max_concurrent_per_account")
|
||||
new_max_screenshot_concurrent = data.get("max_screenshot_concurrent")
|
||||
db_slow_query_ms = data.get("db_slow_query_ms")
|
||||
enable_screenshot = data.get("enable_screenshot")
|
||||
auto_approve_enabled = data.get("auto_approve_enabled")
|
||||
auto_approve_hourly_limit = data.get("auto_approve_hourly_limit")
|
||||
@@ -62,6 +63,14 @@ def update_system_config_api():
|
||||
if not isinstance(new_max_screenshot_concurrent, int) or new_max_screenshot_concurrent < 1:
|
||||
return jsonify({"error": "截图并发数必须大于0(建议根据服务器配置设置,wkhtmltoimage 资源占用较低)"}), 400
|
||||
|
||||
if db_slow_query_ms is not None:
|
||||
try:
|
||||
db_slow_query_ms = int(db_slow_query_ms)
|
||||
except (ValueError, TypeError):
|
||||
return jsonify({"error": "慢 SQL 阈值必须是数字(毫秒)"}), 400
|
||||
if db_slow_query_ms < 0 or db_slow_query_ms > 60000:
|
||||
return jsonify({"error": "慢 SQL 阈值范围应在 0-60000 毫秒之间"}), 400
|
||||
|
||||
if enable_screenshot is not None:
|
||||
if isinstance(enable_screenshot, bool):
|
||||
enable_screenshot = 1 if enable_screenshot else 0
|
||||
@@ -197,6 +206,7 @@ def update_system_config_api():
|
||||
kdocs_admin_notify_email=kdocs_admin_notify_email,
|
||||
kdocs_row_start=kdocs_row_start,
|
||||
kdocs_row_end=kdocs_row_end,
|
||||
db_slow_query_ms=db_slow_query_ms,
|
||||
):
|
||||
return jsonify({"error": "更新失败"}), 400
|
||||
|
||||
@@ -207,6 +217,14 @@ def update_system_config_api():
|
||||
max_global=int(new_config.get("max_concurrent_global", old_config.get("max_concurrent_global", 2))),
|
||||
max_per_user=int(new_config.get("max_concurrent_per_account", old_config.get("max_concurrent_per_account", 1))),
|
||||
)
|
||||
|
||||
try:
|
||||
import db_pool
|
||||
|
||||
db_pool.configure_slow_query_runtime(threshold_ms=new_config.get("db_slow_query_ms"))
|
||||
except Exception as slow_sql_error:
|
||||
logger.warning(f"慢 SQL 运行时阈值更新失败: {slow_sql_error}")
|
||||
|
||||
if new_max_screenshot_concurrent is not None:
|
||||
try:
|
||||
from browser_pool_worker import resize_browser_worker_pool
|
||||
@@ -224,5 +242,7 @@ def update_system_config_api():
|
||||
logger.info(f"单用户并发数已更新为: {new_max_concurrent_per_account}")
|
||||
if new_max_screenshot_concurrent is not None:
|
||||
logger.info(f"截图并发数已更新为: {new_max_screenshot_concurrent}")
|
||||
if db_slow_query_ms is not None:
|
||||
logger.info(f"慢 SQL 阈值已更新为: {db_slow_query_ms}ms")
|
||||
|
||||
return jsonify({"message": "系统配置已更新"})
|
||||
|
||||
@@ -21,6 +21,10 @@ _RECENT_LIMIT = max(10, int(os.environ.get("DB_SLOW_SQL_RECENT_LIMIT", "50") or
|
||||
_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))
|
||||
|
||||
_runtime_lock = threading.Lock()
|
||||
_runtime_threshold_ms = _SLOW_SQL_THRESHOLD_MS
|
||||
_runtime_sql_max_len = _SQL_MAX_LEN
|
||||
|
||||
_lock = threading.Lock()
|
||||
|
||||
_state = {
|
||||
@@ -38,8 +42,29 @@ def _compact_text(value: str, max_len: int) -> str:
|
||||
return f"{text[: max_len - 3]}..."
|
||||
|
||||
|
||||
def _get_runtime_values() -> tuple[float, int]:
|
||||
with _runtime_lock:
|
||||
return float(_runtime_threshold_ms), int(_runtime_sql_max_len)
|
||||
|
||||
|
||||
def configure_slow_sql_runtime(*, threshold_ms=None, sql_max_len=None) -> dict:
|
||||
global _runtime_threshold_ms, _runtime_sql_max_len
|
||||
|
||||
with _runtime_lock:
|
||||
if threshold_ms is not None:
|
||||
_runtime_threshold_ms = max(0.0, float(threshold_ms))
|
||||
if sql_max_len is not None:
|
||||
_runtime_sql_max_len = max(80, int(sql_max_len))
|
||||
|
||||
return {
|
||||
"threshold_ms": float(_runtime_threshold_ms),
|
||||
"sql_max_len": int(_runtime_sql_max_len),
|
||||
}
|
||||
|
||||
|
||||
def _compact_sql(sql: str) -> str:
|
||||
return _compact_text(str(sql or ""), _SQL_MAX_LEN)
|
||||
_, sql_max_len = _get_runtime_values()
|
||||
return _compact_text(str(sql or ""), sql_max_len)
|
||||
|
||||
|
||||
def _compact_params(params_info: str) -> str:
|
||||
@@ -165,12 +190,14 @@ def get_slow_sql_metrics_snapshot() -> dict:
|
||||
total_events = len(events)
|
||||
avg_duration_ms = round((total_duration_ms / total_events), 2) if total_events > 0 else 0.0
|
||||
|
||||
runtime_threshold_ms, _ = _get_runtime_values()
|
||||
|
||||
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,
|
||||
"slow_threshold_ms": runtime_threshold_ms,
|
||||
"total_slow_queries": total_events,
|
||||
"unique_sql": len(grouped),
|
||||
"avg_duration_ms": avg_duration_ms,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"_MetricGrid-Cm-6uQPC.js": {
|
||||
"file": "assets/MetricGrid-Cm-6uQPC.js",
|
||||
"_MetricGrid-tQwTnWXY.js": {
|
||||
"file": "assets/MetricGrid-tQwTnWXY.js",
|
||||
"name": "MetricGrid",
|
||||
"imports": [
|
||||
"index.html",
|
||||
@@ -14,29 +14,29 @@
|
||||
"file": "assets/MetricGrid-yP_dkP6X.css",
|
||||
"src": "_MetricGrid-yP_dkP6X.css"
|
||||
},
|
||||
"_email-UCdCFkiw.js": {
|
||||
"file": "assets/email-UCdCFkiw.js",
|
||||
"_email-BgLsevU3.js": {
|
||||
"file": "assets/email-BgLsevU3.js",
|
||||
"name": "email",
|
||||
"imports": [
|
||||
"index.html"
|
||||
]
|
||||
},
|
||||
"_system-BqUnjCUC.js": {
|
||||
"file": "assets/system-BqUnjCUC.js",
|
||||
"_system-COpUDNdl.js": {
|
||||
"file": "assets/system-COpUDNdl.js",
|
||||
"name": "system",
|
||||
"imports": [
|
||||
"index.html"
|
||||
]
|
||||
},
|
||||
"_tasks-DW-oE78v.js": {
|
||||
"file": "assets/tasks-DW-oE78v.js",
|
||||
"_tasks-Bprl-b9c.js": {
|
||||
"file": "assets/tasks-Bprl-b9c.js",
|
||||
"name": "tasks",
|
||||
"imports": [
|
||||
"index.html"
|
||||
]
|
||||
},
|
||||
"_users-LqVgYOIa.js": {
|
||||
"file": "assets/users-LqVgYOIa.js",
|
||||
"_users-oERXj-b9.js": {
|
||||
"file": "assets/users-oERXj-b9.js",
|
||||
"name": "users",
|
||||
"imports": [
|
||||
"index.html"
|
||||
@@ -54,7 +54,7 @@
|
||||
"src": "_vendor-C68yOrAy.css"
|
||||
},
|
||||
"index.html": {
|
||||
"file": "assets/index-C51y9xCM.js",
|
||||
"file": "assets/index-z8QOtKVT.js",
|
||||
"name": "index",
|
||||
"src": "index.html",
|
||||
"isEntry": true,
|
||||
@@ -77,7 +77,7 @@
|
||||
]
|
||||
},
|
||||
"src/pages/AnnouncementsPage.vue": {
|
||||
"file": "assets/AnnouncementsPage-D4vGLsnP.js",
|
||||
"file": "assets/AnnouncementsPage-B1ijXegw.js",
|
||||
"name": "AnnouncementsPage",
|
||||
"src": "src/pages/AnnouncementsPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
@@ -90,14 +90,14 @@
|
||||
]
|
||||
},
|
||||
"src/pages/EmailPage.vue": {
|
||||
"file": "assets/EmailPage-BgnCtp6d.js",
|
||||
"file": "assets/EmailPage-BA_Nuk9g.js",
|
||||
"name": "EmailPage",
|
||||
"src": "src/pages/EmailPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_email-UCdCFkiw.js",
|
||||
"_email-BgLsevU3.js",
|
||||
"index.html",
|
||||
"_MetricGrid-Cm-6uQPC.js",
|
||||
"_MetricGrid-tQwTnWXY.js",
|
||||
"_vendor-BczUEOE_.js"
|
||||
],
|
||||
"css": [
|
||||
@@ -105,13 +105,13 @@
|
||||
]
|
||||
},
|
||||
"src/pages/FeedbacksPage.vue": {
|
||||
"file": "assets/FeedbacksPage-B-5AB1AS.js",
|
||||
"file": "assets/FeedbacksPage-Dg3BwCE1.js",
|
||||
"name": "FeedbacksPage",
|
||||
"src": "src/pages/FeedbacksPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"index.html",
|
||||
"_MetricGrid-Cm-6uQPC.js",
|
||||
"_MetricGrid-tQwTnWXY.js",
|
||||
"_vendor-BczUEOE_.js"
|
||||
],
|
||||
"css": [
|
||||
@@ -119,13 +119,13 @@
|
||||
]
|
||||
},
|
||||
"src/pages/LogsPage.vue": {
|
||||
"file": "assets/LogsPage-DF5QqYRJ.js",
|
||||
"file": "assets/LogsPage-B80ridTr.js",
|
||||
"name": "LogsPage",
|
||||
"src": "src/pages/LogsPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_users-LqVgYOIa.js",
|
||||
"_tasks-DW-oE78v.js",
|
||||
"_users-oERXj-b9.js",
|
||||
"_tasks-Bprl-b9c.js",
|
||||
"index.html",
|
||||
"_vendor-BczUEOE_.js"
|
||||
],
|
||||
@@ -134,30 +134,30 @@
|
||||
]
|
||||
},
|
||||
"src/pages/ReportPage.vue": {
|
||||
"file": "assets/ReportPage-CJumwOUP.js",
|
||||
"file": "assets/ReportPage-CVnILNWh.js",
|
||||
"name": "ReportPage",
|
||||
"src": "src/pages/ReportPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_vendor-BczUEOE_.js",
|
||||
"index.html",
|
||||
"_email-UCdCFkiw.js",
|
||||
"_tasks-DW-oE78v.js",
|
||||
"_system-BqUnjCUC.js",
|
||||
"_MetricGrid-Cm-6uQPC.js"
|
||||
"_email-BgLsevU3.js",
|
||||
"_tasks-Bprl-b9c.js",
|
||||
"_system-COpUDNdl.js",
|
||||
"_MetricGrid-tQwTnWXY.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/ReportPage-Csv-Q6wC.css"
|
||||
]
|
||||
},
|
||||
"src/pages/SecurityPage.vue": {
|
||||
"file": "assets/SecurityPage-rmlzXSVY.js",
|
||||
"file": "assets/SecurityPage-tMcfFXjg.js",
|
||||
"name": "SecurityPage",
|
||||
"src": "src/pages/SecurityPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"index.html",
|
||||
"_MetricGrid-Cm-6uQPC.js",
|
||||
"_MetricGrid-tQwTnWXY.js",
|
||||
"_vendor-BczUEOE_.js"
|
||||
],
|
||||
"css": [
|
||||
@@ -165,7 +165,7 @@
|
||||
]
|
||||
},
|
||||
"src/pages/SettingsPage.vue": {
|
||||
"file": "assets/SettingsPage-Czik5vp6.js",
|
||||
"file": "assets/SettingsPage-Bagp2mPP.js",
|
||||
"name": "SettingsPage",
|
||||
"src": "src/pages/SettingsPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
@@ -178,26 +178,26 @@
|
||||
]
|
||||
},
|
||||
"src/pages/SystemPage.vue": {
|
||||
"file": "assets/SystemPage-CYpwmM8C.js",
|
||||
"file": "assets/SystemPage-8v_X5n5H.js",
|
||||
"name": "SystemPage",
|
||||
"src": "src/pages/SystemPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_system-BqUnjCUC.js",
|
||||
"_system-COpUDNdl.js",
|
||||
"index.html",
|
||||
"_vendor-BczUEOE_.js"
|
||||
],
|
||||
"css": [
|
||||
"assets/SystemPage-DMyNBO3N.css"
|
||||
"assets/SystemPage-DYBocGi2.css"
|
||||
]
|
||||
},
|
||||
"src/pages/UsersPage.vue": {
|
||||
"file": "assets/UsersPage-hjzizufJ.js",
|
||||
"file": "assets/UsersPage-CJwQe-N8.js",
|
||||
"name": "UsersPage",
|
||||
"src": "src/pages/UsersPage.vue",
|
||||
"isDynamicEntry": true,
|
||||
"imports": [
|
||||
"_users-LqVgYOIa.js",
|
||||
"_users-oERXj-b9.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-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};
|
||||
import{_}from"./index-z8QOtKVT.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
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
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};
|
||||
import{a as m,_ as h}from"./index-z8QOtKVT.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};
|
||||
6
static/admin/assets/SystemPage-8v_X5n5H.js
Normal file
6
static/admin/assets/SystemPage-8v_X5n5H.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
.page-stack[data-v-c90d5cb2]{display:flex;flex-direction:column;gap:14px;min-width:0}.config-grid[data-v-c90d5cb2]{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.card[data-v-c90d5cb2]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-card[data-v-c90d5cb2]{min-width:0}.section-title[data-v-c90d5cb2]{margin:0;font-size:15px;font-weight:800;letter-spacing:.2px}.section-sub[data-v-c90d5cb2]{margin-top:6px;margin-bottom:10px;font-size:12px}.section-head[data-v-c90d5cb2]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px}.status-inline[data-v-c90d5cb2]{font-size:12px}.kdocs-form[data-v-c90d5cb2]{margin-top:6px}.kdocs-inline[data-v-c90d5cb2]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;width:100%}.kdocs-range[data-v-c90d5cb2]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.kdocs-qr[data-v-c90d5cb2]{display:flex;flex-direction:column;align-items:center;gap:12px}.kdocs-qr img[data-v-c90d5cb2]{width:260px;max-width:100%;border:1px solid var(--app-border);border-radius:8px;padding:8px;background:#fff}.help[data-v-c90d5cb2]{margin-top:6px;font-size:12px;color:var(--app-muted)}.row-actions[data-v-c90d5cb2]{display:flex;flex-wrap:wrap;gap:10px}@media(max-width:1200px){.config-grid[data-v-c90d5cb2]{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:768px){.config-grid[data-v-c90d5cb2],.kdocs-inline[data-v-c90d5cb2]{grid-template-columns:1fr}.kdocs-range[data-v-c90d5cb2]{align-items:stretch}}
|
||||
1
static/admin/assets/SystemPage-DYBocGi2.css
Normal file
1
static/admin/assets/SystemPage-DYBocGi2.css
Normal file
@@ -0,0 +1 @@
|
||||
.page-stack[data-v-a5c40f1a]{display:flex;flex-direction:column;gap:14px;min-width:0}.config-grid[data-v-a5c40f1a]{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.card[data-v-a5c40f1a]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-card[data-v-a5c40f1a]{min-width:0}.section-title[data-v-a5c40f1a]{margin:0;font-size:15px;font-weight:800;letter-spacing:.2px}.section-sub[data-v-a5c40f1a]{margin-top:6px;margin-bottom:10px;font-size:12px}.section-head[data-v-a5c40f1a]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px}.status-inline[data-v-a5c40f1a]{font-size:12px}.kdocs-form[data-v-a5c40f1a]{margin-top:6px}.kdocs-inline[data-v-a5c40f1a]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;width:100%}.kdocs-range[data-v-a5c40f1a]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.kdocs-qr[data-v-a5c40f1a]{display:flex;flex-direction:column;align-items:center;gap:12px}.kdocs-qr img[data-v-a5c40f1a]{width:260px;max-width:100%;border:1px solid var(--app-border);border-radius:8px;padding:8px;background:#fff}.help[data-v-a5c40f1a]{margin-top:6px;font-size:12px;color:var(--app-muted)}.row-actions[data-v-a5c40f1a]{display:flex;flex-wrap:wrap;gap:10px}@media(max-width:1200px){.config-grid[data-v-a5c40f1a]{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:768px){.config-grid[data-v-a5c40f1a],.kdocs-inline[data-v-a5c40f1a]{grid-template-columns:1fr}.kdocs-range[data-v-a5c40f1a]{align-items:stretch}}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
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};
|
||||
import{a as n}from"./index-z8QOtKVT.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-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};
|
||||
import{a}from"./index-z8QOtKVT.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 +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};
|
||||
import{a}from"./index-z8QOtKVT.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 +1 @@
|
||||
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};
|
||||
import{a as t}from"./index-z8QOtKVT.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-C51y9xCM.js"></script>
|
||||
<script type="module" crossorigin src="./assets/index-z8QOtKVT.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