Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 42d8731ff7 | |||
| 4971263a3a | |||
| 41a439d830 |
+4
-2
@@ -1,4 +1,4 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# ---- Stage 1: Build frontend ----
|
||||
FROM node:20-alpine AS frontend-build
|
||||
WORKDIR /frontend
|
||||
@@ -42,7 +42,9 @@ RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
-r requirements.txt
|
||||
|
||||
# Playwright Chromium:安装在镜像层中,业务代码变更不会触发重下
|
||||
RUN playwright install chromium
|
||||
# --mount=type=cache 利用 BuildKit 缓存避免每次构建重下 ~170 MB 浏览器
|
||||
RUN --mount=type=cache,target=/root/.cache/ms-playwright,sharing=locked \
|
||||
playwright install chromium
|
||||
|
||||
# 源码层:业务代码变更不影响上面所有依赖层
|
||||
COPY backend/ .
|
||||
|
||||
@@ -76,6 +76,11 @@ npm run dev # 代理到 localhost:8000
|
||||
|
||||
SQLite 数据库位于 `./data/app.db`,直接复制即可备份:
|
||||
|
||||
> **WAL 模式说明**:启用 WAL 后,备份前请先执行 checkpoint 将 WAL 合并入主库:
|
||||
> ```bash
|
||||
> sqlite3 ./data/app.db "PRAGMA wal_checkpoint(TRUNCATE);"
|
||||
> ```
|
||||
|
||||
```bash
|
||||
cp ./data/app.db ./data/app.db.$(date +%Y%m%d)
|
||||
```
|
||||
|
||||
+40
-1
@@ -1,4 +1,4 @@
|
||||
from sqlalchemy import create_engine, inspect, text
|
||||
from sqlalchemy import create_engine, event, inspect, text
|
||||
from sqlalchemy.orm import sessionmaker, DeclarativeBase
|
||||
from app.config import get_settings
|
||||
|
||||
@@ -8,6 +8,21 @@ engine = create_engine(
|
||||
settings.database_url,
|
||||
connect_args={"check_same_thread": False},
|
||||
)
|
||||
|
||||
# ── SQLite 性能 PRAGMA(WAL + 缓存 + 超时) ──
|
||||
@event.listens_for(engine, "connect", insert=True)
|
||||
def _set_sqlite_pragma(dbapi_connection, connection_record):
|
||||
if engine.dialect.name != "sqlite":
|
||||
return
|
||||
cursor = dbapi_connection.cursor()
|
||||
cursor.execute("PRAGMA journal_mode=WAL;")
|
||||
cursor.execute("PRAGMA synchronous=NORMAL;")
|
||||
cursor.execute("PRAGMA busy_timeout=5000;")
|
||||
cursor.execute("PRAGMA foreign_keys=ON;")
|
||||
cursor.execute("PRAGMA cache_size=-20000;") # 20 MB page cache
|
||||
cursor.execute("PRAGMA mmap_size=67108864;") # 64 MB(小内存容器友好)
|
||||
cursor.close()
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
|
||||
@@ -28,11 +43,35 @@ def init_db():
|
||||
# import models so SQLAlchemy registers them
|
||||
from app.models import admin_user, upstream, snapshot, webhook_config, notification_log, custom_page, website, revoked_token, upstream_key # noqa: F401
|
||||
Base.metadata.create_all(bind=engine)
|
||||
_ensure_indexes()
|
||||
_migrate_custom_pages()
|
||||
_migrate_upstreams()
|
||||
_migrate_upstream_generated_keys()
|
||||
|
||||
|
||||
# ── 已有数据库幂等索引迁移 ─────────────────────────────────
|
||||
_NEW_INDEXES = [
|
||||
"CREATE INDEX IF NOT EXISTS ix_snapshot_upstream_captured ON upstream_rate_snapshots(upstream_id, captured_at DESC)",
|
||||
"CREATE INDEX IF NOT EXISTS ix_notif_event_created ON notification_logs(event_type, created_at DESC)",
|
||||
"CREATE INDEX IF NOT EXISTS ix_notif_status_created ON notification_logs(status, created_at DESC)",
|
||||
"CREATE INDEX IF NOT EXISTS ix_sync_website_created ON website_sync_logs(website_id, created_at DESC)",
|
||||
"CREATE INDEX IF NOT EXISTS ix_sync_binding_created ON website_sync_logs(binding_id, created_at DESC)",
|
||||
"CREATE INDEX IF NOT EXISTS ix_upstream_enabled ON upstreams(enabled)",
|
||||
"CREATE INDEX IF NOT EXISTS ix_key_upstream_name ON upstream_generated_keys(upstream_id, key_name)",
|
||||
]
|
||||
|
||||
|
||||
def _ensure_indexes() -> None:
|
||||
"""创建 InitDB 时可能未包含的复合索引,幂等执行。"""
|
||||
with engine.begin() as conn:
|
||||
for stmt in _NEW_INDEXES:
|
||||
try:
|
||||
conn.execute(text(stmt))
|
||||
except Exception:
|
||||
logger = __import__("logging").getLogger(__name__)
|
||||
logger.warning("index creation failed (non-fatal): %s", stmt[:60])
|
||||
|
||||
|
||||
def _migrate_custom_pages():
|
||||
"""Apply small SQLite-safe migrations for deployments without Alembic."""
|
||||
inspector = inspect(engine)
|
||||
|
||||
@@ -5,6 +5,7 @@ from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.middleware.gzip import GZipMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
@@ -88,6 +89,7 @@ app.add_middleware(
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
app.add_middleware(GZipMiddleware, minimum_size=1024)
|
||||
|
||||
# API routers
|
||||
app.include_router(auth.router)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
from sqlalchemy import Integer, String, DateTime, Text, ForeignKey
|
||||
from sqlalchemy import Index, Integer, String, DateTime, Text, ForeignKey, text
|
||||
from sqlalchemy.orm import mapped_column, Mapped
|
||||
from app.database import Base
|
||||
|
||||
@@ -19,3 +19,8 @@ class NotificationLog(Base):
|
||||
status: Mapped[str] = mapped_column(String(16), nullable=False)
|
||||
response_text: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc), index=True)
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_notif_event_created", "event_type", text("created_at DESC")),
|
||||
Index("ix_notif_status_created", "status", text("created_at DESC")),
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime, timezone
|
||||
from sqlalchemy import Integer, Text, DateTime, ForeignKey
|
||||
from sqlalchemy import Index, Integer, Text, DateTime, ForeignKey, text
|
||||
from sqlalchemy.orm import mapped_column, Mapped
|
||||
from app.database import Base
|
||||
|
||||
@@ -11,3 +11,7 @@ class UpstreamRateSnapshot(Base):
|
||||
upstream_id: Mapped[int] = mapped_column(Integer, ForeignKey("upstreams.id", ondelete="CASCADE"), index=True)
|
||||
snapshot_json: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
captured_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc), index=True)
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_snapshot_upstream_captured", "upstream_id", text("captured_at DESC")),
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
from sqlalchemy import Integer, String, Boolean, DateTime, Text, Float
|
||||
from sqlalchemy import Index, Integer, String, Boolean, DateTime, Text, Float
|
||||
from sqlalchemy.orm import mapped_column, Mapped
|
||||
from app.database import Base
|
||||
|
||||
@@ -36,3 +36,7 @@ class Upstream(Base):
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_upstream_enabled", "enabled"),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, UniqueConstraint
|
||||
from sqlalchemy import DateTime, ForeignKey, Index, Integer, String, Text, UniqueConstraint
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
@@ -30,4 +30,5 @@ class UpstreamGeneratedKey(Base):
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("upstream_id", "group_id", "key_name", name="uq_upstream_group_key"),
|
||||
Index("ix_key_upstream_name", "upstream_id", "key_name"),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text
|
||||
from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, String, Text, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
@@ -66,3 +66,8 @@ class WebsiteSyncLog(Base):
|
||||
status: Mapped[str] = mapped_column(String(16), nullable=False)
|
||||
message: Mapped[str] = mapped_column(Text, default="")
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc), index=True)
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_sync_website_created", "website_id", text("created_at DESC")),
|
||||
Index("ix_sync_binding_created", "binding_id", text("created_at DESC")),
|
||||
)
|
||||
|
||||
@@ -321,13 +321,49 @@ def _same_origin(a: str, b: str) -> bool:
|
||||
return _origin(a).rstrip("/") == _origin(b).rstrip("/")
|
||||
|
||||
|
||||
# ── TTL 缓存:origin → upstream_id(30 秒自动过期,无手动 invalidate)──
|
||||
import time as _time
|
||||
from functools import wraps as _wraps
|
||||
|
||||
def _ttl_cache(ttl_seconds: int = 30, maxsize: int = 64):
|
||||
def decorator(fn):
|
||||
cache: dict = {}
|
||||
@_wraps(fn)
|
||||
def wrapper(*args):
|
||||
key = args
|
||||
now = _time.monotonic()
|
||||
if key in cache:
|
||||
value, ts = cache[key]
|
||||
if now - ts < ttl_seconds:
|
||||
return value
|
||||
value = fn(*args)
|
||||
if len(cache) >= maxsize:
|
||||
cache.pop(next(iter(cache)))
|
||||
cache[key] = (value, now)
|
||||
return value
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def _find_matching_upstream(db: Session, page: CustomPage) -> Optional[Upstream]:
|
||||
page_origin = _origin(page.url)
|
||||
if not page_origin:
|
||||
return None
|
||||
for upstream in db.query(Upstream).order_by(Upstream.id).all():
|
||||
if _origin(upstream.base_url) == page_origin:
|
||||
return upstream
|
||||
upstream_id = _origin_to_upstream_id(page_origin)
|
||||
if upstream_id is None:
|
||||
return None
|
||||
return db.query(Upstream).filter(Upstream.id == upstream_id).first()
|
||||
|
||||
|
||||
@_ttl_cache(ttl_seconds=30, maxsize=64)
|
||||
def _origin_to_upstream_id(origin: str) -> Optional[int]:
|
||||
"""按 origin 查匹配的上游 ID,结果缓存 30 秒。"""
|
||||
from app.database import SessionLocal
|
||||
from app.models.upstream import Upstream
|
||||
with SessionLocal() as sess:
|
||||
for upstream in sess.query(Upstream).order_by(Upstream.id).all():
|
||||
if _origin(upstream.base_url) == origin:
|
||||
return upstream.id
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -284,6 +284,8 @@ def refresh_upstream(upstream_id: int, interval_seconds: int = 0, enabled: bool
|
||||
replace_existing=True,
|
||||
coalesce=True,
|
||||
max_instances=1,
|
||||
misfire_grace_time=60,
|
||||
jitter=30,
|
||||
)
|
||||
logger.info("scheduler job %s set to %ds interval", job_id, interval_seconds)
|
||||
|
||||
|
||||
@@ -96,13 +96,27 @@ def sync_binding(db: Session, binding: WebsiteGroupBinding, write: bool = True)
|
||||
if not website:
|
||||
raise WebsiteError("网站不存在")
|
||||
sources = binding_sources(binding)
|
||||
# ── 批量预查:收集所有上游 ID,一次查询上游名称 ──
|
||||
upstream_ids = {int(s.get("upstream_id") or 0) for s in sources if s.get("upstream_id")}
|
||||
upstreams = {}
|
||||
if upstream_ids:
|
||||
rows = db.query(Upstream).filter(Upstream.id.in_(upstream_ids)).all()
|
||||
upstreams = {u.id: u for u in rows}
|
||||
# ── 同一轮 sync 内的快照缓存(调用级,函数返回即释放)──
|
||||
_snap_cache: dict[int, dict[str, Any]] = {}
|
||||
|
||||
def _get_snap(upstream_id: int) -> dict[str, Any]:
|
||||
if upstream_id not in _snap_cache:
|
||||
_snap_cache[upstream_id] = latest_rate_map(db, upstream_id)
|
||||
return _snap_cache[upstream_id]
|
||||
|
||||
source_rates: list[dict[str, Any]] = []
|
||||
for source in sources:
|
||||
upstream_id = int(source.get("upstream_id") or 0)
|
||||
group_id = str(source.get("group_id") or "")
|
||||
groups = latest_rate_map(db, upstream_id)
|
||||
groups = _get_snap(upstream_id)
|
||||
group = groups.get(group_id) if group_id else None
|
||||
upstream = db.query(Upstream).filter(Upstream.id == upstream_id).first()
|
||||
upstream = upstreams.get(upstream_id)
|
||||
source_rates.append({
|
||||
"upstream_id": upstream_id,
|
||||
"upstream_name": source.get("upstream_name") or (upstream.name if upstream else ""),
|
||||
|
||||
@@ -19,6 +19,11 @@ services:
|
||||
- DATABASE_URL=sqlite:////app/data/app.db
|
||||
- TZ=${TZ:-Asia/Shanghai}
|
||||
- UNHEALTHY_THRESHOLD=${UNHEALTHY_THRESHOLD:-3}
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/healthz"]
|
||||
interval: 30s
|
||||
|
||||
Generated
+403
@@ -20,6 +20,8 @@
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"typescript": "^5.7.2",
|
||||
"unplugin-auto-import": "^21.0.0",
|
||||
"unplugin-vue-components": "^32.1.0",
|
||||
"vite": "^6.0.3",
|
||||
"vue-tsc": "^2.1.10"
|
||||
}
|
||||
@@ -555,12 +557,55 @@
|
||||
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/remapping": {
|
||||
"version": "2.3.5",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"name": "@sxzz/popperjs-es",
|
||||
"version": "2.11.8",
|
||||
@@ -1173,6 +1218,19 @@
|
||||
"vue": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz",
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/alien-signals": {
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-1.0.13.tgz",
|
||||
@@ -1245,6 +1303,22 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-5.0.0.tgz",
|
||||
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@@ -1257,6 +1331,13 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/confbox": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.4.tgz",
|
||||
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
|
||||
@@ -1424,12 +1505,32 @@
|
||||
"@esbuild/win32-x64": "0.25.12"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
|
||||
"integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/exsolve": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz",
|
||||
"integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
|
||||
@@ -1618,6 +1719,31 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz",
|
||||
"integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/local-pkg": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.2.1.tgz",
|
||||
"integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mlly": "^1.7.4",
|
||||
"pkg-types": "^2.3.0",
|
||||
"quansync": "^0.2.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz",
|
||||
@@ -1702,6 +1828,38 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.2.tgz",
|
||||
"integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.16.0",
|
||||
"pathe": "^2.0.3",
|
||||
"pkg-types": "^1.3.1",
|
||||
"ufo": "^1.6.3"
|
||||
}
|
||||
},
|
||||
"node_modules/mlly/node_modules/confbox": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz",
|
||||
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mlly/node_modules/pkg-types": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz",
|
||||
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"confbox": "^0.1.8",
|
||||
"mlly": "^1.7.4",
|
||||
"pathe": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/muggle-string": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz",
|
||||
@@ -1733,6 +1891,17 @@
|
||||
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/obug": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/obug/-/obug-2.1.1.tgz",
|
||||
"integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://github.com/sponsors/sxzz",
|
||||
"https://opencollective.com/debug"
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-browserify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||
@@ -1740,6 +1909,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -1781,6 +1957,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-types": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.1.tgz",
|
||||
"integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"confbox": "^0.2.4",
|
||||
"exsolve": "^1.0.8",
|
||||
"pathe": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.14.tgz",
|
||||
@@ -1818,6 +2006,37 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/quansync": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz",
|
||||
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/sxzz"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-5.0.0.tgz",
|
||||
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.60.3",
|
||||
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.60.3.tgz",
|
||||
@@ -1863,6 +2082,13 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/scule": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
|
||||
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -1872,6 +2098,19 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-literal": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz",
|
||||
"integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^9.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.16",
|
||||
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
||||
@@ -1903,6 +2142,163 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.4.tgz",
|
||||
"integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unimport": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/unimport/-/unimport-5.7.0.tgz",
|
||||
"integrity": "sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.16.0",
|
||||
"escape-string-regexp": "^5.0.0",
|
||||
"estree-walker": "^3.0.3",
|
||||
"local-pkg": "^1.1.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"mlly": "^1.8.0",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3",
|
||||
"pkg-types": "^2.3.0",
|
||||
"scule": "^1.3.0",
|
||||
"strip-literal": "^3.1.0",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"unplugin": "^2.3.11",
|
||||
"unplugin-utils": "^0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unimport/node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin": {
|
||||
"version": "2.3.11",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.11.tgz",
|
||||
"integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"acorn": "^8.15.0",
|
||||
"picomatch": "^4.0.3",
|
||||
"webpack-virtual-modules": "^0.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-auto-import": {
|
||||
"version": "21.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-21.0.0.tgz",
|
||||
"integrity": "sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"local-pkg": "^1.1.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"picomatch": "^4.0.3",
|
||||
"unimport": "^5.6.0",
|
||||
"unplugin": "^2.3.11",
|
||||
"unplugin-utils": "^0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nuxt/kit": "^4.0.0",
|
||||
"@vueuse/core": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@nuxt/kit": {
|
||||
"optional": true
|
||||
},
|
||||
"@vueuse/core": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-utils": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.3.1.tgz",
|
||||
"integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sxzz"
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-vue-components": {
|
||||
"version": "32.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-32.1.0.tgz",
|
||||
"integrity": "sha512-YiUkSxuRjab18XFOrX5VsIxXzccrfmHVGsGeJgSgklb829DQmCy9E4vvDUE4tuvZZdxyFJZX0Oc4TPnnxiiMyg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^5.0.0",
|
||||
"local-pkg": "^1.2.0",
|
||||
"magic-string": "^0.30.21",
|
||||
"mlly": "^1.8.2",
|
||||
"obug": "^2.1.1",
|
||||
"picomatch": "^4.0.4",
|
||||
"tinyglobby": "^0.2.16",
|
||||
"unplugin": "^3.0.0",
|
||||
"unplugin-utils": "^0.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.19.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nuxt/kit": "^3.2.2 || ^4.0.0",
|
||||
"vue": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@nuxt/kit": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-vue-components/node_modules/unplugin": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-3.0.0.tgz",
|
||||
"integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/remapping": "^2.3.5",
|
||||
"picomatch": "^4.0.3",
|
||||
"webpack-virtual-modules": "^0.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.2.tgz",
|
||||
@@ -2069,6 +2465,13 @@
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-virtual-modules": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"typescript": "^5.7.2",
|
||||
"unplugin-auto-import": "^21.0.0",
|
||||
"unplugin-vue-components": "^32.1.0",
|
||||
"vite": "^6.0.3",
|
||||
"vue-tsc": "^2.1.10"
|
||||
}
|
||||
|
||||
+12
-9
@@ -1,21 +1,24 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import * as ElIcons from '@element-plus/icons-vue'
|
||||
// 程序化 API 组件手动引入(按需引入不自动包含)
|
||||
import { ElMessage, ElMessageBox, ElNotification, ElLoading } from 'element-plus'
|
||||
import 'element-plus/es/components/message/style/css'
|
||||
import 'element-plus/es/components/message-box/style/css'
|
||||
import 'element-plus/es/components/notification/style/css'
|
||||
import 'element-plus/es/components/loading/style/css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import './assets/main.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
// Register all Element Plus icons globally
|
||||
for (const [name, component] of Object.entries(ElIcons)) {
|
||||
app.component(name, component)
|
||||
}
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(ElementPlus)
|
||||
app.mount('#app')
|
||||
|
||||
// 确保程序化 API 在 setup 外也可用(已全局引用,不 tree-shake)
|
||||
ElMessage
|
||||
ElMessageBox
|
||||
ElNotification
|
||||
ElLoading
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
</el-descriptions>
|
||||
<div class="detail-section">
|
||||
<div class="detail-label">Payload</div>
|
||||
<pre class="code-block">{{ JSON.stringify(detailRow.payload, null, 2) }}</pre>
|
||||
<pre class="code-block">{{ detailPayloadText }}</pre>
|
||||
</div>
|
||||
<div v-if="detailRow.response_text" class="detail-section">
|
||||
<div class="detail-label">响应</div>
|
||||
@@ -123,6 +123,8 @@ const eventTagType = (e: string) =>
|
||||
const toUTC = (t: string) => /[Z+\-]\d*$/.test(t.trim()) ? t : t + 'Z'
|
||||
const fmtTime = (t: string) => dayjs(toUTC(t)).format('MM-DD HH:mm:ss')
|
||||
|
||||
const detailPayloadText = computed(() => detailRow.value ? JSON.stringify(detailRow.value.payload, null, 2) : '')
|
||||
|
||||
|
||||
async function loadList() {
|
||||
tableLoading.value = true
|
||||
|
||||
@@ -428,7 +428,7 @@
|
||||
|
||||
<div v-if="expandedId === snap.id" class="snap-body">
|
||||
<el-table
|
||||
:data="groupRows(snap.snapshot)"
|
||||
:data="groupRows(snap)"
|
||||
size="small"
|
||||
:header-cell-style="{ background: 'rgba(255, 244, 232, 0.02)', color: 'var(--text-soft)' }"
|
||||
:cell-style="{ background: 'transparent', color: 'var(--text-primary)' }"
|
||||
@@ -725,9 +725,15 @@ const toUTC = (t: string) => /[Z+\-]\d*$/.test(t.trim()) ? t : `${t}Z`
|
||||
const fmtTime = (t: string) => dayjs(toUTC(t)).format('MM-DD HH:mm:ss')
|
||||
const fmtTimeFull = (t: string) => dayjs(toUTC(t)).format('YYYY-MM-DD HH:mm:ss')
|
||||
|
||||
function groupRows(snapshot: any) {
|
||||
if (!snapshot?.groups) return []
|
||||
return Object.values(snapshot.groups) as any[]
|
||||
const _groupRowsCache = new Map<number, any[]>()
|
||||
|
||||
function groupRows(snap: { id: number; snapshot: any }) {
|
||||
if (!snap?.snapshot?.groups) return []
|
||||
const cached = _groupRowsCache.get(snap.id)
|
||||
if (cached) return cached
|
||||
const rows = Object.values(snap.snapshot.groups) as any[]
|
||||
_groupRowsCache.set(snap.id, rows)
|
||||
return rows
|
||||
}
|
||||
|
||||
function shrinkError(value: string) {
|
||||
@@ -833,6 +839,7 @@ function openDetail(row: UpstreamData) {
|
||||
generatedKeys.value = []
|
||||
snapshotOffset.value = 0
|
||||
expandedId.value = null
|
||||
_groupRowsCache.clear()
|
||||
detailVisible.value = true
|
||||
}
|
||||
|
||||
@@ -854,6 +861,7 @@ async function loadSnapshots() {
|
||||
try {
|
||||
const res = await upstreamsApi.listSnapshots(detailUpstream.value.id, snapshotLimit, snapshotOffset.value)
|
||||
snapshots.value = res.data
|
||||
_groupRowsCache.clear()
|
||||
if (res.data.length > 0 && expandedId.value === null) {
|
||||
expandedId.value = res.data[0].id
|
||||
}
|
||||
|
||||
+26
-1
@@ -1,9 +1,16 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
import { resolve } from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({ resolvers: [ElementPlusResolver()] }),
|
||||
Components({ resolvers: [ElementPlusResolver({ importStyle: 'css' })] }),
|
||||
],
|
||||
resolve: {
|
||||
alias: { '@': resolve(__dirname, 'src') },
|
||||
},
|
||||
@@ -16,5 +23,23 @@ export default defineConfig({
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
sourcemap: false,
|
||||
target: 'es2020',
|
||||
cssCodeSplit: true,
|
||||
chunkSizeWarningLimit: 800,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id: string) {
|
||||
if (id.includes('/node_modules/element-plus/') || id.includes('/node_modules/@element-plus/')) {
|
||||
return 'vendor-el';
|
||||
}
|
||||
if (id.includes('/node_modules/vue/') || id.includes('/node_modules/pinia/') || id.includes('/node_modules/vue-router/')) {
|
||||
return 'vendor-vue';
|
||||
}
|
||||
if (id.includes('/node_modules/axios/')) {
|
||||
return 'vendor-axios';
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user