diff --git a/faceai/.env.development b/faceai/.env.development index cf9786a9..c5dc6fff 100644 --- a/faceai/.env.development +++ b/faceai/.env.development @@ -1,5 +1,5 @@ NODE_ENV=development -FACEAI_CLIENT_DEV_IMAGE=node:20-alpine +FACEAI_CLIENT_DEV_IMAGE=node:22-trixie-slim FACEAI_PROCESSOR_DEV_IMAGE=regalami-faceai-processor-local FACEAI_PORT=3001 FACEAI_PUBLISHED_PORT=3001 diff --git a/faceai/README.md b/faceai/README.md index 50f9ead5..ac554117 100644 --- a/faceai/README.md +++ b/faceai/README.md @@ -5,6 +5,7 @@ This folder scaffolds the new FaceAI app described in the integration plan. It includes: - a Vue frontend for the FaceAI upload and polling flow +- a separate Vue monitor frontend for querying the FaceAI audit database - a Node/Express backend for session exchange, queueing, and return handoff - a dedicated processor runner that consumes matcher jobs from Redis and executes `face_matcher` - a local Dockerized Tomcat/JSP stack so the launch and return flow can be tested against the real legacy race pages under `www/` @@ -16,6 +17,7 @@ faceai/ apps/ backend/ frontend/ + monitor-frontend/ processor/ docker/ Dockerfile @@ -26,11 +28,13 @@ faceai/ The production-oriented application topology is still three services: - `faceai`: public HTTP service on port `3001`, serving the built Vue app and the authenticated API +- `faceai-monitor`: read-only audit monitor on port `3002`, serving a lightweight Vue dashboard backed by the audit API - `processor`: background matcher runner consuming BullMQ jobs from Redis and executing the Linux `face_matcher` binary - `redis`: short-lived queue and search-state store The checked-in development override now expands that into the full local integration stack: +- `faceai-monitor`: local audit dashboard that proxies read-only audit-monitor API calls to the backend - `tomcat-www`: local Tomcat runtime serving the real legacy JSP race pages from `www/` - `mysql`: local legacy database used by the Tomcat stack - `maildump`: local SMTP sink and viewer for the Tomcat stack @@ -81,6 +85,7 @@ The checked-in Compose setup now uses: The local development stack started by the command above combines the base file and the override and starts: - FaceAI public site on `http://localhost:3001` +- FaceAI monitor on `http://localhost:3002` - processor runner on the internal Compose network - Redis on the internal Compose network - Tomcat serving the real local legacy site on `http://localhost:8080` @@ -93,6 +98,8 @@ The local stack also mounts: - `./logs` into both the public FaceAI container and the processor container as the persistent diagnostics directory - `../www` into the Tomcat build/runtime inputs so the real legacy JSP pages and assets are used +The separate monitor container does not mount the SQLite database directly. Instead it serves a static Vue app and proxies read-only requests under `/api/audit-monitor/*` to the existing backend service, which remains the only process that opens the audit database. + The `processor` service is built from `docker/processor.Dockerfile` using the repository root as Docker build context. That image copies only the checked-in Unix `face_matcher` into the image, so the matcher is baked into the processor runtime without bringing along the other Unix or Windows binaries. ### Persistent Logs @@ -131,6 +138,19 @@ Open: http://localhost:8080/Foto2.abl?id_gara=1018557&pageRow=96&pageNumber=1 ``` +For audit visibility during local runs, also open: + +```text +http://localhost:3002 +``` + +The monitor shows: + +- lifetime and recent-window search totals from the SQLite audit log +- the latest searches with filter-by-status and free-text lookup for race, user, result, error code, selfie name, or search id +- per-search event timelines and stored match snapshots +- recent events and a recent top-race breakdown + That is the real local Tomcat race page. It loads the original race-page JavaScript from `www/_js/rus-ecom-240621.js`, lets the script replace the visible `tipoPuntoFoto` selector with the new `Face ID` button, and launches the backend handoff endpoint configured through the JSP page. ### Expected Local Flow @@ -473,6 +493,16 @@ FACEAI_FEATURE_ENABLED=1 The checked-in `docker-compose.override.yml` sets that on the local `tomcat-www` service so the race page can launch the FaceAI handoff flow locally. +### Audit Monitor API + +The monitor frontend uses these read-only backend routes: + +- `GET /api/audit-monitor/summary` +- `GET /api/audit-monitor/searches?status=...&query=...&limit=...` +- `GET /api/audit-monitor/searches/` + +They read the same SQLite audit database already configured through `FACEAI_AUDIT_DB_PATH`. The monitor container proxies those requests to `faceai`, so no browser-side direct SQLite access is involved. + ## Notes - Search orchestration now uses Redis and a dedicated processor worker. diff --git a/faceai/apps/backend/src/audit-store.js b/faceai/apps/backend/src/audit-store.js index 0daf02e1..6db040c4 100644 --- a/faceai/apps/backend/src/audit-store.js +++ b/faceai/apps/backend/src/audit-store.js @@ -21,6 +21,89 @@ function jsonOrNull(value) { return JSON.stringify(value); } +function parseJsonOrNull(value) { + if (value === undefined || value === null || value === '') { + return null; + } + + try { + return JSON.parse(value); + } catch { + return null; + } +} + +function toBoundedInteger(value, fallback, { min = 1, max = Number.MAX_SAFE_INTEGER } = {}) { + const parsed = Number.parseInt(value, 10); + if (!Number.isFinite(parsed)) { + return fallback; + } + + return Math.min(max, Math.max(min, parsed)); +} + +function mapSearchRow(row) { + if (!row) { + return null; + } + + return { + searchId: row.search_id, + requestedAt: row.requested_at, + updatedAt: row.updated_at, + completedAt: row.completed_at, + redirectIssuedAt: row.redirect_issued_at, + status: row.status, + completionCode: row.completion_code, + errorCode: row.error_code, + errorMessage: row.error_message, + userId: row.user_id, + userDisplayName: row.user_display_name, + userMembershipStatus: row.user_membership_status, + raceId: row.race_id, + raceName: row.race_name, + raceStorage: row.race_storage, + lang: row.lang, + requestIp: row.request_ip, + requestUserAgent: row.request_user_agent, + returnUrl: row.return_url, + selfieName: row.selfie_name, + selfieSha256: row.selfie_sha256, + selfieSizeBytes: row.selfie_size_bytes, + uploadPath: row.upload_path, + resultId: row.result_id, + matchCount: row.match_count, + matches: parseJsonOrNull(row.matches_json) + }; +} + +function mapEventRow(row) { + if (!row) { + return null; + } + + return { + id: row.id, + searchId: row.search_id, + eventType: row.event_type, + happenedAt: row.happened_at, + status: row.status, + userId: row.user_id, + userDisplayName: row.user_display_name, + raceId: row.race_id, + raceName: row.race_name, + raceStorage: row.race_storage, + requestIp: row.request_ip, + requestUserAgent: row.request_user_agent, + selfieSha256: row.selfie_sha256, + resultId: row.result_id, + matchCount: row.match_count, + completionCode: row.completion_code, + errorCode: row.error_code, + payload: parseJsonOrNull(row.payload_json) + }; +} + function openDatabase(dbPath) { fs.mkdirSync(path.dirname(dbPath), { recursive: true }); @@ -239,6 +322,231 @@ export function createAuditStore({ dbPath, retentionDays }) { WHERE search_id IS NULL AND happened_at < @cutoff `); + const detailSearchStatement = db.prepare(` + SELECT + search_id, + requested_at, + updated_at, + completed_at, + redirect_issued_at, + status, + completion_code, + error_code, + error_message, + user_id, + user_display_name, + user_membership_status, + race_id, + race_name, + race_storage, + lang, + request_ip, + request_user_agent, + return_url, + selfie_name, + selfie_sha256, + selfie_size_bytes, + upload_path, + result_id, + match_count, + matches_json + FROM faceai_audit_searches + WHERE search_id = @search_id + `); + + const searchEventsStatement = db.prepare(` + SELECT + id, + search_id, + event_type, + happened_at, + status, + user_id, + user_display_name, + race_id, + race_name, + race_storage, + request_ip, + request_user_agent, + selfie_sha256, + result_id, + match_count, + completion_code, + error_code, + payload_json + FROM faceai_audit_events + WHERE search_id = @search_id + ORDER BY happened_at DESC, id DESC + LIMIT @limit + `); + + const recentSearchesStatement = db.prepare(` + SELECT + search_id, + requested_at, + updated_at, + completed_at, + redirect_issued_at, + status, + completion_code, + error_code, + error_message, + user_id, + user_display_name, + user_membership_status, + race_id, + race_name, + race_storage, + lang, + request_ip, + request_user_agent, + return_url, + selfie_name, + selfie_sha256, + selfie_size_bytes, + upload_path, + result_id, + match_count, + matches_json + FROM faceai_audit_searches + ORDER BY requested_at DESC + LIMIT @limit + `); + + const recentEventsStatement = db.prepare(` + SELECT + id, + search_id, + event_type, + happened_at, + status, + user_id, + user_display_name, + race_id, + race_name, + race_storage, + request_ip, + request_user_agent, + selfie_sha256, + result_id, + match_count, + completion_code, + error_code, + payload_json + FROM faceai_audit_events + ORDER BY happened_at DESC, id DESC + LIMIT @limit + `); + + const latestFailureStatement = db.prepare(` + SELECT + search_id, + requested_at, + updated_at, + completed_at, + redirect_issued_at, + status, + completion_code, + error_code, + error_message, + user_id, + user_display_name, + user_membership_status, + race_id, + race_name, + race_storage, + lang, + request_ip, + request_user_agent, + return_url, + selfie_name, + selfie_sha256, + selfie_size_bytes, + upload_path, + result_id, + match_count, + matches_json + FROM faceai_audit_searches + WHERE status = 'failed' + ORDER BY COALESCE(completed_at, updated_at, requested_at) DESC + LIMIT 1 + `); + + const recentRaceBreakdownStatement = db.prepare(` + SELECT + race_id, + race_name, + race_storage, + COUNT(*) AS search_count, + SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count, + SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed_count, + MAX(requested_at) AS last_requested_at + FROM faceai_audit_searches + WHERE requested_at >= @requested_after + GROUP BY race_id, race_name, race_storage + ORDER BY search_count DESC, last_requested_at DESC + LIMIT @limit + `); + + function summarizeSearches(whereClause = '', params = {}) { + const row = db.prepare(` + SELECT + COUNT(*) AS total_searches, + COUNT(DISTINCT user_id) AS unique_users, + SUM(CASE WHEN status = 'queued' THEN 1 ELSE 0 END) AS queued_searches, + SUM(CASE WHEN status = 'processing' THEN 1 ELSE 0 END) AS processing_searches, + SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_searches, + SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed_searches, + COALESCE(AVG(CASE WHEN completed_at IS NOT NULL THEN completed_at - requested_at END), 0) AS average_duration_ms, + MAX(requested_at) AS latest_requested_at + FROM faceai_audit_searches + ${whereClause} + `).get(params); + + return { + totalSearches: Number(row?.total_searches || 0), + uniqueUsers: Number(row?.unique_users || 0), + queuedSearches: Number(row?.queued_searches || 0), + processingSearches: Number(row?.processing_searches || 0), + completedSearches: Number(row?.completed_searches || 0), + failedSearches: Number(row?.failed_searches || 0), + averageDurationMs: Math.round(Number(row?.average_duration_ms || 0)), + latestRequestedAt: row?.latest_requested_at || null + }; + } + + function buildSearchFilters({ status = 'all', query = '' } = {}) { + const clauses = []; + const params = {}; + + if (status && status !== 'all') { + clauses.push('status = @status'); + params.status = String(status); + } + + const normalizedQuery = String(query || '').trim(); + if (normalizedQuery) { + clauses.push(`( + search_id = @exact_query OR + user_id LIKE @query_like OR + COALESCE(user_display_name, '') LIKE @query_like OR + race_id LIKE @query_like OR + COALESCE(race_name, '') LIKE @query_like OR + COALESCE(selfie_name, '') LIKE @query_like OR + COALESCE(selfie_sha256, '') LIKE @query_like OR + COALESCE(result_id, '') LIKE @query_like OR + COALESCE(error_code, '') LIKE @query_like + )`); + params.exact_query = normalizedQuery; + params.query_like = `%${normalizedQuery}%`; + } + + return { + whereClause: clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : '', + params + }; + } + function maybePrune(now = Date.now()) { if (now - lastPrunedAt < ONE_DAY_MS) { return; @@ -456,10 +764,117 @@ export function createAuditStore({ dbPath, retentionDays }) { }); } + function getMonitorSummary({ + windowHours = 24, + recentSearchLimit = 12, + recentEventLimit = 20, + topRaceLimit = 8 + } = {}) { + maybePrune(); + + const boundedWindowHours = toBoundedInteger(windowHours, 24, { min: 1, max: 24 * 30 }); + const requestedAfter = Date.now() - (boundedWindowHours * 60 * 60 * 1000); + + return { + dbPath, + generatedAt: Date.now(), + windowHours: boundedWindowHours, + lifetime: summarizeSearches(), + recentWindow: summarizeSearches('WHERE requested_at >= @requested_after', { requested_after: requestedAfter }), + recentSearches: recentSearchesStatement + .all({ limit: toBoundedInteger(recentSearchLimit, 12, { min: 1, max: 100 }) }) + .map(mapSearchRow), + recentEvents: recentEventsStatement + .all({ limit: toBoundedInteger(recentEventLimit, 20, { min: 1, max: 100 }) }) + .map(mapEventRow), + topRaces: recentRaceBreakdownStatement.all({ + requested_after: requestedAfter, + limit: toBoundedInteger(topRaceLimit, 8, { min: 1, max: 25 }) + }).map((row) => ({ + raceId: row.race_id, + raceName: row.race_name, + raceStorage: row.race_storage, + searchCount: Number(row.search_count || 0), + completedCount: Number(row.completed_count || 0), + failedCount: Number(row.failed_count || 0), + lastRequestedAt: row.last_requested_at || null + })), + latestFailure: mapSearchRow(latestFailureStatement.get()) + }; + } + + function listSearches({ status = 'all', query = '', limit = 50 } = {}) { + maybePrune(); + + const { whereClause, params } = buildSearchFilters({ status, query }); + const rows = db.prepare(` + SELECT + search_id, + requested_at, + updated_at, + completed_at, + redirect_issued_at, + status, + completion_code, + error_code, + error_message, + user_id, + user_display_name, + user_membership_status, + race_id, + race_name, + race_storage, + lang, + request_ip, + request_user_agent, + return_url, + selfie_name, + selfie_sha256, + selfie_size_bytes, + upload_path, + result_id, + match_count, + matches_json + FROM faceai_audit_searches + ${whereClause} + ORDER BY requested_at DESC + LIMIT @limit + `).all({ + ...params, + limit: toBoundedInteger(limit, 50, { min: 1, max: 200 }) + }); + + return rows.map(mapSearchRow); + } + + function getSearchDetail(searchId, { eventLimit = 100 } = {}) { + maybePrune(); + + const search = mapSearchRow(detailSearchStatement.get({ search_id: String(searchId) })); + if (!search) { + return null; + } + + const events = searchEventsStatement + .all({ + search_id: String(searchId), + limit: toBoundedInteger(eventLimit, 100, { min: 1, max: 500 }) + }) + .map(mapEventRow); + + return { + search, + events + }; + } + maybePrune(); return { dbPath, + getMonitorSummary, + getSearchDetail, + listSearches, recordEvent, recordSearchRequested, markSearchCompleted, diff --git a/faceai/apps/backend/src/server.js b/faceai/apps/backend/src/server.js index d1a1a90b..ad8f6535 100644 --- a/faceai/apps/backend/src/server.js +++ b/faceai/apps/backend/src/server.js @@ -821,6 +821,50 @@ app.get('/api/health/queue', async (req, res) => { } }); +app.get('/api/audit-monitor/summary', (req, res) => { + try { + res.json(auditStore.getMonitorSummary({ + windowHours: req.query.windowHours, + recentSearchLimit: req.query.recentSearchLimit, + recentEventLimit: req.query.recentEventLimit, + topRaceLimit: req.query.topRaceLimit + })); + } catch (error) { + res.status(500).json({ error: error.message || 'Unable to read audit summary.' }); + } +}); + +app.get('/api/audit-monitor/searches', (req, res) => { + try { + res.json({ + items: auditStore.listSearches({ + status: req.query.status, + query: req.query.query, + limit: req.query.limit + }) + }); + } catch (error) { + res.status(500).json({ error: error.message || 'Unable to read audit searches.' }); + } +}); + +app.get('/api/audit-monitor/searches/:id', (req, res) => { + try { + const detail = auditStore.getSearchDetail(req.params.id, { + eventLimit: req.query.eventLimit + }); + + if (!detail) { + res.status(404).json({ error: 'Search not found' }); + return; + } + + res.json(detail); + } catch (error) { + res.status(500).json({ error: error.message || 'Unable to read audit search detail.' }); + } +}); + if (fs.existsSync(frontendDist)) { app.use(express.static(frontendDist)); app.get('*', (req, res, next) => { diff --git a/faceai/apps/monitor-frontend/dist/assets/index-D3u-qXp5.css b/faceai/apps/monitor-frontend/dist/assets/index-D3u-qXp5.css new file mode 100644 index 00000000..34bba742 --- /dev/null +++ b/faceai/apps/monitor-frontend/dist/assets/index-D3u-qXp5.css @@ -0,0 +1 @@ +:root{color-scheme:light;--canvas: #f6f1e8;--surface: rgba(255, 251, 245, .88);--surface-strong: #fffdf9;--ink: #1f1a14;--muted: #6c6258;--line: rgba(60, 43, 26, .14);--shadow: 0 24px 60px rgba(70, 49, 28, .12);--sand: #d49f61;--olive: #70835f;--sky: #5f8399;--stone: #7e6c5c;--danger: #9c3c2c;--queued: #8a6f43;--processing: #336b87;--completed: #567a46;--failed: #9a3b35;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}*{box-sizing:border-box}body{margin:0;min-height:100vh;color:var(--ink);background:radial-gradient(circle at top left,rgba(212,159,97,.35),transparent 30%),radial-gradient(circle at top right,rgba(95,131,153,.22),transparent 26%),linear-gradient(180deg,#fbf6ef 0%,var(--canvas) 100%)}button,input,select{font:inherit}#app{min-height:100vh}.monitor-shell{width:min(1440px,calc(100vw - 32px));margin:0 auto;padding:24px 0 40px}.hero-panel,.panel,.stat-card{border:1px solid var(--line);background:var(--surface);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);box-shadow:var(--shadow)}.hero-panel{display:grid;grid-template-columns:minmax(0,2.2fr) minmax(280px,1fr);gap:20px;border-radius:28px;padding:28px}.eyebrow{margin:0 0 10px;text-transform:uppercase;letter-spacing:.18em;font-size:.78rem;color:var(--muted)}.hero-panel h1,.panel h2,.code-block h3{margin:0;font-family:Georgia,Times New Roman,serif}.hero-panel h1{max-width:12ch;font-size:clamp(2rem,4vw,3.6rem);line-height:.96}.hero-copy,.panel-header p,.mini-row p,.empty-state,.detail-grid dt,.stat-card small,.hero-meta span{color:var(--muted)}.hero-copy{margin:16px 0 0;max-width:62ch;line-height:1.55}.hero-meta{display:grid;gap:16px;align-content:start}.hero-meta div,.stat-card{padding:18px;border-radius:22px}.hero-meta .locale-field{display:grid;gap:6px}.hero-meta .locale-field select{width:100%;min-height:46px;border-radius:14px;border:1px solid var(--line);background:var(--surface-strong);padding:10px 12px;color:var(--ink)}.hero-meta strong,.stat-card strong{display:block;margin-top:6px;font-size:1.35rem}.primary-button,.secondary-button{border:0;border-radius:999px;padding:12px 18px;cursor:pointer;transition:transform .12s ease,opacity .12s ease}.primary-button{color:#fff8ef;background:linear-gradient(135deg,#8f532c,#b77436)}.secondary-button{color:var(--ink);background:#76685b1f}.primary-button:hover,.secondary-button:hover,.search-row:hover{transform:translateY(-1px)}.primary-button:disabled{opacity:.6;cursor:wait}.stat-grid,.content-grid,.split-panel,.filter-grid,.detail-grid{display:grid;gap:18px}.stat-grid{grid-template-columns:repeat(4,minmax(0,1fr));margin:18px 0}.stat-card{min-height:154px}.stat-card strong{font-size:clamp(1.8rem,4vw,3rem)}.stat-label{text-transform:uppercase;letter-spacing:.08em;font-size:.82rem;color:var(--muted)}.accent-sand{background:linear-gradient(180deg,#e9c79f6b,#fffbf5eb)}.accent-olive{background:linear-gradient(180deg,#97b08154,#fffbf5eb)}.accent-sky{background:linear-gradient(180deg,#8db4c959,#fffbf5eb)}.accent-stone{background:linear-gradient(180deg,#aa968447,#fffbf5eb)}.content-grid{grid-template-columns:minmax(360px,.95fr) minmax(0,1.35fr);align-items:start}.panel{border-radius:28px;padding:22px}.stack-gap{display:grid;gap:18px}.panel-header{display:flex;justify-content:space-between;gap:16px;align-items:start}.panel-header.compact{margin-bottom:12px}.panel-header h2{font-size:1.55rem}.panel-header p{margin:8px 0 0;line-height:1.45}.pill,.status-badge{display:inline-flex;align-items:center;justify-content:center;border-radius:999px;padding:6px 12px;font-size:.78rem;text-transform:uppercase;letter-spacing:.06em}.pill{background:#76685b1f}.filter-grid{grid-template-columns:140px minmax(0,1fr) 120px 110px;align-items:end}.filter-grid label{display:grid;gap:6px}.filter-grid span{font-size:.82rem;color:var(--muted)}.filter-grid input,.filter-grid select{width:100%;min-height:46px;border-radius:14px;border:1px solid var(--line);background:var(--surface-strong);padding:10px 12px}.banner-error,.empty-state{margin:0;border-radius:18px;padding:14px 16px;background:#9c3c2c14}.banner-error{color:var(--danger)}.search-list,.event-list,.mini-list{display:grid;gap:12px}.search-row,.event-row,.mini-row,.code-block{border:1px solid var(--line);border-radius:20px;background:#fffdf9db}.search-row{width:100%;padding:16px;text-align:left;cursor:pointer}.search-row.selected{border-color:#8f532c6b;box-shadow:inset 0 0 0 1px #8f532c4d}.search-row-top,.search-row-meta,.search-row-bottom,.event-header,.mini-row,.mini-metrics{display:flex;gap:10px;justify-content:space-between;flex-wrap:wrap}.search-row-top strong,.mini-row strong,.event-header strong,.detail-grid dd,.code-block h3{color:var(--ink)}.search-row-meta,.search-row-bottom,.mini-row p,.mini-metrics,.event-row p{margin-top:8px;font-size:.92rem;color:var(--muted)}.status-badge[data-status=queued]{background:#8a6f4324;color:var(--queued)}.status-badge[data-status=processing]{background:#336b871f;color:var(--processing)}.status-badge[data-status=completed]{background:#567a4624;color:var(--completed)}.status-badge[data-status=failed]{background:#9a3b351f;color:var(--failed)}.status-badge[data-status=unknown]{background:#76685b1f;color:var(--stone)}.detail-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.detail-grid div{border:1px solid var(--line);border-radius:18px;padding:14px;background:#fffdf9d1}.detail-grid dt{font-size:.78rem;text-transform:uppercase;letter-spacing:.08em}.detail-grid dd{margin:10px 0 0;line-height:1.4}.code-block{padding:16px}.code-block pre,.event-row pre{overflow:auto;margin:12px 0 0;padding:14px;border-radius:16px;background:#201a17;color:#f8f1e7;font-size:.82rem}.event-row{padding:14px}.event-row p{margin-bottom:0}.split-panel{grid-template-columns:repeat(2,minmax(0,1fr))}.mini-row{padding:14px;align-items:center}.compact-row{align-items:start}@media(max-width:1180px){.stat-grid,.detail-grid,.split-panel{grid-template-columns:repeat(2,minmax(0,1fr))}.content-grid,.hero-panel{grid-template-columns:1fr}}@media(max-width:760px){.monitor-shell{width:min(100vw - 20px,100%);padding-top:12px}.panel,.hero-panel{padding:18px;border-radius:22px}.stat-grid,.detail-grid,.split-panel,.filter-grid{grid-template-columns:1fr}.panel-header{display:grid}} diff --git a/faceai/apps/monitor-frontend/dist/assets/index-DlzypR8h.js b/faceai/apps/monitor-frontend/dist/assets/index-DlzypR8h.js new file mode 100644 index 00000000..fb2e1a53 --- /dev/null +++ b/faceai/apps/monitor-frontend/dist/assets/index-DlzypR8h.js @@ -0,0 +1,17 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))n(r);new MutationObserver(r=>{for(const i of r)if(i.type==="childList")for(const l of i.addedNodes)l.tagName==="LINK"&&l.rel==="modulepreload"&&n(l)}).observe(document,{childList:!0,subtree:!0});function s(r){const i={};return r.integrity&&(i.integrity=r.integrity),r.referrerPolicy&&(i.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?i.credentials="include":r.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function n(r){if(r.ep)return;r.ep=!0;const i=s(r);fetch(r.href,i)}})();/** +* @vue/shared v3.5.32 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function Js(e){const t=Object.create(null);for(const s of e.split(","))t[s]=1;return s=>s in t}const G={},_t=[],$e=()=>{},zn=()=>!1,ds=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),hs=e=>e.startsWith("onUpdate:"),ue=Object.assign,zs=(e,t)=>{const s=e.indexOf(t);s>-1&&e.splice(s,1)},si=Object.prototype.hasOwnProperty,K=(e,t)=>si.call(e,t),L=Array.isArray,vt=e=>Kt(e)==="[object Map]",ps=e=>Kt(e)==="[object Set]",pn=e=>Kt(e)==="[object Date]",D=e=>typeof e=="function",ne=e=>typeof e=="string",Ue=e=>typeof e=="symbol",B=e=>e!==null&&typeof e=="object",Yn=e=>(B(e)||D(e))&&D(e.then)&&D(e.catch),Qn=Object.prototype.toString,Kt=e=>Qn.call(e),ni=e=>Kt(e).slice(8,-1),Xn=e=>Kt(e)==="[object Object]",Ys=e=>ne(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Ot=Js(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),gs=e=>{const t=Object.create(null);return(s=>t[s]||(t[s]=e(s)))},ri=/-\w/g,Ee=gs(e=>e.replace(ri,t=>t.slice(1).toUpperCase())),ii=/\B([A-Z])/g,ht=gs(e=>e.replace(ii,"-$1").toLowerCase()),Zn=gs(e=>e.charAt(0).toUpperCase()+e.slice(1)),Es=gs(e=>e?`on${Zn(e)}`:""),He=(e,t)=>!Object.is(e,t),Zt=(e,...t)=>{for(let s=0;s{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:n,value:s})},ms=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let gn;const _s=()=>gn||(gn=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Qs(e){if(L(e)){const t={};for(let s=0;s{if(s){const n=s.split(oi);n.length>1&&(t[n[0].trim()]=n[1].trim())}}),t}function vs(e){let t="";if(ne(e))t=e;else if(L(e))for(let s=0;sWt(s,t))}const sr=e=>!!(e&&e.__v_isRef===!0),y=e=>ne(e)?e:e==null?"":L(e)||B(e)&&(e.toString===Qn||!D(e.toString))?sr(e)?y(e.value):JSON.stringify(e,nr,2):String(e),nr=(e,t)=>sr(t)?nr(e,t.value):vt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((s,[n,r],i)=>(s[Rs(n,i)+" =>"]=r,s),{})}:ps(t)?{[`Set(${t.size})`]:[...t.values()].map(s=>Rs(s))}:Ue(t)?Rs(t):B(t)&&!L(t)&&!Xn(t)?String(t):t,Rs=(e,t="")=>{var s;return Ue(e)?`Symbol(${(s=e.description)!=null?s:t})`:e};/** +* @vue/reactivity v3.5.32 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let me;class pi{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.__v_skip=!0,this.parent=me,!t&&me&&(this.index=(me.scopes||(me.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,s;if(this.scopes)for(t=0,s=this.scopes.length;t0&&--this._on===0&&(me=this.prevScope,this.prevScope=void 0)}stop(t){if(this._active){this._active=!1;let s,n;for(s=0,n=this.effects.length;s0)return;if(Mt){let t=Mt;for(Mt=void 0;t;){const s=t.next;t.next=void 0,t.flags&=-9,t=s}}let e;for(;Pt;){let t=Pt;for(Pt=void 0;t;){const s=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(n){e||(e=n)}t=s}}if(e)throw e}function or(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function cr(e){let t,s=e.depsTail,n=s;for(;n;){const r=n.prevDep;n.version===-1?(n===s&&(s=r),en(n),mi(n)):t=n,n.dep.activeLink=n.prevActiveLink,n.prevActiveLink=void 0,n=r}e.deps=t,e.depsTail=s}function js(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(ar(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function ar(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===jt)||(e.globalVersion=jt,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!js(e))))return;e.flags|=2;const t=e.dep,s=Q,n=Re;Q=e,Re=!0;try{or(e);const r=e.fn(e._value);(t.version===0||He(r,e._value))&&(e.flags|=128,e._value=r,t.version++)}catch(r){throw t.version++,r}finally{Q=s,Re=n,cr(e),e.flags&=-3}}function en(e,t=!1){const{dep:s,prevSub:n,nextSub:r}=e;if(n&&(n.nextSub=r,e.prevSub=void 0),r&&(r.prevSub=n,e.nextSub=void 0),s.subs===e&&(s.subs=n,!n&&s.computed)){s.computed.flags&=-5;for(let i=s.computed.deps;i;i=i.nextDep)en(i,!0)}!t&&!--s.sc&&s.map&&s.map.delete(s.key)}function mi(e){const{prevDep:t,nextDep:s}=e;t&&(t.nextDep=s,e.prevDep=void 0),s&&(s.prevDep=t,e.nextDep=void 0)}let Re=!0;const ur=[];function Qe(){ur.push(Re),Re=!1}function Xe(){const e=ur.pop();Re=e===void 0?!0:e}function mn(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const s=Q;Q=void 0;try{t()}finally{Q=s}}}let jt=0;class _i{constructor(t,s){this.sub=t,this.dep=s,this.version=s.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class tn{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!Q||!Re||Q===this.computed)return;let s=this.activeLink;if(s===void 0||s.sub!==Q)s=this.activeLink=new _i(Q,this),Q.deps?(s.prevDep=Q.depsTail,Q.depsTail.nextDep=s,Q.depsTail=s):Q.deps=Q.depsTail=s,fr(s);else if(s.version===-1&&(s.version=this.version,s.nextDep)){const n=s.nextDep;n.prevDep=s.prevDep,s.prevDep&&(s.prevDep.nextDep=n),s.prevDep=Q.depsTail,s.nextDep=void 0,Q.depsTail.nextDep=s,Q.depsTail=s,Q.deps===s&&(Q.deps=n)}return s}trigger(t){this.version++,jt++,this.notify(t)}notify(t){Xs();try{for(let s=this.subs;s;s=s.prevSub)s.sub.notify()&&s.sub.dep.notify()}finally{Zs()}}}function fr(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let n=t.deps;n;n=n.nextDep)fr(n)}const s=e.dep.subs;s!==e&&(e.prevSub=s,s&&(s.nextSub=e)),e.dep.subs=e}}const Hs=new WeakMap,ft=Symbol(""),$s=Symbol(""),Ht=Symbol("");function ce(e,t,s){if(Re&&Q){let n=Hs.get(e);n||Hs.set(e,n=new Map);let r=n.get(s);r||(n.set(s,r=new tn),r.map=n,r.key=s),r.track()}}function Je(e,t,s,n,r,i){const l=Hs.get(e);if(!l){jt++;return}const o=a=>{a&&a.trigger()};if(Xs(),t==="clear")l.forEach(o);else{const a=L(e),h=a&&Ys(s);if(a&&s==="length"){const f=Number(n);l.forEach((m,A)=>{(A==="length"||A===Ht||!Ue(A)&&A>=f)&&o(m)})}else switch((s!==void 0||l.has(void 0))&&o(l.get(s)),h&&o(l.get(Ht)),t){case"add":a?h&&o(l.get("length")):(o(l.get(ft)),vt(e)&&o(l.get($s)));break;case"delete":a||(o(l.get(ft)),vt(e)&&o(l.get($s)));break;case"set":vt(e)&&o(l.get(ft));break}}Zs()}function gt(e){const t=V(e);return t===e?t:(ce(t,"iterate",Ht),we(e)?t:t.map(Ie))}function bs(e){return ce(e=V(e),"iterate",Ht),e}function Ne(e,t){return Ze(e)?xt(dt(e)?Ie(t):t):Ie(t)}const vi={__proto__:null,[Symbol.iterator](){return Os(this,Symbol.iterator,e=>Ne(this,e))},concat(...e){return gt(this).concat(...e.map(t=>L(t)?gt(t):t))},entries(){return Os(this,"entries",e=>(e[1]=Ne(this,e[1]),e))},every(e,t){return qe(this,"every",e,t,void 0,arguments)},filter(e,t){return qe(this,"filter",e,t,s=>s.map(n=>Ne(this,n)),arguments)},find(e,t){return qe(this,"find",e,t,s=>Ne(this,s),arguments)},findIndex(e,t){return qe(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return qe(this,"findLast",e,t,s=>Ne(this,s),arguments)},findLastIndex(e,t){return qe(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return qe(this,"forEach",e,t,void 0,arguments)},includes(...e){return Ps(this,"includes",e)},indexOf(...e){return Ps(this,"indexOf",e)},join(e){return gt(this).join(e)},lastIndexOf(...e){return Ps(this,"lastIndexOf",e)},map(e,t){return qe(this,"map",e,t,void 0,arguments)},pop(){return Et(this,"pop")},push(...e){return Et(this,"push",e)},reduce(e,...t){return _n(this,"reduce",e,t)},reduceRight(e,...t){return _n(this,"reduceRight",e,t)},shift(){return Et(this,"shift")},some(e,t){return qe(this,"some",e,t,void 0,arguments)},splice(...e){return Et(this,"splice",e)},toReversed(){return gt(this).toReversed()},toSorted(e){return gt(this).toSorted(e)},toSpliced(...e){return gt(this).toSpliced(...e)},unshift(...e){return Et(this,"unshift",e)},values(){return Os(this,"values",e=>Ne(this,e))}};function Os(e,t,s){const n=bs(e),r=n[t]();return n!==e&&!we(e)&&(r._next=r.next,r.next=()=>{const i=r._next();return i.done||(i.value=s(i.value)),i}),r}const bi=Array.prototype;function qe(e,t,s,n,r,i){const l=bs(e),o=l!==e&&!we(e),a=l[t];if(a!==bi[t]){const m=a.apply(e,i);return o?Ie(m):m}let h=s;l!==e&&(o?h=function(m,A){return s.call(this,Ne(e,m),A,e)}:s.length>2&&(h=function(m,A){return s.call(this,m,A,e)}));const f=a.call(l,h,n);return o&&r?r(f):f}function _n(e,t,s,n){const r=bs(e),i=r!==e&&!we(e);let l=s,o=!1;r!==e&&(i?(o=n.length===0,l=function(h,f,m){return o&&(o=!1,h=Ne(e,h)),s.call(this,h,Ne(e,f),m,e)}):s.length>3&&(l=function(h,f,m){return s.call(this,h,f,m,e)}));const a=r[t](l,...n);return o?Ne(e,a):a}function Ps(e,t,s){const n=V(e);ce(n,"iterate",Ht);const r=n[t](...s);return(r===-1||r===!1)&&rn(s[0])?(s[0]=V(s[0]),n[t](...s)):r}function Et(e,t,s=[]){Qe(),Xs();const n=V(e)[t].apply(e,s);return Zs(),Xe(),n}const yi=Js("__proto__,__v_isRef,__isVue"),dr=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Ue));function Si(e){Ue(e)||(e=String(e));const t=V(this);return ce(t,"has",e),t.hasOwnProperty(e)}class hr{constructor(t=!1,s=!1){this._isReadonly=t,this._isShallow=s}get(t,s,n){if(s==="__v_skip")return t.__v_skip;const r=this._isReadonly,i=this._isShallow;if(s==="__v_isReactive")return!r;if(s==="__v_isReadonly")return r;if(s==="__v_isShallow")return i;if(s==="__v_raw")return n===(r?i?Pi:_r:i?mr:gr).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(n)?t:void 0;const l=L(t);if(!r){let a;if(l&&(a=vi[s]))return a;if(s==="hasOwnProperty")return Si}const o=Reflect.get(t,s,ae(t)?t:n);if((Ue(s)?dr.has(s):yi(s))||(r||ce(t,"get",s),i))return o;if(ae(o)){const a=l&&Ys(s)?o:o.value;return r&&B(a)?Vs(a):a}return B(o)?r?Vs(o):ys(o):o}}class pr extends hr{constructor(t=!1){super(!1,t)}set(t,s,n,r){let i=t[s];const l=L(t)&&Ys(s);if(!this._isShallow){const h=Ze(i);if(!we(n)&&!Ze(n)&&(i=V(i),n=V(n)),!l&&ae(i)&&!ae(n))return h||(i.value=n),!0}const o=l?Number(s)e,Gt=e=>Reflect.getPrototypeOf(e);function Ai(e,t,s){return function(...n){const r=this.__v_raw,i=V(r),l=vt(i),o=e==="entries"||e===Symbol.iterator&&l,a=e==="keys"&&l,h=r[e](...n),f=s?Us:t?xt:Ie;return!t&&ce(i,"iterate",a?$s:ft),ue(Object.create(h),{next(){const{value:m,done:A}=h.next();return A?{value:m,done:A}:{value:o?[f(m[0]),f(m[1])]:f(m),done:A}}})}}function Jt(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function Ei(e,t){const s={get(r){const i=this.__v_raw,l=V(i),o=V(r);e||(He(r,o)&&ce(l,"get",r),ce(l,"get",o));const{has:a}=Gt(l),h=t?Us:e?xt:Ie;if(a.call(l,r))return h(i.get(r));if(a.call(l,o))return h(i.get(o));i!==l&&i.get(r)},get size(){const r=this.__v_raw;return!e&&ce(V(r),"iterate",ft),r.size},has(r){const i=this.__v_raw,l=V(i),o=V(r);return e||(He(r,o)&&ce(l,"has",r),ce(l,"has",o)),r===o?i.has(r):i.has(r)||i.has(o)},forEach(r,i){const l=this,o=l.__v_raw,a=V(o),h=t?Us:e?xt:Ie;return!e&&ce(a,"iterate",ft),o.forEach((f,m)=>r.call(i,h(f),h(m),l))}};return ue(s,e?{add:Jt("add"),set:Jt("set"),delete:Jt("delete"),clear:Jt("clear")}:{add(r){const i=V(this),l=Gt(i),o=V(r),a=!t&&!we(r)&&!Ze(r)?o:r;return l.has.call(i,a)||He(r,a)&&l.has.call(i,r)||He(o,a)&&l.has.call(i,o)||(i.add(a),Je(i,"add",a,a)),this},set(r,i){!t&&!we(i)&&!Ze(i)&&(i=V(i));const l=V(this),{has:o,get:a}=Gt(l);let h=o.call(l,r);h||(r=V(r),h=o.call(l,r));const f=a.call(l,r);return l.set(r,i),h?He(i,f)&&Je(l,"set",r,i):Je(l,"add",r,i),this},delete(r){const i=V(this),{has:l,get:o}=Gt(i);let a=l.call(i,r);a||(r=V(r),a=l.call(i,r)),o&&o.call(i,r);const h=i.delete(r);return a&&Je(i,"delete",r,void 0),h},clear(){const r=V(this),i=r.size!==0,l=r.clear();return i&&Je(r,"clear",void 0,void 0),l}}),["keys","values","entries",Symbol.iterator].forEach(r=>{s[r]=Ai(r,e,t)}),s}function sn(e,t){const s=Ei(e,t);return(n,r,i)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?n:Reflect.get(K(s,r)&&r in n?s:n,r,i)}const Ri={get:sn(!1,!1)},Ii={get:sn(!1,!0)},Oi={get:sn(!0,!1)};const gr=new WeakMap,mr=new WeakMap,_r=new WeakMap,Pi=new WeakMap;function Mi(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Fi(e){return e.__v_skip||!Object.isExtensible(e)?0:Mi(ni(e))}function ys(e){return Ze(e)?e:nn(e,!1,wi,Ri,gr)}function Li(e){return nn(e,!1,Ci,Ii,mr)}function Vs(e){return nn(e,!0,Ti,Oi,_r)}function nn(e,t,s,n,r){if(!B(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const i=Fi(e);if(i===0)return e;const l=r.get(e);if(l)return l;const o=new Proxy(e,i===2?n:s);return r.set(e,o),o}function dt(e){return Ze(e)?dt(e.__v_raw):!!(e&&e.__v_isReactive)}function Ze(e){return!!(e&&e.__v_isReadonly)}function we(e){return!!(e&&e.__v_isShallow)}function rn(e){return e?!!e.__v_raw:!1}function V(e){const t=e&&e.__v_raw;return t?V(t):e}function Di(e){return!K(e,"__v_skip")&&Object.isExtensible(e)&&er(e,"__v_skip",!0),e}const Ie=e=>B(e)?ys(e):e,xt=e=>B(e)?Vs(e):e;function ae(e){return e?e.__v_isRef===!0:!1}function Fe(e){return Ni(e,!1)}function Ni(e,t){return ae(e)?e:new ji(e,t)}class ji{constructor(t,s){this.dep=new tn,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=s?t:V(t),this._value=s?t:Ie(t),this.__v_isShallow=s}get value(){return this.dep.track(),this._value}set value(t){const s=this._rawValue,n=this.__v_isShallow||we(t)||Ze(t);t=n?t:V(t),He(t,s)&&(this._rawValue=t,this._value=n?t:Ie(t),this.dep.trigger())}}function Hi(e){return ae(e)?e.value:e}const $i={get:(e,t,s)=>t==="__v_raw"?e:Hi(Reflect.get(e,t,s)),set:(e,t,s,n)=>{const r=e[t];return ae(r)&&!ae(s)?(r.value=s,!0):Reflect.set(e,t,s,n)}};function vr(e){return dt(e)?e:new Proxy(e,$i)}class Ui{constructor(t,s,n){this.fn=t,this.setter=s,this._value=void 0,this.dep=new tn(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=jt-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!s,this.isSSR=n}notify(){if(this.flags|=16,!(this.flags&8)&&Q!==this)return lr(this,!0),!0}get value(){const t=this.dep.track();return ar(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function Vi(e,t,s=!1){let n,r;return D(e)?n=e:(n=e.get,r=e.set),new Ui(n,r,s)}const zt={},rs=new WeakMap;let at;function Ki(e,t=!1,s=at){if(s){let n=rs.get(s);n||rs.set(s,n=[]),n.push(e)}}function Wi(e,t,s=G){const{immediate:n,deep:r,once:i,scheduler:l,augmentJob:o,call:a}=s,h=P=>r?P:we(P)||r===!1||r===0?ze(P,1):ze(P);let f,m,A,R,H=!1,I=!1;if(ae(e)?(m=()=>e.value,H=we(e)):dt(e)?(m=()=>h(e),H=!0):L(e)?(I=!0,H=e.some(P=>dt(P)||we(P)),m=()=>e.map(P=>{if(ae(P))return P.value;if(dt(P))return h(P);if(D(P))return a?a(P,2):P()})):D(e)?t?m=a?()=>a(e,2):e:m=()=>{if(A){Qe();try{A()}finally{Xe()}}const P=at;at=f;try{return a?a(e,3,[R]):e(R)}finally{at=P}}:m=$e,t&&r){const P=m,ee=r===!0?1/0:r;m=()=>ze(P(),ee)}const Z=gi(),W=()=>{f.stop(),Z&&Z.active&&zs(Z.effects,f)};if(i&&t){const P=t;t=(...ee)=>{P(...ee),W()}}let j=I?new Array(e.length).fill(zt):zt;const q=P=>{if(!(!(f.flags&1)||!f.dirty&&!P))if(t){const ee=f.run();if(r||H||(I?ee.some((Te,ve)=>He(Te,j[ve])):He(ee,j))){A&&A();const Te=at;at=f;try{const ve=[ee,j===zt?void 0:I&&j[0]===zt?[]:j,R];j=ee,a?a(t,3,ve):t(...ve)}finally{at=Te}}}else f.run()};return o&&o(q),f=new rr(m),f.scheduler=l?()=>l(q,!1):q,R=P=>Ki(P,!1,f),A=f.onStop=()=>{const P=rs.get(f);if(P){if(a)a(P,4);else for(const ee of P)ee();rs.delete(f)}},t?n?q(!0):j=f.run():l?l(q.bind(null,!0),!0):f.run(),W.pause=f.pause.bind(f),W.resume=f.resume.bind(f),W.stop=W,W}function ze(e,t=1/0,s){if(t<=0||!B(e)||e.__v_skip||(s=s||new Map,(s.get(e)||0)>=t))return e;if(s.set(e,t),t--,ae(e))ze(e.value,t,s);else if(L(e))for(let n=0;n{ze(n,t,s)});else if(Xn(e)){for(const n in e)ze(e[n],t,s);for(const n of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,n)&&ze(e[n],t,s)}return e}/** +* @vue/runtime-core v3.5.32 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function Bt(e,t,s,n){try{return n?e(...n):e()}catch(r){Ss(r,t,s)}}function Ve(e,t,s,n){if(D(e)){const r=Bt(e,t,s,n);return r&&Yn(r)&&r.catch(i=>{Ss(i,t,s)}),r}if(L(e)){const r=[];for(let i=0;i>>1,r=de[n],i=$t(r);i=$t(s)?de.push(e):de.splice(qi(t),0,e),e.flags|=1,Sr()}}function Sr(){is||(is=br.then(wr))}function ki(e){L(e)?bt.push(...e):rt&&e.id===-1?rt.splice(mt+1,0,e):e.flags&1||(bt.push(e),e.flags|=1),Sr()}function vn(e,t,s=De+1){for(;s$t(s)-$t(n));if(bt.length=0,rt){rt.push(...t);return}for(rt=t,mt=0;mte.id==null?e.flags&2?-1:1/0:e.id;function wr(e){try{for(De=0;De{n._d&&In(-1);const i=ls(t);let l;try{l=e(...r)}finally{ls(i),n._d&&In(1)}return l};return n._n=!0,n._c=!0,n._d=!0,n}function Yt(e,t){if(xe===null)return e;const s=Cs(xe),n=e.dirs||(e.dirs=[]);for(let r=0;r1)return s&&D(t)?t.call(n&&n.proxy):t}}const zi=Symbol.for("v-scx"),Yi=()=>es(zi);function ts(e,t,s){return Cr(e,t,s)}function Cr(e,t,s=G){const{immediate:n,deep:r,flush:i,once:l}=s,o=ue({},s),a=t&&n||!t&&i!=="post";let h;if(Vt){if(i==="sync"){const R=Yi();h=R.__watcherHandles||(R.__watcherHandles=[])}else if(!a){const R=()=>{};return R.stop=$e,R.resume=$e,R.pause=$e,R}}const f=he;o.call=(R,H,I)=>Ve(R,f,H,I);let m=!1;i==="post"?o.scheduler=R=>{ge(R,f&&f.suspense)}:i!=="sync"&&(m=!0,o.scheduler=(R,H)=>{H?R():ln(R)}),o.augmentJob=R=>{t&&(R.flags|=4),m&&(R.flags|=2,f&&(R.id=f.uid,R.i=f))};const A=Wi(e,t,o);return Vt&&(h?h.push(A):a&&A()),A}function Qi(e,t,s){const n=this.proxy,r=ne(e)?e.includes(".")?Ar(n,e):()=>n[e]:e.bind(n,n);let i;D(t)?i=t:(i=t.handler,s=t);const l=qt(this),o=Cr(r,i.bind(n),s);return l(),o}function Ar(e,t){const s=t.split(".");return()=>{let n=e;for(let r=0;re.__isTeleport,el=Symbol("_leaveCb");function on(e,t){e.shapeFlag&6&&e.component?(e.transition=t,on(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Er(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}function bn(e,t){let s;return!!((s=Object.getOwnPropertyDescriptor(e,t))&&!s.configurable)}const os=new WeakMap;function Ft(e,t,s,n,r=!1){if(L(e)){e.forEach((I,Z)=>Ft(I,t&&(L(t)?t[Z]:t),s,n,r));return}if(Lt(n)&&!r){n.shapeFlag&512&&n.type.__asyncResolved&&n.component.subTree.component&&Ft(e,t,s,n.component.subTree);return}const i=n.shapeFlag&4?Cs(n.component):n.el,l=r?null:i,{i:o,r:a}=e,h=t&&t.r,f=o.refs===G?o.refs={}:o.refs,m=o.setupState,A=V(m),R=m===G?zn:I=>bn(f,I)?!1:K(A,I),H=(I,Z)=>!(Z&&bn(f,Z));if(h!=null&&h!==a){if(yn(t),ne(h))f[h]=null,R(h)&&(m[h]=null);else if(ae(h)){const I=t;H(h,I.k)&&(h.value=null),I.k&&(f[I.k]=null)}}if(D(a))Bt(a,o,12,[l,f]);else{const I=ne(a),Z=ae(a);if(I||Z){const W=()=>{if(e.f){const j=I?R(a)?m[a]:f[a]:H()||!e.k?a.value:f[e.k];if(r)L(j)&&zs(j,i);else if(L(j))j.includes(i)||j.push(i);else if(I)f[a]=[i],R(a)&&(m[a]=f[a]);else{const q=[i];H(a,e.k)&&(a.value=q),e.k&&(f[e.k]=q)}}else I?(f[a]=l,R(a)&&(m[a]=l)):Z&&(H(a,e.k)&&(a.value=l),e.k&&(f[e.k]=l))};if(l){const j=()=>{W(),os.delete(e)};j.id=-1,os.set(e,j),ge(j,s)}else yn(e),W()}}}function yn(e){const t=os.get(e);t&&(t.flags|=8,os.delete(e))}_s().requestIdleCallback;_s().cancelIdleCallback;const Lt=e=>!!e.type.__asyncLoader,Rr=e=>e.type.__isKeepAlive;function tl(e,t){Ir(e,"a",t)}function sl(e,t){Ir(e,"da",t)}function Ir(e,t,s=he){const n=e.__wdc||(e.__wdc=()=>{let r=s;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(xs(t,n,s),s){let r=s.parent;for(;r&&r.parent;)Rr(r.parent.vnode)&&nl(n,t,s,r),r=r.parent}}function nl(e,t,s,n){const r=xs(t,e,n,!0);Mr(()=>{zs(n[t],r)},s)}function xs(e,t,s=he,n=!1){if(s){const r=s[e]||(s[e]=[]),i=t.__weh||(t.__weh=(...l)=>{Qe();const o=qt(s),a=Ve(t,s,e,l);return o(),Xe(),a});return n?r.unshift(i):r.push(i),i}}const et=e=>(t,s=he)=>{(!Vt||e==="sp")&&xs(e,(...n)=>t(...n),s)},rl=et("bm"),Or=et("m"),il=et("bu"),ll=et("u"),Pr=et("bum"),Mr=et("um"),ol=et("sp"),cl=et("rtg"),al=et("rtc");function ul(e,t=he){xs("ec",e,t)}const fl=Symbol.for("v-ndc");function Qt(e,t,s,n){let r;const i=s,l=L(e);if(l||ne(e)){const o=l&&dt(e);let a=!1,h=!1;o&&(a=!we(e),h=Ze(e),e=bs(e)),r=new Array(e.length);for(let f=0,m=e.length;ft(o,a,void 0,i));else{const o=Object.keys(e);r=new Array(o.length);for(let a=0,h=o.length;ae?Zr(e)?Cs(e):Ks(e.parent):null,Dt=ue(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Ks(e.parent),$root:e=>Ks(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Lr(e),$forceUpdate:e=>e.f||(e.f=()=>{ln(e.update)}),$nextTick:e=>e.n||(e.n=yr.bind(e.proxy)),$watch:e=>Qi.bind(e)}),Ms=(e,t)=>e!==G&&!e.__isScriptSetup&&K(e,t),dl={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:s,setupState:n,data:r,props:i,accessCache:l,type:o,appContext:a}=e;if(t[0]!=="$"){const A=l[t];if(A!==void 0)switch(A){case 1:return n[t];case 2:return r[t];case 4:return s[t];case 3:return i[t]}else{if(Ms(n,t))return l[t]=1,n[t];if(r!==G&&K(r,t))return l[t]=2,r[t];if(K(i,t))return l[t]=3,i[t];if(s!==G&&K(s,t))return l[t]=4,s[t];Ws&&(l[t]=0)}}const h=Dt[t];let f,m;if(h)return t==="$attrs"&&ce(e.attrs,"get",""),h(e);if((f=o.__cssModules)&&(f=f[t]))return f;if(s!==G&&K(s,t))return l[t]=4,s[t];if(m=a.config.globalProperties,K(m,t))return m[t]},set({_:e},t,s){const{data:n,setupState:r,ctx:i}=e;return Ms(r,t)?(r[t]=s,!0):n!==G&&K(n,t)?(n[t]=s,!0):K(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(i[t]=s,!0)},has({_:{data:e,setupState:t,accessCache:s,ctx:n,appContext:r,props:i,type:l}},o){let a;return!!(s[o]||e!==G&&o[0]!=="$"&&K(e,o)||Ms(t,o)||K(i,o)||K(n,o)||K(Dt,o)||K(r.config.globalProperties,o)||(a=l.__cssModules)&&a[o])},defineProperty(e,t,s){return s.get!=null?e._.accessCache[t]=0:K(s,"value")&&this.set(e,t,s.value,null),Reflect.defineProperty(e,t,s)}};function Sn(e){return L(e)?e.reduce((t,s)=>(t[s]=null,t),{}):e}let Ws=!0;function hl(e){const t=Lr(e),s=e.proxy,n=e.ctx;Ws=!1,t.beforeCreate&&xn(t.beforeCreate,e,"bc");const{data:r,computed:i,methods:l,watch:o,provide:a,inject:h,created:f,beforeMount:m,mounted:A,beforeUpdate:R,updated:H,activated:I,deactivated:Z,beforeDestroy:W,beforeUnmount:j,destroyed:q,unmounted:P,render:ee,renderTracked:Te,renderTriggered:ve,errorCaptured:S,serverPrefetch:lt,expose:Se,inheritAttrs:Ke,components:We,directives:tt,filters:Tt}=t;if(h&&pl(h,n,null),l)for(const J in l){const U=l[J];D(U)&&(n[J]=U.bind(s))}if(r){const J=r.call(s,s);B(J)&&(e.data=ys(J))}if(Ws=!0,i)for(const J in i){const U=i[J],Ce=D(U)?U.bind(s,s):D(U.get)?U.get.bind(s,s):$e,pt=!D(U)&&D(U.set)?U.set.bind(s):$e,Be=nt({get:Ce,set:pt});Object.defineProperty(n,J,{enumerable:!0,configurable:!0,get:()=>Be.value,set:E=>Be.value=E})}if(o)for(const J in o)Fr(o[J],n,s,J);if(a){const J=D(a)?a.call(s):a;Reflect.ownKeys(J).forEach(U=>{Ji(U,J[U])})}f&&xn(f,e,"c");function re(J,U){L(U)?U.forEach(Ce=>J(Ce.bind(s))):U&&J(U.bind(s))}if(re(rl,m),re(Or,A),re(il,R),re(ll,H),re(tl,I),re(sl,Z),re(ul,S),re(al,Te),re(cl,ve),re(Pr,j),re(Mr,P),re(ol,lt),L(Se))if(Se.length){const J=e.exposed||(e.exposed={});Se.forEach(U=>{Object.defineProperty(J,U,{get:()=>s[U],set:Ce=>s[U]=Ce,enumerable:!0})})}else e.exposed||(e.exposed={});ee&&e.render===$e&&(e.render=ee),Ke!=null&&(e.inheritAttrs=Ke),We&&(e.components=We),tt&&(e.directives=tt),lt&&Er(e)}function pl(e,t,s=$e){L(e)&&(e=Bs(e));for(const n in e){const r=e[n];let i;B(r)?"default"in r?i=es(r.from||n,r.default,!0):i=es(r.from||n):i=es(r),ae(i)?Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:()=>i.value,set:l=>i.value=l}):t[n]=i}}function xn(e,t,s){Ve(L(e)?e.map(n=>n.bind(t.proxy)):e.bind(t.proxy),t,s)}function Fr(e,t,s,n){let r=n.includes(".")?Ar(s,n):()=>s[n];if(ne(e)){const i=t[e];D(i)&&ts(r,i)}else if(D(e))ts(r,e.bind(s));else if(B(e))if(L(e))e.forEach(i=>Fr(i,t,s,n));else{const i=D(e.handler)?e.handler.bind(s):t[e.handler];D(i)&&ts(r,i,e)}}function Lr(e){const t=e.type,{mixins:s,extends:n}=t,{mixins:r,optionsCache:i,config:{optionMergeStrategies:l}}=e.appContext,o=i.get(t);let a;return o?a=o:!r.length&&!s&&!n?a=t:(a={},r.length&&r.forEach(h=>cs(a,h,l,!0)),cs(a,t,l)),B(t)&&i.set(t,a),a}function cs(e,t,s,n=!1){const{mixins:r,extends:i}=t;i&&cs(e,i,s,!0),r&&r.forEach(l=>cs(e,l,s,!0));for(const l in t)if(!(n&&l==="expose")){const o=gl[l]||s&&s[l];e[l]=o?o(e[l],t[l]):t[l]}return e}const gl={data:wn,props:Tn,emits:Tn,methods:It,computed:It,beforeCreate:fe,created:fe,beforeMount:fe,mounted:fe,beforeUpdate:fe,updated:fe,beforeDestroy:fe,beforeUnmount:fe,destroyed:fe,unmounted:fe,activated:fe,deactivated:fe,errorCaptured:fe,serverPrefetch:fe,components:It,directives:It,watch:_l,provide:wn,inject:ml};function wn(e,t){return t?e?function(){return ue(D(e)?e.call(this,this):e,D(t)?t.call(this,this):t)}:t:e}function ml(e,t){return It(Bs(e),Bs(t))}function Bs(e){if(L(e)){const t={};for(let s=0;st==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${Ee(t)}Modifiers`]||e[`${ht(t)}Modifiers`];function Sl(e,t,...s){if(e.isUnmounted)return;const n=e.vnode.props||G;let r=s;const i=t.startsWith("update:"),l=i&&yl(n,t.slice(7));l&&(l.trim&&(r=s.map(f=>ne(f)?f.trim():f)),l.number&&(r=s.map(ms)));let o,a=n[o=Es(t)]||n[o=Es(Ee(t))];!a&&i&&(a=n[o=Es(ht(t))]),a&&Ve(a,e,6,r);const h=n[o+"Once"];if(h){if(!e.emitted)e.emitted={};else if(e.emitted[o])return;e.emitted[o]=!0,Ve(h,e,6,r)}}const xl=new WeakMap;function Nr(e,t,s=!1){const n=s?xl:t.emitsCache,r=n.get(e);if(r!==void 0)return r;const i=e.emits;let l={},o=!1;if(!D(e)){const a=h=>{const f=Nr(h,t,!0);f&&(o=!0,ue(l,f))};!s&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}return!i&&!o?(B(e)&&n.set(e,null),null):(L(i)?i.forEach(a=>l[a]=null):ue(l,i),B(e)&&n.set(e,l),l)}function ws(e,t){return!e||!ds(t)?!1:(t=t.slice(2).replace(/Once$/,""),K(e,t[0].toLowerCase()+t.slice(1))||K(e,ht(t))||K(e,t))}function Cn(e){const{type:t,vnode:s,proxy:n,withProxy:r,propsOptions:[i],slots:l,attrs:o,emit:a,render:h,renderCache:f,props:m,data:A,setupState:R,ctx:H,inheritAttrs:I}=e,Z=ls(e);let W,j;try{if(s.shapeFlag&4){const P=r||n,ee=P;W=je(h.call(ee,P,f,m,R,A,H)),j=o}else{const P=t;W=je(P.length>1?P(m,{attrs:o,slots:l,emit:a}):P(m,null)),j=t.props?o:wl(o)}}catch(P){Nt.length=0,Ss(P,e,1),W=Ye(it)}let q=W;if(j&&I!==!1){const P=Object.keys(j),{shapeFlag:ee}=q;P.length&&ee&7&&(i&&P.some(hs)&&(j=Tl(j,i)),q=wt(q,j,!1,!0))}return s.dirs&&(q=wt(q,null,!1,!0),q.dirs=q.dirs?q.dirs.concat(s.dirs):s.dirs),s.transition&&on(q,s.transition),W=q,ls(Z),W}const wl=e=>{let t;for(const s in e)(s==="class"||s==="style"||ds(s))&&((t||(t={}))[s]=e[s]);return t},Tl=(e,t)=>{const s={};for(const n in e)(!hs(n)||!(n.slice(9)in t))&&(s[n]=e[n]);return s};function Cl(e,t,s){const{props:n,children:r,component:i}=e,{props:l,children:o,patchFlag:a}=t,h=i.emitsOptions;if(t.dirs||t.transition)return!0;if(s&&a>=0){if(a&1024)return!0;if(a&16)return n?An(n,l,h):!!l;if(a&8){const f=t.dynamicProps;for(let m=0;mObject.create(Hr),Ur=e=>Object.getPrototypeOf(e)===Hr;function El(e,t,s,n=!1){const r={},i=$r();e.propsDefaults=Object.create(null),Vr(e,t,r,i);for(const l in e.propsOptions[0])l in r||(r[l]=void 0);s?e.props=n?r:Li(r):e.type.props?e.props=r:e.props=i,e.attrs=i}function Rl(e,t,s,n){const{props:r,attrs:i,vnode:{patchFlag:l}}=e,o=V(r),[a]=e.propsOptions;let h=!1;if((n||l>0)&&!(l&16)){if(l&8){const f=e.vnode.dynamicProps;for(let m=0;m{a=!0;const[A,R]=Kr(m,t,!0);ue(l,A),R&&o.push(...R)};!s&&t.mixins.length&&t.mixins.forEach(f),e.extends&&f(e.extends),e.mixins&&e.mixins.forEach(f)}if(!i&&!a)return B(e)&&n.set(e,_t),_t;if(L(i))for(let f=0;fe==="_"||e==="_ctx"||e==="$stable",an=e=>L(e)?e.map(je):[je(e)],Ol=(e,t,s)=>{if(t._n)return t;const n=Gi((...r)=>an(t(...r)),s);return n._c=!1,n},Wr=(e,t,s)=>{const n=e._ctx;for(const r in e){if(cn(r))continue;const i=e[r];if(D(i))t[r]=Ol(r,i,n);else if(i!=null){const l=an(i);t[r]=()=>l}}},Br=(e,t)=>{const s=an(t);e.slots.default=()=>s},qr=(e,t,s)=>{for(const n in t)(s||!cn(n))&&(e[n]=t[n])},Pl=(e,t,s)=>{const n=e.slots=$r();if(e.vnode.shapeFlag&32){const r=t._;r?(qr(n,t,s),s&&er(n,"_",r,!0)):Wr(t,n)}else t&&Br(e,t)},Ml=(e,t,s)=>{const{vnode:n,slots:r}=e;let i=!0,l=G;if(n.shapeFlag&32){const o=t._;o?s&&o===1?i=!1:qr(r,t,s):(i=!t.$stable,Wr(t,r)),l=t}else t&&(Br(e,t),l={default:1});if(i)for(const o in r)!cn(o)&&l[o]==null&&delete r[o]},ge=jl;function Fl(e){return Ll(e)}function Ll(e,t){const s=_s();s.__VUE__=!0;const{insert:n,remove:r,patchProp:i,createElement:l,createText:o,createComment:a,setText:h,setElementText:f,parentNode:m,nextSibling:A,setScopeId:R=$e,insertStaticContent:H}=e,I=(c,u,p,v=null,_=null,d=null,T=void 0,w=null,x=!!u.dynamicChildren)=>{if(c===u)return;c&&!Rt(c,u)&&(v=oe(c),E(c,_,d,!0),c=null),u.patchFlag===-2&&(x=!1,u.dynamicChildren=null);const{type:b,ref:M,shapeFlag:C}=u;switch(b){case Ts:Z(c,u,p,v);break;case it:W(c,u,p,v);break;case Ls:c==null&&j(u,p,v,T);break;case _e:We(c,u,p,v,_,d,T,w,x);break;default:C&1?ee(c,u,p,v,_,d,T,w,x):C&6?tt(c,u,p,v,_,d,T,w,x):(C&64||C&128)&&b.process(c,u,p,v,_,d,T,w,x,st)}M!=null&&_?Ft(M,c&&c.ref,d,u||c,!u):M==null&&c&&c.ref!=null&&Ft(c.ref,null,d,c,!0)},Z=(c,u,p,v)=>{if(c==null)n(u.el=o(u.children),p,v);else{const _=u.el=c.el;u.children!==c.children&&h(_,u.children)}},W=(c,u,p,v)=>{c==null?n(u.el=a(u.children||""),p,v):u.el=c.el},j=(c,u,p,v)=>{[c.el,c.anchor]=H(c.children,u,p,v,c.el,c.anchor)},q=({el:c,anchor:u},p,v)=>{let _;for(;c&&c!==u;)_=A(c),n(c,p,v),c=_;n(u,p,v)},P=({el:c,anchor:u})=>{let p;for(;c&&c!==u;)p=A(c),r(c),c=p;r(u)},ee=(c,u,p,v,_,d,T,w,x)=>{if(u.type==="svg"?T="svg":u.type==="math"&&(T="mathml"),c==null)Te(u,p,v,_,d,T,w,x);else{const b=c.el&&c.el._isVueCE?c.el:null;try{b&&b._beginPatch(),lt(c,u,_,d,T,w,x)}finally{b&&b._endPatch()}}},Te=(c,u,p,v,_,d,T,w)=>{let x,b;const{props:M,shapeFlag:C,transition:O,dirs:F}=c;if(x=c.el=l(c.type,d,M&&M.is,M),C&8?f(x,c.children):C&16&&S(c.children,x,null,v,_,Fs(c,d),T,w),F&&ot(c,null,v,"created"),ve(x,c,c.scopeId,T,v),M){for(const k in M)k!=="value"&&!Ot(k)&&i(x,k,null,M[k],d,v);"value"in M&&i(x,"value",null,M.value,d),(b=M.onVnodeBeforeMount)&&Le(b,v,c)}F&&ot(c,null,v,"beforeMount");const $=Dl(_,O);$&&O.beforeEnter(x),n(x,u,p),((b=M&&M.onVnodeMounted)||$||F)&&ge(()=>{try{b&&Le(b,v,c),$&&O.enter(x),F&&ot(c,null,v,"mounted")}finally{}},_)},ve=(c,u,p,v,_)=>{if(p&&R(c,p),v)for(let d=0;d{for(let b=x;b{const w=u.el=c.el;let{patchFlag:x,dynamicChildren:b,dirs:M}=u;x|=c.patchFlag&16;const C=c.props||G,O=u.props||G;let F;if(p&&ct(p,!1),(F=O.onVnodeBeforeUpdate)&&Le(F,p,u,c),M&&ot(u,c,p,"beforeUpdate"),p&&ct(p,!0),(C.innerHTML&&O.innerHTML==null||C.textContent&&O.textContent==null)&&f(w,""),b?Se(c.dynamicChildren,b,w,p,v,Fs(u,_),d):T||U(c,u,w,null,p,v,Fs(u,_),d,!1),x>0){if(x&16)Ke(w,C,O,p,_);else if(x&2&&C.class!==O.class&&i(w,"class",null,O.class,_),x&4&&i(w,"style",C.style,O.style,_),x&8){const $=u.dynamicProps;for(let k=0;k<$.length;k++){const z=$[k],se=C[z],le=O[z];(le!==se||z==="value")&&i(w,z,se,le,_,p)}}x&1&&c.children!==u.children&&f(w,u.children)}else!T&&b==null&&Ke(w,C,O,p,_);((F=O.onVnodeUpdated)||M)&&ge(()=>{F&&Le(F,p,u,c),M&&ot(u,c,p,"updated")},v)},Se=(c,u,p,v,_,d,T)=>{for(let w=0;w{if(u!==p){if(u!==G)for(const d in u)!Ot(d)&&!(d in p)&&i(c,d,u[d],null,_,v);for(const d in p){if(Ot(d))continue;const T=p[d],w=u[d];T!==w&&d!=="value"&&i(c,d,w,T,_,v)}"value"in p&&i(c,"value",u.value,p.value,_)}},We=(c,u,p,v,_,d,T,w,x)=>{const b=u.el=c?c.el:o(""),M=u.anchor=c?c.anchor:o("");let{patchFlag:C,dynamicChildren:O,slotScopeIds:F}=u;F&&(w=w?w.concat(F):F),c==null?(n(b,p,v),n(M,p,v),S(u.children||[],p,M,_,d,T,w,x)):C>0&&C&64&&O&&c.dynamicChildren&&c.dynamicChildren.length===O.length?(Se(c.dynamicChildren,O,p,_,d,T,w),(u.key!=null||_&&u===_.subTree)&&kr(c,u,!0)):U(c,u,p,M,_,d,T,w,x)},tt=(c,u,p,v,_,d,T,w,x)=>{u.slotScopeIds=w,c==null?u.shapeFlag&512?_.ctx.activate(u,p,v,T,x):Tt(u,p,v,_,d,T,x):kt(c,u,x)},Tt=(c,u,p,v,_,d,T)=>{const w=c.component=kl(c,v,_);if(Rr(c)&&(w.ctx.renderer=st),Jl(w,!1,T),w.asyncDep){if(_&&_.registerDep(w,re,T),!c.el){const x=w.subTree=Ye(it);W(null,x,u,p),c.placeholder=x.el}}else re(w,c,u,p,_,d,T)},kt=(c,u,p)=>{const v=u.component=c.component;if(Cl(c,u,p))if(v.asyncDep&&!v.asyncResolved){J(v,u,p);return}else v.next=u,v.update();else u.el=c.el,v.vnode=u},re=(c,u,p,v,_,d,T)=>{const w=()=>{if(c.isMounted){let{next:C,bu:O,u:F,parent:$,vnode:k}=c;{const Pe=Gr(c);if(Pe){C&&(C.el=k.el,J(c,C,T)),Pe.asyncDep.then(()=>{ge(()=>{c.isUnmounted||b()},_)});return}}let z=C,se;ct(c,!1),C?(C.el=k.el,J(c,C,T)):C=k,O&&Zt(O),(se=C.props&&C.props.onVnodeBeforeUpdate)&&Le(se,$,C,k),ct(c,!0);const le=Cn(c),Oe=c.subTree;c.subTree=le,I(Oe,le,m(Oe.el),oe(Oe),c,_,d),C.el=le.el,z===null&&Al(c,le.el),F&&ge(F,_),(se=C.props&&C.props.onVnodeUpdated)&&ge(()=>Le(se,$,C,k),_)}else{let C;const{el:O,props:F}=u,{bm:$,m:k,parent:z,root:se,type:le}=c,Oe=Lt(u);ct(c,!1),$&&Zt($),!Oe&&(C=F&&F.onVnodeBeforeMount)&&Le(C,z,u),ct(c,!0);{se.ce&&se.ce._hasShadowRoot()&&se.ce._injectChildStyle(le,c.parent?c.parent.type:void 0);const Pe=c.subTree=Cn(c);I(null,Pe,p,v,c,_,d),u.el=Pe.el}if(k&&ge(k,_),!Oe&&(C=F&&F.onVnodeMounted)){const Pe=u;ge(()=>Le(C,z,Pe),_)}(u.shapeFlag&256||z&&Lt(z.vnode)&&z.vnode.shapeFlag&256)&&c.a&&ge(c.a,_),c.isMounted=!0,u=p=v=null}};c.scope.on();const x=c.effect=new rr(w);c.scope.off();const b=c.update=x.run.bind(x),M=c.job=x.runIfDirty.bind(x);M.i=c,M.id=c.uid,x.scheduler=()=>ln(M),ct(c,!0),b()},J=(c,u,p)=>{u.component=c;const v=c.vnode.props;c.vnode=u,c.next=null,Rl(c,u.props,v,p),Ml(c,u.children,p),Qe(),vn(c),Xe()},U=(c,u,p,v,_,d,T,w,x=!1)=>{const b=c&&c.children,M=c?c.shapeFlag:0,C=u.children,{patchFlag:O,shapeFlag:F}=u;if(O>0){if(O&128){pt(b,C,p,v,_,d,T,w,x);return}else if(O&256){Ce(b,C,p,v,_,d,T,w,x);return}}F&8?(M&16&&pe(b,_,d),C!==b&&f(p,C)):M&16?F&16?pt(b,C,p,v,_,d,T,w,x):pe(b,_,d,!0):(M&8&&f(p,""),F&16&&S(C,p,v,_,d,T,w,x))},Ce=(c,u,p,v,_,d,T,w,x)=>{c=c||_t,u=u||_t;const b=c.length,M=u.length,C=Math.min(b,M);let O;for(O=0;OM?pe(c,_,d,!0,!1,C):S(u,p,v,_,d,T,w,x,C)},pt=(c,u,p,v,_,d,T,w,x)=>{let b=0;const M=u.length;let C=c.length-1,O=M-1;for(;b<=C&&b<=O;){const F=c[b],$=u[b]=x?Ge(u[b]):je(u[b]);if(Rt(F,$))I(F,$,p,null,_,d,T,w,x);else break;b++}for(;b<=C&&b<=O;){const F=c[C],$=u[O]=x?Ge(u[O]):je(u[O]);if(Rt(F,$))I(F,$,p,null,_,d,T,w,x);else break;C--,O--}if(b>C){if(b<=O){const F=O+1,$=FO)for(;b<=C;)E(c[b],_,d,!0),b++;else{const F=b,$=b,k=new Map;for(b=$;b<=O;b++){const be=u[b]=x?Ge(u[b]):je(u[b]);be.key!=null&&k.set(be.key,b)}let z,se=0;const le=O-$+1;let Oe=!1,Pe=0;const At=new Array(le);for(b=0;b=le){E(be,_,d,!0);continue}let Me;if(be.key!=null)Me=k.get(be.key);else for(z=$;z<=O;z++)if(At[z-$]===0&&Rt(be,u[z])){Me=z;break}Me===void 0?E(be,_,d,!0):(At[Me-$]=b+1,Me>=Pe?Pe=Me:Oe=!0,I(be,u[Me],p,null,_,d,T,w,x),se++)}const fn=Oe?Nl(At):_t;for(z=fn.length-1,b=le-1;b>=0;b--){const be=$+b,Me=u[be],dn=u[be+1],hn=be+1{const{el:d,type:T,transition:w,children:x,shapeFlag:b}=c;if(b&6){Be(c.component.subTree,u,p,v);return}if(b&128){c.suspense.move(u,p,v);return}if(b&64){T.move(c,u,p,st);return}if(T===_e){n(d,u,p);for(let C=0;Cw.enter(d),_);else{const{leave:C,delayLeave:O,afterLeave:F}=w,$=()=>{c.ctx.isUnmounted?r(d):n(d,u,p)},k=()=>{d._isLeaving&&d[el](!0),C(d,()=>{$(),F&&F()})};O?O(d,$,k):k()}else n(d,u,p)},E=(c,u,p,v=!1,_=!1)=>{const{type:d,props:T,ref:w,children:x,dynamicChildren:b,shapeFlag:M,patchFlag:C,dirs:O,cacheIndex:F,memo:$}=c;if(C===-2&&(_=!1),w!=null&&(Qe(),Ft(w,null,p,c,!0),Xe()),F!=null&&(u.renderCache[F]=void 0),M&256){u.ctx.deactivate(c);return}const k=M&1&&O,z=!Lt(c);let se;if(z&&(se=T&&T.onVnodeBeforeUnmount)&&Le(se,u,c),M&6)ie(c.component,p,v);else{if(M&128){c.suspense.unmount(p,v);return}k&&ot(c,null,u,"beforeUnmount"),M&64?c.type.remove(c,u,p,st,v):b&&!b.hasOnce&&(d!==_e||C>0&&C&64)?pe(b,u,p,!1,!0):(d===_e&&C&384||!_&&M&16)&&pe(x,u,p),v&&N(c)}const le=$!=null&&F==null;(z&&(se=T&&T.onVnodeUnmounted)||k||le)&&ge(()=>{se&&Le(se,u,c),k&&ot(c,null,u,"unmounted"),le&&(c.el=null)},p)},N=c=>{const{type:u,el:p,anchor:v,transition:_}=c;if(u===_e){te(p,v);return}if(u===Ls){P(c);return}const d=()=>{r(p),_&&!_.persisted&&_.afterLeave&&_.afterLeave()};if(c.shapeFlag&1&&_&&!_.persisted){const{leave:T,delayLeave:w}=_,x=()=>T(p,d);w?w(c.el,d,x):x()}else d()},te=(c,u)=>{let p;for(;c!==u;)p=A(c),r(c),c=p;r(u)},ie=(c,u,p)=>{const{bum:v,scope:_,job:d,subTree:T,um:w,m:x,a:b}=c;Rn(x),Rn(b),v&&Zt(v),_.stop(),d&&(d.flags|=8,E(T,c,u,p)),w&&ge(w,u),ge(()=>{c.isUnmounted=!0},u)},pe=(c,u,p,v=!1,_=!1,d=0)=>{for(let T=d;T{if(c.shapeFlag&6)return oe(c.component.subTree);if(c.shapeFlag&128)return c.suspense.next();const u=A(c.anchor||c.el),p=u&&u[Xi];return p?A(p):u};let Ae=!1;const Ct=(c,u,p)=>{let v;c==null?u._vnode&&(E(u._vnode,null,null,!0),v=u._vnode.component):I(u._vnode||null,c,u,null,null,null,p),u._vnode=c,Ae||(Ae=!0,vn(v),xr(),Ae=!1)},st={p:I,um:E,m:Be,r:N,mt:Tt,mc:S,pc:U,pbc:Se,n:oe,o:e};return{render:Ct,hydrate:void 0,createApp:bl(Ct)}}function Fs({type:e,props:t},s){return s==="svg"&&e==="foreignObject"||s==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:s}function ct({effect:e,job:t},s){s?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function Dl(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function kr(e,t,s=!1){const n=e.children,r=t.children;if(L(n)&&L(r))for(let i=0;i>1,e[s[o]]0&&(t[n]=s[i-1]),s[i]=n)}}for(i=s.length,l=s[i-1];i-- >0;)s[i]=l,l=t[l];return s}function Gr(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Gr(t)}function Rn(e){if(e)for(let t=0;te.__isSuspense;function jl(e,t){t&&t.pendingBranch?L(e)?t.effects.push(...e):t.effects.push(e):ki(e)}const _e=Symbol.for("v-fgt"),Ts=Symbol.for("v-txt"),it=Symbol.for("v-cmt"),Ls=Symbol.for("v-stc"),Nt=[];let ye=null;function Y(e=!1){Nt.push(ye=e?null:[])}function Hl(){Nt.pop(),ye=Nt[Nt.length-1]||null}let Ut=1;function In(e,t=!1){Ut+=e,e<0&&ye&&t&&(ye.hasOnce=!0)}function Yr(e){return e.dynamicChildren=Ut>0?ye||_t:null,Hl(),Ut>0&&ye&&ye.push(e),e}function X(e,t,s,n,r,i){return Yr(g(e,t,s,n,r,i,!0))}function $l(e,t,s,n,r){return Yr(Ye(e,t,s,n,r,!0))}function Qr(e){return e?e.__v_isVNode===!0:!1}function Rt(e,t){return e.type===t.type&&e.key===t.key}const Xr=({key:e})=>e??null,ss=({ref:e,ref_key:t,ref_for:s})=>(typeof e=="number"&&(e=""+e),e!=null?ne(e)||ae(e)||D(e)?{i:xe,r:e,k:t,f:!!s}:e:null);function g(e,t=null,s=null,n=0,r=null,i=e===_e?0:1,l=!1,o=!1){const a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Xr(t),ref:t&&ss(t),scopeId:Tr,slotScopeIds:null,children:s,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:n,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:xe};return o?(un(a,s),i&128&&e.normalize(a)):s&&(a.shapeFlag|=ne(s)?8:16),Ut>0&&!l&&ye&&(a.patchFlag>0||i&6)&&a.patchFlag!==32&&ye.push(a),a}const Ye=Ul;function Ul(e,t=null,s=null,n=0,r=null,i=!1){if((!e||e===fl)&&(e=it),Qr(e)){const o=wt(e,t,!0);return s&&un(o,s),Ut>0&&!i&&ye&&(o.shapeFlag&6?ye[ye.indexOf(e)]=o:ye.push(o)),o.patchFlag=-2,o}if(Xl(e)&&(e=e.__vccOpts),t){t=Vl(t);let{class:o,style:a}=t;o&&!ne(o)&&(t.class=vs(o)),B(a)&&(rn(a)&&!L(a)&&(a=ue({},a)),t.style=Qs(a))}const l=ne(e)?1:zr(e)?128:Zi(e)?64:B(e)?4:D(e)?2:0;return g(e,t,s,n,r,l,i,!0)}function Vl(e){return e?rn(e)||Ur(e)?ue({},e):e:null}function wt(e,t,s=!1,n=!1){const{props:r,ref:i,patchFlag:l,children:o,transition:a}=e,h=t?Wl(r||{},t):r,f={__v_isVNode:!0,__v_skip:!0,type:e.type,props:h,key:h&&Xr(h),ref:t&&t.ref?s&&i?L(i)?i.concat(ss(t)):[i,ss(t)]:ss(t):i,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:o,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==_e?l===-1?16:l|16:l,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:a,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&wt(e.ssContent),ssFallback:e.ssFallback&&wt(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return a&&n&&on(f,a.clone(f)),f}function Kl(e=" ",t=0){return Ye(Ts,null,e,t)}function Xt(e="",t=!1){return t?(Y(),$l(it,null,e)):Ye(it,null,e)}function je(e){return e==null||typeof e=="boolean"?Ye(it):L(e)?Ye(_e,null,e.slice()):Qr(e)?Ge(e):Ye(Ts,null,String(e))}function Ge(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:wt(e)}function un(e,t){let s=0;const{shapeFlag:n}=e;if(t==null)t=null;else if(L(t))s=16;else if(typeof t=="object")if(n&65){const r=t.default;r&&(r._c&&(r._d=!1),un(e,r()),r._c&&(r._d=!0));return}else{s=32;const r=t._;!r&&!Ur(t)?t._ctx=xe:r===3&&xe&&(xe.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else D(t)?(t={default:t,_ctx:xe},s=32):(t=String(t),n&64?(s=16,t=[Kl(t)]):s=8);e.children=t,e.shapeFlag|=s}function Wl(...e){const t={};for(let s=0;she||xe;let as,ks;{const e=_s(),t=(s,n)=>{let r;return(r=e[s])||(r=e[s]=[]),r.push(n),i=>{r.length>1?r.forEach(l=>l(i)):r[0](i)}};as=t("__VUE_INSTANCE_SETTERS__",s=>he=s),ks=t("__VUE_SSR_SETTERS__",s=>Vt=s)}const qt=e=>{const t=he;return as(e),e.scope.on(),()=>{e.scope.off(),as(t)}},On=()=>{he&&he.scope.off(),as(null)};function Zr(e){return e.vnode.shapeFlag&4}let Vt=!1;function Jl(e,t=!1,s=!1){t&&ks(t);const{props:n,children:r}=e.vnode,i=Zr(e);El(e,n,i,t),Pl(e,r,s||t);const l=i?zl(e,t):void 0;return t&&ks(!1),l}function zl(e,t){const s=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,dl);const{setup:n}=s;if(n){Qe();const r=e.setupContext=n.length>1?Ql(e):null,i=qt(e),l=Bt(n,e,0,[e.props,r]),o=Yn(l);if(Xe(),i(),(o||e.sp)&&!Lt(e)&&Er(e),o){if(l.then(On,On),t)return l.then(a=>{Pn(e,a)}).catch(a=>{Ss(a,e,0)});e.asyncDep=l}else Pn(e,l)}else ei(e)}function Pn(e,t,s){D(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:B(t)&&(e.setupState=vr(t)),ei(e)}function ei(e,t,s){const n=e.type;e.render||(e.render=n.render||$e);{const r=qt(e);Qe();try{hl(e)}finally{Xe(),r()}}}const Yl={get(e,t){return ce(e,"get",""),e[t]}};function Ql(e){const t=s=>{e.exposed=s||{}};return{attrs:new Proxy(e.attrs,Yl),slots:e.slots,emit:e.emit,expose:t}}function Cs(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(vr(Di(e.exposed)),{get(t,s){if(s in t)return t[s];if(s in Dt)return Dt[s](e)},has(t,s){return s in t||s in Dt}})):e.proxy}function Xl(e){return D(e)&&"__vccOpts"in e}const nt=(e,t)=>Vi(e,t,Vt),Zl="3.5.32";/** +* @vue/runtime-dom v3.5.32 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let Gs;const Mn=typeof window<"u"&&window.trustedTypes;if(Mn)try{Gs=Mn.createPolicy("vue",{createHTML:e=>e})}catch{}const ti=Gs?e=>Gs.createHTML(e):e=>e,eo="http://www.w3.org/2000/svg",to="http://www.w3.org/1998/Math/MathML",ke=typeof document<"u"?document:null,Fn=ke&&ke.createElement("template"),so={insert:(e,t,s)=>{t.insertBefore(e,s||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,s,n)=>{const r=t==="svg"?ke.createElementNS(eo,e):t==="mathml"?ke.createElementNS(to,e):s?ke.createElement(e,{is:s}):ke.createElement(e);return e==="select"&&n&&n.multiple!=null&&r.setAttribute("multiple",n.multiple),r},createText:e=>ke.createTextNode(e),createComment:e=>ke.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>ke.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,s,n,r,i){const l=s?s.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),s),!(r===i||!(r=r.nextSibling)););else{Fn.innerHTML=ti(n==="svg"?`${e}`:n==="mathml"?`${e}`:e);const o=Fn.content;if(n==="svg"||n==="mathml"){const a=o.firstChild;for(;a.firstChild;)o.appendChild(a.firstChild);o.removeChild(a)}t.insertBefore(o,s)}return[l?l.nextSibling:t.firstChild,s?s.previousSibling:t.lastChild]}},no=Symbol("_vtc");function ro(e,t,s){const n=e[no];n&&(t=(t?[t,...n]:[...n]).join(" ")),t==null?e.removeAttribute("class"):s?e.setAttribute("class",t):e.className=t}const Ln=Symbol("_vod"),io=Symbol("_vsh"),lo=Symbol(""),oo=/(?:^|;)\s*display\s*:/;function co(e,t,s){const n=e.style,r=ne(s);let i=!1;if(s&&!r){if(t)if(ne(t))for(const l of t.split(";")){const o=l.slice(0,l.indexOf(":")).trim();s[o]==null&&ns(n,o,"")}else for(const l in t)s[l]==null&&ns(n,l,"");for(const l in s)l==="display"&&(i=!0),ns(n,l,s[l])}else if(r){if(t!==s){const l=n[lo];l&&(s+=";"+l),n.cssText=s,i=oo.test(s)}}else t&&e.removeAttribute("style");Ln in e&&(e[Ln]=i?n.display:"",e[io]&&(n.display="none"))}const Dn=/\s*!important$/;function ns(e,t,s){if(L(s))s.forEach(n=>ns(e,t,n));else if(s==null&&(s=""),t.startsWith("--"))e.setProperty(t,s);else{const n=ao(e,t);Dn.test(s)?e.setProperty(ht(n),s.replace(Dn,""),"important"):e[n]=s}}const Nn=["Webkit","Moz","ms"],Ds={};function ao(e,t){const s=Ds[t];if(s)return s;let n=Ee(t);if(n!=="filter"&&n in e)return Ds[t]=n;n=Zn(n);for(let r=0;rNs||(po.then(()=>Ns=0),Ns=Date.now());function mo(e,t){const s=n=>{if(!n._vts)n._vts=Date.now();else if(n._vts<=s.attached)return;Ve(_o(n,s.value),t,5,[n])};return s.value=e,s.attached=go(),s}function _o(e,t){if(L(t)){const s=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{s.call(e),e._stopped=!0},t.map(n=>r=>!r._stopped&&n&&n(r))}else return t}const Kn=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,vo=(e,t,s,n,r,i)=>{const l=r==="svg";t==="class"?ro(e,n,l):t==="style"?co(e,s,n):ds(t)?hs(t)||fo(e,t,s,n,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):bo(e,t,n,l))?($n(e,t,n),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Hn(e,t,n,l,i,t!=="value")):e._isVueCE&&(yo(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!ne(n)))?$n(e,Ee(t),n,i,t):(t==="true-value"?e._trueValue=n:t==="false-value"&&(e._falseValue=n),Hn(e,t,n,l))};function bo(e,t,s,n){if(n)return!!(t==="innerHTML"||t==="textContent"||t in e&&Kn(t)&&D(s));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return Kn(t)&&ne(s)?!1:t in e}function yo(e,t){const s=e._def.props;if(!s)return!1;const n=Ee(t);return Array.isArray(s)?s.some(r=>Ee(r)===n):Object.keys(s).some(r=>Ee(r)===n)}const us=e=>{const t=e.props["onUpdate:modelValue"]||!1;return L(t)?s=>Zt(t,s):t};function So(e){e.target.composing=!0}function Wn(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const St=Symbol("_assign");function Bn(e,t,s){return t&&(e=e.trim()),s&&(e=ms(e)),e}const qn={created(e,{modifiers:{lazy:t,trim:s,number:n}},r){e[St]=us(r);const i=n||r.props&&r.props.type==="number";ut(e,t?"change":"input",l=>{l.target.composing||e[St](Bn(e.value,s,i))}),(s||i)&&ut(e,"change",()=>{e.value=Bn(e.value,s,i)}),t||(ut(e,"compositionstart",So),ut(e,"compositionend",Wn),ut(e,"change",Wn))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:s,modifiers:{lazy:n,trim:r,number:i}},l){if(e[St]=us(l),e.composing)return;const o=(i||e.type==="number")&&!/^0\d/.test(e.value)?ms(e.value):e.value,a=t??"";if(o===a)return;const h=e.getRootNode();(h instanceof Document||h instanceof ShadowRoot)&&h.activeElement===e&&e.type!=="range"&&(n&&t===s||r&&e.value.trim()===a)||(e.value=a)}},kn={deep:!0,created(e,{value:t,modifiers:{number:s}},n){const r=ps(t);ut(e,"change",()=>{const i=Array.prototype.filter.call(e.options,l=>l.selected).map(l=>s?ms(fs(l)):fs(l));e[St](e.multiple?r?new Set(i):i:i[0]),e._assigning=!0,yr(()=>{e._assigning=!1})}),e[St]=us(n)},mounted(e,{value:t}){Gn(e,t)},beforeUpdate(e,t,s){e[St]=us(s)},updated(e,{value:t}){e._assigning||Gn(e,t)}};function Gn(e,t){const s=e.multiple,n=L(t);if(!(s&&!n&&!ps(t))){for(let r=0,i=e.options.length;rString(h)===String(o)):l.selected=hi(t,o)>-1}else l.selected=t.has(o);else if(Wt(fs(l),t)){e.selectedIndex!==r&&(e.selectedIndex=r);return}}!s&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function fs(e){return"_value"in e?e._value:e.value}const xo=["ctrl","shift","alt","meta"],wo={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>xo.some(s=>e[`${s}Key`]&&!t.includes(s))},To=(e,t)=>{if(!e)return e;const s=e._withMods||(e._withMods={}),n=t.join(".");return s[n]||(s[n]=((r,...i)=>{for(let l=0;l{const t=Ao().createApp(...e),{mount:s}=t;return t.mount=n=>{const r=Io(n);if(!r)return;const i=t._component;!D(i)&&!i.render&&!i.template&&(i.template=r.innerHTML),r.nodeType===1&&(r.textContent="");const l=s(r,!1,Ro(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),l},t});function Ro(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function Io(e){return ne(e)?document.querySelector(e):e}const Oo={class:"monitor-shell"},Po={class:"hero-panel"},Mo={class:"eyebrow"},Fo={class:"hero-copy"},Lo={class:"hero-meta"},Do={class:"locale-field"},No={value:"it"},jo={value:"en"},Ho=["disabled"],$o={class:"stat-grid"},Uo={class:"stat-card accent-sand"},Vo={class:"stat-label"},Ko={class:"stat-card accent-olive"},Wo={class:"stat-label"},Bo={class:"stat-card accent-sky"},qo={class:"stat-label"},ko={class:"stat-card accent-stone"},Go={class:"stat-label"},Jo={key:0,class:"banner-error"},zo={class:"content-grid"},Yo={class:"panel stack-gap"},Qo={class:"panel-header"},Xo={class:"pill"},Zo={value:"all"},ec={value:"queued"},tc={value:"processing"},sc={value:"completed"},nc={value:"failed"},rc=["placeholder"],ic={class:"secondary-button",type:"submit"},lc={key:0,class:"banner-error"},oc={key:1,class:"search-list"},cc=["onClick"],ac={class:"search-row-top"},uc=["data-status"],fc={class:"search-row-meta"},dc={class:"search-row-bottom"},hc={key:2,class:"empty-state"},pc={class:"stack-gap"},gc={class:"panel stack-gap"},mc={class:"panel-header"},_c={key:0,class:"pill"},vc={key:0,class:"banner-error"},bc={key:1,class:"empty-state"},yc={key:2,class:"empty-state"},Sc={class:"detail-grid"},xc={class:"code-block"},wc={class:"code-block"},Tc={key:0,class:"event-list"},Cc={class:"event-header"},Ac={key:0},Ec={key:1,class:"empty-state"},Rc={class:"panel split-panel"},Ic={class:"panel-header compact"},Oc={key:0,class:"mini-list"},Pc={class:"mini-metrics"},Mc={key:1,class:"empty-state"},Fc={class:"panel-header compact"},Lc={key:0,class:"mini-list"},Dc={class:"mini-metrics"},Nc={key:1,class:"empty-state"},jc=15e3,Hc={__name:"App",setup(e){const t={queued:"statusQueued",processing:"statusProcessing",completed:"statusCompleted",failed:"statusFailed"},s={it:{pageTitle:"FaceAI Audit Monitor",heroEyebrow:"Monitor Audit FaceAI",heroTitle:"Telemetria in sola lettura delle ricerche dal log SQLite.",heroCopy:"Il monitor interroga lo stesso database audit leggero usato dal backend e si aggiorna automaticamente ogni 15 secondi.",windowLabel:"Finestra",lastRefreshLabel:"Ultimo aggiornamento",pendingLabel:"in attesa",refreshNow:"Aggiorna ora",refreshing:"Aggiornamento...",languageLabel:"Lingua",languageEnglish:"English",languageItalian:"Italiano",recentSearchesLabel:"Ricerche recenti",recentSearchesDetail:"{count} utenti unici nelle ultime {hours}h",completedLabel:"Completate",completedDetail:"{failed} fallite, {processing} ancora attive",averageRuntimeLabel:"Durata media",averageRuntimeDetail:"Basata sulle ricerche completate nella finestra recente",lifetimeSearchesLabel:"Ricerche totali",lifetimeSearchesDetail:"Ultima richiesta {value}",searchesTitle:"Ricerche",searchesIntro:"Filtra per stato o testo libero su utente, gara, risultato, selfie, codice errore o id ricerca.",shownCount:"{count} visibili",statusFilterLabel:"Stato",statusAll:"Tutti",statusQueued:"In coda",statusProcessing:"In lavorazione",statusCompleted:"Completata",statusFailed:"Fallita",statusUnknown:"Sconosciuto",lookupLabel:"Ricerca",lookupPlaceholder:"gara, utente, risultato, selfie, errore, id ricerca",limitLabel:"Limite",applyFilters:"Applica",noSelfieName:"nessun nome selfie",matchesCount:"{count} corrispondenze",noSearches:"Nessuna ricerca corrisponde ai filtri correnti.",searchDetailTitle:"Dettaglio ricerca",searchDetailIntro:"Metadati della ricerca selezionata, corrispondenze e cronologia eventi.",loadingSearchDetail:"Caricamento dettaglio ricerca...",chooseSearch:"Scegli una ricerca per ispezionarne la cronologia.",fieldRace:"Gara",fieldUser:"Utente",fieldRequested:"Richiesta",fieldCompleted:"Completata",fieldResult:"Risultato",fieldError:"Errore",notAvailable:"n/d",matchPayloadTitle:"Payload corrispondenze",eventTimelineTitle:"Cronologia eventi",eventSummary:"stato={status}, risultato={result}, errore={error}, corrispondenze={matches}",noEventsForSearch:"Nessun evento registrato per questa ricerca.",topRacesTitle:"Gare principali",topRacesIntro:"Distribuzione del carico recente per gara.",noStoragePath:"nessun percorso storage",topRacesTotal:"{count} totali",topRacesDone:"{count} completate",topRacesFailed:"{count} fallite",noRecentRaceActivity:"Nessuna attivita recente sulle gare.",recentEventsTitle:"Eventi recenti",recentEventsIntro:"Ultimi eventi audit su tutte le ricerche.",noSearchId:"nessun id ricerca",noRecentEvents:"Nessun evento recente.",requestFailed:"Richiesta fallita con stato {status}"},en:{pageTitle:"FaceAI Audit Monitor",heroEyebrow:"FaceAI Audit Monitor",heroTitle:"Read-only search telemetry from the SQLite audit log.",heroCopy:"The monitor queries the same lightweight audit database used by the backend and refreshes automatically every 15 seconds.",windowLabel:"Window",lastRefreshLabel:"Last refresh",pendingLabel:"pending",refreshNow:"Refresh now",refreshing:"Refreshing...",languageLabel:"Language",languageEnglish:"English",languageItalian:"Italiano",recentSearchesLabel:"Recent searches",recentSearchesDetail:"{count} unique users in the last {hours}h",completedLabel:"Completed",completedDetail:"{failed} failed, {processing} still active",averageRuntimeLabel:"Average runtime",averageRuntimeDetail:"Based on completed searches in the recent window",lifetimeSearchesLabel:"Lifetime searches",lifetimeSearchesDetail:"Latest request {value}",searchesTitle:"Searches",searchesIntro:"Filter by status or free text across user, race, result, selfie, error code, or search id.",shownCount:"{count} shown",statusFilterLabel:"Status",statusAll:"All",statusQueued:"Queued",statusProcessing:"Processing",statusCompleted:"Completed",statusFailed:"Failed",statusUnknown:"Unknown",lookupLabel:"Lookup",lookupPlaceholder:"race, user, result, selfie, error, search id",limitLabel:"Limit",applyFilters:"Apply",noSelfieName:"no selfie name",matchesCount:"{count} matches",noSearches:"No searches matched the current filters.",searchDetailTitle:"Search detail",searchDetailIntro:"Selected search metadata, matches, and event trail.",loadingSearchDetail:"Loading search detail...",chooseSearch:"Choose a search to inspect its timeline.",fieldRace:"Race",fieldUser:"User",fieldRequested:"Requested",fieldCompleted:"Completed",fieldResult:"Result",fieldError:"Error",notAvailable:"n/a",matchPayloadTitle:"Match payload",eventTimelineTitle:"Event timeline",eventSummary:"status={status}, result={result}, error={error}, matches={matches}",noEventsForSearch:"No events recorded for this search.",topRacesTitle:"Top races",topRacesIntro:"Recent-window workload split by race.",noStoragePath:"no storage path",topRacesTotal:"{count} total",topRacesDone:"{count} done",topRacesFailed:"{count} failed",noRecentRaceActivity:"No recent race activity yet.",recentEventsTitle:"Recent events",recentEventsIntro:"Latest audit events across all searches.",noSearchId:"no search id",noRecentEvents:"No recent events yet.",requestFailed:"Request failed with {status}"}};function n(E){return String(E||"").toLowerCase().startsWith("en")?"en":"it"}function r(){var E;return typeof navigator<"u"?n(((E=navigator.languages)==null?void 0:E[0])||navigator.language):typeof document<"u"?n(document.documentElement.lang||"it"):"it"}const i=Fe(null),l=Fe([]),o=Fe(null),a=Fe(""),h=Fe(r()),f=Fe(""),m=Fe(""),A=Fe(""),R=Fe(!1),H=Fe(!1),I=ys({status:"all",query:"",limit:40});let Z=null;const W=nt(()=>n(h.value)),j=nt(()=>W.value==="en"?"en-GB":"it-IT"),q=nt(()=>l.value.length>0),P=nt(()=>{var E;return((E=i.value)==null?void 0:E.recentWindow)||null}),ee=nt(()=>{var E;return((E=i.value)==null?void 0:E.lifetime)||null}),Te=nt(()=>{var E;return((E=i.value)==null?void 0:E.topRaces)||[]}),ve=nt(()=>{var E;return((E=i.value)==null?void 0:E.recentEvents)||[]});function S(E,N={}){var ie;const te=((ie=s[W.value])==null?void 0:ie[E])||s.it[E]||E;return Object.keys(N).reduce((pe,oe)=>pe.replace(`{${oe}}`,String(N[oe])),te)}function lt(E){return S(E==="en"?"languageEnglish":"languageItalian")}function Se(E){const N=t[E]||"statusUnknown";return S(N)}async function Ke(E){const N=await fetch(E,{headers:{accept:"application/json"}});if(!N.ok){const te=await N.json().catch(()=>({}));throw new Error(te.error||S("requestFailed",{status:N.status}))}return N.json()}function We(E){return E?new Intl.DateTimeFormat(j.value,{dateStyle:"medium",timeStyle:"medium"}).format(new Date(E)):S("notAvailable")}function tt(E){if(!E)return S("notAvailable");const N=E-Date.now(),te=Math.abs(N),ie=new Intl.RelativeTimeFormat(W.value,{numeric:"auto"});return te<6e4?ie.format(Math.round(N/1e3),"second"):te<36e5?ie.format(Math.round(N/6e4),"minute"):te<864e5?ie.format(Math.round(N/36e5),"hour"):ie.format(Math.round(N/864e5),"day")}function Tt(E){const N=Number(E||0);if(!Number.isFinite(N)||N<=0)return S("notAvailable");if(N<1e3)return`${N} ms`;const te=Math.round(N/1e3),ie=te%60,pe=Math.floor(te/60)%60,oe=Math.floor(te/3600),Ae=[];return oe>0&&Ae.push(`${oe}h`),(pe>0||oe>0)&&Ae.push(`${pe}m`),Ae.push(`${ie}s`),Ae.join(" ")}function kt(E){return E||"unknown"}async function re(){f.value="";try{i.value=await Ke("/api/audit-monitor/summary")}catch(E){f.value=E.message}}async function J({keepSelection:E=!0}={}){var N;m.value="";try{const te=new URLSearchParams({status:I.status,query:I.query,limit:String(I.limit)}),ie=await Ke(`/api/audit-monitor/searches?${te.toString()}`);l.value=Array.isArray(ie.items)?ie.items:[],E&&a.value&&l.value.some(oe=>oe.searchId===a.value)||(a.value=((N=l.value[0])==null?void 0:N.searchId)||"")}catch(te){l.value=[],m.value=te.message}}async function U(E){if(!E){o.value=null,A.value="";return}H.value=!0,A.value="";try{o.value=await Ke(`/api/audit-monitor/searches/${encodeURIComponent(E)}`)}catch(N){o.value=null,A.value=N.message}finally{H.value=!1}}async function Ce({keepSelection:E=!0}={}){R.value=!0,await Promise.all([re(),J({keepSelection:E})]),await U(a.value),R.value=!1}async function pt(){await J({keepSelection:!1}),await U(a.value)}function Be(E){a.value!==E&&(a.value=E,U(E))}return ts(W,E=>{typeof document<"u"&&(document.documentElement.lang=E,document.title=s[E].pageTitle)},{immediate:!0}),Or(async()=>{await Ce({keepSelection:!1}),Z=window.setInterval(()=>{Ce()},jc)}),Pr(()=>{Z&&window.clearInterval(Z)}),(E,N)=>{var te,ie,pe,oe,Ae,Ct,st,As,c,u,p,v,_;return Y(),X("div",Oo,[g("header",Po,[g("div",null,[g("p",Mo,y(S("heroEyebrow")),1),g("h1",null,y(S("heroTitle")),1),g("p",Fo,y(S("heroCopy")),1)]),g("div",Lo,[g("div",null,[g("span",null,y(S("windowLabel")),1),g("strong",null,y(((te=i.value)==null?void 0:te.windowHours)||24)+"h",1)]),g("div",null,[g("span",null,y(S("lastRefreshLabel")),1),g("strong",null,y(i.value?We(i.value.generatedAt):S("pendingLabel")),1)]),g("label",Do,[g("span",null,y(S("languageLabel")),1),Yt(g("select",{"onUpdate:modelValue":N[0]||(N[0]=d=>h.value=d)},[g("option",No,y(lt("it")),1),g("option",jo,y(lt("en")),1)],512),[[kn,h.value]])]),g("button",{class:"primary-button",type:"button",onClick:N[1]||(N[1]=d=>Ce()),disabled:R.value},y(R.value?S("refreshing"):S("refreshNow")),9,Ho)])]),g("section",$o,[g("article",Uo,[g("span",Vo,y(S("recentSearchesLabel")),1),g("strong",null,y(((ie=P.value)==null?void 0:ie.totalSearches)??0),1),g("small",null,y(S("recentSearchesDetail",{count:((pe=P.value)==null?void 0:pe.uniqueUsers)??0,hours:((oe=i.value)==null?void 0:oe.windowHours)||24})),1)]),g("article",Ko,[g("span",Wo,y(S("completedLabel")),1),g("strong",null,y(((Ae=P.value)==null?void 0:Ae.completedSearches)??0),1),g("small",null,y(S("completedDetail",{failed:((Ct=P.value)==null?void 0:Ct.failedSearches)??0,processing:((st=P.value)==null?void 0:st.processingSearches)??0})),1)]),g("article",Bo,[g("span",qo,y(S("averageRuntimeLabel")),1),g("strong",null,y(Tt((As=P.value)==null?void 0:As.averageDurationMs)),1),g("small",null,y(S("averageRuntimeDetail")),1)]),g("article",ko,[g("span",Go,y(S("lifetimeSearchesLabel")),1),g("strong",null,y(((c=ee.value)==null?void 0:c.totalSearches)??0),1),g("small",null,y(S("lifetimeSearchesDetail",{value:tt((u=ee.value)==null?void 0:u.latestRequestedAt)})),1)])]),f.value?(Y(),X("p",Jo,y(f.value),1)):Xt("",!0),g("section",zo,[g("div",Yo,[g("div",Qo,[g("div",null,[g("h2",null,y(S("searchesTitle")),1),g("p",null,y(S("searchesIntro")),1)]),g("span",Xo,y(S("shownCount",{count:l.value.length})),1)]),g("form",{class:"filter-grid",onSubmit:To(pt,["prevent"])},[g("label",null,[g("span",null,y(S("statusFilterLabel")),1),Yt(g("select",{"onUpdate:modelValue":N[2]||(N[2]=d=>I.status=d)},[g("option",Zo,y(S("statusAll")),1),g("option",ec,y(S("statusQueued")),1),g("option",tc,y(S("statusProcessing")),1),g("option",sc,y(S("statusCompleted")),1),g("option",nc,y(S("statusFailed")),1)],512),[[kn,I.status]])]),g("label",null,[g("span",null,y(S("lookupLabel")),1),Yt(g("input",{"onUpdate:modelValue":N[3]||(N[3]=d=>I.query=d),type:"search",placeholder:S("lookupPlaceholder")},null,8,rc),[[qn,I.query,void 0,{trim:!0}]])]),g("label",null,[g("span",null,y(S("limitLabel")),1),Yt(g("input",{"onUpdate:modelValue":N[4]||(N[4]=d=>I.limit=d),type:"number",min:"1",max:"200"},null,512),[[qn,I.limit,void 0,{number:!0}]])]),g("button",ic,y(S("applyFilters")),1)],32),m.value?(Y(),X("p",lc,y(m.value),1)):Xt("",!0),q.value?(Y(),X("div",oc,[(Y(!0),X(_e,null,Qt(l.value,d=>(Y(),X("button",{key:d.searchId,type:"button",class:vs(["search-row",{selected:d.searchId===a.value}]),onClick:T=>Be(d.searchId)},[g("div",ac,[g("strong",null,y(d.searchId),1),g("span",{class:"status-badge","data-status":kt(d.status)},y(Se(d.status)),9,uc)]),g("div",fc,[g("span",null,y(d.raceName||d.raceId),1),g("span",null,y(d.userDisplayName||d.userId),1),g("span",null,y(tt(d.requestedAt)),1)]),g("div",dc,[g("span",null,y(d.selfieName||S("noSelfieName")),1),g("span",null,y(S("matchesCount",{count:d.matchCount||0})),1)])],10,cc))),128))])):(Y(),X("p",hc,y(S("noSearches")),1))]),g("div",pc,[g("section",gc,[g("div",mc,[g("div",null,[g("h2",null,y(S("searchDetailTitle")),1),g("p",null,y(S("searchDetailIntro")),1)]),(p=o.value)!=null&&p.search?(Y(),X("span",_c,y(Se(o.value.search.status)),1)):Xt("",!0)]),A.value?(Y(),X("p",vc,y(A.value),1)):H.value?(Y(),X("p",bc,y(S("loadingSearchDetail")),1)):(v=o.value)!=null&&v.search?(Y(),X(_e,{key:3},[g("dl",Sc,[g("div",null,[g("dt",null,y(S("fieldRace")),1),g("dd",null,y(o.value.search.raceName||o.value.search.raceId),1)]),g("div",null,[g("dt",null,y(S("fieldUser")),1),g("dd",null,y(o.value.search.userDisplayName||o.value.search.userId),1)]),g("div",null,[g("dt",null,y(S("fieldRequested")),1),g("dd",null,y(We(o.value.search.requestedAt)),1)]),g("div",null,[g("dt",null,y(S("fieldCompleted")),1),g("dd",null,y(We(o.value.search.completedAt)),1)]),g("div",null,[g("dt",null,y(S("fieldResult")),1),g("dd",null,y(o.value.search.resultId||S("notAvailable")),1)]),g("div",null,[g("dt",null,y(S("fieldError")),1),g("dd",null,y(o.value.search.errorCode||S("notAvailable")),1)])]),g("div",xc,[g("h3",null,y(S("matchPayloadTitle")),1),g("pre",null,y(JSON.stringify(o.value.search.matches,null,2)),1)]),g("div",wc,[g("h3",null,y(S("eventTimelineTitle")),1),(_=o.value.events)!=null&&_.length?(Y(),X("div",Tc,[(Y(!0),X(_e,null,Qt(o.value.events,d=>(Y(),X("article",{key:d.id,class:"event-row"},[g("div",Cc,[g("strong",null,y(d.eventType),1),g("span",null,y(We(d.happenedAt)),1)]),g("p",null,y(S("eventSummary",{status:d.status?Se(d.status):S("notAvailable"),result:d.resultId||S("notAvailable"),error:d.errorCode||S("notAvailable"),matches:d.matchCount??S("notAvailable")})),1),d.payload?(Y(),X("pre",Ac,y(JSON.stringify(d.payload,null,2)),1)):Xt("",!0)]))),128))])):(Y(),X("p",Ec,y(S("noEventsForSearch")),1))])],64)):(Y(),X("p",yc,y(S("chooseSearch")),1))]),g("section",Rc,[g("div",null,[g("div",Ic,[g("div",null,[g("h2",null,y(S("topRacesTitle")),1),g("p",null,y(S("topRacesIntro")),1)])]),Te.value.length?(Y(),X("div",Oc,[(Y(!0),X(_e,null,Qt(Te.value,d=>(Y(),X("article",{key:`${d.raceId}-${d.lastRequestedAt}`,class:"mini-row"},[g("div",null,[g("strong",null,y(d.raceName||d.raceId),1),g("p",null,y(d.raceStorage||S("noStoragePath")),1)]),g("div",Pc,[g("span",null,y(S("topRacesTotal",{count:d.searchCount})),1),g("span",null,y(S("topRacesDone",{count:d.completedCount})),1),g("span",null,y(S("topRacesFailed",{count:d.failedCount})),1)])]))),128))])):(Y(),X("p",Mc,y(S("noRecentRaceActivity")),1))]),g("div",null,[g("div",Fc,[g("div",null,[g("h2",null,y(S("recentEventsTitle")),1),g("p",null,y(S("recentEventsIntro")),1)])]),ve.value.length?(Y(),X("div",Lc,[(Y(!0),X(_e,null,Qt(ve.value,d=>(Y(),X("article",{key:d.id,class:"mini-row compact-row"},[g("div",null,[g("strong",null,y(d.eventType),1),g("p",null,y(d.searchId||S("noSearchId")),1)]),g("div",Dc,[g("span",null,y(d.status?Se(d.status):S("notAvailable")),1),g("span",null,y(tt(d.happenedAt)),1)])]))),128))])):(Y(),X("p",Nc,y(S("noRecentEvents")),1))])])])])])}}};Eo(Hc).mount("#app"); diff --git a/faceai/apps/monitor-frontend/dist/index.html b/faceai/apps/monitor-frontend/dist/index.html new file mode 100644 index 00000000..064fe012 --- /dev/null +++ b/faceai/apps/monitor-frontend/dist/index.html @@ -0,0 +1,13 @@ + + + + + + FaceAI Audit Monitor + + + + +
+ + \ No newline at end of file diff --git a/faceai/apps/monitor-frontend/index.html b/faceai/apps/monitor-frontend/index.html new file mode 100644 index 00000000..c498c62b --- /dev/null +++ b/faceai/apps/monitor-frontend/index.html @@ -0,0 +1,12 @@ + + + + + + FaceAI Audit Monitor + + +
+ + + \ No newline at end of file diff --git a/faceai/apps/monitor-frontend/package.json b/faceai/apps/monitor-frontend/package.json new file mode 100644 index 00000000..9292dfe5 --- /dev/null +++ b/faceai/apps/monitor-frontend/package.json @@ -0,0 +1,17 @@ +{ + "name": "@regalami/faceai-monitor-frontend", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.5.13" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^6.1.0" + } +} \ No newline at end of file diff --git a/faceai/apps/monitor-frontend/src/App.vue b/faceai/apps/monitor-frontend/src/App.vue new file mode 100644 index 00000000..e389bdc2 --- /dev/null +++ b/faceai/apps/monitor-frontend/src/App.vue @@ -0,0 +1,618 @@ + + + \ No newline at end of file diff --git a/faceai/apps/monitor-frontend/src/main.js b/faceai/apps/monitor-frontend/src/main.js new file mode 100644 index 00000000..90f82b2c --- /dev/null +++ b/faceai/apps/monitor-frontend/src/main.js @@ -0,0 +1,5 @@ +import { createApp } from 'vue'; +import App from './App.vue'; +import './styles.css'; + +createApp(App).mount('#app'); \ No newline at end of file diff --git a/faceai/apps/monitor-frontend/src/styles.css b/faceai/apps/monitor-frontend/src/styles.css new file mode 100644 index 00000000..095f41df --- /dev/null +++ b/faceai/apps/monitor-frontend/src/styles.css @@ -0,0 +1,479 @@ +:root { + color-scheme: light; + --canvas: #f6f1e8; + --surface: rgba(255, 251, 245, 0.88); + --surface-strong: #fffdf9; + --ink: #1f1a14; + --muted: #6c6258; + --line: rgba(60, 43, 26, 0.14); + --shadow: 0 24px 60px rgba(70, 49, 28, 0.12); + --sand: #d49f61; + --olive: #70835f; + --sky: #5f8399; + --stone: #7e6c5c; + --danger: #9c3c2c; + --queued: #8a6f43; + --processing: #336b87; + --completed: #567a46; + --failed: #9a3b35; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + color: var(--ink); + background: + radial-gradient(circle at top left, rgba(212, 159, 97, 0.35), transparent 30%), + radial-gradient(circle at top right, rgba(95, 131, 153, 0.22), transparent 26%), + linear-gradient(180deg, #fbf6ef 0%, var(--canvas) 100%); +} + +button, +input, +select { + font: inherit; +} + +#app { + min-height: 100vh; +} + +.monitor-shell { + width: min(1440px, calc(100vw - 32px)); + margin: 0 auto; + padding: 24px 0 40px; +} + +.hero-panel, +.panel, +.stat-card { + border: 1px solid var(--line); + background: var(--surface); + backdrop-filter: blur(14px); + box-shadow: var(--shadow); +} + +.hero-panel { + display: grid; + grid-template-columns: minmax(0, 2.2fr) minmax(280px, 1fr); + gap: 20px; + border-radius: 28px; + padding: 28px; +} + +.eyebrow { + margin: 0 0 10px; + text-transform: uppercase; + letter-spacing: 0.18em; + font-size: 0.78rem; + color: var(--muted); +} + +.hero-panel h1, +.panel h2, +.code-block h3 { + margin: 0; + font-family: Georgia, "Times New Roman", serif; +} + +.hero-panel h1 { + max-width: 12ch; + font-size: clamp(2rem, 4vw, 3.6rem); + line-height: 0.96; +} + +.hero-copy, +.panel-header p, +.mini-row p, +.empty-state, +.detail-grid dt, +.stat-card small, +.hero-meta span { + color: var(--muted); +} + +.hero-copy { + margin: 16px 0 0; + max-width: 62ch; + line-height: 1.55; +} + +.hero-meta { + display: grid; + gap: 16px; + align-content: start; +} + +.hero-meta div, +.stat-card { + padding: 18px; + border-radius: 22px; +} + +.hero-meta .locale-field { + display: grid; + gap: 6px; +} + +.hero-meta .locale-field select { + width: 100%; + min-height: 46px; + border-radius: 14px; + border: 1px solid var(--line); + background: var(--surface-strong); + padding: 10px 12px; + color: var(--ink); +} + +.hero-meta strong, +.stat-card strong { + display: block; + margin-top: 6px; + font-size: 1.35rem; +} + +.primary-button, +.secondary-button { + border: 0; + border-radius: 999px; + padding: 12px 18px; + cursor: pointer; + transition: transform 120ms ease, opacity 120ms ease; +} + +.primary-button { + color: #fff8ef; + background: linear-gradient(135deg, #8f532c, #b77436); +} + +.secondary-button { + color: var(--ink); + background: rgba(118, 104, 91, 0.12); +} + +.primary-button:hover, +.secondary-button:hover, +.search-row:hover { + transform: translateY(-1px); +} + +.primary-button:disabled { + opacity: 0.6; + cursor: wait; +} + +.stat-grid, +.content-grid, +.split-panel, +.filter-grid, +.detail-grid { + display: grid; + gap: 18px; +} + +.stat-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + margin: 18px 0; +} + +.stat-card { + min-height: 154px; +} + +.stat-card strong { + font-size: clamp(1.8rem, 4vw, 3rem); +} + +.stat-label { + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 0.82rem; + color: var(--muted); +} + +.accent-sand { + background: linear-gradient(180deg, rgba(233, 199, 159, 0.42), rgba(255, 251, 245, 0.92)); +} + +.accent-olive { + background: linear-gradient(180deg, rgba(151, 176, 129, 0.33), rgba(255, 251, 245, 0.92)); +} + +.accent-sky { + background: linear-gradient(180deg, rgba(141, 180, 201, 0.35), rgba(255, 251, 245, 0.92)); +} + +.accent-stone { + background: linear-gradient(180deg, rgba(170, 150, 132, 0.28), rgba(255, 251, 245, 0.92)); +} + +.content-grid { + grid-template-columns: minmax(360px, 0.95fr) minmax(0, 1.35fr); + align-items: start; +} + +.panel { + border-radius: 28px; + padding: 22px; +} + +.stack-gap { + display: grid; + gap: 18px; +} + +.panel-header { + display: flex; + justify-content: space-between; + gap: 16px; + align-items: start; +} + +.panel-header.compact { + margin-bottom: 12px; +} + +.panel-header h2 { + font-size: 1.55rem; +} + +.panel-header p { + margin: 8px 0 0; + line-height: 1.45; +} + +.pill, +.status-badge { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 999px; + padding: 6px 12px; + font-size: 0.78rem; + text-transform: uppercase; + letter-spacing: 0.06em; +} + +.pill { + background: rgba(118, 104, 91, 0.12); +} + +.filter-grid { + grid-template-columns: 140px minmax(0, 1fr) 120px 110px; + align-items: end; +} + +.filter-grid label { + display: grid; + gap: 6px; +} + +.filter-grid span { + font-size: 0.82rem; + color: var(--muted); +} + +.filter-grid input, +.filter-grid select { + width: 100%; + min-height: 46px; + border-radius: 14px; + border: 1px solid var(--line); + background: var(--surface-strong); + padding: 10px 12px; +} + +.banner-error, +.empty-state { + margin: 0; + border-radius: 18px; + padding: 14px 16px; + background: rgba(156, 60, 44, 0.08); +} + +.banner-error { + color: var(--danger); +} + +.search-list, +.event-list, +.mini-list { + display: grid; + gap: 12px; +} + +.search-row, +.event-row, +.mini-row, +.code-block { + border: 1px solid var(--line); + border-radius: 20px; + background: rgba(255, 253, 249, 0.86); +} + +.search-row { + width: 100%; + padding: 16px; + text-align: left; + cursor: pointer; +} + +.search-row.selected { + border-color: rgba(143, 83, 44, 0.42); + box-shadow: inset 0 0 0 1px rgba(143, 83, 44, 0.3); +} + +.search-row-top, +.search-row-meta, +.search-row-bottom, +.event-header, +.mini-row, +.mini-metrics { + display: flex; + gap: 10px; + justify-content: space-between; + flex-wrap: wrap; +} + +.search-row-top strong, +.mini-row strong, +.event-header strong, +.detail-grid dd, +.code-block h3 { + color: var(--ink); +} + +.search-row-meta, +.search-row-bottom, +.mini-row p, +.mini-metrics, +.event-row p { + margin-top: 8px; + font-size: 0.92rem; + color: var(--muted); +} + +.status-badge[data-status='queued'] { + background: rgba(138, 111, 67, 0.14); + color: var(--queued); +} + +.status-badge[data-status='processing'] { + background: rgba(51, 107, 135, 0.12); + color: var(--processing); +} + +.status-badge[data-status='completed'] { + background: rgba(86, 122, 70, 0.14); + color: var(--completed); +} + +.status-badge[data-status='failed'] { + background: rgba(154, 59, 53, 0.12); + color: var(--failed); +} + +.status-badge[data-status='unknown'] { + background: rgba(118, 104, 91, 0.12); + color: var(--stone); +} + +.detail-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.detail-grid div { + border: 1px solid var(--line); + border-radius: 18px; + padding: 14px; + background: rgba(255, 253, 249, 0.82); +} + +.detail-grid dt { + font-size: 0.78rem; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.detail-grid dd { + margin: 10px 0 0; + line-height: 1.4; +} + +.code-block { + padding: 16px; +} + +.code-block pre, +.event-row pre { + overflow: auto; + margin: 12px 0 0; + padding: 14px; + border-radius: 16px; + background: #201a17; + color: #f8f1e7; + font-size: 0.82rem; +} + +.event-row { + padding: 14px; +} + +.event-row p { + margin-bottom: 0; +} + +.split-panel { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.mini-row { + padding: 14px; + align-items: center; +} + +.compact-row { + align-items: start; +} + +@media (max-width: 1180px) { + .stat-grid, + .detail-grid, + .split-panel { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .content-grid, + .hero-panel { + grid-template-columns: 1fr; + } +} + +@media (max-width: 760px) { + .monitor-shell { + width: min(100vw - 20px, 100%); + padding-top: 12px; + } + + .panel, + .hero-panel { + padding: 18px; + border-radius: 22px; + } + + .stat-grid, + .detail-grid, + .split-panel, + .filter-grid { + grid-template-columns: 1fr; + } + + .panel-header { + display: grid; + } +} \ No newline at end of file diff --git a/faceai/apps/monitor-frontend/vite.config.js b/faceai/apps/monitor-frontend/vite.config.js new file mode 100644 index 00000000..e1c872b4 --- /dev/null +++ b/faceai/apps/monitor-frontend/vite.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; + +export default defineConfig({ + plugins: [vue()], + server: { + port: 5174, + proxy: { + '/api/audit-monitor': 'http://localhost:3001' + } + } +}); \ No newline at end of file diff --git a/faceai/docker-compose.override.yml b/faceai/docker-compose.override.yml index 1946c01f..e1f4cd4c 100644 --- a/faceai/docker-compose.override.yml +++ b/faceai/docker-compose.override.yml @@ -38,6 +38,15 @@ services: - ../test_pkl:${FACEAI_PKL_ROOT:-/data/pkl}:ro - faceai-runtime:${FACEAI_RUNTIME_ROOT:-/data/runtime} + faceai-monitor: + build: + context: .. + dockerfile: faceai/docker/monitor-frontend.Dockerfile + image: ${FACEAI_MONITOR_DEV_IMAGE:-regalami-faceai-monitor-local} + depends_on: + faceai: + condition: service_healthy + processor: build: context: .. diff --git a/faceai/docker-compose.yml b/faceai/docker-compose.yml index 47bc5287..6911e10c 100644 --- a/faceai/docker-compose.yml +++ b/faceai/docker-compose.yml @@ -44,6 +44,22 @@ services: redis: condition: service_healthy + faceai-monitor: + image: ${FACEAI_MONITOR_IMAGE:-forgejo.maddoscientisto.net/maddo/faceai-monitor:latest} + container_name: ${FACEAI_MONITOR_CONTAINER_NAME:-regalami-faceai-monitor} + restart: unless-stopped + ports: + - "${FACEAI_MONITOR_PUBLISHED_PORT:-127.0.0.1:3002:80}" + healthcheck: + test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1/healthz >/dev/null 2>&1 || exit 1"] + interval: 15s + timeout: 5s + retries: 6 + start_period: 10s + depends_on: + faceai: + condition: service_healthy + processor: image: ${FACEAI_PROCESSOR_IMAGE:-forgejo.maddoscientisto.net/maddo/faceai-processor:latest} container_name: ${FACEAI_PROCESSOR_CONTAINER_NAME:-regalami-faceai-processor} diff --git a/faceai/docker/Dockerfile b/faceai/docker/Dockerfile index b17aa1a5..aede9c1b 100644 --- a/faceai/docker/Dockerfile +++ b/faceai/docker/Dockerfile @@ -9,6 +9,7 @@ WORKDIR /app COPY faceai/package.json ./package.json COPY faceai/apps/frontend/package.json apps/frontend/package.json +COPY faceai/apps/monitor-frontend/package.json apps/monitor-frontend/package.json COPY faceai/apps/backend/package.json apps/backend/package.json COPY faceai/apps/processor/package.json apps/processor/package.json diff --git a/faceai/docker/monitor-frontend.Dockerfile b/faceai/docker/monitor-frontend.Dockerfile new file mode 100644 index 00000000..e963947b --- /dev/null +++ b/faceai/docker/monitor-frontend.Dockerfile @@ -0,0 +1,22 @@ +FROM node:22-trixie-slim AS build + +WORKDIR /app + +COPY faceai/package.json ./package.json +COPY faceai/apps/frontend/package.json apps/frontend/package.json +COPY faceai/apps/monitor-frontend/package.json apps/monitor-frontend/package.json +COPY faceai/apps/backend/package.json apps/backend/package.json +COPY faceai/apps/processor/package.json apps/processor/package.json + +RUN npm install + +COPY faceai/ . + +RUN npm run build --workspace @regalami/faceai-monitor-frontend + +FROM nginx:1.27-alpine AS runtime + +COPY faceai/docker/monitor-nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/apps/monitor-frontend/dist /usr/share/nginx/html + +EXPOSE 80 \ No newline at end of file diff --git a/faceai/docker/monitor-nginx.conf b/faceai/docker/monitor-nginx.conf new file mode 100644 index 00000000..75dfce44 --- /dev/null +++ b/faceai/docker/monitor-nginx.conf @@ -0,0 +1,25 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location = /healthz { + add_header Content-Type text/plain; + return 200 'ok'; + } + + location /api/audit-monitor/ { + proxy_pass http://faceai:3001; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location / { + try_files $uri $uri/ /index.html; + } +} \ No newline at end of file diff --git a/faceai/package-lock.json b/faceai/package-lock.json index 8cc6420c..f54b7c10 100644 --- a/faceai/package-lock.json +++ b/faceai/package-lock.json @@ -7,6 +7,7 @@ "name": "faceai", "workspaces": [ "apps/frontend", + "apps/monitor-frontend", "apps/backend", "apps/processor" ], @@ -37,6 +38,16 @@ "vite": "^6.1.0" } }, + "apps/monitor-frontend": { + "name": "@regalami/faceai-monitor-frontend", + "dependencies": { + "vue": "^3.5.13" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "vite": "^6.1.0" + } + }, "apps/processor": { "name": "@regalami/faceai-processor", "dependencies": { @@ -646,6 +657,10 @@ "resolved": "apps/frontend", "link": true }, + "node_modules/@regalami/faceai-monitor-frontend": { + "resolved": "apps/monitor-frontend", + "link": true + }, "node_modules/@regalami/faceai-processor": { "resolved": "apps/processor", "link": true diff --git a/faceai/package.json b/faceai/package.json index b48d265a..bdb3210f 100644 --- a/faceai/package.json +++ b/faceai/package.json @@ -3,6 +3,7 @@ "private": true, "workspaces": [ "apps/frontend", + "apps/monitor-frontend", "apps/backend", "apps/processor" ], @@ -10,12 +11,13 @@ "dev": "concurrently \"npm:dev:backend\" \"npm:dev:frontend\"", "dev:backend": "npm run dev --workspace @regalami/faceai-backend", "dev:frontend": "npm run dev --workspace @regalami/faceai-frontend", + "dev:monitor": "npm run dev --workspace @regalami/faceai-monitor-frontend", "dev:processor": "npm run dev --workspace @regalami/faceai-processor", "compose:prod:up": "docker compose -f docker-compose.yml --env-file .env.production up -d", "compose:prod:down": "docker compose -f docker-compose.yml --env-file .env.production down", "compose:dev:up": "docker compose --env-file .env.development up --build", "compose:dev:down": "docker compose --env-file .env.development down", - "build": "npm run build --workspace @regalami/faceai-frontend && npm run build --workspace @regalami/faceai-backend", + "build": "npm run build --workspace @regalami/faceai-frontend && npm run build --workspace @regalami/faceai-monitor-frontend && npm run build --workspace @regalami/faceai-backend", "start": "npm run start --workspace @regalami/faceai-backend", "start:processor": "npm run start --workspace @regalami/faceai-processor", "test:e2e": "playwright test", diff --git a/faceai/tmp/audit-monitor-smoke.sqlite b/faceai/tmp/audit-monitor-smoke.sqlite new file mode 100644 index 00000000..e3b63ab7 Binary files /dev/null and b/faceai/tmp/audit-monitor-smoke.sqlite differ