@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.
Files changed (105) hide show
  1. package/README.md +37 -0
  2. package/dist/index.js +961 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/index.min.js +1 -0
  5. package/es/core/EventEmitter.d.ts +11 -0
  6. package/es/core/EventEmitter.d.ts.map +1 -0
  7. package/es/core/EventEmitter.js +40 -0
  8. package/es/core/EventEmitter.js.map +1 -0
  9. package/es/core/LogViewer.d.ts +46 -0
  10. package/es/core/LogViewer.d.ts.map +1 -0
  11. package/es/core/LogViewer.js +186 -0
  12. package/es/core/LogViewer.js.map +1 -0
  13. package/es/filters/FilterEngine.d.ts +26 -0
  14. package/es/filters/FilterEngine.d.ts.map +1 -0
  15. package/es/filters/FilterEngine.js +79 -0
  16. package/es/filters/FilterEngine.js.map +1 -0
  17. package/es/index.d.ts +26 -0
  18. package/es/index.d.ts.map +1 -0
  19. package/es/index.js +26 -0
  20. package/es/index.js.map +1 -0
  21. package/es/renderers/DiffRenderer.d.ts +18 -0
  22. package/es/renderers/DiffRenderer.d.ts.map +1 -0
  23. package/es/renderers/DiffRenderer.js +60 -0
  24. package/es/renderers/DiffRenderer.js.map +1 -0
  25. package/es/renderers/TimelineRenderer.d.ts +22 -0
  26. package/es/renderers/TimelineRenderer.d.ts.map +1 -0
  27. package/es/renderers/TimelineRenderer.js +135 -0
  28. package/es/renderers/TimelineRenderer.js.map +1 -0
  29. package/es/styles/index.d.ts +9 -0
  30. package/es/styles/index.d.ts.map +1 -0
  31. package/es/styles/index.js +414 -0
  32. package/es/styles/index.js.map +1 -0
  33. package/es/types/index.d.ts +113 -0
  34. package/es/types/index.d.ts.map +1 -0
  35. package/es/types/index.js +40 -0
  36. package/es/types/index.js.map +1 -0
  37. package/es/utils/index.d.ts +18 -0
  38. package/es/utils/index.d.ts.map +1 -0
  39. package/es/utils/index.js +73 -0
  40. package/es/utils/index.js.map +1 -0
  41. package/esm/core/EventEmitter.d.ts +11 -0
  42. package/esm/core/EventEmitter.d.ts.map +1 -0
  43. package/esm/core/EventEmitter.js +40 -0
  44. package/esm/core/EventEmitter.js.map +1 -0
  45. package/esm/core/LogViewer.d.ts +46 -0
  46. package/esm/core/LogViewer.d.ts.map +1 -0
  47. package/esm/core/LogViewer.js +186 -0
  48. package/esm/core/LogViewer.js.map +1 -0
  49. package/esm/filters/FilterEngine.d.ts +26 -0
  50. package/esm/filters/FilterEngine.d.ts.map +1 -0
  51. package/esm/filters/FilterEngine.js +79 -0
  52. package/esm/filters/FilterEngine.js.map +1 -0
  53. package/esm/index.d.ts +26 -0
  54. package/esm/index.d.ts.map +1 -0
  55. package/esm/index.js +26 -0
  56. package/esm/index.js.map +1 -0
  57. package/esm/renderers/DiffRenderer.d.ts +18 -0
  58. package/esm/renderers/DiffRenderer.d.ts.map +1 -0
  59. package/esm/renderers/DiffRenderer.js +60 -0
  60. package/esm/renderers/DiffRenderer.js.map +1 -0
  61. package/esm/renderers/TimelineRenderer.d.ts +22 -0
  62. package/esm/renderers/TimelineRenderer.d.ts.map +1 -0
  63. package/esm/renderers/TimelineRenderer.js +135 -0
  64. package/esm/renderers/TimelineRenderer.js.map +1 -0
  65. package/esm/styles/index.d.ts +9 -0
  66. package/esm/styles/index.d.ts.map +1 -0
  67. package/esm/styles/index.js +414 -0
  68. package/esm/styles/index.js.map +1 -0
  69. package/esm/types/index.d.ts +113 -0
  70. package/esm/types/index.d.ts.map +1 -0
  71. package/esm/types/index.js +40 -0
  72. package/esm/types/index.js.map +1 -0
  73. package/esm/utils/index.d.ts +18 -0
  74. package/esm/utils/index.d.ts.map +1 -0
  75. package/esm/utils/index.js +73 -0
  76. package/esm/utils/index.js.map +1 -0
  77. package/lib/core/EventEmitter.cjs +41 -0
  78. package/lib/core/EventEmitter.cjs.map +1 -0
  79. package/lib/core/EventEmitter.d.ts +11 -0
  80. package/lib/core/LogViewer.cjs +187 -0
  81. package/lib/core/LogViewer.cjs.map +1 -0
  82. package/lib/core/LogViewer.d.ts +46 -0
  83. package/lib/filters/FilterEngine.cjs +80 -0
  84. package/lib/filters/FilterEngine.cjs.map +1 -0
  85. package/lib/filters/FilterEngine.d.ts +26 -0
  86. package/lib/index.cjs +43 -0
  87. package/lib/index.cjs.map +1 -0
  88. package/lib/index.d.ts +26 -0
  89. package/lib/package.json +3 -0
  90. package/lib/renderers/DiffRenderer.cjs +61 -0
  91. package/lib/renderers/DiffRenderer.cjs.map +1 -0
  92. package/lib/renderers/DiffRenderer.d.ts +18 -0
  93. package/lib/renderers/TimelineRenderer.cjs +136 -0
  94. package/lib/renderers/TimelineRenderer.cjs.map +1 -0
  95. package/lib/renderers/TimelineRenderer.d.ts +22 -0
  96. package/lib/styles/index.cjs +415 -0
  97. package/lib/styles/index.cjs.map +1 -0
  98. package/lib/styles/index.d.ts +9 -0
  99. package/lib/types/index.cjs +42 -0
  100. package/lib/types/index.cjs.map +1 -0
  101. package/lib/types/index.d.ts +113 -0
  102. package/lib/utils/index.cjs +80 -0
  103. package/lib/utils/index.cjs.map +1 -0
  104. package/lib/utils/index.d.ts +18 -0
  105. package/package.json +119 -0
package/dist/index.js ADDED
@@ -0,0 +1,961 @@
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
+ (function(global, factory) {
11
+ 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 = {}));
12
+ })(this, function(exports) {
13
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
14
+ //#region src/types/index.ts
15
+ const ACTION_LABELS = {
16
+ create: "新增",
17
+ update: "修改",
18
+ delete: "删除",
19
+ query: "查询",
20
+ login: "登录",
21
+ logout: "登出",
22
+ export: "导出",
23
+ import: "导入",
24
+ approve: "审批",
25
+ reject: "驳回",
26
+ other: "其他"
27
+ };
28
+ const ACTION_COLORS = {
29
+ create: "#52c41a",
30
+ update: "#1890ff",
31
+ delete: "#ff4d4f",
32
+ query: "#8c8c8c",
33
+ login: "#722ed1",
34
+ logout: "#eb2f96",
35
+ export: "#fa8c16",
36
+ import: "#13c2c2",
37
+ approve: "#52c41a",
38
+ reject: "#ff4d4f",
39
+ other: "#d9d9d9"
40
+ };
41
+ //#endregion
42
+ //#region src/core/EventEmitter.ts
43
+ var EventEmitter = class {
44
+ constructor() {
45
+ this.listeners = /* @__PURE__ */ new Map();
46
+ }
47
+ on(event, callback) {
48
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
49
+ this.listeners.get(event).add(callback);
50
+ return () => this.off(event, callback);
51
+ }
52
+ off(event, callback) {
53
+ this.listeners.get(event)?.delete(callback);
54
+ }
55
+ emit(event, ...args) {
56
+ this.listeners.get(event)?.forEach((cb) => {
57
+ try {
58
+ cb(...args);
59
+ } catch (e) {
60
+ console.error(`[LogViewer] Event handler error (${event}):`, e);
61
+ }
62
+ });
63
+ }
64
+ removeAllListeners(event) {
65
+ if (event) this.listeners.delete(event);
66
+ else this.listeners.clear();
67
+ }
68
+ };
69
+ //#endregion
70
+ //#region src/filters/FilterEngine.ts
71
+ var FilterEngine = class {
72
+ /**
73
+ * 对日志条目进行多维筛选
74
+ */
75
+ filter(entries, config) {
76
+ let result = entries;
77
+ if (config.user) {
78
+ const keyword = config.user.toLowerCase();
79
+ result = result.filter((e) => e.user.toLowerCase().includes(keyword) || e.userId && e.userId.toLowerCase().includes(keyword));
80
+ }
81
+ if (config.action) {
82
+ const actions = Array.isArray(config.action) ? config.action : [config.action];
83
+ result = result.filter((e) => actions.includes(e.action));
84
+ }
85
+ if (config.module) {
86
+ const mod = config.module.toLowerCase();
87
+ result = result.filter((e) => e.module.toLowerCase().includes(mod));
88
+ }
89
+ if (config.ip) {
90
+ const ip = config.ip.toLowerCase();
91
+ result = result.filter((e) => e.ip && e.ip.toLowerCase().includes(ip));
92
+ }
93
+ if (config.timeRange) {
94
+ const [start, end] = config.timeRange;
95
+ const startTime = new Date(start).getTime();
96
+ const endTime = new Date(end).getTime();
97
+ result = result.filter((e) => {
98
+ const t = new Date(e.timestamp).getTime();
99
+ return t >= startTime && t <= endTime;
100
+ });
101
+ }
102
+ if (config.keyword) {
103
+ const kw = config.keyword.toLowerCase();
104
+ result = result.filter((e) => {
105
+ return [
106
+ e.user,
107
+ e.action,
108
+ e.module,
109
+ e.detail || "",
110
+ e.ip || "",
111
+ e.id
112
+ ].some((t) => t.toLowerCase().includes(kw));
113
+ });
114
+ }
115
+ return result;
116
+ }
117
+ /**
118
+ * 获取所有唯一操作人
119
+ */
120
+ getUniqueUsers(entries) {
121
+ return [...new Set(entries.map((e) => e.user))].sort();
122
+ }
123
+ /**
124
+ * 获取所有唯一模块
125
+ */
126
+ getUniqueModules(entries) {
127
+ return [...new Set(entries.map((e) => e.module))].sort();
128
+ }
129
+ /**
130
+ * 获取所有唯一操作类型
131
+ */
132
+ getUniqueActions(entries) {
133
+ return [...new Set(entries.map((e) => e.action))].sort();
134
+ }
135
+ };
136
+ //#endregion
137
+ //#region src/utils/index.ts
138
+ let _uid = 0;
139
+ function uid(prefix = "lv") {
140
+ return `${prefix}-${++_uid}-${Math.random().toString(36).slice(2, 8)}`;
141
+ }
142
+ function formatDate(value, fmt = "YYYY-MM-DD HH:mm:ss") {
143
+ const d = value instanceof Date ? value : new Date(value);
144
+ if (isNaN(d.getTime())) return String(value);
145
+ const pad = (n) => String(n).padStart(2, "0");
146
+ const year = String(d.getFullYear());
147
+ const month = pad(d.getMonth() + 1);
148
+ const day = pad(d.getDate());
149
+ const hours = pad(d.getHours());
150
+ const minutes = pad(d.getMinutes());
151
+ const seconds = pad(d.getSeconds());
152
+ return fmt.replace("YYYY", year).replace("MM", month).replace("DD", day).replace("HH", hours).replace("mm", minutes).replace("ss", seconds);
153
+ }
154
+ function formatRelativeTime(value) {
155
+ const d = value instanceof Date ? value : new Date(value);
156
+ const diff = Date.now() - d.getTime();
157
+ if (diff < 6e4) return "刚刚";
158
+ if (diff < 36e5) return `${Math.floor(diff / 6e4)} \u5206\u949F\u524D`;
159
+ if (diff < 864e5) return `${Math.floor(diff / 36e5)} \u5C0F\u65F6\u524D`;
160
+ if (diff < 6048e5) return `${Math.floor(diff / 864e5)} \u5929\u524D`;
161
+ return formatDate(d);
162
+ }
163
+ function escapeHtml(str) {
164
+ const map = {
165
+ "&": "&amp;",
166
+ "<": "&lt;",
167
+ ">": "&gt;",
168
+ "\"": "&quot;",
169
+ "'": "&#39;"
170
+ };
171
+ return str.replace(/[&<>"']/g, (c) => map[c] || c);
172
+ }
173
+ function debounce(fn, delay) {
174
+ let timer = null;
175
+ return ((...args) => {
176
+ if (timer) clearTimeout(timer);
177
+ timer = setTimeout(() => fn(...args), delay);
178
+ });
179
+ }
180
+ function deepClone(obj) {
181
+ if (obj === null || typeof obj !== "object") return obj;
182
+ try {
183
+ return JSON.parse(JSON.stringify(obj));
184
+ } catch {
185
+ return obj;
186
+ }
187
+ }
188
+ function formatValue(val) {
189
+ if (val === null || val === void 0) return "-";
190
+ if (typeof val === "object") try {
191
+ return JSON.stringify(val, null, 2);
192
+ } catch {
193
+ return String(val);
194
+ }
195
+ return String(val);
196
+ }
197
+ //#endregion
198
+ //#region src/renderers/TimelineRenderer.ts
199
+ const ANOMALY_LABELS = {
200
+ "off-hours": "非工作时间",
201
+ sensitive: "敏感操作",
202
+ "high-frequency": "高频操作",
203
+ none: ""
204
+ };
205
+ var TimelineRenderer = class {
206
+ /**
207
+ * 渲染时间线
208
+ */
209
+ render(entries, expandedIds) {
210
+ const container = document.createElement("div");
211
+ container.className = "lv-timeline";
212
+ if (entries.length === 0) {
213
+ container.innerHTML = `<div class="lv-empty"><div class="lv-empty-icon">--</div><div>\u6682\u65E0\u65E5\u5FD7\u8BB0\u5F55</div></div>`;
214
+ container.className = "";
215
+ return container;
216
+ }
217
+ for (const entry of entries) container.appendChild(this.renderItem(entry, expandedIds.has(entry.id)));
218
+ return container;
219
+ }
220
+ /**
221
+ * 渲染单条时间线条目
222
+ */
223
+ renderItem(entry, expanded) {
224
+ const item = document.createElement("div");
225
+ item.className = "lv-timeline-item";
226
+ item.setAttribute("data-id", entry.id);
227
+ const isAnomaly = entry.anomaly && entry.anomaly !== "none";
228
+ const dot = document.createElement("div");
229
+ dot.className = `lv-timeline-dot${isAnomaly ? " lv-timeline-dot--anomaly" : ""}`;
230
+ item.appendChild(dot);
231
+ const card = document.createElement("div");
232
+ card.className = `lv-timeline-card${isAnomaly ? " lv-timeline-card--anomaly" : ""}`;
233
+ const header = document.createElement("div");
234
+ header.className = "lv-timeline-header";
235
+ const time = document.createElement("span");
236
+ time.className = "lv-timeline-time";
237
+ time.textContent = formatDate(entry.timestamp);
238
+ header.appendChild(time);
239
+ const user = document.createElement("span");
240
+ user.className = "lv-timeline-user";
241
+ user.textContent = entry.user;
242
+ header.appendChild(user);
243
+ const actionTag = document.createElement("span");
244
+ actionTag.className = "lv-action-tag";
245
+ actionTag.textContent = ACTION_LABELS[entry.action] || entry.action;
246
+ actionTag.style.backgroundColor = ACTION_COLORS[entry.action] || "#8c8c8c";
247
+ header.appendChild(actionTag);
248
+ const moduleBadge = document.createElement("span");
249
+ moduleBadge.className = "lv-timeline-module";
250
+ moduleBadge.textContent = entry.module;
251
+ header.appendChild(moduleBadge);
252
+ if (entry.ip) {
253
+ const ip = document.createElement("span");
254
+ ip.className = "lv-timeline-ip";
255
+ ip.textContent = entry.ip;
256
+ header.appendChild(ip);
257
+ }
258
+ if (isAnomaly) {
259
+ const badge = document.createElement("span");
260
+ badge.className = "lv-anomaly-badge";
261
+ badge.textContent = ANOMALY_LABELS[entry.anomaly];
262
+ header.appendChild(badge);
263
+ }
264
+ card.appendChild(header);
265
+ if (entry.detail) {
266
+ const detail = document.createElement("div");
267
+ detail.className = "lv-timeline-detail";
268
+ detail.textContent = entry.detail;
269
+ card.appendChild(detail);
270
+ }
271
+ if (entry.changes && entry.changes.length > 0) {
272
+ const hint = document.createElement("div");
273
+ hint.className = "lv-timeline-expand-hint";
274
+ hint.textContent = expanded ? "[ 收起变更详情 ]" : `[ \u67E5\u770B\u53D8\u66F4\u8BE6\u60C5 (${entry.changes.length} \u4E2A\u5B57\u6BB5) ]`;
275
+ card.appendChild(hint);
276
+ }
277
+ item.appendChild(card);
278
+ return item;
279
+ }
280
+ /**
281
+ * 渲染分页
282
+ */
283
+ renderPagination(page, pageSize, total) {
284
+ const container = document.createElement("div");
285
+ container.className = "lv-pagination";
286
+ const totalPages = Math.ceil(total / pageSize);
287
+ if (totalPages <= 1) return container;
288
+ const prev = document.createElement("button");
289
+ prev.className = "lv-pagination-btn";
290
+ prev.textContent = "<";
291
+ prev.disabled = page <= 1;
292
+ prev.setAttribute("data-page", String(page - 1));
293
+ container.appendChild(prev);
294
+ const maxVisible = 7;
295
+ let startPage = Math.max(1, page - Math.floor(maxVisible / 2));
296
+ const endPage = Math.min(totalPages, startPage + maxVisible - 1);
297
+ if (endPage - startPage < maxVisible - 1) startPage = Math.max(1, endPage - maxVisible + 1);
298
+ for (let i = startPage; i <= endPage; i++) {
299
+ const btn = document.createElement("button");
300
+ btn.className = `lv-pagination-btn${i === page ? " lv-pagination-btn--active" : ""}`;
301
+ btn.textContent = String(i);
302
+ btn.setAttribute("data-page", String(i));
303
+ container.appendChild(btn);
304
+ }
305
+ const next = document.createElement("button");
306
+ next.className = "lv-pagination-btn";
307
+ next.textContent = ">";
308
+ next.disabled = page >= totalPages;
309
+ next.setAttribute("data-page", String(page + 1));
310
+ container.appendChild(next);
311
+ const info = document.createElement("span");
312
+ info.className = "lv-pagination-info";
313
+ info.textContent = `\u5171 ${total} \u6761`;
314
+ container.appendChild(info);
315
+ return container;
316
+ }
317
+ };
318
+ //#endregion
319
+ //#region src/renderers/DiffRenderer.ts
320
+ var DiffRenderer = class {
321
+ /**
322
+ * 渲染详情面板(包含 diff 表格和 metadata)
323
+ */
324
+ renderDetail(entry) {
325
+ const container = document.createElement("div");
326
+ container.className = "lv-detail";
327
+ if (entry.changes && entry.changes.length > 0) container.appendChild(this.renderDiffTable(entry.changes));
328
+ if (entry.metadata && Object.keys(entry.metadata).length > 0) {
329
+ const meta = document.createElement("div");
330
+ meta.className = "lv-metadata";
331
+ meta.textContent = JSON.stringify(entry.metadata, null, 2);
332
+ container.appendChild(meta);
333
+ }
334
+ return container;
335
+ }
336
+ /**
337
+ * 渲染 Diff 表格
338
+ */
339
+ renderDiffTable(changes) {
340
+ const table = document.createElement("table");
341
+ table.className = "lv-diff-table";
342
+ const thead = document.createElement("thead");
343
+ thead.innerHTML = `<tr><th>\u5B57\u6BB5</th><th>\u53D8\u66F4\u524D</th><th>\u53D8\u66F4\u540E</th></tr>`;
344
+ table.appendChild(thead);
345
+ const tbody = document.createElement("tbody");
346
+ for (const change of changes) {
347
+ const tr = document.createElement("tr");
348
+ const tdField = document.createElement("td");
349
+ tdField.textContent = change.label || change.field;
350
+ tr.appendChild(tdField);
351
+ const tdOld = document.createElement("td");
352
+ tdOld.className = "lv-diff-old";
353
+ tdOld.textContent = formatValue(change.oldValue);
354
+ tr.appendChild(tdOld);
355
+ const tdNew = document.createElement("td");
356
+ tdNew.className = "lv-diff-new";
357
+ tdNew.textContent = formatValue(change.newValue);
358
+ tr.appendChild(tdNew);
359
+ tbody.appendChild(tr);
360
+ }
361
+ table.appendChild(tbody);
362
+ return table;
363
+ }
364
+ };
365
+ //#endregion
366
+ //#region src/styles/index.ts
367
+ const STYLE_ID = "ldesign-log-viewer-styles";
368
+ function injectStyles() {
369
+ if (typeof document === "undefined") return;
370
+ if (document.getElementById(STYLE_ID)) return;
371
+ const style = document.createElement("style");
372
+ style.id = STYLE_ID;
373
+ style.textContent = CSS_TEXT;
374
+ document.head.appendChild(style);
375
+ }
376
+ const CSS_TEXT = `
377
+ /* ===== CSS Variables ===== */
378
+ .lv-root {
379
+ --lv-bg: #ffffff;
380
+ --lv-bg-secondary: #f7f8fa;
381
+ --lv-bg-hover: #f0f3ff;
382
+ --lv-border: #ebedf0;
383
+ --lv-text: #1a1a2e;
384
+ --lv-text-secondary: #666;
385
+ --lv-text-muted: #999;
386
+ --lv-primary: #4f6ef7;
387
+ --lv-primary-light: #edf0ff;
388
+ --lv-shadow: rgba(0, 0, 0, 0.04);
389
+ --lv-timeline-line: #e0e0e0;
390
+ --lv-timeline-dot: #4f6ef7;
391
+ --lv-diff-add-bg: #f0fff4;
392
+ --lv-diff-add-text: #22863a;
393
+ --lv-diff-del-bg: #fff5f5;
394
+ --lv-diff-del-text: #cb2431;
395
+ --lv-anomaly-bg: #fff2e8;
396
+ --lv-anomaly-border: #ffbb96;
397
+ --lv-radius: 8px;
398
+ --lv-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
399
+ --lv-font-mono: 'Fira Code', 'SF Mono', Consolas, monospace;
400
+ }
401
+
402
+ .lv-root[data-theme="dark"] {
403
+ --lv-bg: #1a1a2e;
404
+ --lv-bg-secondary: #16213e;
405
+ --lv-bg-hover: #1e2a4a;
406
+ --lv-border: #2a2a4a;
407
+ --lv-text: #e0e0e0;
408
+ --lv-text-secondary: #a0a0b0;
409
+ --lv-text-muted: #666680;
410
+ --lv-primary: #6b85f9;
411
+ --lv-primary-light: #252545;
412
+ --lv-shadow: rgba(0, 0, 0, 0.2);
413
+ --lv-timeline-line: #333355;
414
+ --lv-timeline-dot: #6b85f9;
415
+ --lv-diff-add-bg: #0d2818;
416
+ --lv-diff-add-text: #85e89d;
417
+ --lv-diff-del-bg: #2d1214;
418
+ --lv-diff-del-text: #f97583;
419
+ --lv-anomaly-bg: #2d1a0a;
420
+ --lv-anomaly-border: #8b5e3c;
421
+ }
422
+
423
+ /* ===== Root ===== */
424
+ .lv-root {
425
+ font-family: var(--lv-font);
426
+ font-size: 14px;
427
+ color: var(--lv-text);
428
+ background: var(--lv-bg);
429
+ line-height: 1.6;
430
+ }
431
+
432
+ /* ===== Filter Bar ===== */
433
+ .lv-filter {
434
+ display: flex;
435
+ flex-wrap: wrap;
436
+ gap: 10px;
437
+ padding: 16px;
438
+ background: var(--lv-bg);
439
+ border: 1px solid var(--lv-border);
440
+ border-radius: var(--lv-radius);
441
+ margin-bottom: 16px;
442
+ align-items: center;
443
+ }
444
+
445
+ .lv-filter-item {
446
+ display: flex;
447
+ align-items: center;
448
+ gap: 6px;
449
+ font-size: 13px;
450
+ }
451
+
452
+ .lv-filter-label {
453
+ color: var(--lv-text-secondary);
454
+ font-size: 12px;
455
+ white-space: nowrap;
456
+ }
457
+
458
+ .lv-filter-input {
459
+ padding: 5px 10px;
460
+ border: 1px solid var(--lv-border);
461
+ border-radius: 6px;
462
+ font-size: 13px;
463
+ background: var(--lv-bg);
464
+ color: var(--lv-text);
465
+ outline: none;
466
+ transition: border-color 0.2s;
467
+ min-width: 120px;
468
+ }
469
+
470
+ .lv-filter-input:focus {
471
+ border-color: var(--lv-primary);
472
+ }
473
+
474
+ .lv-filter-select {
475
+ padding: 5px 10px;
476
+ border: 1px solid var(--lv-border);
477
+ border-radius: 6px;
478
+ font-size: 13px;
479
+ background: var(--lv-bg);
480
+ color: var(--lv-text);
481
+ outline: none;
482
+ cursor: pointer;
483
+ }
484
+
485
+ .lv-filter-btn {
486
+ display: inline-flex;
487
+ align-items: center;
488
+ gap: 4px;
489
+ padding: 5px 14px;
490
+ font-size: 13px;
491
+ border: 1px solid var(--lv-border);
492
+ border-radius: 6px;
493
+ background: var(--lv-bg);
494
+ color: var(--lv-text);
495
+ cursor: pointer;
496
+ transition: all 0.15s;
497
+ }
498
+
499
+ .lv-filter-btn:hover {
500
+ border-color: var(--lv-primary);
501
+ color: var(--lv-primary);
502
+ }
503
+
504
+ .lv-filter-btn--primary {
505
+ background: var(--lv-primary);
506
+ border-color: var(--lv-primary);
507
+ color: #fff;
508
+ }
509
+
510
+ .lv-filter-btn--primary:hover {
511
+ opacity: 0.85;
512
+ }
513
+
514
+ /* ===== Timeline ===== */
515
+ .lv-timeline {
516
+ position: relative;
517
+ padding-left: 28px;
518
+ }
519
+
520
+ .lv-timeline::before {
521
+ content: '';
522
+ position: absolute;
523
+ left: 9px;
524
+ top: 0;
525
+ bottom: 0;
526
+ width: 2px;
527
+ background: var(--lv-timeline-line);
528
+ }
529
+
530
+ .lv-timeline-item {
531
+ position: relative;
532
+ padding: 0 0 20px 0;
533
+ }
534
+
535
+ .lv-timeline-dot {
536
+ position: absolute;
537
+ left: -25px;
538
+ top: 6px;
539
+ width: 12px;
540
+ height: 12px;
541
+ border-radius: 50%;
542
+ background: var(--lv-bg);
543
+ border: 2px solid var(--lv-timeline-dot);
544
+ z-index: 1;
545
+ }
546
+
547
+ .lv-timeline-dot--anomaly {
548
+ border-color: #ff4d4f;
549
+ background: #fff1f0;
550
+ }
551
+
552
+ .lv-timeline-card {
553
+ background: var(--lv-bg);
554
+ border: 1px solid var(--lv-border);
555
+ border-radius: var(--lv-radius);
556
+ padding: 14px 18px;
557
+ cursor: pointer;
558
+ transition: all 0.2s;
559
+ box-shadow: 0 1px 3px var(--lv-shadow);
560
+ }
561
+
562
+ .lv-timeline-card:hover {
563
+ border-color: var(--lv-primary);
564
+ box-shadow: 0 2px 8px var(--lv-shadow);
565
+ }
566
+
567
+ .lv-timeline-card--anomaly {
568
+ border-left: 3px solid var(--lv-anomaly-border);
569
+ background: var(--lv-anomaly-bg);
570
+ }
571
+
572
+ .lv-timeline-header {
573
+ display: flex;
574
+ align-items: center;
575
+ gap: 10px;
576
+ flex-wrap: wrap;
577
+ margin-bottom: 4px;
578
+ }
579
+
580
+ .lv-timeline-time {
581
+ font-size: 12px;
582
+ color: var(--lv-text-muted);
583
+ font-family: var(--lv-font-mono);
584
+ }
585
+
586
+ .lv-timeline-user {
587
+ font-weight: 600;
588
+ font-size: 14px;
589
+ }
590
+
591
+ .lv-action-tag {
592
+ display: inline-block;
593
+ padding: 1px 8px;
594
+ font-size: 12px;
595
+ border-radius: 4px;
596
+ font-weight: 500;
597
+ color: #fff;
598
+ }
599
+
600
+ .lv-timeline-module {
601
+ font-size: 12px;
602
+ color: var(--lv-text-secondary);
603
+ background: var(--lv-bg-secondary);
604
+ padding: 1px 8px;
605
+ border-radius: 4px;
606
+ }
607
+
608
+ .lv-timeline-ip {
609
+ font-size: 12px;
610
+ color: var(--lv-text-muted);
611
+ font-family: var(--lv-font-mono);
612
+ }
613
+
614
+ .lv-anomaly-badge {
615
+ font-size: 11px;
616
+ padding: 1px 6px;
617
+ border-radius: 3px;
618
+ background: #fff2e8;
619
+ color: #d46b08;
620
+ border: 1px solid #ffd591;
621
+ }
622
+
623
+ .lv-timeline-detail {
624
+ font-size: 13px;
625
+ color: var(--lv-text-secondary);
626
+ margin-top: 6px;
627
+ }
628
+
629
+ .lv-timeline-expand-hint {
630
+ font-size: 12px;
631
+ color: var(--lv-primary);
632
+ margin-top: 6px;
633
+ cursor: pointer;
634
+ }
635
+
636
+ /* ===== Detail Panel ===== */
637
+ .lv-detail {
638
+ margin-top: 12px;
639
+ padding-top: 12px;
640
+ border-top: 1px dashed var(--lv-border);
641
+ animation: lv-slide-down 0.2s ease;
642
+ }
643
+
644
+ @keyframes lv-slide-down {
645
+ from { opacity: 0; transform: translateY(-8px); }
646
+ to { opacity: 1; transform: translateY(0); }
647
+ }
648
+
649
+ /* ===== Diff Table ===== */
650
+ .lv-diff-table {
651
+ width: 100%;
652
+ border-collapse: collapse;
653
+ font-size: 13px;
654
+ border: 1px solid var(--lv-border);
655
+ border-radius: 6px;
656
+ overflow: hidden;
657
+ }
658
+
659
+ .lv-diff-table th {
660
+ background: var(--lv-bg-secondary);
661
+ padding: 8px 12px;
662
+ text-align: left;
663
+ font-weight: 600;
664
+ font-size: 12px;
665
+ color: var(--lv-text-secondary);
666
+ border-bottom: 1px solid var(--lv-border);
667
+ }
668
+
669
+ .lv-diff-table td {
670
+ padding: 8px 12px;
671
+ border-bottom: 1px solid var(--lv-border);
672
+ vertical-align: top;
673
+ }
674
+
675
+ .lv-diff-table tr:last-child td {
676
+ border-bottom: none;
677
+ }
678
+
679
+ .lv-diff-old {
680
+ background: var(--lv-diff-del-bg);
681
+ color: var(--lv-diff-del-text);
682
+ text-decoration: line-through;
683
+ font-family: var(--lv-font-mono);
684
+ font-size: 12px;
685
+ }
686
+
687
+ .lv-diff-new {
688
+ background: var(--lv-diff-add-bg);
689
+ color: var(--lv-diff-add-text);
690
+ font-family: var(--lv-font-mono);
691
+ font-size: 12px;
692
+ }
693
+
694
+ /* ===== Metadata ===== */
695
+ .lv-metadata {
696
+ margin-top: 10px;
697
+ background: var(--lv-bg-secondary);
698
+ border-radius: 6px;
699
+ padding: 10px 14px;
700
+ font-size: 12px;
701
+ font-family: var(--lv-font-mono);
702
+ white-space: pre-wrap;
703
+ word-break: break-all;
704
+ color: var(--lv-text-secondary);
705
+ }
706
+
707
+ /* ===== Pagination ===== */
708
+ .lv-pagination {
709
+ display: flex;
710
+ align-items: center;
711
+ justify-content: center;
712
+ gap: 6px;
713
+ padding: 16px 0;
714
+ }
715
+
716
+ .lv-pagination-btn {
717
+ display: inline-flex;
718
+ align-items: center;
719
+ justify-content: center;
720
+ width: 32px;
721
+ height: 32px;
722
+ border: 1px solid var(--lv-border);
723
+ border-radius: 6px;
724
+ background: var(--lv-bg);
725
+ color: var(--lv-text);
726
+ font-size: 13px;
727
+ cursor: pointer;
728
+ transition: all 0.15s;
729
+ }
730
+
731
+ .lv-pagination-btn:hover {
732
+ border-color: var(--lv-primary);
733
+ color: var(--lv-primary);
734
+ }
735
+
736
+ .lv-pagination-btn--active {
737
+ background: var(--lv-primary);
738
+ border-color: var(--lv-primary);
739
+ color: #fff;
740
+ }
741
+
742
+ .lv-pagination-btn:disabled {
743
+ opacity: 0.4;
744
+ cursor: not-allowed;
745
+ }
746
+
747
+ .lv-pagination-info {
748
+ font-size: 13px;
749
+ color: var(--lv-text-secondary);
750
+ margin: 0 8px;
751
+ }
752
+
753
+ /* ===== Empty state ===== */
754
+ .lv-empty {
755
+ text-align: center;
756
+ padding: 48px 16px;
757
+ color: var(--lv-text-muted);
758
+ font-size: 14px;
759
+ }
760
+
761
+ .lv-empty-icon {
762
+ font-size: 48px;
763
+ margin-bottom: 12px;
764
+ opacity: 0.3;
765
+ }
766
+ `;
767
+ //#endregion
768
+ //#region src/core/LogViewer.ts
769
+ var LogViewer = class {
770
+ constructor(config) {
771
+ this.emitter = new EventEmitter();
772
+ this.filterEngine = new FilterEngine();
773
+ this.timelineRenderer = new TimelineRenderer();
774
+ this.diffRenderer = new DiffRenderer();
775
+ this.data = [];
776
+ this.filteredData = [];
777
+ this.filter = {};
778
+ this.pagination = {
779
+ page: 1,
780
+ pageSize: 20
781
+ };
782
+ this.theme = "light";
783
+ this.expandedIds = /* @__PURE__ */ new Set();
784
+ this.timelineEl = null;
785
+ this.paginationEl = null;
786
+ this.destroyed = false;
787
+ this.config = config;
788
+ const container = typeof config.container === "string" ? document.querySelector(config.container) : config.container;
789
+ if (!container) throw new Error(`[LogViewer] Container not found: ${config.container}`);
790
+ injectStyles();
791
+ this.root = document.createElement("div");
792
+ this.root.className = "lv-root";
793
+ container.appendChild(this.root);
794
+ this.theme = config.theme || "light";
795
+ this.root.setAttribute("data-theme", this.theme);
796
+ if (config.pageSize) this.pagination.pageSize = config.pageSize;
797
+ if (config.pagination) Object.assign(this.pagination, config.pagination);
798
+ if (config.filter) this.filter = deepClone(config.filter);
799
+ if (config.data) this.data = [...config.data];
800
+ this.applyFilterAndRender();
801
+ this.bindEvents();
802
+ }
803
+ getData() {
804
+ return [...this.data];
805
+ }
806
+ setData(entries) {
807
+ this.data = [...entries];
808
+ this.pagination.page = 1;
809
+ this.applyFilterAndRender();
810
+ this.emitter.emit("data:change", this.data);
811
+ }
812
+ appendData(entries) {
813
+ this.data.push(...entries);
814
+ this.applyFilterAndRender();
815
+ this.emitter.emit("data:change", this.data);
816
+ }
817
+ getFilter() {
818
+ return deepClone(this.filter);
819
+ }
820
+ setFilter(filter) {
821
+ this.filter = deepClone(filter);
822
+ this.pagination.page = 1;
823
+ this.applyFilterAndRender();
824
+ this.emitter.emit("filter:change", this.filter);
825
+ this.config.onFilterChange?.(this.filter);
826
+ }
827
+ resetFilter() {
828
+ this.filter = {};
829
+ this.pagination.page = 1;
830
+ this.applyFilterAndRender();
831
+ this.emitter.emit("filter:change", this.filter);
832
+ }
833
+ getTheme() {
834
+ return this.theme;
835
+ }
836
+ setTheme(theme) {
837
+ this.theme = theme;
838
+ this.root.setAttribute("data-theme", theme);
839
+ this.emitter.emit("theme:change", theme);
840
+ this.config.onThemeChange?.(theme);
841
+ }
842
+ expandEntry(id) {
843
+ this.expandedIds.add(id);
844
+ const entry = this.data.find((e) => e.id === id);
845
+ if (entry) this.emitter.emit("entry:expand", entry);
846
+ this.renderTimeline();
847
+ }
848
+ collapseEntry(id) {
849
+ this.expandedIds.delete(id);
850
+ const entry = this.data.find((e) => e.id === id);
851
+ if (entry) this.emitter.emit("entry:collapse", entry);
852
+ this.renderTimeline();
853
+ }
854
+ collapseAll() {
855
+ this.expandedIds.clear();
856
+ this.renderTimeline();
857
+ }
858
+ goToPage(page) {
859
+ const totalPages = Math.ceil(this.filteredData.length / this.pagination.pageSize);
860
+ if (page < 1 || page > totalPages) return;
861
+ this.pagination.page = page;
862
+ this.emitter.emit("page:change", page);
863
+ this.renderTimeline();
864
+ }
865
+ getPagination() {
866
+ return {
867
+ ...this.pagination,
868
+ total: this.filteredData.length
869
+ };
870
+ }
871
+ on(event, callback) {
872
+ return this.emitter.on(event, callback);
873
+ }
874
+ refresh() {
875
+ this.applyFilterAndRender();
876
+ }
877
+ destroy() {
878
+ if (this.destroyed) return;
879
+ this.destroyed = true;
880
+ this.emitter.removeAllListeners();
881
+ this.root.remove();
882
+ }
883
+ applyFilterAndRender() {
884
+ this.filteredData = this.filterEngine.filter(this.data, this.filter);
885
+ this.filteredData.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
886
+ this.pagination.total = this.filteredData.length;
887
+ this.renderTimeline();
888
+ }
889
+ getPageData() {
890
+ const { page, pageSize } = this.pagination;
891
+ const start = (page - 1) * pageSize;
892
+ return this.filteredData.slice(start, start + pageSize);
893
+ }
894
+ renderTimeline() {
895
+ if (this.destroyed) return;
896
+ const pageData = this.getPageData();
897
+ const newTimeline = this.timelineRenderer.render(pageData, this.expandedIds);
898
+ for (const entry of pageData) if (this.expandedIds.has(entry.id)) {
899
+ const itemEl = newTimeline.querySelector(`[data-id="${entry.id}"] .lv-timeline-card`);
900
+ if (itemEl) itemEl.appendChild(this.diffRenderer.renderDetail(entry));
901
+ }
902
+ if (this.timelineEl) this.root.removeChild(this.timelineEl);
903
+ this.timelineEl = newTimeline;
904
+ this.root.appendChild(this.timelineEl);
905
+ const newPagination = this.timelineRenderer.renderPagination(this.pagination.page, this.pagination.pageSize, this.filteredData.length);
906
+ if (this.paginationEl) this.root.removeChild(this.paginationEl);
907
+ this.paginationEl = newPagination;
908
+ this.root.appendChild(this.paginationEl);
909
+ }
910
+ bindEvents() {
911
+ this.root.addEventListener("click", (e) => {
912
+ const target = e.target;
913
+ const card = target.closest(".lv-timeline-card");
914
+ if (card) {
915
+ const id = card.closest(".lv-timeline-item")?.getAttribute("data-id");
916
+ if (!id) return;
917
+ const entry = this.filteredData.find((en) => en.id === id);
918
+ if (!entry) return;
919
+ if (target.classList.contains("lv-timeline-expand-hint")) {
920
+ if (this.expandedIds.has(id)) this.collapseEntry(id);
921
+ else this.expandEntry(id);
922
+ return;
923
+ }
924
+ this.emitter.emit("entry:click", entry);
925
+ this.config.onEntryClick?.(entry);
926
+ }
927
+ const pageBtn = target.closest(".lv-pagination-btn");
928
+ if (pageBtn && !pageBtn.disabled) {
929
+ const page = parseInt(pageBtn.getAttribute("data-page") || "1", 10);
930
+ this.goToPage(page);
931
+ }
932
+ });
933
+ }
934
+ };
935
+ //#endregion
936
+ //#region src/index.ts
937
+ const VERSION = "0.1.0";
938
+ function createLogViewer(config) {
939
+ return new LogViewer(config);
940
+ }
941
+ //#endregion
942
+ exports.ACTION_COLORS = ACTION_COLORS;
943
+ exports.ACTION_LABELS = ACTION_LABELS;
944
+ exports.DiffRenderer = DiffRenderer;
945
+ exports.EventEmitter = EventEmitter;
946
+ exports.FilterEngine = FilterEngine;
947
+ exports.LogViewer = LogViewer;
948
+ exports.TimelineRenderer = TimelineRenderer;
949
+ exports.VERSION = VERSION;
950
+ exports.createLogViewer = createLogViewer;
951
+ exports.debounce = debounce;
952
+ exports.deepClone = deepClone;
953
+ exports.escapeHtml = escapeHtml;
954
+ exports.formatDate = formatDate;
955
+ exports.formatRelativeTime = formatRelativeTime;
956
+ exports.formatValue = formatValue;
957
+ exports.injectStyles = injectStyles;
958
+ exports.uid = uid;
959
+ });
960
+ /*! End of @composy/log-viewer-core | Powered by @composy/builder */
961
+ //# sourceMappingURL=index.js.map