@composy/log-viewer-core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/index.js +961 -0
- package/dist/index.js.map +1 -0
- package/dist/index.min.js +1 -0
- package/es/core/EventEmitter.d.ts +11 -0
- package/es/core/EventEmitter.d.ts.map +1 -0
- package/es/core/EventEmitter.js +40 -0
- package/es/core/EventEmitter.js.map +1 -0
- package/es/core/LogViewer.d.ts +46 -0
- package/es/core/LogViewer.d.ts.map +1 -0
- package/es/core/LogViewer.js +186 -0
- package/es/core/LogViewer.js.map +1 -0
- package/es/filters/FilterEngine.d.ts +26 -0
- package/es/filters/FilterEngine.d.ts.map +1 -0
- package/es/filters/FilterEngine.js +79 -0
- package/es/filters/FilterEngine.js.map +1 -0
- package/es/index.d.ts +26 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +26 -0
- package/es/index.js.map +1 -0
- package/es/renderers/DiffRenderer.d.ts +18 -0
- package/es/renderers/DiffRenderer.d.ts.map +1 -0
- package/es/renderers/DiffRenderer.js +60 -0
- package/es/renderers/DiffRenderer.js.map +1 -0
- package/es/renderers/TimelineRenderer.d.ts +22 -0
- package/es/renderers/TimelineRenderer.d.ts.map +1 -0
- package/es/renderers/TimelineRenderer.js +135 -0
- package/es/renderers/TimelineRenderer.js.map +1 -0
- package/es/styles/index.d.ts +9 -0
- package/es/styles/index.d.ts.map +1 -0
- package/es/styles/index.js +414 -0
- package/es/styles/index.js.map +1 -0
- package/es/types/index.d.ts +113 -0
- package/es/types/index.d.ts.map +1 -0
- package/es/types/index.js +40 -0
- package/es/types/index.js.map +1 -0
- package/es/utils/index.d.ts +18 -0
- package/es/utils/index.d.ts.map +1 -0
- package/es/utils/index.js +73 -0
- package/es/utils/index.js.map +1 -0
- package/esm/core/EventEmitter.d.ts +11 -0
- package/esm/core/EventEmitter.d.ts.map +1 -0
- package/esm/core/EventEmitter.js +40 -0
- package/esm/core/EventEmitter.js.map +1 -0
- package/esm/core/LogViewer.d.ts +46 -0
- package/esm/core/LogViewer.d.ts.map +1 -0
- package/esm/core/LogViewer.js +186 -0
- package/esm/core/LogViewer.js.map +1 -0
- package/esm/filters/FilterEngine.d.ts +26 -0
- package/esm/filters/FilterEngine.d.ts.map +1 -0
- package/esm/filters/FilterEngine.js +79 -0
- package/esm/filters/FilterEngine.js.map +1 -0
- package/esm/index.d.ts +26 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +26 -0
- package/esm/index.js.map +1 -0
- package/esm/renderers/DiffRenderer.d.ts +18 -0
- package/esm/renderers/DiffRenderer.d.ts.map +1 -0
- package/esm/renderers/DiffRenderer.js +60 -0
- package/esm/renderers/DiffRenderer.js.map +1 -0
- package/esm/renderers/TimelineRenderer.d.ts +22 -0
- package/esm/renderers/TimelineRenderer.d.ts.map +1 -0
- package/esm/renderers/TimelineRenderer.js +135 -0
- package/esm/renderers/TimelineRenderer.js.map +1 -0
- package/esm/styles/index.d.ts +9 -0
- package/esm/styles/index.d.ts.map +1 -0
- package/esm/styles/index.js +414 -0
- package/esm/styles/index.js.map +1 -0
- package/esm/types/index.d.ts +113 -0
- package/esm/types/index.d.ts.map +1 -0
- package/esm/types/index.js +40 -0
- package/esm/types/index.js.map +1 -0
- package/esm/utils/index.d.ts +18 -0
- package/esm/utils/index.d.ts.map +1 -0
- package/esm/utils/index.js +73 -0
- package/esm/utils/index.js.map +1 -0
- package/lib/core/EventEmitter.cjs +41 -0
- package/lib/core/EventEmitter.cjs.map +1 -0
- package/lib/core/EventEmitter.d.ts +11 -0
- package/lib/core/LogViewer.cjs +187 -0
- package/lib/core/LogViewer.cjs.map +1 -0
- package/lib/core/LogViewer.d.ts +46 -0
- package/lib/filters/FilterEngine.cjs +80 -0
- package/lib/filters/FilterEngine.cjs.map +1 -0
- package/lib/filters/FilterEngine.d.ts +26 -0
- package/lib/index.cjs +43 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.ts +26 -0
- package/lib/package.json +3 -0
- package/lib/renderers/DiffRenderer.cjs +61 -0
- package/lib/renderers/DiffRenderer.cjs.map +1 -0
- package/lib/renderers/DiffRenderer.d.ts +18 -0
- package/lib/renderers/TimelineRenderer.cjs +136 -0
- package/lib/renderers/TimelineRenderer.cjs.map +1 -0
- package/lib/renderers/TimelineRenderer.d.ts +22 -0
- package/lib/styles/index.cjs +415 -0
- package/lib/styles/index.cjs.map +1 -0
- package/lib/styles/index.d.ts +9 -0
- package/lib/types/index.cjs +42 -0
- package/lib/types/index.cjs.map +1 -0
- package/lib/types/index.d.ts +113 -0
- package/lib/utils/index.cjs +80 -0
- package/lib/utils/index.cjs.map +1 -0
- package/lib/utils/index.d.ts +18 -0
- package/package.json +119 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["LogViewer"],"sources":["../src/types/index.ts","../src/core/EventEmitter.ts","../src/filters/FilterEngine.ts","../src/utils/index.ts","../src/renderers/TimelineRenderer.ts","../src/renderers/DiffRenderer.ts","../src/styles/index.ts","../src/core/LogViewer.ts","../src/index.ts"],"sourcesContent":["/**\n * @composy/log-viewer-core 类型定义\n */\n\n/** 操作类型 */\nexport type ActionType =\n | 'create'\n | 'update'\n | 'delete'\n | 'query'\n | 'login'\n | 'logout'\n | 'export'\n | 'import'\n | 'approve'\n | 'reject'\n | 'other'\n\n/** 主题模式 */\nexport type ThemeMode = 'light' | 'dark'\n\n/** 异常标记类型 */\nexport type AnomalyType = 'off-hours' | 'sensitive' | 'high-frequency' | 'none'\n\n/** 字段变更条目 */\nexport interface DiffEntry {\n field: string\n label?: string\n oldValue: unknown\n newValue: unknown\n}\n\n/** 日志条目 */\nexport interface LogEntry {\n id: string\n timestamp: string | number | Date\n user: string\n userId?: string\n action: ActionType\n module: string\n detail?: string\n ip?: string\n changes?: DiffEntry[]\n metadata?: Record<string, unknown>\n anomaly?: AnomalyType\n}\n\n/** 筛选配置 */\nexport interface FilterConfig {\n user?: string\n action?: ActionType | ActionType[]\n module?: string\n ip?: string\n timeRange?: [Date | string | number, Date | string | number]\n keyword?: string\n}\n\n/** 分页配置 */\nexport interface PaginationConfig {\n page: number\n pageSize: number\n total?: number\n}\n\n/** LogViewer 配置 */\nexport interface LogViewerConfig {\n container: string | HTMLElement\n data?: LogEntry[]\n dataSource?: string | (() => Promise<LogEntry[]>)\n columns?: string[]\n filter?: FilterConfig\n pagination?: Partial<PaginationConfig>\n theme?: ThemeMode\n locale?: 'zh-CN' | 'en-US'\n showFilter?: boolean\n showDiff?: boolean\n showAnomalyBadge?: boolean\n pageSize?: number\n onEntryClick?: (entry: LogEntry) => void\n onFilterChange?: (filter: FilterConfig) => void\n onThemeChange?: (theme: ThemeMode) => void\n}\n\n/** LogViewer 实例接口 */\nexport interface LogViewerInstance {\n /** 获取当前数据 */\n getData(): LogEntry[]\n /** 设置数据 */\n setData(entries: LogEntry[]): void\n /** 追加数据 */\n appendData(entries: LogEntry[]): void\n /** 获取当前筛选条件 */\n getFilter(): FilterConfig\n /** 设置筛选条件 */\n setFilter(filter: FilterConfig): void\n /** 重置筛选 */\n resetFilter(): void\n /** 获取当前主题 */\n getTheme(): ThemeMode\n /** 设置主题 */\n setTheme(theme: ThemeMode): void\n /** 展开指定条目详情 */\n expandEntry(id: string): void\n /** 折叠指定条目详情 */\n collapseEntry(id: string): void\n /** 折叠所有详情 */\n collapseAll(): void\n /** 跳转分页 */\n goToPage(page: number): void\n /** 获取分页信息 */\n getPagination(): PaginationConfig\n /** 注册事件监听 */\n on(event: string, callback: (...args: unknown[]) => void): () => void\n /** 刷新渲染 */\n refresh(): void\n /** 销毁实例 */\n destroy(): void\n}\n\n/** 事件映射 */\nexport interface LogViewerEvents {\n 'filter:change': (filter: FilterConfig) => void\n 'entry:click': (entry: LogEntry) => void\n 'entry:expand': (entry: LogEntry) => void\n 'entry:collapse': (entry: LogEntry) => void\n 'theme:change': (theme: ThemeMode) => void\n 'page:change': (page: number) => void\n 'data:change': (entries: LogEntry[]) => void\n}\n\n/** 操作类型标签映射 */\nexport const ACTION_LABELS: Record<ActionType, string> = {\n create: '新增',\n update: '修改',\n delete: '删除',\n query: '查询',\n login: '登录',\n logout: '登出',\n export: '导出',\n import: '导入',\n approve: '审批',\n reject: '驳回',\n other: '其他',\n}\n\n/** 操作类型颜色映射 */\nexport const ACTION_COLORS: Record<ActionType, string> = {\n create: '#52c41a',\n update: '#1890ff',\n delete: '#ff4d4f',\n query: '#8c8c8c',\n login: '#722ed1',\n logout: '#eb2f96',\n export: '#fa8c16',\n import: '#13c2c2',\n approve: '#52c41a',\n reject: '#ff4d4f',\n other: '#d9d9d9',\n}\n","/**\n * 简单的事件发射器\n */\nexport class EventEmitter {\n private listeners = new Map<string, Set<(...args: unknown[]) => void>>()\n\n on(event: string, callback: (...args: unknown[]) => void): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set())\n }\n this.listeners.get(event)!.add(callback)\n return () => this.off(event, callback)\n }\n\n off(event: string, callback: (...args: unknown[]) => void): void {\n this.listeners.get(event)?.delete(callback)\n }\n\n emit(event: string, ...args: unknown[]): void {\n this.listeners.get(event)?.forEach((cb) => {\n try {\n cb(...args)\n } catch (e) {\n console.error(`[LogViewer] Event handler error (${event}):`, e)\n }\n })\n }\n\n removeAllListeners(event?: string): void {\n if (event) {\n this.listeners.delete(event)\n } else {\n this.listeners.clear()\n }\n }\n}\n","/**\n * 筛选引擎 - 支持按操作人/操作类型/时间范围/模块/IP/关键词筛选\n */\n\nimport type { LogEntry, FilterConfig } from '../types'\n\n/**\n * FilterEngine - Filter Engine class\n */\nexport class FilterEngine {\n /**\n * 对日志条目进行多维筛选\n */\n filter(entries: LogEntry[], config: FilterConfig): LogEntry[] {\n let result = entries\n\n // 按操作人筛选\n if (config.user) {\n const keyword = config.user.toLowerCase()\n result = result.filter(\n (e) =>\n e.user.toLowerCase().includes(keyword) ||\n (e.userId && e.userId.toLowerCase().includes(keyword)),\n )\n }\n\n // 按操作类型筛选\n if (config.action) {\n const actions = Array.isArray(config.action) ? config.action : [config.action]\n result = result.filter((e) => actions.includes(e.action))\n }\n\n // 按模块筛选\n if (config.module) {\n const mod = config.module.toLowerCase()\n result = result.filter((e) => e.module.toLowerCase().includes(mod))\n }\n\n // 按 IP 筛选\n if (config.ip) {\n const ip = config.ip.toLowerCase()\n result = result.filter((e) => e.ip && e.ip.toLowerCase().includes(ip))\n }\n\n // 按时间范围筛选\n if (config.timeRange) {\n const [start, end] = config.timeRange\n const startTime = new Date(start).getTime()\n const endTime = new Date(end).getTime()\n result = result.filter((e) => {\n const t = new Date(e.timestamp).getTime()\n return t >= startTime && t <= endTime\n })\n }\n\n // 按关键词全文搜索\n if (config.keyword) {\n const kw = config.keyword.toLowerCase()\n result = result.filter((e) => {\n const texts = [\n e.user,\n e.action,\n e.module,\n e.detail || '',\n e.ip || '',\n e.id,\n ]\n return texts.some((t) => t.toLowerCase().includes(kw))\n })\n }\n\n return result\n }\n\n /**\n * 获取所有唯一操作人\n */\n getUniqueUsers(entries: LogEntry[]): string[] {\n return [...new Set(entries.map((e) => e.user))].sort()\n }\n\n /**\n * 获取所有唯一模块\n */\n getUniqueModules(entries: LogEntry[]): string[] {\n return [...new Set(entries.map((e) => e.module))].sort()\n }\n\n /**\n * 获取所有唯一操作类型\n */\n getUniqueActions(entries: LogEntry[]): string[] {\n return [...new Set(entries.map((e) => e.action))].sort()\n }\n}\n","/**\n * 工具函数\n */\n\nlet _uid = 0\n\n/** 生成唯一 ID */\nexport function uid(prefix = 'lv'): string {\n return `${prefix}-${++_uid}-${Math.random().toString(36).slice(2, 8)}`\n}\n\n/** 格式化日期 */\nexport function formatDate(value: string | number | Date, fmt = 'YYYY-MM-DD HH:mm:ss'): string {\n const d = value instanceof Date ? value : new Date(value)\n if (isNaN(d.getTime())) return String(value)\n\n const pad = (n: number) => String(n).padStart(2, '0')\n const year = String(d.getFullYear())\n const month = pad(d.getMonth() + 1)\n const day = pad(d.getDate())\n const hours = pad(d.getHours())\n const minutes = pad(d.getMinutes())\n const seconds = pad(d.getSeconds())\n\n return fmt\n .replace('YYYY', year)\n .replace('MM', month)\n .replace('DD', day)\n .replace('HH', hours)\n .replace('mm', minutes)\n .replace('ss', seconds)\n}\n\n/** 格式化相对时间 */\nexport function formatRelativeTime(value: string | number | Date): string {\n const d = value instanceof Date ? value : new Date(value)\n const now = Date.now()\n const diff = now - d.getTime()\n\n if (diff < 60_000) return '刚刚'\n if (diff < 3_600_000) return `${Math.floor(diff / 60_000)} 分钟前`\n if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)} 小时前`\n if (diff < 604_800_000) return `${Math.floor(diff / 86_400_000)} 天前`\n return formatDate(d)\n}\n\n/** HTML 转义 */\nexport function escapeHtml(str: string): string {\n const map: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n }\n return str.replace(/[&<>\"']/g, (c) => map[c] || c)\n}\n\n/** 防抖 */\nexport function debounce<T extends (...args: unknown[]) => void>(fn: T, delay: number): T {\n let timer: ReturnType<typeof setTimeout> | null = null\n return ((...args: unknown[]) => {\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => fn(...args), delay)\n }) as unknown as T\n}\n\n/** 深拷贝简易实现 */\nexport function deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== 'object') return obj\n try {\n return JSON.parse(JSON.stringify(obj))\n } catch {\n return obj\n }\n}\n\n/** 将值格式化为可展示的字符串 */\nexport function formatValue(val: unknown): string {\n if (val === null || val === undefined) return '-'\n if (typeof val === 'object') {\n try {\n return JSON.stringify(val, null, 2)\n } catch {\n return String(val)\n }\n }\n return String(val)\n}\n","/**\n * 时间线渲染器 - 将日志条目渲染为时间线 DOM\n */\n\nimport type { LogEntry, AnomalyType } from '../types'\nimport { ACTION_LABELS, ACTION_COLORS } from '../types'\nimport { formatDate, escapeHtml } from '../utils'\n\nconst ANOMALY_LABELS: Record<AnomalyType, string> = {\n 'off-hours': '非工作时间',\n sensitive: '敏感操作',\n 'high-frequency': '高频操作',\n none: '',\n}\n\n/**\n * TimelineRenderer - Timeline Renderer class\n */\nexport class TimelineRenderer {\n /**\n * 渲染时间线\n */\n render(entries: LogEntry[], expandedIds: Set<string>): HTMLElement {\n const container = document.createElement('div')\n container.className = 'lv-timeline'\n\n if (entries.length === 0) {\n container.innerHTML = `<div class=\"lv-empty\"><div class=\"lv-empty-icon\">--</div><div>暂无日志记录</div></div>`\n container.className = ''\n return container\n }\n\n for (const entry of entries) {\n container.appendChild(this.renderItem(entry, expandedIds.has(entry.id)))\n }\n\n return container\n }\n\n /**\n * 渲染单条时间线条目\n */\n renderItem(entry: LogEntry, expanded: boolean): HTMLElement {\n const item = document.createElement('div')\n item.className = 'lv-timeline-item'\n item.setAttribute('data-id', entry.id)\n\n const isAnomaly = entry.anomaly && entry.anomaly !== 'none'\n\n // Dot\n const dot = document.createElement('div')\n dot.className = `lv-timeline-dot${isAnomaly ? ' lv-timeline-dot--anomaly' : ''}`\n item.appendChild(dot)\n\n // Card\n const card = document.createElement('div')\n card.className = `lv-timeline-card${isAnomaly ? ' lv-timeline-card--anomaly' : ''}`\n\n // Header\n const header = document.createElement('div')\n header.className = 'lv-timeline-header'\n\n const time = document.createElement('span')\n time.className = 'lv-timeline-time'\n time.textContent = formatDate(entry.timestamp)\n header.appendChild(time)\n\n const user = document.createElement('span')\n user.className = 'lv-timeline-user'\n user.textContent = entry.user\n header.appendChild(user)\n\n const actionTag = document.createElement('span')\n actionTag.className = 'lv-action-tag'\n actionTag.textContent = ACTION_LABELS[entry.action] || entry.action\n actionTag.style.backgroundColor = ACTION_COLORS[entry.action] || '#8c8c8c'\n header.appendChild(actionTag)\n\n const moduleBadge = document.createElement('span')\n moduleBadge.className = 'lv-timeline-module'\n moduleBadge.textContent = entry.module\n header.appendChild(moduleBadge)\n\n if (entry.ip) {\n const ip = document.createElement('span')\n ip.className = 'lv-timeline-ip'\n ip.textContent = entry.ip\n header.appendChild(ip)\n }\n\n if (isAnomaly) {\n const badge = document.createElement('span')\n badge.className = 'lv-anomaly-badge'\n badge.textContent = ANOMALY_LABELS[entry.anomaly!]\n header.appendChild(badge)\n }\n\n card.appendChild(header)\n\n // Detail text\n if (entry.detail) {\n const detail = document.createElement('div')\n detail.className = 'lv-timeline-detail'\n detail.textContent = entry.detail\n card.appendChild(detail)\n }\n\n // Expand hint\n if (entry.changes && entry.changes.length > 0) {\n const hint = document.createElement('div')\n hint.className = 'lv-timeline-expand-hint'\n hint.textContent = expanded ? '[ 收起变更详情 ]' : `[ 查看变更详情 (${entry.changes.length} 个字段) ]`\n card.appendChild(hint)\n }\n\n item.appendChild(card)\n\n return item\n }\n\n /**\n * 渲染分页\n */\n renderPagination(page: number, pageSize: number, total: number): HTMLElement {\n const container = document.createElement('div')\n container.className = 'lv-pagination'\n\n const totalPages = Math.ceil(total / pageSize)\n if (totalPages <= 1) return container\n\n // Prev\n const prev = document.createElement('button')\n prev.className = 'lv-pagination-btn'\n prev.textContent = '<'\n prev.disabled = page <= 1\n prev.setAttribute('data-page', String(page - 1))\n container.appendChild(prev)\n\n // Page buttons\n const maxVisible = 7\n let startPage = Math.max(1, page - Math.floor(maxVisible / 2))\n const endPage = Math.min(totalPages, startPage + maxVisible - 1)\n if (endPage - startPage < maxVisible - 1) {\n startPage = Math.max(1, endPage - maxVisible + 1)\n }\n\n for (let i = startPage; i <= endPage; i++) {\n const btn = document.createElement('button')\n btn.className = `lv-pagination-btn${i === page ? ' lv-pagination-btn--active' : ''}`\n btn.textContent = String(i)\n btn.setAttribute('data-page', String(i))\n container.appendChild(btn)\n }\n\n // Next\n const next = document.createElement('button')\n next.className = 'lv-pagination-btn'\n next.textContent = '>'\n next.disabled = page >= totalPages\n next.setAttribute('data-page', String(page + 1))\n container.appendChild(next)\n\n // Info\n const info = document.createElement('span')\n info.className = 'lv-pagination-info'\n info.textContent = `共 ${total} 条`\n container.appendChild(info)\n\n return container\n }\n}\n","/**\n * 变更对比渲染器 - 渲染字段变更前后的 Diff 表格\n */\n\nimport type { DiffEntry, LogEntry } from '../types'\nimport { escapeHtml, formatValue } from '../utils'\n\n/**\n * DiffRenderer - Diff Renderer class\n */\nexport class DiffRenderer {\n /**\n * 渲染详情面板(包含 diff 表格和 metadata)\n */\n renderDetail(entry: LogEntry): HTMLElement {\n const container = document.createElement('div')\n container.className = 'lv-detail'\n\n if (entry.changes && entry.changes.length > 0) {\n container.appendChild(this.renderDiffTable(entry.changes))\n }\n\n if (entry.metadata && Object.keys(entry.metadata).length > 0) {\n const meta = document.createElement('div')\n meta.className = 'lv-metadata'\n meta.textContent = JSON.stringify(entry.metadata, null, 2)\n container.appendChild(meta)\n }\n\n return container\n }\n\n /**\n * 渲染 Diff 表格\n */\n renderDiffTable(changes: DiffEntry[]): HTMLElement {\n const table = document.createElement('table')\n table.className = 'lv-diff-table'\n\n // Header\n const thead = document.createElement('thead')\n thead.innerHTML = `<tr><th>字段</th><th>变更前</th><th>变更后</th></tr>`\n table.appendChild(thead)\n\n // Body\n const tbody = document.createElement('tbody')\n for (const change of changes) {\n const tr = document.createElement('tr')\n\n const tdField = document.createElement('td')\n tdField.textContent = change.label || change.field\n tr.appendChild(tdField)\n\n const tdOld = document.createElement('td')\n tdOld.className = 'lv-diff-old'\n tdOld.textContent = formatValue(change.oldValue)\n tr.appendChild(tdOld)\n\n const tdNew = document.createElement('td')\n tdNew.className = 'lv-diff-new'\n tdNew.textContent = formatValue(change.newValue)\n tr.appendChild(tdNew)\n\n tbody.appendChild(tr)\n }\n table.appendChild(tbody)\n\n return table\n }\n}\n","/**\n * 样式注入 - 通过 CSS 变量支持亮色/暗色主题\n */\n\nconst STYLE_ID = 'ldesign-log-viewer-styles'\n\n/**\n * injectStyles - inject Styles function\n * @returns 返回值说明\n */\nexport function injectStyles(): void {\n if (typeof document === 'undefined') return\n if (document.getElementById(STYLE_ID)) return\n\n const style = document.createElement('style')\n style.id = STYLE_ID\n style.textContent = CSS_TEXT\n document.head.appendChild(style)\n}\n\nconst CSS_TEXT = /* css */ `\n/* ===== CSS Variables ===== */\n.lv-root {\n --lv-bg: #ffffff;\n --lv-bg-secondary: #f7f8fa;\n --lv-bg-hover: #f0f3ff;\n --lv-border: #ebedf0;\n --lv-text: #1a1a2e;\n --lv-text-secondary: #666;\n --lv-text-muted: #999;\n --lv-primary: #4f6ef7;\n --lv-primary-light: #edf0ff;\n --lv-shadow: rgba(0, 0, 0, 0.04);\n --lv-timeline-line: #e0e0e0;\n --lv-timeline-dot: #4f6ef7;\n --lv-diff-add-bg: #f0fff4;\n --lv-diff-add-text: #22863a;\n --lv-diff-del-bg: #fff5f5;\n --lv-diff-del-text: #cb2431;\n --lv-anomaly-bg: #fff2e8;\n --lv-anomaly-border: #ffbb96;\n --lv-radius: 8px;\n --lv-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;\n --lv-font-mono: 'Fira Code', 'SF Mono', Consolas, monospace;\n}\n\n.lv-root[data-theme=\"dark\"] {\n --lv-bg: #1a1a2e;\n --lv-bg-secondary: #16213e;\n --lv-bg-hover: #1e2a4a;\n --lv-border: #2a2a4a;\n --lv-text: #e0e0e0;\n --lv-text-secondary: #a0a0b0;\n --lv-text-muted: #666680;\n --lv-primary: #6b85f9;\n --lv-primary-light: #252545;\n --lv-shadow: rgba(0, 0, 0, 0.2);\n --lv-timeline-line: #333355;\n --lv-timeline-dot: #6b85f9;\n --lv-diff-add-bg: #0d2818;\n --lv-diff-add-text: #85e89d;\n --lv-diff-del-bg: #2d1214;\n --lv-diff-del-text: #f97583;\n --lv-anomaly-bg: #2d1a0a;\n --lv-anomaly-border: #8b5e3c;\n}\n\n/* ===== Root ===== */\n.lv-root {\n font-family: var(--lv-font);\n font-size: 14px;\n color: var(--lv-text);\n background: var(--lv-bg);\n line-height: 1.6;\n}\n\n/* ===== Filter Bar ===== */\n.lv-filter {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n padding: 16px;\n background: var(--lv-bg);\n border: 1px solid var(--lv-border);\n border-radius: var(--lv-radius);\n margin-bottom: 16px;\n align-items: center;\n}\n\n.lv-filter-item {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 13px;\n}\n\n.lv-filter-label {\n color: var(--lv-text-secondary);\n font-size: 12px;\n white-space: nowrap;\n}\n\n.lv-filter-input {\n padding: 5px 10px;\n border: 1px solid var(--lv-border);\n border-radius: 6px;\n font-size: 13px;\n background: var(--lv-bg);\n color: var(--lv-text);\n outline: none;\n transition: border-color 0.2s;\n min-width: 120px;\n}\n\n.lv-filter-input:focus {\n border-color: var(--lv-primary);\n}\n\n.lv-filter-select {\n padding: 5px 10px;\n border: 1px solid var(--lv-border);\n border-radius: 6px;\n font-size: 13px;\n background: var(--lv-bg);\n color: var(--lv-text);\n outline: none;\n cursor: pointer;\n}\n\n.lv-filter-btn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 5px 14px;\n font-size: 13px;\n border: 1px solid var(--lv-border);\n border-radius: 6px;\n background: var(--lv-bg);\n color: var(--lv-text);\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.lv-filter-btn:hover {\n border-color: var(--lv-primary);\n color: var(--lv-primary);\n}\n\n.lv-filter-btn--primary {\n background: var(--lv-primary);\n border-color: var(--lv-primary);\n color: #fff;\n}\n\n.lv-filter-btn--primary:hover {\n opacity: 0.85;\n}\n\n/* ===== Timeline ===== */\n.lv-timeline {\n position: relative;\n padding-left: 28px;\n}\n\n.lv-timeline::before {\n content: '';\n position: absolute;\n left: 9px;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--lv-timeline-line);\n}\n\n.lv-timeline-item {\n position: relative;\n padding: 0 0 20px 0;\n}\n\n.lv-timeline-dot {\n position: absolute;\n left: -25px;\n top: 6px;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: var(--lv-bg);\n border: 2px solid var(--lv-timeline-dot);\n z-index: 1;\n}\n\n.lv-timeline-dot--anomaly {\n border-color: #ff4d4f;\n background: #fff1f0;\n}\n\n.lv-timeline-card {\n background: var(--lv-bg);\n border: 1px solid var(--lv-border);\n border-radius: var(--lv-radius);\n padding: 14px 18px;\n cursor: pointer;\n transition: all 0.2s;\n box-shadow: 0 1px 3px var(--lv-shadow);\n}\n\n.lv-timeline-card:hover {\n border-color: var(--lv-primary);\n box-shadow: 0 2px 8px var(--lv-shadow);\n}\n\n.lv-timeline-card--anomaly {\n border-left: 3px solid var(--lv-anomaly-border);\n background: var(--lv-anomaly-bg);\n}\n\n.lv-timeline-header {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n margin-bottom: 4px;\n}\n\n.lv-timeline-time {\n font-size: 12px;\n color: var(--lv-text-muted);\n font-family: var(--lv-font-mono);\n}\n\n.lv-timeline-user {\n font-weight: 600;\n font-size: 14px;\n}\n\n.lv-action-tag {\n display: inline-block;\n padding: 1px 8px;\n font-size: 12px;\n border-radius: 4px;\n font-weight: 500;\n color: #fff;\n}\n\n.lv-timeline-module {\n font-size: 12px;\n color: var(--lv-text-secondary);\n background: var(--lv-bg-secondary);\n padding: 1px 8px;\n border-radius: 4px;\n}\n\n.lv-timeline-ip {\n font-size: 12px;\n color: var(--lv-text-muted);\n font-family: var(--lv-font-mono);\n}\n\n.lv-anomaly-badge {\n font-size: 11px;\n padding: 1px 6px;\n border-radius: 3px;\n background: #fff2e8;\n color: #d46b08;\n border: 1px solid #ffd591;\n}\n\n.lv-timeline-detail {\n font-size: 13px;\n color: var(--lv-text-secondary);\n margin-top: 6px;\n}\n\n.lv-timeline-expand-hint {\n font-size: 12px;\n color: var(--lv-primary);\n margin-top: 6px;\n cursor: pointer;\n}\n\n/* ===== Detail Panel ===== */\n.lv-detail {\n margin-top: 12px;\n padding-top: 12px;\n border-top: 1px dashed var(--lv-border);\n animation: lv-slide-down 0.2s ease;\n}\n\n@keyframes lv-slide-down {\n from { opacity: 0; transform: translateY(-8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* ===== Diff Table ===== */\n.lv-diff-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n border: 1px solid var(--lv-border);\n border-radius: 6px;\n overflow: hidden;\n}\n\n.lv-diff-table th {\n background: var(--lv-bg-secondary);\n padding: 8px 12px;\n text-align: left;\n font-weight: 600;\n font-size: 12px;\n color: var(--lv-text-secondary);\n border-bottom: 1px solid var(--lv-border);\n}\n\n.lv-diff-table td {\n padding: 8px 12px;\n border-bottom: 1px solid var(--lv-border);\n vertical-align: top;\n}\n\n.lv-diff-table tr:last-child td {\n border-bottom: none;\n}\n\n.lv-diff-old {\n background: var(--lv-diff-del-bg);\n color: var(--lv-diff-del-text);\n text-decoration: line-through;\n font-family: var(--lv-font-mono);\n font-size: 12px;\n}\n\n.lv-diff-new {\n background: var(--lv-diff-add-bg);\n color: var(--lv-diff-add-text);\n font-family: var(--lv-font-mono);\n font-size: 12px;\n}\n\n/* ===== Metadata ===== */\n.lv-metadata {\n margin-top: 10px;\n background: var(--lv-bg-secondary);\n border-radius: 6px;\n padding: 10px 14px;\n font-size: 12px;\n font-family: var(--lv-font-mono);\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--lv-text-secondary);\n}\n\n/* ===== Pagination ===== */\n.lv-pagination {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 16px 0;\n}\n\n.lv-pagination-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border: 1px solid var(--lv-border);\n border-radius: 6px;\n background: var(--lv-bg);\n color: var(--lv-text);\n font-size: 13px;\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.lv-pagination-btn:hover {\n border-color: var(--lv-primary);\n color: var(--lv-primary);\n}\n\n.lv-pagination-btn--active {\n background: var(--lv-primary);\n border-color: var(--lv-primary);\n color: #fff;\n}\n\n.lv-pagination-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.lv-pagination-info {\n font-size: 13px;\n color: var(--lv-text-secondary);\n margin: 0 8px;\n}\n\n/* ===== Empty state ===== */\n.lv-empty {\n text-align: center;\n padding: 48px 16px;\n color: var(--lv-text-muted);\n font-size: 14px;\n}\n\n.lv-empty-icon {\n font-size: 48px;\n margin-bottom: 12px;\n opacity: 0.3;\n}\n`\n","/**\n * LogViewer 核心引擎\n */\n\nimport type {\n LogEntry,\n LogViewerConfig,\n LogViewerInstance,\n FilterConfig,\n PaginationConfig,\n ThemeMode,\n} from '../types'\nimport { EventEmitter } from './EventEmitter'\nimport { FilterEngine } from '../filters/FilterEngine'\nimport { TimelineRenderer } from '../renderers/TimelineRenderer'\nimport { DiffRenderer } from '../renderers/DiffRenderer'\nimport { injectStyles } from '../styles'\nimport { deepClone } from '../utils'\n\n/**\n * LogViewer - Log Viewer class\n */\nexport class LogViewer implements LogViewerInstance {\n private root: HTMLElement\n private emitter = new EventEmitter()\n private filterEngine = new FilterEngine()\n private timelineRenderer = new TimelineRenderer()\n private diffRenderer = new DiffRenderer()\n\n private data: LogEntry[] = []\n private filteredData: LogEntry[] = []\n private filter: FilterConfig = {}\n private pagination: PaginationConfig = { page: 1, pageSize: 20 }\n private theme: ThemeMode = 'light'\n private expandedIds = new Set<string>()\n private config: LogViewerConfig\n\n private timelineEl: HTMLElement | null = null\n private paginationEl: HTMLElement | null = null\n private destroyed = false\n\n constructor(config: LogViewerConfig) {\n this.config = config\n\n // Resolve container\n const container =\n typeof config.container === 'string'\n ? document.querySelector<HTMLElement>(config.container)\n : config.container\n if (!container) throw new Error(`[LogViewer] Container not found: ${config.container}`)\n\n // Inject global styles\n injectStyles()\n\n // Create root\n this.root = document.createElement('div')\n this.root.className = 'lv-root'\n container.appendChild(this.root)\n\n // Apply config\n this.theme = config.theme || 'light'\n this.root.setAttribute('data-theme', this.theme)\n\n if (config.pageSize) this.pagination.pageSize = config.pageSize\n if (config.pagination) Object.assign(this.pagination, config.pagination)\n if (config.filter) this.filter = deepClone(config.filter)\n\n // Load data\n if (config.data) {\n this.data = [...config.data]\n }\n\n this.applyFilterAndRender()\n this.bindEvents()\n }\n\n // ===== Public API =====\n\n getData(): LogEntry[] {\n return [...this.data]\n }\n\n setData(entries: LogEntry[]): void {\n this.data = [...entries]\n this.pagination.page = 1\n this.applyFilterAndRender()\n this.emitter.emit('data:change', this.data)\n }\n\n appendData(entries: LogEntry[]): void {\n this.data.push(...entries)\n this.applyFilterAndRender()\n this.emitter.emit('data:change', this.data)\n }\n\n getFilter(): FilterConfig {\n return deepClone(this.filter)\n }\n\n setFilter(filter: FilterConfig): void {\n this.filter = deepClone(filter)\n this.pagination.page = 1\n this.applyFilterAndRender()\n this.emitter.emit('filter:change', this.filter)\n this.config.onFilterChange?.(this.filter)\n }\n\n resetFilter(): void {\n this.filter = {}\n this.pagination.page = 1\n this.applyFilterAndRender()\n this.emitter.emit('filter:change', this.filter)\n }\n\n getTheme(): ThemeMode {\n return this.theme\n }\n\n setTheme(theme: ThemeMode): void {\n this.theme = theme\n this.root.setAttribute('data-theme', theme)\n this.emitter.emit('theme:change', theme)\n this.config.onThemeChange?.(theme)\n }\n\n expandEntry(id: string): void {\n this.expandedIds.add(id)\n const entry = this.data.find((e) => e.id === id)\n if (entry) this.emitter.emit('entry:expand', entry)\n this.renderTimeline()\n }\n\n collapseEntry(id: string): void {\n this.expandedIds.delete(id)\n const entry = this.data.find((e) => e.id === id)\n if (entry) this.emitter.emit('entry:collapse', entry)\n this.renderTimeline()\n }\n\n collapseAll(): void {\n this.expandedIds.clear()\n this.renderTimeline()\n }\n\n goToPage(page: number): void {\n const totalPages = Math.ceil(this.filteredData.length / this.pagination.pageSize)\n if (page < 1 || page > totalPages) return\n this.pagination.page = page\n this.emitter.emit('page:change', page)\n this.renderTimeline()\n }\n\n getPagination(): PaginationConfig {\n return {\n ...this.pagination,\n total: this.filteredData.length,\n }\n }\n\n on(event: string, callback: (...args: unknown[]) => void): () => void {\n return this.emitter.on(event, callback)\n }\n\n refresh(): void {\n this.applyFilterAndRender()\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n this.emitter.removeAllListeners()\n this.root.remove()\n }\n\n // ===== Internal =====\n\n private applyFilterAndRender(): void {\n this.filteredData = this.filterEngine.filter(this.data, this.filter)\n this.filteredData.sort(\n (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),\n )\n this.pagination.total = this.filteredData.length\n this.renderTimeline()\n }\n\n private getPageData(): LogEntry[] {\n const { page, pageSize } = this.pagination\n const start = (page - 1) * pageSize\n return this.filteredData.slice(start, start + pageSize)\n }\n\n private renderTimeline(): void {\n if (this.destroyed) return\n\n const pageData = this.getPageData()\n\n // Timeline\n const newTimeline = this.timelineRenderer.render(pageData, this.expandedIds)\n\n // Render detail panels for expanded items\n for (const entry of pageData) {\n if (this.expandedIds.has(entry.id)) {\n const itemEl = newTimeline.querySelector(`[data-id=\"${entry.id}\"] .lv-timeline-card`)\n if (itemEl) {\n itemEl.appendChild(this.diffRenderer.renderDetail(entry))\n }\n }\n }\n\n if (this.timelineEl) {\n this.root.removeChild(this.timelineEl)\n }\n this.timelineEl = newTimeline\n this.root.appendChild(this.timelineEl)\n\n // Pagination\n const newPagination = this.timelineRenderer.renderPagination(\n this.pagination.page,\n this.pagination.pageSize,\n this.filteredData.length,\n )\n if (this.paginationEl) {\n this.root.removeChild(this.paginationEl)\n }\n this.paginationEl = newPagination\n this.root.appendChild(this.paginationEl)\n }\n\n private bindEvents(): void {\n this.root.addEventListener('click', (e) => {\n const target = e.target as HTMLElement\n\n // Timeline card click\n const card = target.closest('.lv-timeline-card') as HTMLElement | null\n if (card) {\n const item = card.closest('.lv-timeline-item') as HTMLElement | null\n const id = item?.getAttribute('data-id')\n if (!id) return\n\n const entry = this.filteredData.find((en) => en.id === id)\n if (!entry) return\n\n // Click on expand hint\n if (target.classList.contains('lv-timeline-expand-hint')) {\n if (this.expandedIds.has(id)) {\n this.collapseEntry(id)\n } else {\n this.expandEntry(id)\n }\n return\n }\n\n // Click on card itself\n this.emitter.emit('entry:click', entry)\n this.config.onEntryClick?.(entry)\n }\n\n // Pagination click\n const pageBtn = target.closest('.lv-pagination-btn') as HTMLButtonElement | null\n if (pageBtn && !pageBtn.disabled) {\n const page = parseInt(pageBtn.getAttribute('data-page') || '1', 10)\n this.goToPage(page)\n }\n })\n }\n}\n","/**\n * @composy/log-viewer-core\n * 审计日志查看器 - 操作日志可视化与追溯\n */\n\nexport const VERSION = '0.1.0'\n\n// 类型\nexport type {\n ActionType,\n ThemeMode,\n AnomalyType,\n DiffEntry,\n LogEntry,\n FilterConfig,\n PaginationConfig,\n LogViewerConfig,\n LogViewerInstance,\n LogViewerEvents,\n} from './types'\nexport { ACTION_LABELS, ACTION_COLORS } from './types'\n\n// 核心\nexport { LogViewer } from './core/LogViewer'\nexport { EventEmitter } from './core/EventEmitter'\n\n// 筛选\nexport { FilterEngine } from './filters/FilterEngine'\n\n// 渲染器\nexport { TimelineRenderer } from './renderers/TimelineRenderer'\nexport { DiffRenderer } from './renderers/DiffRenderer'\n\n// 工具\nexport { formatDate, formatRelativeTime, escapeHtml, debounce, uid, formatValue, deepClone } from './utils'\n\n// 样式\nexport { injectStyles } from './styles'\n\n/**\n * 创建 LogViewer 实例 - 工厂函数\n */\nimport type { LogViewerConfig } from './types'\nimport { LogViewer } from './core/LogViewer'\n\n/**\n * createLogViewer - create Log Viewer function\n * @param config - 参数说明\n * @returns 返回值说明\n */\nexport function createLogViewer(config: LogViewerConfig): LogViewer {\n return new LogViewer(config)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?factory(exports):typeof define==="function"&&define.amd?define(["exports"],factory):(global=typeof globalThis!=="undefined"?globalThis:global||self,factory(global.LDesignLogViewer={}));})(this,function(exports){Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const ACTION_LABELS={create:"新增",update:"修改",delete:"删除",query:"查询",login:"登录",logout:"登出",export:"导出",import:"导入",approve:"审批",reject:"驳回",other:"其他"};const ACTION_COLORS={create:"#52c41a",update:"#1890ff",delete:"#ff4d4f",query:"#8c8c8c",login:"#722ed1",logout:"#eb2f96",export:"#fa8c16",import:"#13c2c2",approve:"#52c41a",reject:"#ff4d4f",other:"#d9d9d9"};var EventEmitter=class{constructor(){this.listeners=new Map();}on(event,callback){if(!this.listeners.has(event))this.listeners.set(event,new Set());this.listeners.get(event).add(callback);return()=>this.off(event,callback);}off(event,callback){this.listeners.get(event)?.delete(callback);}emit(event,...args){this.listeners.get(event)?.forEach((cb)=>{try{cb(...args);}catch(e){console.error(`[LogViewer]Event handler error(${event}):`,e);}});}removeAllListeners(event){if(event)this.listeners.delete(event);else this.listeners.clear();}};var FilterEngine=class{filter(entries,config){let result=entries;if(config.user){const keyword=config.user.toLowerCase();result=result.filter((e)=>e.user.toLowerCase().includes(keyword)||e.userId&&e.userId.toLowerCase().includes(keyword));}if(config.action){const actions=Array.isArray(config.action)?config.action:[config.action];result=result.filter((e)=>actions.includes(e.action));}if(config.module){const mod=config.module.toLowerCase();result=result.filter((e)=>e.module.toLowerCase().includes(mod));}if(config.ip){const ip=config.ip.toLowerCase();result=result.filter((e)=>e.ip&&e.ip.toLowerCase().includes(ip));}if(config.timeRange){const[start,end]=config.timeRange;const startTime=new Date(start).getTime();const endTime=new Date(end).getTime();result=result.filter((e)=>{const t=new Date(e.timestamp).getTime();return t>=startTime&&t<=endTime;});}if(config.keyword){const kw=config.keyword.toLowerCase();result=result.filter((e)=>{return[e.user,e.action,e.module,e.detail||"",e.ip||"",e.id].some((t)=>t.toLowerCase().includes(kw));});}return result;}getUniqueUsers(entries){return[...new Set(entries.map((e)=>e.user))].sort();}getUniqueModules(entries){return[...new Set(entries.map((e)=>e.module))].sort();}getUniqueActions(entries){return[...new Set(entries.map((e)=>e.action))].sort();}};let _uid=0;function uid(prefix="lv"){return `${prefix}-${++_uid}-${Math.random().toString(36).slice(2,8)}`;}function formatDate(value,fmt="YYYY-MM-DD HH:mm:ss"){const d=value instanceof Date?value:new Date(value);if(isNaN(d.getTime()))return String(value);const pad=(n)=>String(n).padStart(2,"0");const year=String(d.getFullYear());const month=pad(d.getMonth()+1);const day=pad(d.getDate());const hours=pad(d.getHours());const minutes=pad(d.getMinutes());const seconds=pad(d.getSeconds());return fmt.replace("YYYY",year).replace("MM",month).replace("DD",day).replace("HH",hours).replace("mm",minutes).replace("ss",seconds);}function formatRelativeTime(value){const d=value instanceof Date?value:new Date(value);const diff=Date.now()-d.getTime();if(diff<6e4)return "刚刚";if(diff<36e5)return `${Math.floor(diff/6e4)}\u5206\u949F\u524D`;if(diff<864e5)return `${Math.floor(diff/36e5)}\u5C0F\u65F6\u524D`;if(diff<6048e5)return `${Math.floor(diff/864e5)}\u5929\u524D`;return formatDate(d);}function escapeHtml(str){const map={"&":"&","<":"<",">":">","\"":""","'":"'"};return str.replace(/[&<>"']/g,(c)=>map[c]||c);}function debounce(fn,delay){let timer=null;return((...args)=>{if(timer)clearTimeout(timer);timer=setTimeout(()=>fn(...args),delay);});}function deepClone(obj){if(obj===null||typeof obj!=="object")return obj;try{return JSON.parse(JSON.stringify(obj));}catch{return obj;}}function formatValue(val){if(val===null||val===void 0)return "-";if(typeof val==="object")try{return JSON.stringify(val,null,2);}catch{return String(val);}return String(val);}const ANOMALY_LABELS={"off-hours":"非工作时间",sensitive:"敏感操作","high-frequency":"高频操作",none:""};var TimelineRenderer=class{render(entries,expandedIds){const container=document.createElement("div");container.className="lv-timeline";if(entries.length===0){container.innerHTML=`<div class="lv-empty"><div class="lv-empty-icon">--</div><div>\u6682\u65E0\u65E5\u5FD7\u8BB0\u5F55</div></div>`;container.className="";return container;}for(const entry of entries)container.appendChild(this.renderItem(entry,expandedIds.has(entry.id)));return container;}renderItem(entry,expanded){const item=document.createElement("div");item.className="lv-timeline-item";item.setAttribute("data-id",entry.id);const isAnomaly=entry.anomaly&&entry.anomaly!=="none";const dot=document.createElement("div");dot.className=`lv-timeline-dot${isAnomaly?" lv-timeline-dot--anomaly":""}`;item.appendChild(dot);const card=document.createElement("div");card.className=`lv-timeline-card${isAnomaly?" lv-timeline-card--anomaly":""}`;const header=document.createElement("div");header.className="lv-timeline-header";const time=document.createElement("span");time.className="lv-timeline-time";time.textContent=formatDate(entry.timestamp);header.appendChild(time);const user=document.createElement("span");user.className="lv-timeline-user";user.textContent=entry.user;header.appendChild(user);const actionTag=document.createElement("span");actionTag.className="lv-action-tag";actionTag.textContent=ACTION_LABELS[entry.action]||entry.action;actionTag.style.backgroundColor=ACTION_COLORS[entry.action]||"#8c8c8c";header.appendChild(actionTag);const moduleBadge=document.createElement("span");moduleBadge.className="lv-timeline-module";moduleBadge.textContent=entry.module;header.appendChild(moduleBadge);if(entry.ip){const ip=document.createElement("span");ip.className="lv-timeline-ip";ip.textContent=entry.ip;header.appendChild(ip);}if(isAnomaly){const badge=document.createElement("span");badge.className="lv-anomaly-badge";badge.textContent=ANOMALY_LABELS[entry.anomaly];header.appendChild(badge);}card.appendChild(header);if(entry.detail){const detail=document.createElement("div");detail.className="lv-timeline-detail";detail.textContent=entry.detail;card.appendChild(detail);}if(entry.changes&&entry.changes.length>0){const hint=document.createElement("div");hint.className="lv-timeline-expand-hint";hint.textContent=expanded?"[收起变更详情]":`[\u67E5\u770B\u53D8\u66F4\u8BE6\u60C5(${entry.changes.length}\u4E2A\u5B57\u6BB5)]`;card.appendChild(hint);}item.appendChild(card);return item;}renderPagination(page,pageSize,total){const container=document.createElement("div");container.className="lv-pagination";const totalPages=Math.ceil(total/pageSize);if(totalPages<=1)return container;const prev=document.createElement("button");prev.className="lv-pagination-btn";prev.textContent="<";prev.disabled=page<=1;prev.setAttribute("data-page",String(page-1));container.appendChild(prev);const maxVisible=7;let startPage=Math.max(1,page-Math.floor(maxVisible/2));const endPage=Math.min(totalPages,startPage+maxVisible-1);if(endPage-startPage<maxVisible-1)startPage=Math.max(1,endPage-maxVisible+1);for(let i=startPage;i<=endPage;i++){const btn=document.createElement("button");btn.className=`lv-pagination-btn${i===page?" lv-pagination-btn--active":""}`;btn.textContent=String(i);btn.setAttribute("data-page",String(i));container.appendChild(btn);}const next=document.createElement("button");next.className="lv-pagination-btn";next.textContent=">";next.disabled=page>=totalPages;next.setAttribute("data-page",String(page+1));container.appendChild(next);const info=document.createElement("span");info.className="lv-pagination-info";info.textContent=`\u5171 ${total}\u6761`;container.appendChild(info);return container;}};var DiffRenderer=class{renderDetail(entry){const container=document.createElement("div");container.className="lv-detail";if(entry.changes&&entry.changes.length>0)container.appendChild(this.renderDiffTable(entry.changes));if(entry.metadata&&Object.keys(entry.metadata).length>0){const meta=document.createElement("div");meta.className="lv-metadata";meta.textContent=JSON.stringify(entry.metadata,null,2);container.appendChild(meta);}return container;}renderDiffTable(changes){const table=document.createElement("table");table.className="lv-diff-table";const thead=document.createElement("thead");thead.innerHTML=`<tr><th>\u5B57\u6BB5</th><th>\u53D8\u66F4\u524D</th><th>\u53D8\u66F4\u540E</th></tr>`;table.appendChild(thead);const tbody=document.createElement("tbody");for(const change of changes){const tr=document.createElement("tr");const tdField=document.createElement("td");tdField.textContent=change.label||change.field;tr.appendChild(tdField);const tdOld=document.createElement("td");tdOld.className="lv-diff-old";tdOld.textContent=formatValue(change.oldValue);tr.appendChild(tdOld);const tdNew=document.createElement("td");tdNew.className="lv-diff-new";tdNew.textContent=formatValue(change.newValue);tr.appendChild(tdNew);tbody.appendChild(tr);}table.appendChild(tbody);return table;}};const STYLE_ID="ldesign-log-viewer-styles";function injectStyles(){if(typeof document==="undefined")return;if(document.getElementById(STYLE_ID))return;const style=document.createElement("style");style.id=STYLE_ID;style.textContent=CSS_TEXT;document.head.appendChild(style);}const CSS_TEXT=` .lv-root{--lv-bg:#ffffff;--lv-bg-secondary:#f7f8fa;--lv-bg-hover:#f0f3ff;--lv-border:#ebedf0;--lv-text:#1a1a2e;--lv-text-secondary:#666;--lv-text-muted:#999;--lv-primary:#4f6ef7;--lv-primary-light:#edf0ff;--lv-shadow:rgba(0,0,0,0.04);--lv-timeline-line:#e0e0e0;--lv-timeline-dot:#4f6ef7;--lv-diff-add-bg:#f0fff4;--lv-diff-add-text:#22863a;--lv-diff-del-bg:#fff5f5;--lv-diff-del-text:#cb2431;--lv-anomaly-bg:#fff2e8;--lv-anomaly-border:#ffbb96;--lv-radius:8px;--lv-font:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;--lv-font-mono:'Fira Code','SF Mono',Consolas,monospace;}.lv-root[data-theme="dark"]{--lv-bg:#1a1a2e;--lv-bg-secondary:#16213e;--lv-bg-hover:#1e2a4a;--lv-border:#2a2a4a;--lv-text:#e0e0e0;--lv-text-secondary:#a0a0b0;--lv-text-muted:#666680;--lv-primary:#6b85f9;--lv-primary-light:#252545;--lv-shadow:rgba(0,0,0,0.2);--lv-timeline-line:#333355;--lv-timeline-dot:#6b85f9;--lv-diff-add-bg:#0d2818;--lv-diff-add-text:#85e89d;--lv-diff-del-bg:#2d1214;--lv-diff-del-text:#f97583;--lv-anomaly-bg:#2d1a0a;--lv-anomaly-border:#8b5e3c;}.lv-root{font-family:var(--lv-font);font-size:14px;color:var(--lv-text);background:var(--lv-bg);line-height:1.6;}.lv-filter{display:flex;flex-wrap:wrap;gap:10px;padding:16px;background:var(--lv-bg);border:1px solid var(--lv-border);border-radius:var(--lv-radius);margin-bottom:16px;align-items:center;}.lv-filter-item{display:flex;align-items:center;gap:6px;font-size:13px;}.lv-filter-label{color:var(--lv-text-secondary);font-size:12px;white-space:nowrap;}.lv-filter-input{padding:5px 10px;border:1px solid var(--lv-border);border-radius:6px;font-size:13px;background:var(--lv-bg);color:var(--lv-text);outline:none;transition:border-color 0.2s;min-width:120px;}.lv-filter-input:focus{border-color:var(--lv-primary);}.lv-filter-select{padding:5px 10px;border:1px solid var(--lv-border);border-radius:6px;font-size:13px;background:var(--lv-bg);color:var(--lv-text);outline:none;cursor:pointer;}.lv-filter-btn{display:inline-flex;align-items:center;gap:4px;padding:5px 14px;font-size:13px;border:1px solid var(--lv-border);border-radius:6px;background:var(--lv-bg);color:var(--lv-text);cursor:pointer;transition:all 0.15s;}.lv-filter-btn:hover{border-color:var(--lv-primary);color:var(--lv-primary);}.lv-filter-btn--primary{background:var(--lv-primary);border-color:var(--lv-primary);color:#fff;}.lv-filter-btn--primary:hover{opacity:0.85;}.lv-timeline{position:relative;padding-left:28px;}.lv-timeline::before{content:'';position:absolute;left:9px;top:0;bottom:0;width:2px;background:var(--lv-timeline-line);}.lv-timeline-item{position:relative;padding:0 0 20px 0;}.lv-timeline-dot{position:absolute;left:-25px;top:6px;width:12px;height:12px;border-radius:50%;background:var(--lv-bg);border:2px solid var(--lv-timeline-dot);z-index:1;}.lv-timeline-dot--anomaly{border-color:#ff4d4f;background:#fff1f0;}.lv-timeline-card{background:var(--lv-bg);border:1px solid var(--lv-border);border-radius:var(--lv-radius);padding:14px 18px;cursor:pointer;transition:all 0.2s;box-shadow:0 1px 3px var(--lv-shadow);}.lv-timeline-card:hover{border-color:var(--lv-primary);box-shadow:0 2px 8px var(--lv-shadow);}.lv-timeline-card--anomaly{border-left:3px solid var(--lv-anomaly-border);background:var(--lv-anomaly-bg);}.lv-timeline-header{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:4px;}.lv-timeline-time{font-size:12px;color:var(--lv-text-muted);font-family:var(--lv-font-mono);}.lv-timeline-user{font-weight:600;font-size:14px;}.lv-action-tag{display:inline-block;padding:1px 8px;font-size:12px;border-radius:4px;font-weight:500;color:#fff;}.lv-timeline-module{font-size:12px;color:var(--lv-text-secondary);background:var(--lv-bg-secondary);padding:1px 8px;border-radius:4px;}.lv-timeline-ip{font-size:12px;color:var(--lv-text-muted);font-family:var(--lv-font-mono);}.lv-anomaly-badge{font-size:11px;padding:1px 6px;border-radius:3px;background:#fff2e8;color:#d46b08;border:1px solid #ffd591;}.lv-timeline-detail{font-size:13px;color:var(--lv-text-secondary);margin-top:6px;}.lv-timeline-expand-hint{font-size:12px;color:var(--lv-primary);margin-top:6px;cursor:pointer;}.lv-detail{margin-top:12px;padding-top:12px;border-top:1px dashed var(--lv-border);animation:lv-slide-down 0.2s ease;}@keyframes lv-slide-down{from{opacity:0;transform:translateY(-8px);}to{opacity:1;transform:translateY(0);}}.lv-diff-table{width:100%;border-collapse:collapse;font-size:13px;border:1px solid var(--lv-border);border-radius:6px;overflow:hidden;}.lv-diff-table th{background:var(--lv-bg-secondary);padding:8px 12px;text-align:left;font-weight:600;font-size:12px;color:var(--lv-text-secondary);border-bottom:1px solid var(--lv-border);}.lv-diff-table td{padding:8px 12px;border-bottom:1px solid var(--lv-border);vertical-align:top;}.lv-diff-table tr:last-child td{border-bottom:none;}.lv-diff-old{background:var(--lv-diff-del-bg);color:var(--lv-diff-del-text);text-decoration:line-through;font-family:var(--lv-font-mono);font-size:12px;}.lv-diff-new{background:var(--lv-diff-add-bg);color:var(--lv-diff-add-text);font-family:var(--lv-font-mono);font-size:12px;}.lv-metadata{margin-top:10px;background:var(--lv-bg-secondary);border-radius:6px;padding:10px 14px;font-size:12px;font-family:var(--lv-font-mono);white-space:pre-wrap;word-break:break-all;color:var(--lv-text-secondary);}.lv-pagination{display:flex;align-items:center;justify-content:center;gap:6px;padding:16px 0;}.lv-pagination-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid var(--lv-border);border-radius:6px;background:var(--lv-bg);color:var(--lv-text);font-size:13px;cursor:pointer;transition:all 0.15s;}.lv-pagination-btn:hover{border-color:var(--lv-primary);color:var(--lv-primary);}.lv-pagination-btn--active{background:var(--lv-primary);border-color:var(--lv-primary);color:#fff;}.lv-pagination-btn:disabled{opacity:0.4;cursor:not-allowed;}.lv-pagination-info{font-size:13px;color:var(--lv-text-secondary);margin:0 8px;}.lv-empty{text-align:center;padding:48px 16px;color:var(--lv-text-muted);font-size:14px;}.lv-empty-icon{font-size:48px;margin-bottom:12px;opacity:0.3;}`;var LogViewer=class{constructor(config){this.emitter=new EventEmitter();this.filterEngine=new FilterEngine();this.timelineRenderer=new TimelineRenderer();this.diffRenderer=new DiffRenderer();this.data=[];this.filteredData=[];this.filter={};this.pagination={page:1,pageSize:20};this.theme="light";this.expandedIds=new Set();this.timelineEl=null;this.paginationEl=null;this.destroyed=false;this.config=config;const container=typeof config.container==="string"?document.querySelector(config.container):config.container;if(!container)throw new Error(`[LogViewer]Container not found:${config.container}`);injectStyles();this.root=document.createElement("div");this.root.className="lv-root";container.appendChild(this.root);this.theme=config.theme||"light";this.root.setAttribute("data-theme",this.theme);if(config.pageSize)this.pagination.pageSize=config.pageSize;if(config.pagination)Object.assign(this.pagination,config.pagination);if(config.filter)this.filter=deepClone(config.filter);if(config.data)this.data=[...config.data];this.applyFilterAndRender();this.bindEvents();}getData(){return[...this.data];}setData(entries){this.data=[...entries];this.pagination.page=1;this.applyFilterAndRender();this.emitter.emit("data:change",this.data);}appendData(entries){this.data.push(...entries);this.applyFilterAndRender();this.emitter.emit("data:change",this.data);}getFilter(){return deepClone(this.filter);}setFilter(filter){this.filter=deepClone(filter);this.pagination.page=1;this.applyFilterAndRender();this.emitter.emit("filter:change",this.filter);this.config.onFilterChange?.(this.filter);}resetFilter(){this.filter={};this.pagination.page=1;this.applyFilterAndRender();this.emitter.emit("filter:change",this.filter);}getTheme(){return this.theme;}setTheme(theme){this.theme=theme;this.root.setAttribute("data-theme",theme);this.emitter.emit("theme:change",theme);this.config.onThemeChange?.(theme);}expandEntry(id){this.expandedIds.add(id);const entry=this.data.find((e)=>e.id===id);if(entry)this.emitter.emit("entry:expand",entry);this.renderTimeline();}collapseEntry(id){this.expandedIds.delete(id);const entry=this.data.find((e)=>e.id===id);if(entry)this.emitter.emit("entry:collapse",entry);this.renderTimeline();}collapseAll(){this.expandedIds.clear();this.renderTimeline();}goToPage(page){const totalPages=Math.ceil(this.filteredData.length/this.pagination.pageSize);if(page<1||page>totalPages)return;this.pagination.page=page;this.emitter.emit("page:change",page);this.renderTimeline();}getPagination(){return{...this.pagination,total:this.filteredData.length};}on(event,callback){return this.emitter.on(event,callback);}refresh(){this.applyFilterAndRender();}destroy(){if(this.destroyed)return;this.destroyed=true;this.emitter.removeAllListeners();this.root.remove();}applyFilterAndRender(){this.filteredData=this.filterEngine.filter(this.data,this.filter);this.filteredData.sort((a,b)=>new Date(b.timestamp).getTime()-new Date(a.timestamp).getTime());this.pagination.total=this.filteredData.length;this.renderTimeline();}getPageData(){const{page,pageSize}=this.pagination;const start=(page-1)*pageSize;return this.filteredData.slice(start,start+pageSize);}renderTimeline(){if(this.destroyed)return;const pageData=this.getPageData();const newTimeline=this.timelineRenderer.render(pageData,this.expandedIds);for(const entry of pageData)if(this.expandedIds.has(entry.id)){const itemEl=newTimeline.querySelector(`[data-id="${entry.id}"].lv-timeline-card`);if(itemEl)itemEl.appendChild(this.diffRenderer.renderDetail(entry));}if(this.timelineEl)this.root.removeChild(this.timelineEl);this.timelineEl=newTimeline;this.root.appendChild(this.timelineEl);const newPagination=this.timelineRenderer.renderPagination(this.pagination.page,this.pagination.pageSize,this.filteredData.length);if(this.paginationEl)this.root.removeChild(this.paginationEl);this.paginationEl=newPagination;this.root.appendChild(this.paginationEl);}bindEvents(){this.root.addEventListener("click",(e)=>{const target=e.target;const card=target.closest(".lv-timeline-card");if(card){const id=card.closest(".lv-timeline-item")?.getAttribute("data-id");if(!id)return;const entry=this.filteredData.find((en)=>en.id===id);if(!entry)return;if(target.classList.contains("lv-timeline-expand-hint")){if(this.expandedIds.has(id))this.collapseEntry(id);else this.expandEntry(id);return;}this.emitter.emit("entry:click",entry);this.config.onEntryClick?.(entry);}const pageBtn=target.closest(".lv-pagination-btn");if(pageBtn&&!pageBtn.disabled){const page=parseInt(pageBtn.getAttribute("data-page")||"1",10);this.goToPage(page);}});}};const VERSION="0.1.0";function createLogViewer(config){return new LogViewer(config);}exports.ACTION_COLORS=ACTION_COLORS;exports.ACTION_LABELS=ACTION_LABELS;exports.DiffRenderer=DiffRenderer;exports.EventEmitter=EventEmitter;exports.FilterEngine=FilterEngine;exports.LogViewer=LogViewer;exports.TimelineRenderer=TimelineRenderer;exports.VERSION=VERSION;exports.createLogViewer=createLogViewer;exports.debounce=debounce;exports.deepClone=deepClone;exports.escapeHtml=escapeHtml;exports.formatDate=formatDate;exports.formatRelativeTime=formatRelativeTime;exports.formatValue=formatValue;exports.injectStyles=injectStyles;exports.uid=uid;});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 简单的事件发射器
|
|
3
|
+
*/
|
|
4
|
+
export declare class EventEmitter {
|
|
5
|
+
private listeners;
|
|
6
|
+
on(event: string, callback: (...args: unknown[]) => void): () => void;
|
|
7
|
+
off(event: string, callback: (...args: unknown[]) => void): void;
|
|
8
|
+
emit(event: string, ...args: unknown[]): void;
|
|
9
|
+
removeAllListeners(event?: string): void;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=EventEmitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventEmitter.d.ts","sourceRoot":"","sources":["../../src/core/EventEmitter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,SAAS,CAAuD;IAExE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI;IAQrE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI;IAIhE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAU7C,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;CAOzC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/log-viewer-core v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:33:32 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
//#region src/core/EventEmitter.ts
|
|
11
|
+
var EventEmitter = class {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
14
|
+
}
|
|
15
|
+
on(event, callback) {
|
|
16
|
+
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
17
|
+
this.listeners.get(event).add(callback);
|
|
18
|
+
return () => this.off(event, callback);
|
|
19
|
+
}
|
|
20
|
+
off(event, callback) {
|
|
21
|
+
this.listeners.get(event)?.delete(callback);
|
|
22
|
+
}
|
|
23
|
+
emit(event, ...args) {
|
|
24
|
+
this.listeners.get(event)?.forEach((cb) => {
|
|
25
|
+
try {
|
|
26
|
+
cb(...args);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error(`[LogViewer] Event handler error (${event}):`, e);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
removeAllListeners(event) {
|
|
33
|
+
if (event) this.listeners.delete(event);
|
|
34
|
+
else this.listeners.clear();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { EventEmitter };
|
|
39
|
+
/*! End of @composy/log-viewer-core | Powered by @composy/builder */
|
|
40
|
+
//# sourceMappingURL=EventEmitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventEmitter.js","names":[],"sources":["../../src/core/EventEmitter.ts"],"sourcesContent":["/**\n * 简单的事件发射器\n */\nexport class EventEmitter {\n private listeners = new Map<string, Set<(...args: unknown[]) => void>>()\n\n on(event: string, callback: (...args: unknown[]) => void): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set())\n }\n this.listeners.get(event)!.add(callback)\n return () => this.off(event, callback)\n }\n\n off(event: string, callback: (...args: unknown[]) => void): void {\n this.listeners.get(event)?.delete(callback)\n }\n\n emit(event: string, ...args: unknown[]): void {\n this.listeners.get(event)?.forEach((cb) => {\n try {\n cb(...args)\n } catch (e) {\n console.error(`[LogViewer] Event handler error (${event}):`, e)\n }\n })\n }\n\n removeAllListeners(event?: string): void {\n if (event) {\n this.listeners.delete(event)\n } else {\n this.listeners.clear()\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAGO,IAAM,eAAN,MAAmB;CAAnB,cAAA;EACL,KAAQ,4BAAY,IAAI,IAA+C;CAAA;CAEvE,GAAG,OAAe,UAAoD;EACpE,IAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAC3B,KAAK,UAAU,IAAI,uBAAO,IAAI,IAAI,CAAC;EAErC,KAAK,UAAU,IAAI,KAAK,CAAA,CAAG,IAAI,QAAQ;EACvC,aAAa,KAAK,IAAI,OAAO,QAAQ;CACvC;CAEA,IAAI,OAAe,UAA8C;EAC/D,KAAK,UAAU,IAAI,KAAK,CAAA,EAAG,OAAO,QAAQ;CAC5C;CAEA,KAAK,OAAA,GAAkB,MAAuB;EAC5C,KAAK,UAAU,IAAI,KAAK,CAAA,EAAG,SAAS,OAAO;GACzC,IAAI;IACF,GAAG,GAAG,IAAI;GACZ,SAAS,GAAG;IACV,QAAQ,MAAM,oCAAoC,MAAK,KAAM,CAAC;GAChE;EACF,CAAC;CACH;CAEA,mBAAmB,OAAsB;EACvC,IAAI,OACF,KAAK,UAAU,OAAO,KAAK;OAE3B,KAAK,UAAU,MAAM;CAEzB;AACF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LogViewer 核心引擎
|
|
3
|
+
*/
|
|
4
|
+
import type { LogEntry, LogViewerConfig, LogViewerInstance, FilterConfig, PaginationConfig, ThemeMode } from '../types';
|
|
5
|
+
/**
|
|
6
|
+
* LogViewer - Log Viewer class
|
|
7
|
+
*/
|
|
8
|
+
export declare class LogViewer implements LogViewerInstance {
|
|
9
|
+
private root;
|
|
10
|
+
private emitter;
|
|
11
|
+
private filterEngine;
|
|
12
|
+
private timelineRenderer;
|
|
13
|
+
private diffRenderer;
|
|
14
|
+
private data;
|
|
15
|
+
private filteredData;
|
|
16
|
+
private filter;
|
|
17
|
+
private pagination;
|
|
18
|
+
private theme;
|
|
19
|
+
private expandedIds;
|
|
20
|
+
private config;
|
|
21
|
+
private timelineEl;
|
|
22
|
+
private paginationEl;
|
|
23
|
+
private destroyed;
|
|
24
|
+
constructor(config: LogViewerConfig);
|
|
25
|
+
getData(): LogEntry[];
|
|
26
|
+
setData(entries: LogEntry[]): void;
|
|
27
|
+
appendData(entries: LogEntry[]): void;
|
|
28
|
+
getFilter(): FilterConfig;
|
|
29
|
+
setFilter(filter: FilterConfig): void;
|
|
30
|
+
resetFilter(): void;
|
|
31
|
+
getTheme(): ThemeMode;
|
|
32
|
+
setTheme(theme: ThemeMode): void;
|
|
33
|
+
expandEntry(id: string): void;
|
|
34
|
+
collapseEntry(id: string): void;
|
|
35
|
+
collapseAll(): void;
|
|
36
|
+
goToPage(page: number): void;
|
|
37
|
+
getPagination(): PaginationConfig;
|
|
38
|
+
on(event: string, callback: (...args: unknown[]) => void): () => void;
|
|
39
|
+
refresh(): void;
|
|
40
|
+
destroy(): void;
|
|
41
|
+
private applyFilterAndRender;
|
|
42
|
+
private getPageData;
|
|
43
|
+
private renderTimeline;
|
|
44
|
+
private bindEvents;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=LogViewer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LogViewer.d.ts","sourceRoot":"","sources":["../../src/core/LogViewer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,QAAQ,EACR,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,SAAS,EACV,MAAM,UAAU,CAAA;AAQjB;;GAEG;AACH,qBAAa,SAAU,YAAW,iBAAiB;IACjD,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,gBAAgB,CAAyB;IACjD,OAAO,CAAC,YAAY,CAAqB;IAEzC,OAAO,CAAC,IAAI,CAAiB;IAC7B,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,MAAM,CAAiB;IAE/B,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,SAAS,CAAQ;gBAEb,MAAM,EAAE,eAAe;IAqCnC,OAAO,IAAI,QAAQ,EAAE;IAIrB,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI;IAOlC,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI;IAMrC,SAAS,IAAI,YAAY;IAIzB,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAQrC,WAAW,IAAI,IAAI;IAOnB,QAAQ,IAAI,SAAS;IAIrB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAOhC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAO7B,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAO/B,WAAW,IAAI,IAAI;IAKnB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQ5B,aAAa,IAAI,gBAAgB;IAOjC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI;IAIrE,OAAO,IAAI,IAAI;IAIf,OAAO,IAAI,IAAI;IASf,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,UAAU;CAqCnB"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/log-viewer-core v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:33:32 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
import { EventEmitter } from "./EventEmitter.js";
|
|
11
|
+
import { FilterEngine } from "../filters/FilterEngine.js";
|
|
12
|
+
import { deepClone } from "../utils/index.js";
|
|
13
|
+
import { TimelineRenderer } from "../renderers/TimelineRenderer.js";
|
|
14
|
+
import { DiffRenderer } from "../renderers/DiffRenderer.js";
|
|
15
|
+
import { injectStyles } from "../styles/index.js";
|
|
16
|
+
//#region src/core/LogViewer.ts
|
|
17
|
+
var LogViewer = class {
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.emitter = new EventEmitter();
|
|
20
|
+
this.filterEngine = new FilterEngine();
|
|
21
|
+
this.timelineRenderer = new TimelineRenderer();
|
|
22
|
+
this.diffRenderer = new DiffRenderer();
|
|
23
|
+
this.data = [];
|
|
24
|
+
this.filteredData = [];
|
|
25
|
+
this.filter = {};
|
|
26
|
+
this.pagination = {
|
|
27
|
+
page: 1,
|
|
28
|
+
pageSize: 20
|
|
29
|
+
};
|
|
30
|
+
this.theme = "light";
|
|
31
|
+
this.expandedIds = /* @__PURE__ */ new Set();
|
|
32
|
+
this.timelineEl = null;
|
|
33
|
+
this.paginationEl = null;
|
|
34
|
+
this.destroyed = false;
|
|
35
|
+
this.config = config;
|
|
36
|
+
const container = typeof config.container === "string" ? document.querySelector(config.container) : config.container;
|
|
37
|
+
if (!container) throw new Error(`[LogViewer] Container not found: ${config.container}`);
|
|
38
|
+
injectStyles();
|
|
39
|
+
this.root = document.createElement("div");
|
|
40
|
+
this.root.className = "lv-root";
|
|
41
|
+
container.appendChild(this.root);
|
|
42
|
+
this.theme = config.theme || "light";
|
|
43
|
+
this.root.setAttribute("data-theme", this.theme);
|
|
44
|
+
if (config.pageSize) this.pagination.pageSize = config.pageSize;
|
|
45
|
+
if (config.pagination) Object.assign(this.pagination, config.pagination);
|
|
46
|
+
if (config.filter) this.filter = deepClone(config.filter);
|
|
47
|
+
if (config.data) this.data = [...config.data];
|
|
48
|
+
this.applyFilterAndRender();
|
|
49
|
+
this.bindEvents();
|
|
50
|
+
}
|
|
51
|
+
getData() {
|
|
52
|
+
return [...this.data];
|
|
53
|
+
}
|
|
54
|
+
setData(entries) {
|
|
55
|
+
this.data = [...entries];
|
|
56
|
+
this.pagination.page = 1;
|
|
57
|
+
this.applyFilterAndRender();
|
|
58
|
+
this.emitter.emit("data:change", this.data);
|
|
59
|
+
}
|
|
60
|
+
appendData(entries) {
|
|
61
|
+
this.data.push(...entries);
|
|
62
|
+
this.applyFilterAndRender();
|
|
63
|
+
this.emitter.emit("data:change", this.data);
|
|
64
|
+
}
|
|
65
|
+
getFilter() {
|
|
66
|
+
return deepClone(this.filter);
|
|
67
|
+
}
|
|
68
|
+
setFilter(filter) {
|
|
69
|
+
this.filter = deepClone(filter);
|
|
70
|
+
this.pagination.page = 1;
|
|
71
|
+
this.applyFilterAndRender();
|
|
72
|
+
this.emitter.emit("filter:change", this.filter);
|
|
73
|
+
this.config.onFilterChange?.(this.filter);
|
|
74
|
+
}
|
|
75
|
+
resetFilter() {
|
|
76
|
+
this.filter = {};
|
|
77
|
+
this.pagination.page = 1;
|
|
78
|
+
this.applyFilterAndRender();
|
|
79
|
+
this.emitter.emit("filter:change", this.filter);
|
|
80
|
+
}
|
|
81
|
+
getTheme() {
|
|
82
|
+
return this.theme;
|
|
83
|
+
}
|
|
84
|
+
setTheme(theme) {
|
|
85
|
+
this.theme = theme;
|
|
86
|
+
this.root.setAttribute("data-theme", theme);
|
|
87
|
+
this.emitter.emit("theme:change", theme);
|
|
88
|
+
this.config.onThemeChange?.(theme);
|
|
89
|
+
}
|
|
90
|
+
expandEntry(id) {
|
|
91
|
+
this.expandedIds.add(id);
|
|
92
|
+
const entry = this.data.find((e) => e.id === id);
|
|
93
|
+
if (entry) this.emitter.emit("entry:expand", entry);
|
|
94
|
+
this.renderTimeline();
|
|
95
|
+
}
|
|
96
|
+
collapseEntry(id) {
|
|
97
|
+
this.expandedIds.delete(id);
|
|
98
|
+
const entry = this.data.find((e) => e.id === id);
|
|
99
|
+
if (entry) this.emitter.emit("entry:collapse", entry);
|
|
100
|
+
this.renderTimeline();
|
|
101
|
+
}
|
|
102
|
+
collapseAll() {
|
|
103
|
+
this.expandedIds.clear();
|
|
104
|
+
this.renderTimeline();
|
|
105
|
+
}
|
|
106
|
+
goToPage(page) {
|
|
107
|
+
const totalPages = Math.ceil(this.filteredData.length / this.pagination.pageSize);
|
|
108
|
+
if (page < 1 || page > totalPages) return;
|
|
109
|
+
this.pagination.page = page;
|
|
110
|
+
this.emitter.emit("page:change", page);
|
|
111
|
+
this.renderTimeline();
|
|
112
|
+
}
|
|
113
|
+
getPagination() {
|
|
114
|
+
return {
|
|
115
|
+
...this.pagination,
|
|
116
|
+
total: this.filteredData.length
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
on(event, callback) {
|
|
120
|
+
return this.emitter.on(event, callback);
|
|
121
|
+
}
|
|
122
|
+
refresh() {
|
|
123
|
+
this.applyFilterAndRender();
|
|
124
|
+
}
|
|
125
|
+
destroy() {
|
|
126
|
+
if (this.destroyed) return;
|
|
127
|
+
this.destroyed = true;
|
|
128
|
+
this.emitter.removeAllListeners();
|
|
129
|
+
this.root.remove();
|
|
130
|
+
}
|
|
131
|
+
applyFilterAndRender() {
|
|
132
|
+
this.filteredData = this.filterEngine.filter(this.data, this.filter);
|
|
133
|
+
this.filteredData.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
134
|
+
this.pagination.total = this.filteredData.length;
|
|
135
|
+
this.renderTimeline();
|
|
136
|
+
}
|
|
137
|
+
getPageData() {
|
|
138
|
+
const { page, pageSize } = this.pagination;
|
|
139
|
+
const start = (page - 1) * pageSize;
|
|
140
|
+
return this.filteredData.slice(start, start + pageSize);
|
|
141
|
+
}
|
|
142
|
+
renderTimeline() {
|
|
143
|
+
if (this.destroyed) return;
|
|
144
|
+
const pageData = this.getPageData();
|
|
145
|
+
const newTimeline = this.timelineRenderer.render(pageData, this.expandedIds);
|
|
146
|
+
for (const entry of pageData) if (this.expandedIds.has(entry.id)) {
|
|
147
|
+
const itemEl = newTimeline.querySelector(`[data-id="${entry.id}"] .lv-timeline-card`);
|
|
148
|
+
if (itemEl) itemEl.appendChild(this.diffRenderer.renderDetail(entry));
|
|
149
|
+
}
|
|
150
|
+
if (this.timelineEl) this.root.removeChild(this.timelineEl);
|
|
151
|
+
this.timelineEl = newTimeline;
|
|
152
|
+
this.root.appendChild(this.timelineEl);
|
|
153
|
+
const newPagination = this.timelineRenderer.renderPagination(this.pagination.page, this.pagination.pageSize, this.filteredData.length);
|
|
154
|
+
if (this.paginationEl) this.root.removeChild(this.paginationEl);
|
|
155
|
+
this.paginationEl = newPagination;
|
|
156
|
+
this.root.appendChild(this.paginationEl);
|
|
157
|
+
}
|
|
158
|
+
bindEvents() {
|
|
159
|
+
this.root.addEventListener("click", (e) => {
|
|
160
|
+
const target = e.target;
|
|
161
|
+
const card = target.closest(".lv-timeline-card");
|
|
162
|
+
if (card) {
|
|
163
|
+
const id = card.closest(".lv-timeline-item")?.getAttribute("data-id");
|
|
164
|
+
if (!id) return;
|
|
165
|
+
const entry = this.filteredData.find((en) => en.id === id);
|
|
166
|
+
if (!entry) return;
|
|
167
|
+
if (target.classList.contains("lv-timeline-expand-hint")) {
|
|
168
|
+
if (this.expandedIds.has(id)) this.collapseEntry(id);
|
|
169
|
+
else this.expandEntry(id);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this.emitter.emit("entry:click", entry);
|
|
173
|
+
this.config.onEntryClick?.(entry);
|
|
174
|
+
}
|
|
175
|
+
const pageBtn = target.closest(".lv-pagination-btn");
|
|
176
|
+
if (pageBtn && !pageBtn.disabled) {
|
|
177
|
+
const page = parseInt(pageBtn.getAttribute("data-page") || "1", 10);
|
|
178
|
+
this.goToPage(page);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
//#endregion
|
|
184
|
+
export { LogViewer };
|
|
185
|
+
/*! End of @composy/log-viewer-core | Powered by @composy/builder */
|
|
186
|
+
//# sourceMappingURL=LogViewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LogViewer.js","names":[],"sources":["../../src/core/LogViewer.ts"],"sourcesContent":["/**\n * LogViewer 核心引擎\n */\n\nimport type {\n LogEntry,\n LogViewerConfig,\n LogViewerInstance,\n FilterConfig,\n PaginationConfig,\n ThemeMode,\n} from '../types'\nimport { EventEmitter } from './EventEmitter'\nimport { FilterEngine } from '../filters/FilterEngine'\nimport { TimelineRenderer } from '../renderers/TimelineRenderer'\nimport { DiffRenderer } from '../renderers/DiffRenderer'\nimport { injectStyles } from '../styles'\nimport { deepClone } from '../utils'\n\n/**\n * LogViewer - Log Viewer class\n */\nexport class LogViewer implements LogViewerInstance {\n private root: HTMLElement\n private emitter = new EventEmitter()\n private filterEngine = new FilterEngine()\n private timelineRenderer = new TimelineRenderer()\n private diffRenderer = new DiffRenderer()\n\n private data: LogEntry[] = []\n private filteredData: LogEntry[] = []\n private filter: FilterConfig = {}\n private pagination: PaginationConfig = { page: 1, pageSize: 20 }\n private theme: ThemeMode = 'light'\n private expandedIds = new Set<string>()\n private config: LogViewerConfig\n\n private timelineEl: HTMLElement | null = null\n private paginationEl: HTMLElement | null = null\n private destroyed = false\n\n constructor(config: LogViewerConfig) {\n this.config = config\n\n // Resolve container\n const container =\n typeof config.container === 'string'\n ? document.querySelector<HTMLElement>(config.container)\n : config.container\n if (!container) throw new Error(`[LogViewer] Container not found: ${config.container}`)\n\n // Inject global styles\n injectStyles()\n\n // Create root\n this.root = document.createElement('div')\n this.root.className = 'lv-root'\n container.appendChild(this.root)\n\n // Apply config\n this.theme = config.theme || 'light'\n this.root.setAttribute('data-theme', this.theme)\n\n if (config.pageSize) this.pagination.pageSize = config.pageSize\n if (config.pagination) Object.assign(this.pagination, config.pagination)\n if (config.filter) this.filter = deepClone(config.filter)\n\n // Load data\n if (config.data) {\n this.data = [...config.data]\n }\n\n this.applyFilterAndRender()\n this.bindEvents()\n }\n\n // ===== Public API =====\n\n getData(): LogEntry[] {\n return [...this.data]\n }\n\n setData(entries: LogEntry[]): void {\n this.data = [...entries]\n this.pagination.page = 1\n this.applyFilterAndRender()\n this.emitter.emit('data:change', this.data)\n }\n\n appendData(entries: LogEntry[]): void {\n this.data.push(...entries)\n this.applyFilterAndRender()\n this.emitter.emit('data:change', this.data)\n }\n\n getFilter(): FilterConfig {\n return deepClone(this.filter)\n }\n\n setFilter(filter: FilterConfig): void {\n this.filter = deepClone(filter)\n this.pagination.page = 1\n this.applyFilterAndRender()\n this.emitter.emit('filter:change', this.filter)\n this.config.onFilterChange?.(this.filter)\n }\n\n resetFilter(): void {\n this.filter = {}\n this.pagination.page = 1\n this.applyFilterAndRender()\n this.emitter.emit('filter:change', this.filter)\n }\n\n getTheme(): ThemeMode {\n return this.theme\n }\n\n setTheme(theme: ThemeMode): void {\n this.theme = theme\n this.root.setAttribute('data-theme', theme)\n this.emitter.emit('theme:change', theme)\n this.config.onThemeChange?.(theme)\n }\n\n expandEntry(id: string): void {\n this.expandedIds.add(id)\n const entry = this.data.find((e) => e.id === id)\n if (entry) this.emitter.emit('entry:expand', entry)\n this.renderTimeline()\n }\n\n collapseEntry(id: string): void {\n this.expandedIds.delete(id)\n const entry = this.data.find((e) => e.id === id)\n if (entry) this.emitter.emit('entry:collapse', entry)\n this.renderTimeline()\n }\n\n collapseAll(): void {\n this.expandedIds.clear()\n this.renderTimeline()\n }\n\n goToPage(page: number): void {\n const totalPages = Math.ceil(this.filteredData.length / this.pagination.pageSize)\n if (page < 1 || page > totalPages) return\n this.pagination.page = page\n this.emitter.emit('page:change', page)\n this.renderTimeline()\n }\n\n getPagination(): PaginationConfig {\n return {\n ...this.pagination,\n total: this.filteredData.length,\n }\n }\n\n on(event: string, callback: (...args: unknown[]) => void): () => void {\n return this.emitter.on(event, callback)\n }\n\n refresh(): void {\n this.applyFilterAndRender()\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n this.emitter.removeAllListeners()\n this.root.remove()\n }\n\n // ===== Internal =====\n\n private applyFilterAndRender(): void {\n this.filteredData = this.filterEngine.filter(this.data, this.filter)\n this.filteredData.sort(\n (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),\n )\n this.pagination.total = this.filteredData.length\n this.renderTimeline()\n }\n\n private getPageData(): LogEntry[] {\n const { page, pageSize } = this.pagination\n const start = (page - 1) * pageSize\n return this.filteredData.slice(start, start + pageSize)\n }\n\n private renderTimeline(): void {\n if (this.destroyed) return\n\n const pageData = this.getPageData()\n\n // Timeline\n const newTimeline = this.timelineRenderer.render(pageData, this.expandedIds)\n\n // Render detail panels for expanded items\n for (const entry of pageData) {\n if (this.expandedIds.has(entry.id)) {\n const itemEl = newTimeline.querySelector(`[data-id=\"${entry.id}\"] .lv-timeline-card`)\n if (itemEl) {\n itemEl.appendChild(this.diffRenderer.renderDetail(entry))\n }\n }\n }\n\n if (this.timelineEl) {\n this.root.removeChild(this.timelineEl)\n }\n this.timelineEl = newTimeline\n this.root.appendChild(this.timelineEl)\n\n // Pagination\n const newPagination = this.timelineRenderer.renderPagination(\n this.pagination.page,\n this.pagination.pageSize,\n this.filteredData.length,\n )\n if (this.paginationEl) {\n this.root.removeChild(this.paginationEl)\n }\n this.paginationEl = newPagination\n this.root.appendChild(this.paginationEl)\n }\n\n private bindEvents(): void {\n this.root.addEventListener('click', (e) => {\n const target = e.target as HTMLElement\n\n // Timeline card click\n const card = target.closest('.lv-timeline-card') as HTMLElement | null\n if (card) {\n const item = card.closest('.lv-timeline-item') as HTMLElement | null\n const id = item?.getAttribute('data-id')\n if (!id) return\n\n const entry = this.filteredData.find((en) => en.id === id)\n if (!entry) return\n\n // Click on expand hint\n if (target.classList.contains('lv-timeline-expand-hint')) {\n if (this.expandedIds.has(id)) {\n this.collapseEntry(id)\n } else {\n this.expandEntry(id)\n }\n return\n }\n\n // Click on card itself\n this.emitter.emit('entry:click', entry)\n this.config.onEntryClick?.(entry)\n }\n\n // Pagination click\n const pageBtn = target.closest('.lv-pagination-btn') as HTMLButtonElement | null\n if (pageBtn && !pageBtn.disabled) {\n const page = parseInt(pageBtn.getAttribute('data-page') || '1', 10)\n this.goToPage(page)\n }\n })\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAsBO,IAAM,YAAN,MAA6C;CAmBlD,YAAY,QAAyB;EAjBrC,KAAQ,UAAU,IAAI,aAAa;EACnC,KAAQ,eAAe,IAAI,aAAa;EACxC,KAAQ,mBAAmB,IAAI,iBAAiB;EAChD,KAAQ,eAAe,IAAI,aAAa;EAExC,KAAQ,OAAmB,CAAC;EAC5B,KAAQ,eAA2B,CAAC;EACpC,KAAQ,SAAuB,CAAC;EAChC,KAAQ,aAA+B;GAAE,MAAM;GAAG,UAAU;EAAG;EAC/D,KAAQ,QAAmB;EAC3B,KAAQ,8BAAc,IAAI,IAAY;EAGtC,KAAQ,aAAiC;EACzC,KAAQ,eAAmC;EAC3C,KAAQ,YAAY;EAGlB,KAAK,SAAS;EAGd,MAAM,YACJ,OAAO,OAAO,cAAc,WACxB,SAAS,cAA2B,OAAO,SAAS,IACpD,OAAO;EACb,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,oCAAoC,OAAO,WAAW;EAGtF,aAAa;EAGb,KAAK,OAAO,SAAS,cAAc,KAAK;EACxC,KAAK,KAAK,YAAY;EACtB,UAAU,YAAY,KAAK,IAAI;EAG/B,KAAK,QAAQ,OAAO,SAAS;EAC7B,KAAK,KAAK,aAAa,cAAc,KAAK,KAAK;EAE/C,IAAI,OAAO,UAAU,KAAK,WAAW,WAAW,OAAO;EACvD,IAAI,OAAO,YAAY,OAAO,OAAO,KAAK,YAAY,OAAO,UAAU;EACvE,IAAI,OAAO,QAAQ,KAAK,SAAS,UAAU,OAAO,MAAM;EAGxD,IAAI,OAAO,MACT,KAAK,OAAO,CAAC,GAAG,OAAO,IAAI;EAG7B,KAAK,qBAAqB;EAC1B,KAAK,WAAW;CAClB;CAIA,UAAsB;EACpB,OAAO,CAAC,GAAG,KAAK,IAAI;CACtB;CAEA,QAAQ,SAA2B;EACjC,KAAK,OAAO,CAAC,GAAG,OAAO;EACvB,KAAK,WAAW,OAAO;EACvB,KAAK,qBAAqB;EAC1B,KAAK,QAAQ,KAAK,eAAe,KAAK,IAAI;CAC5C;CAEA,WAAW,SAA2B;EACpC,KAAK,KAAK,KAAK,GAAG,OAAO;EACzB,KAAK,qBAAqB;EAC1B,KAAK,QAAQ,KAAK,eAAe,KAAK,IAAI;CAC5C;CAEA,YAA0B;EACxB,OAAO,UAAU,KAAK,MAAM;CAC9B;CAEA,UAAU,QAA4B;EACpC,KAAK,SAAS,UAAU,MAAM;EAC9B,KAAK,WAAW,OAAO;EACvB,KAAK,qBAAqB;EAC1B,KAAK,QAAQ,KAAK,iBAAiB,KAAK,MAAM;EAC9C,KAAK,OAAO,iBAAiB,KAAK,MAAM;CAC1C;CAEA,cAAoB;EAClB,KAAK,SAAS,CAAC;EACf,KAAK,WAAW,OAAO;EACvB,KAAK,qBAAqB;EAC1B,KAAK,QAAQ,KAAK,iBAAiB,KAAK,MAAM;CAChD;CAEA,WAAsB;EACpB,OAAO,KAAK;CACd;CAEA,SAAS,OAAwB;EAC/B,KAAK,QAAQ;EACb,KAAK,KAAK,aAAa,cAAc,KAAK;EAC1C,KAAK,QAAQ,KAAK,gBAAgB,KAAK;EACvC,KAAK,OAAO,gBAAgB,KAAK;CACnC;CAEA,YAAY,IAAkB;EAC5B,KAAK,YAAY,IAAI,EAAE;EACvB,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,EAAE,OAAO,EAAE;EAC/C,IAAI,OAAO,KAAK,QAAQ,KAAK,gBAAgB,KAAK;EAClD,KAAK,eAAe;CACtB;CAEA,cAAc,IAAkB;EAC9B,KAAK,YAAY,OAAO,EAAE;EAC1B,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,EAAE,OAAO,EAAE;EAC/C,IAAI,OAAO,KAAK,QAAQ,KAAK,kBAAkB,KAAK;EACpD,KAAK,eAAe;CACtB;CAEA,cAAoB;EAClB,KAAK,YAAY,MAAM;EACvB,KAAK,eAAe;CACtB;CAEA,SAAS,MAAoB;EAC3B,MAAM,aAAa,KAAK,KAAK,KAAK,aAAa,SAAS,KAAK,WAAW,QAAQ;EAChF,IAAI,OAAO,KAAK,OAAO,YAAY;EACnC,KAAK,WAAW,OAAO;EACvB,KAAK,QAAQ,KAAK,eAAe,IAAI;EACrC,KAAK,eAAe;CACtB;CAEA,gBAAkC;EAChC,OAAO;GACL,GAAG,KAAK;GACR,OAAO,KAAK,aAAa;EAC3B;CACF;CAEA,GAAG,OAAe,UAAoD;EACpE,OAAO,KAAK,QAAQ,GAAG,OAAO,QAAQ;CACxC;CAEA,UAAgB;EACd,KAAK,qBAAqB;CAC5B;CAEA,UAAgB;EACd,IAAI,KAAK,WAAW;EACpB,KAAK,YAAY;EACjB,KAAK,QAAQ,mBAAmB;EAChC,KAAK,KAAK,OAAO;CACnB;CAIQ,uBAA6B;EACnC,KAAK,eAAe,KAAK,aAAa,OAAO,KAAK,MAAM,KAAK,MAAM;EACnE,KAAK,aAAa,MACf,GAAG,MAAM,IAAI,KAAK,EAAE,SAAS,CAAA,CAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,SAAS,CAAA,CAAE,QAAQ,CAC5E;EACA,KAAK,WAAW,QAAQ,KAAK,aAAa;EAC1C,KAAK,eAAe;CACtB;CAEQ,cAA0B;EAChC,MAAM,EAAE,MAAM,aAAa,KAAK;EAChC,MAAM,SAAS,OAAO,KAAK;EAC3B,OAAO,KAAK,aAAa,MAAM,OAAO,QAAQ,QAAQ;CACxD;CAEQ,iBAAuB;EAC7B,IAAI,KAAK,WAAW;EAEpB,MAAM,WAAW,KAAK,YAAY;EAGlC,MAAM,cAAc,KAAK,iBAAiB,OAAO,UAAU,KAAK,WAAW;EAG3E,KAAA,MAAW,SAAS,UAClB,IAAI,KAAK,YAAY,IAAI,MAAM,EAAE,GAAG;GAClC,MAAM,SAAS,YAAY,cAAc,aAAa,MAAM,GAAE,qBAAsB;GACpF,IAAI,QACF,OAAO,YAAY,KAAK,aAAa,aAAa,KAAK,CAAC;EAE5D;EAGF,IAAI,KAAK,YACP,KAAK,KAAK,YAAY,KAAK,UAAU;EAEvC,KAAK,aAAa;EAClB,KAAK,KAAK,YAAY,KAAK,UAAU;EAGrC,MAAM,gBAAgB,KAAK,iBAAiB,iBAC1C,KAAK,WAAW,MAChB,KAAK,WAAW,UAChB,KAAK,aAAa,MACpB;EACA,IAAI,KAAK,cACP,KAAK,KAAK,YAAY,KAAK,YAAY;EAEzC,KAAK,eAAe;EACpB,KAAK,KAAK,YAAY,KAAK,YAAY;CACzC;CAEQ,aAAmB;EACzB,KAAK,KAAK,iBAAiB,UAAU,MAAM;GACzC,MAAM,SAAS,EAAE;GAGjB,MAAM,OAAO,OAAO,QAAQ,mBAAmB;GAC/C,IAAI,MAAM;IAER,MAAM,KADO,KAAK,QAAQ,mBACf,CAAA,EAAM,aAAa,SAAS;IACvC,IAAI,CAAC,IAAI;IAET,MAAM,QAAQ,KAAK,aAAa,MAAM,OAAO,GAAG,OAAO,EAAE;IACzD,IAAI,CAAC,OAAO;IAGZ,IAAI,OAAO,UAAU,SAAS,yBAAyB,GAAG;KACxD,IAAI,KAAK,YAAY,IAAI,EAAE,GACzB,KAAK,cAAc,EAAE;UAErB,KAAK,YAAY,EAAE;KAErB;IACF;IAGA,KAAK,QAAQ,KAAK,eAAe,KAAK;IACtC,KAAK,OAAO,eAAe,KAAK;GAClC;GAGA,MAAM,UAAU,OAAO,QAAQ,oBAAoB;GACnD,IAAI,WAAW,CAAC,QAAQ,UAAU;IAChC,MAAM,OAAO,SAAS,QAAQ,aAAa,WAAW,KAAK,KAAK,EAAE;IAClE,KAAK,SAAS,IAAI;GACpB;EACF,CAAC;CACH;AACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 筛选引擎 - 支持按操作人/操作类型/时间范围/模块/IP/关键词筛选
|
|
3
|
+
*/
|
|
4
|
+
import type { LogEntry, FilterConfig } from '../types';
|
|
5
|
+
/**
|
|
6
|
+
* FilterEngine - Filter Engine class
|
|
7
|
+
*/
|
|
8
|
+
export declare class FilterEngine {
|
|
9
|
+
/**
|
|
10
|
+
* 对日志条目进行多维筛选
|
|
11
|
+
*/
|
|
12
|
+
filter(entries: LogEntry[], config: FilterConfig): LogEntry[];
|
|
13
|
+
/**
|
|
14
|
+
* 获取所有唯一操作人
|
|
15
|
+
*/
|
|
16
|
+
getUniqueUsers(entries: LogEntry[]): string[];
|
|
17
|
+
/**
|
|
18
|
+
* 获取所有唯一模块
|
|
19
|
+
*/
|
|
20
|
+
getUniqueModules(entries: LogEntry[]): string[];
|
|
21
|
+
/**
|
|
22
|
+
* 获取所有唯一操作类型
|
|
23
|
+
*/
|
|
24
|
+
getUniqueActions(entries: LogEntry[]): string[];
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=FilterEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilterEngine.d.ts","sourceRoot":"","sources":["../../src/filters/FilterEngine.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAEtD;;GAEG;AACH,qBAAa,YAAY;IACvB;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,YAAY,GAAG,QAAQ,EAAE;IA6D7D;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,EAAE;IAI7C;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,EAAE;IAI/C;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,EAAE;CAGhD"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/log-viewer-core v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:33:32 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
//#region src/filters/FilterEngine.ts
|
|
11
|
+
var FilterEngine = class {
|
|
12
|
+
/**
|
|
13
|
+
* 对日志条目进行多维筛选
|
|
14
|
+
*/
|
|
15
|
+
filter(entries, config) {
|
|
16
|
+
let result = entries;
|
|
17
|
+
if (config.user) {
|
|
18
|
+
const keyword = config.user.toLowerCase();
|
|
19
|
+
result = result.filter((e) => e.user.toLowerCase().includes(keyword) || e.userId && e.userId.toLowerCase().includes(keyword));
|
|
20
|
+
}
|
|
21
|
+
if (config.action) {
|
|
22
|
+
const actions = Array.isArray(config.action) ? config.action : [config.action];
|
|
23
|
+
result = result.filter((e) => actions.includes(e.action));
|
|
24
|
+
}
|
|
25
|
+
if (config.module) {
|
|
26
|
+
const mod = config.module.toLowerCase();
|
|
27
|
+
result = result.filter((e) => e.module.toLowerCase().includes(mod));
|
|
28
|
+
}
|
|
29
|
+
if (config.ip) {
|
|
30
|
+
const ip = config.ip.toLowerCase();
|
|
31
|
+
result = result.filter((e) => e.ip && e.ip.toLowerCase().includes(ip));
|
|
32
|
+
}
|
|
33
|
+
if (config.timeRange) {
|
|
34
|
+
const [start, end] = config.timeRange;
|
|
35
|
+
const startTime = new Date(start).getTime();
|
|
36
|
+
const endTime = new Date(end).getTime();
|
|
37
|
+
result = result.filter((e) => {
|
|
38
|
+
const t = new Date(e.timestamp).getTime();
|
|
39
|
+
return t >= startTime && t <= endTime;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (config.keyword) {
|
|
43
|
+
const kw = config.keyword.toLowerCase();
|
|
44
|
+
result = result.filter((e) => {
|
|
45
|
+
return [
|
|
46
|
+
e.user,
|
|
47
|
+
e.action,
|
|
48
|
+
e.module,
|
|
49
|
+
e.detail || "",
|
|
50
|
+
e.ip || "",
|
|
51
|
+
e.id
|
|
52
|
+
].some((t) => t.toLowerCase().includes(kw));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 获取所有唯一操作人
|
|
59
|
+
*/
|
|
60
|
+
getUniqueUsers(entries) {
|
|
61
|
+
return [...new Set(entries.map((e) => e.user))].sort();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 获取所有唯一模块
|
|
65
|
+
*/
|
|
66
|
+
getUniqueModules(entries) {
|
|
67
|
+
return [...new Set(entries.map((e) => e.module))].sort();
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 获取所有唯一操作类型
|
|
71
|
+
*/
|
|
72
|
+
getUniqueActions(entries) {
|
|
73
|
+
return [...new Set(entries.map((e) => e.action))].sort();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
//#endregion
|
|
77
|
+
export { FilterEngine };
|
|
78
|
+
/*! End of @composy/log-viewer-core | Powered by @composy/builder */
|
|
79
|
+
//# sourceMappingURL=FilterEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FilterEngine.js","names":[],"sources":["../../src/filters/FilterEngine.ts"],"sourcesContent":["/**\n * 筛选引擎 - 支持按操作人/操作类型/时间范围/模块/IP/关键词筛选\n */\n\nimport type { LogEntry, FilterConfig } from '../types'\n\n/**\n * FilterEngine - Filter Engine class\n */\nexport class FilterEngine {\n /**\n * 对日志条目进行多维筛选\n */\n filter(entries: LogEntry[], config: FilterConfig): LogEntry[] {\n let result = entries\n\n // 按操作人筛选\n if (config.user) {\n const keyword = config.user.toLowerCase()\n result = result.filter(\n (e) =>\n e.user.toLowerCase().includes(keyword) ||\n (e.userId && e.userId.toLowerCase().includes(keyword)),\n )\n }\n\n // 按操作类型筛选\n if (config.action) {\n const actions = Array.isArray(config.action) ? config.action : [config.action]\n result = result.filter((e) => actions.includes(e.action))\n }\n\n // 按模块筛选\n if (config.module) {\n const mod = config.module.toLowerCase()\n result = result.filter((e) => e.module.toLowerCase().includes(mod))\n }\n\n // 按 IP 筛选\n if (config.ip) {\n const ip = config.ip.toLowerCase()\n result = result.filter((e) => e.ip && e.ip.toLowerCase().includes(ip))\n }\n\n // 按时间范围筛选\n if (config.timeRange) {\n const [start, end] = config.timeRange\n const startTime = new Date(start).getTime()\n const endTime = new Date(end).getTime()\n result = result.filter((e) => {\n const t = new Date(e.timestamp).getTime()\n return t >= startTime && t <= endTime\n })\n }\n\n // 按关键词全文搜索\n if (config.keyword) {\n const kw = config.keyword.toLowerCase()\n result = result.filter((e) => {\n const texts = [\n e.user,\n e.action,\n e.module,\n e.detail || '',\n e.ip || '',\n e.id,\n ]\n return texts.some((t) => t.toLowerCase().includes(kw))\n })\n }\n\n return result\n }\n\n /**\n * 获取所有唯一操作人\n */\n getUniqueUsers(entries: LogEntry[]): string[] {\n return [...new Set(entries.map((e) => e.user))].sort()\n }\n\n /**\n * 获取所有唯一模块\n */\n getUniqueModules(entries: LogEntry[]): string[] {\n return [...new Set(entries.map((e) => e.module))].sort()\n }\n\n /**\n * 获取所有唯一操作类型\n */\n getUniqueActions(entries: LogEntry[]): string[] {\n return [...new Set(entries.map((e) => e.action))].sort()\n }\n}\n"],"mappings":";;;;;;;;;;AASO,IAAM,eAAN,MAAmB;;;;CAIxB,OAAO,SAAqB,QAAkC;EAC5D,IAAI,SAAS;EAGb,IAAI,OAAO,MAAM;GACf,MAAM,UAAU,OAAO,KAAK,YAAY;GACxC,SAAS,OAAO,QACb,MACC,EAAE,KAAK,YAAY,CAAA,CAAE,SAAS,OAAO,KACpC,EAAE,UAAU,EAAE,OAAO,YAAY,CAAA,CAAE,SAAS,OAAO,CACxD;EACF;EAGA,IAAI,OAAO,QAAQ;GACjB,MAAM,UAAU,MAAM,QAAQ,OAAO,MAAM,IAAI,OAAO,SAAS,CAAC,OAAO,MAAM;GAC7E,SAAS,OAAO,QAAQ,MAAM,QAAQ,SAAS,EAAE,MAAM,CAAC;EAC1D;EAGA,IAAI,OAAO,QAAQ;GACjB,MAAM,MAAM,OAAO,OAAO,YAAY;GACtC,SAAS,OAAO,QAAQ,MAAM,EAAE,OAAO,YAAY,CAAA,CAAE,SAAS,GAAG,CAAC;EACpE;EAGA,IAAI,OAAO,IAAI;GACb,MAAM,KAAK,OAAO,GAAG,YAAY;GACjC,SAAS,OAAO,QAAQ,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,CAAA,CAAE,SAAS,EAAE,CAAC;EACvE;EAGA,IAAI,OAAO,WAAW;GACpB,MAAM,CAAC,OAAO,OAAO,OAAO;GAC5B,MAAM,YAAY,IAAI,KAAK,KAAK,CAAA,CAAE,QAAQ;GAC1C,MAAM,UAAU,IAAI,KAAK,GAAG,CAAA,CAAE,QAAQ;GACtC,SAAS,OAAO,QAAQ,MAAM;IAC5B,MAAM,IAAI,IAAI,KAAK,EAAE,SAAS,CAAA,CAAE,QAAQ;IACxC,OAAO,KAAK,aAAa,KAAK;GAChC,CAAC;EACH;EAGA,IAAI,OAAO,SAAS;GAClB,MAAM,KAAK,OAAO,QAAQ,YAAY;GACtC,SAAS,OAAO,QAAQ,MAAM;IAS5B,OAAO;KAPL,EAAE;KACF,EAAE;KACF,EAAE;KACF,EAAE,UAAU;KACZ,EAAE,MAAM;KACR,EAAE;IAEG,CAAA,CAAM,MAAM,MAAM,EAAE,YAAY,CAAA,CAAE,SAAS,EAAE,CAAC;GACvD,CAAC;EACH;EAEA,OAAO;CACT;;;;CAKA,eAAe,SAA+B;EAC5C,OAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA,CAAE,KAAK;CACvD;;;;CAKA,iBAAiB,SAA+B;EAC9C,OAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA,CAAE,KAAK;CACzD;;;;CAKA,iBAAiB,SAA+B;EAC9C,OAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA,CAAE,KAAK;CACzD;AACF"}
|