@composy/log-viewer-core 0.0.1 → 0.0.2

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 (108) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +175 -37
  3. package/dist/index.js +614 -299
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.min.js +548 -1
  6. package/dist/index.min.js.map +1 -0
  7. package/es/.dts-output-manifest.json +1 -0
  8. package/es/.dts.tsbuildinfo +1 -0
  9. package/es/core/EventEmitter.d.ts +0 -1
  10. package/es/core/EventEmitter.js +13 -9
  11. package/es/core/EventEmitter.js.map +1 -1
  12. package/es/core/LogViewer.d.ts +14 -11
  13. package/es/core/LogViewer.js +243 -64
  14. package/es/core/LogViewer.js.map +1 -1
  15. package/es/filters/FilterEngine.d.ts +0 -1
  16. package/es/filters/FilterEngine.js +31 -26
  17. package/es/filters/FilterEngine.js.map +1 -1
  18. package/es/index.d.ts +7 -7
  19. package/es/index.js +3 -3
  20. package/es/index.js.map +1 -1
  21. package/es/renderers/DiffRenderer.d.ts +0 -1
  22. package/es/renderers/DiffRenderer.js +13 -3
  23. package/es/renderers/DiffRenderer.js.map +1 -1
  24. package/es/renderers/TimelineRenderer.d.ts +7 -3
  25. package/es/renderers/TimelineRenderer.js +56 -22
  26. package/es/renderers/TimelineRenderer.js.map +1 -1
  27. package/es/styles/index.d.ts +1 -5
  28. package/es/styles/index.js +167 -131
  29. package/es/styles/index.js.map +1 -1
  30. package/es/types/index.d.ts +4 -1
  31. package/es/types/index.js +1 -1
  32. package/es/types/index.js.map +1 -1
  33. package/es/utils/index.d.ts +1 -2
  34. package/es/utils/index.js +54 -6
  35. package/es/utils/index.js.map +1 -1
  36. package/esm/core/EventEmitter.js +13 -9
  37. package/esm/core/EventEmitter.js.map +1 -1
  38. package/esm/core/LogViewer.js +243 -64
  39. package/esm/core/LogViewer.js.map +1 -1
  40. package/esm/filters/FilterEngine.js +31 -26
  41. package/esm/filters/FilterEngine.js.map +1 -1
  42. package/esm/index.js +2 -2
  43. package/esm/index.js.map +1 -1
  44. package/esm/renderers/DiffRenderer.js +13 -3
  45. package/esm/renderers/DiffRenderer.js.map +1 -1
  46. package/esm/renderers/TimelineRenderer.js +56 -22
  47. package/esm/renderers/TimelineRenderer.js.map +1 -1
  48. package/esm/styles/index.js +167 -131
  49. package/esm/styles/index.js.map +1 -1
  50. package/esm/types/index.js +1 -1
  51. package/esm/types/index.js.map +1 -1
  52. package/esm/utils/index.js +54 -6
  53. package/esm/utils/index.js.map +1 -1
  54. package/lib/core/EventEmitter.cjs +13 -9
  55. package/lib/core/EventEmitter.cjs.map +1 -1
  56. package/{esm/core/EventEmitter.d.ts → lib/core/EventEmitter.d.cts} +0 -1
  57. package/lib/core/LogViewer.cjs +247 -68
  58. package/lib/core/LogViewer.cjs.map +1 -1
  59. package/{esm/core/LogViewer.d.ts → lib/core/LogViewer.d.cts} +14 -11
  60. package/lib/filters/FilterEngine.cjs +31 -26
  61. package/lib/filters/FilterEngine.cjs.map +1 -1
  62. package/lib/filters/{FilterEngine.d.ts → FilterEngine.d.cts} +0 -1
  63. package/lib/index.cjs +3 -3
  64. package/lib/index.cjs.map +1 -1
  65. package/{esm/index.d.ts → lib/index.d.cts} +7 -7
  66. package/lib/renderers/DiffRenderer.cjs +13 -3
  67. package/lib/renderers/DiffRenderer.cjs.map +1 -1
  68. package/lib/renderers/{DiffRenderer.d.ts → DiffRenderer.d.cts} +0 -1
  69. package/lib/renderers/TimelineRenderer.cjs +55 -21
  70. package/lib/renderers/TimelineRenderer.cjs.map +1 -1
  71. package/lib/renderers/{TimelineRenderer.d.ts → TimelineRenderer.d.cts} +7 -3
  72. package/lib/styles/index.cjs +167 -131
  73. package/lib/styles/index.cjs.map +1 -1
  74. package/{esm/styles/index.d.ts → lib/styles/index.d.cts} +1 -5
  75. package/lib/types/index.cjs +1 -1
  76. package/lib/types/index.cjs.map +1 -1
  77. package/{esm/types/index.d.ts → lib/types/index.d.cts} +4 -1
  78. package/lib/utils/index.cjs +54 -6
  79. package/lib/utils/index.cjs.map +1 -1
  80. package/{esm/utils/index.d.ts → lib/utils/index.d.cts} +1 -2
  81. package/package.json +15 -9
  82. package/es/core/EventEmitter.d.ts.map +0 -1
  83. package/es/core/LogViewer.d.ts.map +0 -1
  84. package/es/filters/FilterEngine.d.ts.map +0 -1
  85. package/es/index.d.ts.map +0 -1
  86. package/es/renderers/DiffRenderer.d.ts.map +0 -1
  87. package/es/renderers/TimelineRenderer.d.ts.map +0 -1
  88. package/es/styles/index.d.ts.map +0 -1
  89. package/es/types/index.d.ts.map +0 -1
  90. package/es/utils/index.d.ts.map +0 -1
  91. package/esm/core/EventEmitter.d.ts.map +0 -1
  92. package/esm/core/LogViewer.d.ts.map +0 -1
  93. package/esm/filters/FilterEngine.d.ts +0 -26
  94. package/esm/filters/FilterEngine.d.ts.map +0 -1
  95. package/esm/index.d.ts.map +0 -1
  96. package/esm/renderers/DiffRenderer.d.ts +0 -18
  97. package/esm/renderers/DiffRenderer.d.ts.map +0 -1
  98. package/esm/renderers/TimelineRenderer.d.ts +0 -22
  99. package/esm/renderers/TimelineRenderer.d.ts.map +0 -1
  100. package/esm/styles/index.d.ts.map +0 -1
  101. package/esm/types/index.d.ts.map +0 -1
  102. package/esm/utils/index.d.ts.map +0 -1
  103. package/lib/core/EventEmitter.d.ts +0 -11
  104. package/lib/core/LogViewer.d.ts +0 -46
  105. package/lib/index.d.ts +0 -26
  106. package/lib/styles/index.d.ts +0 -9
  107. package/lib/types/index.d.ts +0 -113
  108. package/lib/utils/index.d.ts +0 -18
package/dist/index.min.js CHANGED
@@ -1 +1,548 @@
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={"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;"};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;});
1
+ /*!
2
+ * ***********************************
3
+ * @composy/log-viewer-core v0.0.1 *
4
+ * Built with rollup *
5
+ * Build time: 2024-10-12 17:29:20 *
6
+ * Build mode: production *
7
+ * Minified: No *
8
+ * ***********************************
9
+ */
10
+ (function(e, t) {
11
+ "object" == typeof exports && "undefined" != typeof module ? t(exports) : "function" == typeof define && define.amd ? define(["exports"], t) : t((e = "undefined" != typeof globalThis ? globalThis : e || self).LDesignLogViewerCore = {});
12
+ })(this, function(exports) {
13
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
14
+ const e = {
15
+ create: "新增",
16
+ update: "修改",
17
+ delete: "删除",
18
+ query: "查询",
19
+ login: "登录",
20
+ logout: "登出",
21
+ export: "导出",
22
+ import: "导入",
23
+ approve: "审批",
24
+ reject: "驳回",
25
+ other: "其他"
26
+ };
27
+ var t = class {
28
+ constructor() {
29
+ this.listeners = /* @__PURE__ */ new Map();
30
+ }
31
+ on(e, t) {
32
+ return e && "function" == typeof t ? (this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(t), () => this.off(e, t)) : () => {};
33
+ }
34
+ off(e, t) {
35
+ const n = this.listeners.get(e);
36
+ n && (n.delete(t), 0 === n.size && this.listeners.delete(e));
37
+ }
38
+ emit(e, ...t) {
39
+ const n = this.listeners.get(e);
40
+ if (n) for (const r of [...n]) try {
41
+ r(...t);
42
+ } catch (t) {
43
+ console.error(`[LogViewer] Event handler error (${e}):`, t);
44
+ }
45
+ }
46
+ removeAllListeners(e) {
47
+ e ? this.listeners.delete(e) : this.listeners.clear();
48
+ }
49
+ }, n = class {
50
+ filter(e, t) {
51
+ let n = Array.isArray(e) ? e.slice() : [];
52
+ const r = t || {};
53
+ if (r.user?.trim()) {
54
+ const e = r.user.trim().toLowerCase();
55
+ n = n.filter((t) => t.user?.toLowerCase().includes(e) || t.userId?.toLowerCase().includes(e));
56
+ }
57
+ if (r.action) {
58
+ const e = Array.isArray(r.action) ? r.action : [r.action];
59
+ e.length > 0 && (n = n.filter((t) => e.includes(t.action)));
60
+ }
61
+ if (r.module?.trim()) {
62
+ const e = r.module.trim().toLowerCase();
63
+ n = n.filter((t) => t.module?.toLowerCase().includes(e));
64
+ }
65
+ if (r.ip?.trim()) {
66
+ const e = r.ip.trim().toLowerCase();
67
+ n = n.filter((t) => t.ip?.toLowerCase().includes(e));
68
+ }
69
+ if (r.timeRange) {
70
+ const [e, t] = r.timeRange, a = new Date(e).getTime(), i = new Date(t).getTime();
71
+ if (Number.isFinite(a) && Number.isFinite(i)) {
72
+ const e = Math.min(a, i), t = Math.max(a, i);
73
+ n = n.filter((n) => {
74
+ const r = new Date(n.timestamp).getTime();
75
+ return Number.isFinite(r) && r >= e && r <= t;
76
+ });
77
+ }
78
+ }
79
+ if (r.keyword?.trim()) {
80
+ const e = r.keyword.trim().toLowerCase();
81
+ n = n.filter((t) => [
82
+ t.user || "",
83
+ t.action,
84
+ t.module || "",
85
+ t.detail || "",
86
+ t.ip || "",
87
+ t.id
88
+ ].some((t) => t.toLowerCase().includes(e)));
89
+ }
90
+ return n;
91
+ }
92
+ getUniqueUsers(e) {
93
+ return [...new Set(e.map((e) => e.user).filter(Boolean))].sort();
94
+ }
95
+ getUniqueModules(e) {
96
+ return [...new Set(e.map((e) => e.module).filter(Boolean))].sort();
97
+ }
98
+ getUniqueActions(e) {
99
+ return [...new Set(e.map((e) => e.action))].sort();
100
+ }
101
+ };
102
+ let r = 0;
103
+ function a(e, t = "YYYY-MM-DD HH:mm:ss") {
104
+ const n = e instanceof Date ? e : new Date(e);
105
+ if (Number.isNaN(n.getTime())) return String(e);
106
+ const r = (e) => String(e).padStart(2, "0"), a = String(n.getFullYear()), i = r(n.getMonth() + 1), o = r(n.getDate()), l = r(n.getHours()), d = r(n.getMinutes()), s = r(n.getSeconds());
107
+ return t.replace("YYYY", a).replace("MM", i).replace("DD", o).replace("HH", l).replace("mm", d).replace("ss", s);
108
+ }
109
+ function i(e) {
110
+ if (null === e || "object" != typeof e) return e;
111
+ if ("function" == typeof structuredClone) try {
112
+ return structuredClone(e);
113
+ } catch {}
114
+ const t = /* @__PURE__ */ new WeakMap(), n = (e) => {
115
+ if (null === e || "object" != typeof e) return e;
116
+ if (t.has(e)) return t.get(e);
117
+ if (e instanceof Date) return new Date(e.getTime());
118
+ if (e instanceof RegExp) return new RegExp(e.source, e.flags);
119
+ if (e instanceof Map) {
120
+ const r = /* @__PURE__ */ new Map();
121
+ return t.set(e, r), e.forEach((e, t) => r.set(n(t), n(e))), r;
122
+ }
123
+ if (e instanceof Set) {
124
+ const r = /* @__PURE__ */ new Set();
125
+ return t.set(e, r), e.forEach((e) => r.add(n(e))), r;
126
+ }
127
+ if (Array.isArray(e)) {
128
+ const r = [];
129
+ return t.set(e, r), e.forEach((e) => r.push(n(e))), r;
130
+ }
131
+ const r = Object.create(Object.getPrototypeOf(e));
132
+ t.set(e, r);
133
+ for (const t of Reflect.ownKeys(e)) {
134
+ const a = Object.getOwnPropertyDescriptor(e, t);
135
+ a && "value" in a && Object.defineProperty(r, t, {
136
+ ...a,
137
+ value: n(a.value)
138
+ });
139
+ }
140
+ return r;
141
+ };
142
+ try {
143
+ return n(e);
144
+ } catch {
145
+ return e;
146
+ }
147
+ }
148
+ function o(e) {
149
+ if (null == e) return "-";
150
+ if ("object" == typeof e) try {
151
+ return JSON.stringify(e, (e, t) => "bigint" == typeof t ? `${t}n` : t, 2);
152
+ } catch {
153
+ return String(e);
154
+ }
155
+ return String(e);
156
+ }
157
+ var l = class {
158
+ renderDetail(e) {
159
+ const t = document.createElement("div");
160
+ if (t.className = "lv-detail", e.changes && e.changes.length > 0 && t.appendChild(this.renderDiffTable(e.changes)), e.metadata && Object.keys(e.metadata).length > 0) {
161
+ const n = document.createElement("div");
162
+ n.className = "lv-metadata", n.textContent = o(e.metadata), t.appendChild(n);
163
+ }
164
+ return t;
165
+ }
166
+ renderDiffTable(e) {
167
+ const t = document.createElement("table");
168
+ t.className = "lv-diff-table";
169
+ const n = document.createElement("thead"), r = document.createElement("tr");
170
+ for (const e of [
171
+ "字段",
172
+ "变更前",
173
+ "变更后"
174
+ ]) {
175
+ const t = document.createElement("th");
176
+ t.textContent = e, r.appendChild(t);
177
+ }
178
+ n.appendChild(r), t.appendChild(n);
179
+ const a = document.createElement("tbody");
180
+ for (const t of e) {
181
+ const e = document.createElement("tr"), n = document.createElement("td");
182
+ n.textContent = t.label || t.field, e.appendChild(n);
183
+ const r = document.createElement("td");
184
+ r.className = "lv-diff-old", r.textContent = o(t.oldValue), e.appendChild(r);
185
+ const i = document.createElement("td");
186
+ i.className = "lv-diff-new", i.textContent = o(t.newValue), e.appendChild(i), a.appendChild(e);
187
+ }
188
+ return t.appendChild(a), t;
189
+ }
190
+ };
191
+ const d = {
192
+ "off-hours": "非工作时间",
193
+ sensitive: "敏感操作",
194
+ "high-frequency": "高频操作",
195
+ none: ""
196
+ }, s = {
197
+ "off-hours": "Off hours",
198
+ sensitive: "Sensitive",
199
+ "high-frequency": "High frequency",
200
+ none: ""
201
+ }, c = {
202
+ create: "Create",
203
+ update: "Update",
204
+ delete: "Delete",
205
+ query: "Query",
206
+ login: "Login",
207
+ logout: "Logout",
208
+ export: "Export",
209
+ import: "Import",
210
+ approve: "Approve",
211
+ reject: "Reject",
212
+ other: "Other"
213
+ };
214
+ var p = class {
215
+ render(e, t, n = {}) {
216
+ const r = document.createElement("div");
217
+ if (r.className = "lv-timeline", 0 === e.length) {
218
+ const e = document.createElement("div");
219
+ e.className = "lv-empty-icon", e.textContent = "--";
220
+ const t = document.createElement("div");
221
+ return t.textContent = "en-US" === n.locale ? "No log entries" : "暂无日志记录", r.append(e, t), r.className = "", r;
222
+ }
223
+ for (const a of e) r.appendChild(this.renderItem(a, t.has(a.id), n));
224
+ return r;
225
+ }
226
+ renderItem(t, n, r = {}) {
227
+ const i = document.createElement("div");
228
+ i.className = "lv-timeline-item", i.setAttribute("data-id", t.id);
229
+ const o = !1 !== r.showAnomalyBadge && t.anomaly && "none" !== t.anomaly, l = document.createElement("div");
230
+ l.className = "lv-timeline-dot" + (o ? " lv-timeline-dot--anomaly" : ""), i.appendChild(l);
231
+ const p = document.createElement("div");
232
+ p.className = "lv-timeline-card" + (o ? " lv-timeline-card--anomaly" : "");
233
+ const v = document.createElement("div");
234
+ v.className = "lv-timeline-header";
235
+ const m = document.createElement("span");
236
+ m.className = "lv-timeline-time", m.textContent = a(t.timestamp), v.appendChild(m);
237
+ const f = document.createElement("span");
238
+ f.className = "lv-timeline-user", f.textContent = t.user, v.appendChild(f);
239
+ const h = document.createElement("span");
240
+ h.className = `lv-action-tag lv-action-tag--${Object.keys(e).includes(t.action) ? t.action : "other"}`, h.textContent = ("en-US" === r.locale ? c[t.action] : e[t.action]) || t.action, v.appendChild(h);
241
+ const u = document.createElement("span");
242
+ if (u.className = "lv-timeline-module", u.textContent = t.module, v.appendChild(u), t.ip) {
243
+ const e = document.createElement("span");
244
+ e.className = "lv-timeline-ip", e.textContent = t.ip, v.appendChild(e);
245
+ }
246
+ if (!1 !== r.showAnomalyBadge && o) {
247
+ const e = document.createElement("span");
248
+ e.className = "lv-anomaly-badge", e.textContent = ("en-US" === r.locale ? s[t.anomaly] : d)[t.anomaly] || t.anomaly, v.appendChild(e);
249
+ }
250
+ if (p.appendChild(v), t.detail) {
251
+ const e = document.createElement("div");
252
+ e.className = "lv-timeline-detail", e.textContent = t.detail, p.appendChild(e);
253
+ }
254
+ if (!1 !== r.showDiff && t.changes && t.changes.length > 0) {
255
+ const e = document.createElement("button");
256
+ e.type = "button", e.className = "lv-timeline-expand-hint", e.setAttribute("aria-expanded", String(n)), e.textContent = n ? "[ 收起变更详情 ]" : `[ 查看变更详情 (${t.changes.length} 个字段) ]`, p.appendChild(e);
257
+ }
258
+ return i.appendChild(p), i;
259
+ }
260
+ renderPagination(e, t, n) {
261
+ const r = document.createElement("div");
262
+ r.className = "lv-pagination";
263
+ const a = Number.isFinite(t) && t > 0 ? Math.floor(t) : 1, i = Number.isFinite(n) && n > 0 ? Math.floor(n) : 0, o = Number.isFinite(e) && e > 0 ? Math.floor(e) : 1, l = Math.ceil(i / a);
264
+ if (l <= 1) return r;
265
+ const d = document.createElement("button");
266
+ d.type = "button", d.className = "lv-pagination-btn", d.textContent = "<", d.disabled = o <= 1, d.setAttribute("aria-label", "Previous page"), d.setAttribute("data-page", String(o - 1)), r.appendChild(d);
267
+ let s = Math.max(1, o - Math.floor(3.5));
268
+ const c = Math.min(l, s + 7 - 1);
269
+ c - s < 6 && (s = Math.max(1, c - 7 + 1));
270
+ for (let e = s; e <= c; e++) {
271
+ const t = document.createElement("button");
272
+ t.type = "button", t.className = "lv-pagination-btn" + (e === o ? " lv-pagination-btn--active" : ""), t.textContent = String(e), t.setAttribute("data-page", String(e)), e === o && t.setAttribute("aria-current", "page"), r.appendChild(t);
273
+ }
274
+ const p = document.createElement("button");
275
+ p.type = "button", p.className = "lv-pagination-btn", p.textContent = ">", p.disabled = o >= l, p.setAttribute("aria-label", "Next page"), p.setAttribute("data-page", String(o + 1)), r.appendChild(p);
276
+ const v = document.createElement("span");
277
+ return v.className = "lv-pagination-info", v.textContent = `共 ${i} 条`, r.appendChild(v), r;
278
+ }
279
+ };
280
+ const v = "ldesign-log-viewer-styles";
281
+ function m() {
282
+ if ("undefined" == typeof document) return;
283
+ if (document.getElementById(v)) return;
284
+ const e = document.createElement("style");
285
+ e.id = v, e.textContent = f, document.head.appendChild(e);
286
+ }
287
+ const f = "\n/* ===== CSS Variables ===== */\n.lv-root {\n --lv-bg: var(--ld-white);\n --lv-bg-secondary: var(--ld-bg-surface-soft);\n --lv-bg-hover: var(--ld-primary-50);\n --lv-border: var(--ld-border);\n --lv-text: var(--ld-text-primary);\n --lv-text-secondary: var(--ld-text-secondary);\n --lv-text-muted: var(--ld-text-muted);\n --lv-primary: var(--ld-primary-500);\n --lv-primary-light: var(--ld-primary-50);\n --lv-shadow: color-mix(in srgb, var(--ld-text-primary) 4%, transparent);\n --lv-timeline-line: var(--ld-border);\n --lv-timeline-dot: var(--ld-primary-500);\n --lv-diff-add-bg: var(--ld-success-50);\n --lv-diff-add-text: var(--ld-success-700);\n --lv-diff-del-bg: var(--ld-danger-50);\n --lv-diff-del-text: var(--ld-danger-700);\n --lv-anomaly-bg: var(--ld-warning-50);\n --lv-anomaly-border: var(--ld-warning-300);\n --lv-radius: var(--ld-radius-md);\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: var(--ld-bg-surface);\n --lv-bg-secondary: var(--ld-bg-surface-soft);\n --lv-bg-hover: var(--ld-primary-50);\n --lv-border: var(--ld-border-strong);\n --lv-text: var(--ld-text-primary);\n --lv-text-secondary: var(--ld-text-secondary);\n --lv-text-muted: var(--ld-text-muted);\n --lv-primary: var(--ld-primary-400);\n --lv-primary-light: var(--ld-primary-50);\n --lv-shadow: color-mix(in srgb, var(--ld-text-primary) 20%, transparent);\n --lv-timeline-line: var(--ld-border-strong);\n --lv-timeline-dot: var(--ld-primary-400);\n --lv-diff-add-bg: var(--ld-success-50);\n --lv-diff-add-text: var(--ld-success-700);\n --lv-diff-del-bg: var(--ld-danger-50);\n --lv-diff-del-text: var(--ld-danger-700);\n --lv-anomaly-bg: var(--ld-warning-50);\n --lv-anomaly-border: var(--ld-warning-700);\n}\n\n/* ===== Root ===== */\n.lv-root {\n font-family: var(--lv-font);\n font-size: var(--ld-px-14);\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: var(--ld-px-10);\n padding: var(--ld-px-16);\n background: var(--lv-bg);\n border: var(--ld-px-1) solid var(--lv-border);\n border-radius: var(--lv-radius);\n margin-bottom: var(--ld-px-16);\n align-items: center;\n}\n\n.lv-filter-item {\n display: flex;\n align-items: center;\n gap: var(--ld-px-6);\n font-size: var(--ld-px-13);\n}\n\n.lv-filter-label {\n color: var(--lv-text-secondary);\n font-size: var(--ld-px-12);\n white-space: nowrap;\n}\n\n.lv-filter-input {\n padding: var(--ld-px-5) var(--ld-px-10);\n border: var(--ld-px-1) solid var(--lv-border);\n border-radius: var(--ld-px-6);\n font-size: var(--ld-px-13);\n background: var(--lv-bg);\n color: var(--lv-text);\n outline: none;\n transition: border-color 0.2s;\n min-width: var(--ld-px-120);\n}\n\n.lv-filter-input:focus {\n border-color: var(--lv-primary);\n}\n\n.lv-filter-select {\n padding: var(--ld-px-5) var(--ld-px-10);\n border: var(--ld-px-1) solid var(--lv-border);\n border-radius: var(--ld-px-6);\n font-size: var(--ld-px-13);\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: var(--ld-px-4);\n padding: var(--ld-px-5) var(--ld-px-14);\n font-size: var(--ld-px-13);\n border: var(--ld-px-1) solid var(--lv-border);\n border-radius: var(--ld-px-6);\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: var(--ld-white);\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: var(--ld-px-28);\n}\n\n.lv-timeline::before {\n content: '';\n position: absolute;\n left: var(--ld-px-9);\n top: 0;\n bottom: 0;\n width: var(--ld-px-2);\n background: var(--lv-timeline-line);\n}\n\n.lv-timeline-item {\n position: relative;\n padding: 0 0 var(--ld-px-20) 0;\n}\n\n.lv-timeline-dot {\n position: absolute;\n left: calc(-1 * var(--ld-px-25));\n top: var(--ld-px-6);\n width: var(--ld-px-12);\n height: var(--ld-px-12);\n border-radius: 50%;\n background: var(--lv-bg);\n border: var(--ld-px-2) solid var(--lv-timeline-dot);\n z-index: 1;\n}\n\n.lv-timeline-dot--anomaly {\n border-color: var(--ld-danger-500);\n background: var(--ld-danger-50);\n}\n\n.lv-timeline-card {\n background: var(--lv-bg);\n border: var(--ld-px-1) solid var(--lv-border);\n border-radius: var(--lv-radius);\n padding: var(--ld-px-14) var(--ld-px-18);\n cursor: pointer;\n transition: all 0.2s;\n box-shadow: 0 var(--ld-px-1) var(--ld-px-3) var(--lv-shadow);\n}\n\n.lv-timeline-card:hover {\n border-color: var(--lv-primary);\n box-shadow: 0 var(--ld-px-2) var(--ld-px-8) var(--lv-shadow);\n}\n\n.lv-timeline-card--anomaly {\n border-left: var(--ld-px-3) 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: var(--ld-px-10);\n flex-wrap: wrap;\n margin-bottom: var(--ld-px-4);\n}\n\n.lv-timeline-time {\n font-size: var(--ld-px-12);\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: var(--ld-px-14);\n}\n\n.lv-action-tag {\n display: inline-block;\n padding: var(--ld-px-1) var(--ld-px-8);\n font-size: var(--ld-px-12);\n border-radius: var(--ld-px-4);\n font-weight: 500;\n color: var(--ld-white);\n}\n\n.lv-action-tag--create,\n.lv-action-tag--approve {\n background: var(--ld-success-500);\n}\n\n.lv-action-tag--update,\n.lv-action-tag--login {\n background: var(--ld-primary-500);\n}\n\n.lv-action-tag--delete,\n.lv-action-tag--reject {\n background: var(--ld-danger-500);\n}\n\n.lv-action-tag--query,\n.lv-action-tag--other {\n background: var(--ld-text-secondary);\n}\n\n.lv-action-tag--logout {\n background: var(--ld-primary-700);\n}\n\n.lv-action-tag--export {\n background: var(--ld-warning-500);\n}\n\n.lv-action-tag--import {\n background: var(--ld-success-500);\n}\n\n.lv-timeline-module {\n font-size: var(--ld-px-12);\n color: var(--lv-text-secondary);\n background: var(--lv-bg-secondary);\n padding: var(--ld-px-1) var(--ld-px-8);\n border-radius: var(--ld-px-4);\n}\n\n.lv-timeline-ip {\n font-size: var(--ld-px-12);\n color: var(--lv-text-muted);\n font-family: var(--lv-font-mono);\n}\n\n.lv-anomaly-badge {\n font-size: var(--ld-px-11);\n padding: var(--ld-px-1) var(--ld-px-6);\n border-radius: var(--ld-px-3);\n background: var(--ld-warning-50);\n color: var(--ld-warning-700);\n border: var(--ld-px-1) solid var(--ld-warning-300);\n}\n\n.lv-timeline-detail {\n font-size: var(--ld-px-13);\n color: var(--lv-text-secondary);\n margin-top: var(--ld-px-6);\n}\n\n.lv-timeline-expand-hint {\n border: 0;\n padding: 0;\n background: transparent;\n font: inherit;\n font-size: var(--ld-px-12);\n color: var(--lv-primary);\n margin-top: var(--ld-px-6);\n cursor: pointer;\n}\n\n/* ===== Detail Panel ===== */\n.lv-detail {\n margin-top: var(--ld-px-12);\n padding-top: var(--ld-px-12);\n border-top: var(--ld-px-1) 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(calc(-1 * var(--ld-px-8))); }\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: var(--ld-px-13);\n border: var(--ld-px-1) solid var(--lv-border);\n border-radius: var(--ld-px-6);\n overflow: hidden;\n}\n\n.lv-diff-table th {\n background: var(--lv-bg-secondary);\n padding: var(--ld-px-8) var(--ld-px-12);\n text-align: left;\n font-weight: 600;\n font-size: var(--ld-px-12);\n color: var(--lv-text-secondary);\n border-bottom: var(--ld-px-1) solid var(--lv-border);\n}\n\n.lv-diff-table td {\n padding: var(--ld-px-8) var(--ld-px-12);\n border-bottom: var(--ld-px-1) 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: var(--ld-px-12);\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: var(--ld-px-12);\n}\n\n/* ===== Metadata ===== */\n.lv-metadata {\n margin-top: var(--ld-px-10);\n background: var(--lv-bg-secondary);\n border-radius: var(--ld-px-6);\n padding: var(--ld-px-10) var(--ld-px-14);\n font-size: var(--ld-px-12);\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: var(--ld-px-6);\n padding: var(--ld-px-16) 0;\n}\n\n.lv-pagination-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--ld-px-32);\n height: var(--ld-px-32);\n border: var(--ld-px-1) solid var(--lv-border);\n border-radius: var(--ld-px-6);\n background: var(--lv-bg);\n color: var(--lv-text);\n font-size: var(--ld-px-13);\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: var(--ld-white);\n}\n\n.lv-pagination-btn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n\n.lv-pagination-info {\n font-size: var(--ld-px-13);\n color: var(--lv-text-secondary);\n margin: 0 var(--ld-px-8);\n}\n\n/* ===== Empty state ===== */\n.lv-empty {\n text-align: center;\n padding: var(--ld-px-48) var(--ld-px-16);\n color: var(--lv-text-muted);\n font-size: var(--ld-px-14);\n}\n\n.lv-empty-icon {\n font-size: var(--ld-px-48);\n margin-bottom: var(--ld-px-12);\n opacity: 0.3;\n}\n";
288
+ function h(e, t = 20) {
289
+ const n = Number(e);
290
+ return !Number.isFinite(n) || n <= 0 ? t : Math.min(1e3, Math.max(1, Math.floor(n)));
291
+ }
292
+ function u(e) {
293
+ const t = Number(e);
294
+ return !Number.isFinite(t) || t < 1 ? 1 : Math.floor(t);
295
+ }
296
+ function g(e) {
297
+ if (void 0 === e) return [];
298
+ if (!Array.isArray(e)) throw new TypeError("[LogViewer] data must be an array");
299
+ return e.map((e, t) => {
300
+ if (!e || "object" != typeof e) throw new TypeError(`[LogViewer] data[${t}] must be an object`);
301
+ const n = i(e), r = String(n.id ?? "").trim();
302
+ if (!r) throw new TypeError(`[LogViewer] data[${t}].id must be a non-empty string`);
303
+ return {
304
+ ...n,
305
+ id: r,
306
+ user: String(n.user ?? ""),
307
+ module: String(n.module ?? ""),
308
+ changes: Array.isArray(n.changes) ? n.changes : void 0
309
+ };
310
+ });
311
+ }
312
+ var b = class {
313
+ constructor(e) {
314
+ if (this.emitter = new t(), this.filterEngine = new n(), this.timelineRenderer = new p(), this.diffRenderer = new l(), this.data = [], this.filteredData = [], this.filter = {}, this.pagination = {
315
+ page: 1,
316
+ pageSize: 20
317
+ }, this.theme = "light", this.expandedIds = /* @__PURE__ */ new Set(), this.filterEl = null, this.timelineEl = null, this.paginationEl = null, this.destroyed = !1, this.requestId = 0, this.abortController = null, "undefined" == typeof document) throw new Error("[LogViewer] A browser document is required");
318
+ const r = "string" == typeof e.container ? document.querySelector(e.container) : e.container;
319
+ if (!r) throw new Error(`[LogViewer] Container not found: ${String(e.container)}`);
320
+ this.config = { ...e }, this.theme = "dark" === e.theme ? "dark" : "light", this.pagination = {
321
+ page: u(e.pagination?.page),
322
+ pageSize: h(e.pageSize ?? e.pagination?.pageSize)
323
+ }, this.filter = i(e.filter || {}), this.data = g(e.data), m(), this.root = document.createElement("div"), this.root.className = "lv-root", this.root.setAttribute("data-theme", this.theme), r.appendChild(this.root), this.bindEvents(), this.applyFilterAndRender(), e.dataSource && this.loadData().catch(() => {});
324
+ }
325
+ getData() {
326
+ return i(this.data);
327
+ }
328
+ setData(e) {
329
+ this.destroyed || (this.data = g(e), this.pagination.page = 1, this.expandedIds.clear(), this.applyFilterAndRender(), this.emitter.emit("data:change", this.getData()));
330
+ }
331
+ appendData(e) {
332
+ this.destroyed || (this.data.push(...g(e)), this.applyFilterAndRender(), this.emitter.emit("data:change", this.getData()));
333
+ }
334
+ getFilter() {
335
+ return i(this.filter);
336
+ }
337
+ setFilter(e) {
338
+ if (this.destroyed) return;
339
+ this.filter = i(e || {}), this.pagination.page = 1, this.applyFilterAndRender();
340
+ const t = this.getFilter();
341
+ this.emitter.emit("filter:change", t), this.config.onFilterChange?.(t);
342
+ }
343
+ resetFilter() {
344
+ this.destroyed || this.setFilter({});
345
+ }
346
+ getTheme() {
347
+ return this.theme;
348
+ }
349
+ setTheme(e) {
350
+ if (!this.destroyed) {
351
+ if ("light" !== e && "dark" !== e) throw new TypeError("[LogViewer] theme must be light or dark");
352
+ this.theme !== e && (this.theme = e, this.root.setAttribute("data-theme", e), this.emitter.emit("theme:change", e), this.config.onThemeChange?.(e));
353
+ }
354
+ }
355
+ expandEntry(e) {
356
+ if (this.destroyed || this.expandedIds.has(e)) return;
357
+ this.expandedIds.add(e);
358
+ const t = this.data.find((t) => t.id === e);
359
+ t && this.emitter.emit("entry:expand", i(t)), this.renderTimeline();
360
+ }
361
+ collapseEntry(e) {
362
+ if (this.destroyed || !this.expandedIds.has(e)) return;
363
+ this.expandedIds.delete(e);
364
+ const t = this.data.find((t) => t.id === e);
365
+ t && this.emitter.emit("entry:collapse", i(t)), this.renderTimeline();
366
+ }
367
+ collapseAll() {
368
+ this.destroyed || 0 === this.expandedIds.size || (this.expandedIds.clear(), this.renderTimeline());
369
+ }
370
+ goToPage(e) {
371
+ if (this.destroyed) return;
372
+ const t = Math.max(1, Math.ceil(this.filteredData.length / this.pagination.pageSize)), n = Math.min(t, Math.max(1, u(e)));
373
+ n !== this.pagination.page && (this.pagination.page = n, this.emitter.emit("page:change", n), this.renderTimeline());
374
+ }
375
+ getPagination() {
376
+ return {
377
+ ...this.pagination,
378
+ total: this.filteredData.length
379
+ };
380
+ }
381
+ on(e, t) {
382
+ return this.emitter.on(e, t);
383
+ }
384
+ refresh() {
385
+ this.destroyed || this.applyFilterAndRender();
386
+ }
387
+ async loadData() {
388
+ if (this.destroyed || !this.config.dataSource) return;
389
+ const e = ++this.requestId;
390
+ this.abortController?.abort(), this.abortController = "undefined" != typeof AbortController ? new AbortController() : null;
391
+ try {
392
+ let t;
393
+ if ("function" == typeof this.config.dataSource) t = await this.config.dataSource();
394
+ else {
395
+ if ("function" != typeof fetch) throw new Error("[LogViewer] fetch is unavailable");
396
+ const e = await fetch(this.config.dataSource, { signal: this.abortController?.signal });
397
+ if (!e.ok) throw new Error(`[LogViewer] dataSource request failed: ${e.status}`);
398
+ t = await e.json();
399
+ }
400
+ if (this.destroyed || e !== this.requestId) return;
401
+ this.setData(function(e) {
402
+ if (Array.isArray(e)) return e;
403
+ if (!e || "object" != typeof e) throw new TypeError("[LogViewer] dataSource response must be an array");
404
+ const t = e;
405
+ for (const e of [
406
+ "data",
407
+ "logs",
408
+ "items",
409
+ "entries"
410
+ ]) if (Array.isArray(t[e])) return t[e];
411
+ throw new TypeError("[LogViewer] dataSource response must contain an array");
412
+ }(t));
413
+ } catch (e) {
414
+ if (this.destroyed || "undefined" != typeof DOMException && e instanceof DOMException && "AbortError" === e.name) return;
415
+ throw this.config.onError?.(e), this.emitter.emit("error", e), e;
416
+ }
417
+ }
418
+ destroy() {
419
+ this.destroyed || (this.destroyed = !0, this.requestId++, this.abortController?.abort(), this.abortController = null, this.emitter.removeAllListeners(), this.expandedIds.clear(), this.root.replaceChildren(), this.root.remove());
420
+ }
421
+ applyFilterAndRender() {
422
+ this.filteredData = this.filterEngine.filter(this.data, this.filter), this.filteredData = this.filteredData.map((e, t) => ({
423
+ entry: e,
424
+ index: t
425
+ })).sort((e, t) => {
426
+ const n = new Date(e.entry.timestamp).getTime(), r = new Date(t.entry.timestamp).getTime(), a = Number.isFinite(n) ? n : Number.MIN_SAFE_INTEGER;
427
+ return (Number.isFinite(r) ? r : Number.MIN_SAFE_INTEGER) - a || e.index - t.index;
428
+ }).map(({ entry: e }) => e);
429
+ const e = Math.max(1, Math.ceil(this.filteredData.length / this.pagination.pageSize));
430
+ this.pagination.page = Math.min(this.pagination.page, e), this.pagination.total = this.filteredData.length, this.renderTimeline();
431
+ }
432
+ getPageData() {
433
+ const { page: e, pageSize: t } = this.pagination, n = (e - 1) * t;
434
+ return this.filteredData.slice(n, n + t);
435
+ }
436
+ renderFilterBar() {
437
+ if (!1 === this.config.showFilter) return null;
438
+ const t = document.createElement("div");
439
+ t.className = "lv-filter", t.setAttribute("role", "search");
440
+ const n = (e, n) => {
441
+ const r = document.createElement("label");
442
+ r.className = "lv-filter-item";
443
+ const a = document.createElement("span");
444
+ a.className = "lv-filter-label", a.textContent = n;
445
+ const i = document.createElement("input");
446
+ i.className = "lv-filter-input", i.type = "search", i.value = String(this.filter[e] || ""), i.setAttribute("data-filter-field", e), i.setAttribute("aria-label", n), r.append(a, i), t.appendChild(r);
447
+ };
448
+ n("keyword", "en-US" === this.config.locale ? "Keyword" : "关键词"), n("user", "en-US" === this.config.locale ? "User" : "用户"), n("module", "en-US" === this.config.locale ? "Module" : "模块"), n("ip", "IP");
449
+ const r = document.createElement("label");
450
+ r.className = "lv-filter-item";
451
+ const a = document.createElement("span");
452
+ a.className = "lv-filter-label", a.textContent = "en-US" === this.config.locale ? "Action" : "操作";
453
+ const i = document.createElement("select");
454
+ i.className = "lv-filter-select", i.setAttribute("data-filter-field", "action"), i.setAttribute("aria-label", a.textContent);
455
+ const o = document.createElement("option");
456
+ o.value = "", o.textContent = "en-US" === this.config.locale ? "All" : "全部", i.appendChild(o);
457
+ for (const [t, n] of Object.entries(e)) {
458
+ const e = document.createElement("option");
459
+ e.value = t, e.textContent = n, i.appendChild(e);
460
+ }
461
+ i.value = Array.isArray(this.filter.action) ? "" : this.filter.action || "", r.append(a, i), t.appendChild(r);
462
+ const l = document.createElement("button");
463
+ return l.type = "button", l.className = "lv-filter-btn", l.setAttribute("data-filter-reset", "true"), l.textContent = "en-US" === this.config.locale ? "Reset" : "重置", t.appendChild(l), t;
464
+ }
465
+ updateFilterField(t, n) {
466
+ const r = this.getFilter();
467
+ n.trim() ? "action" === t && function(t) {
468
+ return Object.keys(e).includes(t);
469
+ }(n) ? r.action = n : "action" !== t && (r[t] = n) : delete r[t], this.setFilter(r);
470
+ }
471
+ renderTimeline() {
472
+ if (this.destroyed) return;
473
+ const e = this.getPageData(), t = this.timelineRenderer.render(e, this.expandedIds, {
474
+ showDiff: this.config.showDiff,
475
+ showAnomalyBadge: this.config.showAnomalyBadge,
476
+ locale: this.config.locale
477
+ });
478
+ if (!1 !== this.config.showDiff) for (const n of e) this.expandedIds.has(n.id) && ([...t.children].find((e) => e.getAttribute("data-id") === n.id)?.querySelector(".lv-timeline-card"))?.appendChild(this.diffRenderer.renderDetail(n));
479
+ this.filterEl?.remove(), this.timelineEl?.remove(), this.paginationEl?.remove(), this.filterEl = this.renderFilterBar(), this.timelineEl = t, this.paginationEl = this.timelineRenderer.renderPagination(this.pagination.page, this.pagination.pageSize, this.filteredData.length), this.filterEl && this.root.appendChild(this.filterEl), this.root.append(this.timelineEl, this.paginationEl);
480
+ }
481
+ bindEvents() {
482
+ this.root.addEventListener("change", (e) => {
483
+ const t = e.target;
484
+ if (!(t instanceof HTMLInputElement || t instanceof HTMLSelectElement)) return;
485
+ const n = t.getAttribute("data-filter-field");
486
+ n && this.updateFilterField(n, t.value);
487
+ }), this.root.addEventListener("click", (e) => {
488
+ const t = e.target;
489
+ if (!(t instanceof Element)) return;
490
+ if (t.closest("[data-filter-reset]")) return void this.resetFilter();
491
+ const n = t.closest(".lv-timeline-expand-hint"), r = (n?.closest(".lv-timeline-item") || t.closest(".lv-timeline-item"))?.getAttribute("data-id");
492
+ if (n && r) return void (this.expandedIds.has(r) ? this.collapseEntry(r) : this.expandEntry(r));
493
+ const a = t.closest(".lv-pagination-btn");
494
+ if (a && !a.disabled) {
495
+ const e = Number(a.getAttribute("data-page"));
496
+ Number.isFinite(e) && this.goToPage(e);
497
+ return;
498
+ }
499
+ if (!t.closest(".lv-timeline-card") || !r) return;
500
+ const o = this.filteredData.find((e) => e.id === r);
501
+ if (o) {
502
+ const e = i(o);
503
+ this.emitter.emit("entry:click", e), this.config.onEntryClick?.(e);
504
+ }
505
+ });
506
+ }
507
+ };
508
+ exports.ACTION_COLORS = {
509
+ create: "#52c41a",
510
+ update: "#1890ff",
511
+ delete: "#ff4d4f",
512
+ query: "#8c8c8c",
513
+ login: "#722ed1",
514
+ logout: "#eb2f96",
515
+ export: "#fa8c16",
516
+ import: "#13c2c2",
517
+ approve: "#52c41a",
518
+ reject: "#ff4d4f",
519
+ other: "#d9d9d9"
520
+ }, exports.ACTION_LABELS = e, exports.DiffRenderer = l, exports.EventEmitter = t, exports.FilterEngine = n, exports.LogViewer = b, exports.TimelineRenderer = p, exports.VERSION = "0.1.0", exports.createLogViewer = function(e) {
521
+ return new b(e);
522
+ }, exports.debounce = function(e, t) {
523
+ let n = null;
524
+ const r = Number.isFinite(t) && t > 0 ? t : 0;
525
+ return (...t) => {
526
+ n && clearTimeout(n), n = setTimeout(() => {
527
+ n = null, e(...t);
528
+ }, r);
529
+ };
530
+ }, exports.deepClone = i, exports.escapeHtml = function(e) {
531
+ const t = {
532
+ "&": "&amp;",
533
+ "<": "&lt;",
534
+ ">": "&gt;",
535
+ "\"": "&quot;",
536
+ "'": "&#39;"
537
+ };
538
+ return e.replace(/[&<>"']/g, (e) => t[e] || e);
539
+ }, exports.formatDate = a, exports.formatRelativeTime = function(e) {
540
+ const t = e instanceof Date ? e : new Date(e), n = Date.now() - t.getTime();
541
+ return !Number.isFinite(n) || n < 0 ? a(t) : n < 6e4 ? "刚刚" : n < 36e5 ? `${Math.floor(n / 6e4)} 分钟前` : n < 864e5 ? `${Math.floor(n / 36e5)} 小时前` : n < 6048e5 ? `${Math.floor(n / 864e5)} 天前` : a(t);
542
+ }, exports.formatValue = o, exports.injectStyles = m, exports.uid = function(e = "lv") {
543
+ const t = "undefined" != typeof crypto && "function" == typeof crypto.randomUUID ? crypto.randomUUID().slice(0, 8) : Math.random().toString(36).slice(2, 8);
544
+ return `${e}-${++r}-${t}`;
545
+ };
546
+ });
547
+ /*! End of @composy/log-viewer-core | Powered by @composy/builder */
548
+ //# sourceMappingURL=index.min.js.map