-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvlan.html
More file actions
458 lines (407 loc) · 22.6 KB
/
Copy pathvlan.html
File metadata and controls
458 lines (407 loc) · 22.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
<!DOCTYPE html>
<html lang="zh-TW" data-theme="light">
<head>
<meta charset="UTF-8">
<title>VLAN Auditor | Network Toolbox</title>
<script src="js/vlan.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"
integrity="sha512-r22gChDnGvBylk90+2e/ycr3RVrDi8DIOkIGNhJlKfuyQM4tIRAI062MaV8sfjQKYVGjOBaZBOA87z+IhZE9DA=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" href="css/output.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;700&display=swap">
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0" />
<style>
body {
font-family: 'Google Sans', system-ui, sans-serif;
overflow: hidden;
}
.code-font {
font-family: 'Cascadia Code', 'Consolas', monospace;
font-size: 12px;
line-height: 1.4;
}
.workspace-height {
height: calc(100vh - 4rem - 3rem - 2rem);
}
/* 讓內容更新時的動畫更平滑 */
.status-transition {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes pulse-green {
0% {
border-color: rgb(209 213 219);
}
/* base-300 */
50% {
border-color: #22c55e;
shadow: 0 0 15px rgba(34, 197, 94, 0.4);
}
/* success */
100% {
border-color: #22c55e;
}
}
.animate-update {
animation: pulse-green 0.8s ease-out;
border-color: #22c55e !important;
}
</style>
</head>
<body class="h-screen bg-base-200 flex flex-col">
<nav class="navbar bg-base-100 shadow-sm px-6 h-16 flex-none">
<div class="flex-1 gap-2">
<a href="index.html" class="flex items-center gap-2 hover:opacity-80 transition-opacity">
<span class="material-symbols-outlined text-primary text-3xl">settings_ethernet</span>
<span class="text-xl font-bold text-base-content">Network <span
class="text-primary">Toolbox</span></span>
</a>
<span class="material-symbols-outlined text-success text-3xl font-bold">fact_check</span>
<span class="text-xl font-bold tracking-tight">VLAN <span class="text-success">Auditor</span></span>
</div>
<div class="flex-none gap-3">
<div class="join">
<button class="btn btn-sm btn-outline btn-success join-item" onclick="handleDownloadExcel()">
<span class="material-symbols-outlined text-sm">table_chart</span> Excel
</button>
<button class="btn btn-sm btn-outline btn-success join-item" onclick="handleDownloadCSV()">
<span class="material-symbols-outlined text-sm">description</span> CSV
</button>
<button class="btn btn-sm btn-outline btn-success join-item" onclick="handleDownloadJSON()">
<span class="material-symbols-outlined text-sm">download</span> JSON
</button>
</div>
</div>
<div class="flex-none gap-2">
<a href="https://github.com/sinnoken/topo-lldp" target="_blank" rel="noopener noreferrer"
class="btn btn-ghost btn-circle" aria-label="GitHub Repository">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
<path
d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.041-1.416-4.041-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
</a>
</div>
</nav>
<main class="flex-grow p-4 overflow-hidden">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-12 gap-4 h-full">
<div
class="lg:col-span-4 card bg-base-100 shadow-sm border border-base-300 flex flex-col min-h-[300px] overflow-hidden">
<div class="px-4 py-3 bg-base-200 border-b border-base-300 flex items-center justify-between">
<h3 class="text-xs font-bold uppercase tracking-wider flex items-center gap-2">
<span class="material-symbols-outlined text-base">settings</span> 1. Config
</h3>
</div>
<div class="indicator w-full h-full p-4 flex flex-col overflow-hidden">
<span id="badge-config"
class="indicator-item badge badge-success scale-0 opacity-0 status-transition font-bold text-[10px] mt-8 mr-8 shadow-md">✓
UPDATED</span>
<textarea id="config-editor"
class="textarea textarea-bordered w-full flex-grow code-font bg-base-100 focus:border-primary status-transition"
placeholder="Paste 'show configuration | display set | match "vlans| ether-options | description | speed | link-mode"' here..."></textarea>
<div
class="flex justify-between items-center mt-2 px-1 text-[10px] text-base-content/40 font-bold uppercase tracking-tighter">
<span id="stats-config">Empty</span>
<button onclick="clearEditor('config-editor')"
class="hover:text-error transition-colors">Clear</button>
</div>
</div>
</div>
<div
class="lg:col-span-4 card bg-base-100 shadow-sm border border-base-300 flex flex-col min-h-[300px] overflow-hidden">
<div class="px-4 py-3 bg-base-200 border-b border-base-300 flex items-center justify-between">
<h3 class="text-xs font-bold uppercase tracking-wider flex items-center gap-2">
<span class="material-symbols-outlined text-base">visibility</span> 2. show vlan
</h3>
</div>
<div class="indicator w-full h-full p-4 flex flex-col overflow-hidden">
<span id="badge-vlan"
class="indicator-item badge badge-success scale-0 opacity-0 status-transition font-bold text-[10px] mt-8 mr-8 shadow-md">✓
UPDATED</span>
<textarea id="vlan-editor"
class="textarea textarea-bordered w-full flex-grow code-font bg-base-100 focus:border-primary status-transition"
placeholder="Paste 'show vlans detail | match "vlan| tagged| trunk| access"'"></textarea>
<div
class="flex justify-between items-center mt-2 px-1 text-[10px] text-base-content/40 font-bold uppercase tracking-tighter">
<span id="stats-vlan">Empty</span>
<button onclick="clearEditor('vlan-editor')"
class="hover:text-error transition-colors">Clear</button>
</div>
</div>
</div>
<div
class="lg:col-span-4 card bg-base-100 shadow-sm border border-base-300 flex flex-col min-h-[300px] overflow-hidden">
<div class="px-4 py-3 bg-base-200 border-b border-base-300 flex items-center justify-between">
<h3 class="text-xs font-bold uppercase tracking-wider flex items-center gap-2">
<span class="material-symbols-outlined text-base">list_alt</span> 3. Mac Table
</h3>
</div>
<div class="indicator w-full h-full p-4 flex flex-col overflow-hidden">
<span id="badge-switch"
class="indicator-item badge badge-success scale-0 opacity-0 status-transition font-bold text-[10px] mt-8 mr-8 shadow-md">✓
UPDATED</span>
<textarea id="switch-editor"
class="textarea textarea-bordered w-full flex-grow code-font bg-base-100 focus:border-primary status-transition"
placeholder="Paste 'show ethernet-switching table'"></textarea>
<div
class="flex justify-between items-center mt-2 px-1 text-[10px] text-base-content/40 font-bold uppercase tracking-tighter">
<span id="stats-switch">Empty</span>
<button onclick="clearEditor('switch-editor')"
class="hover:text-error transition-colors">Clear</button>
</div>
</div>
</div>
<div
class="lg:col-span-4 card bg-base-100 shadow-sm border border-base-300 flex flex-col min-h-[300px] overflow-hidden">
<div class="px-4 py-3 bg-base-200 border-b border-base-300 flex items-center justify-between">
<h3 class="text-xs font-bold uppercase tracking-wider flex items-center gap-2">
<span class="material-symbols-outlined text-base">hub</span> 4. Topology
</h3>
</div>
<div class="indicator w-full h-full p-4 flex flex-col overflow-hidden">
<span id="badge-topo"
class="indicator-item badge badge-success scale-0 opacity-0 status-transition font-bold text-[10px] mt-8 mr-8 shadow-md">✓
UPDATED</span>
<textarea id="topo-editor"
class="textarea textarea-bordered w-full flex-grow code-font bg-base-100 focus:border-primary status-transition"
placeholder='{"edges": []}'></textarea>
<div
class="flex justify-between items-center mt-2 px-1 text-[10px] text-base-content/40 font-bold uppercase tracking-tighter">
<span id="stats-topo">Empty</span>
<button onclick="clearEditor('topo-editor')"
class="hover:text-error transition-colors">Clear</button>
</div>
</div>
</div>
<div
class="lg:col-span-4 card bg-base-100 shadow-sm border border-base-300 flex flex-col min-h-[300px] overflow-hidden">
<div class="px-4 py-3 bg-base-200 border-b border-base-300 flex items-center justify-between">
<h3 class="text-xs font-bold uppercase tracking-wider flex items-center gap-2">
<span class="material-symbols-outlined text-base">router</span> 5. ARP Table
</h3>
</div>
<div class="indicator w-full h-full p-4 flex flex-col overflow-hidden">
<span id="badge-arp"
class="indicator-item badge badge-success scale-0 opacity-0 status-transition font-bold text-[10px] mt-8 mr-8 shadow-md">✓
UPDATED</span>
<textarea id="arp-editor"
class="textarea textarea-bordered w-full flex-grow code-font bg-base-100 focus:border-primary status-transition"
placeholder="貼上 show arp...">{
"arpMap": {
"ER_TEST_1": {
"mgmtIp": "10.1.1.1",
"entries": [
{ "port": "poer1", "mac": "11:11:11:11:11:11", "ip": "10.1.1.1" }
]
},}}</textarea>
<div
class="flex justify-between items-center mt-2 px-1 text-[10px] text-base-content/40 font-bold uppercase tracking-tighter">
<span id="stats-arp">Empty</span>
<button onclick="clearEditor('arp-editor')"
class="hover:text-error transition-colors">Clear</button>
</div>
</div>
</div>
<div id="card-result"
class="lg:col-span-4 card bg-base-100 shadow-sm border-2 border-base-300 flex flex-col min-h-[300px] overflow-hidden status-transition">
<div
class="px-4 py-3 bg-base-200 text-base-content flex items-center justify-between border-b border-base-300">
<h3 class="text-xs font-bold uppercase tracking-wider flex items-center gap-2">
<span class="material-symbols-outlined text-base">analytics</span> 6. Result
</h3>
<span id="badge-result"
class="badge badge-primary scale-0 opacity-0 status-transition font-bold text-[10px]">READY</span>
</div>
<div class="indicator w-full h-full flex flex-col overflow-hidden">
<textarea id="vlan-viewer" readonly
class="textarea textarea-ghost flex-grow p-4 code-font bg-base-50/50 resize-none font-medium transition-colors duration-500"
placeholder="解析結果將自動生成於此..."></textarea>
<div
class="flex justify-end items-center p-2 text-[10px] text-base-content/40 font-bold uppercase tracking-tighter bg-base-200/50">
<span id="stats-result">No Data</span>
</div>
</div>
</div>
</div>
</main>
<footer class="footer footer-center p-3 bg-base-100 text-base-content/50 border-t border-base-300 flex-none">
© 2026 Network Admin Suite. Open Source Infrastructure Tools.
</footer>
<script>
const CONFIG = {
STORAGE: {
POSITIONS: 'networkNodesPosition',
DATA: 'networkTopologyData',
ARP_MAP: 'networkArpMap',
EXPIRY: 'storageExpiry' // 過期時間戳
},
STORAGE_EXPIRY_MS: 1 * 8 * 60 * 60 * 1000, // 8小時(毫秒)
}
let auditor;
const editorConfigs = [
{ id: 'config-editor', prefix: 'config' },
{ id: 'vlan-editor', prefix: 'vlan' },
{ id: 'switch-editor', prefix: 'switch' },
{ id: 'topo-editor', prefix: 'topo' },
{ id: 'arp-editor', prefix: 'arp' }
];
const viewer = document.getElementById('vlan-viewer');
async function init() {
auditor = new VlanAuditor();
// --- 1. 從 localStorage 載入 ARP 資料 ---
const arpEditor = document.getElementById('arp-editor');
if (!isStorageExpired()) {
const savedArp = localStorage.getItem(CONFIG.STORAGE.ARP_MAP);
// 檢查是否為空或是預設的測試資料,若是則覆蓋
if (savedArp && (!arpEditor.value || arpEditor.value.trim() === "" || arpEditor.value.includes("ER_TEST_1"))) {
arpEditor.value = savedArp;
auditor.parseArpData(savedArp);
}
}
// --- 2. 新增:從 localStorage 載入 Topology 資料 ---
const topoEditor = document.getElementById('topo-editor');
if (!isStorageExpired()) {
const savedTopo = localStorage.getItem(CONFIG.STORAGE.DATA); // 對應 'networkTopologyData'
if (savedTopo) {
// 將物件轉回漂亮的 JSON 字串填入
topoEditor.value = JSON.stringify(JSON.parse(savedTopo), null, 4);
}
} else {
console.log("📂 Storage expired, clearing old topology data.");
localStorage.removeItem(CONFIG.STORAGE.DATA);
localStorage.removeItem(CONFIG.STORAGE.ARP_MAP);
localStorage.removeItem(CONFIG.STORAGE.EXPIRY);
}
// 1. 設定事件監聽器
setupEventListeners();
console.log("setupEventListeners called");
// 2. 背景載入 OUI 資料庫,不使用 await 阻塞後續邏輯
console.log("🌐 正在初始化 OUI 資料庫...");
try {
await auditor.initOuiDatabase();
console.log("✅ OUI 資料庫已就緒");
} catch (err) {
console.error("OUI 初始化失敗:", err);
}
// 3. 載入完成後,如果編輯器已經有資料,手動觸發一次解析
triggerFullProcess();
}
// 將原本在 init 裡的循環提取成獨立函式
function setupEventListeners() {
editorConfigs.forEach(conf => {
const el = document.getElementById(conf.id);
const badge = document.getElementById(`badge-${conf.prefix}`);
const stats = document.getElementById(`stats-${conf.prefix}`);
if (!el) return;
el.addEventListener('input', () => {
const val = el.value.trim();
if (conf.id === 'arp-editor') {
auditor.parseArpData(val);
}
// UI 回饋邏輯
if (val.length > 0) {
el.classList.add('textarea-success', 'bg-success/5');
badge?.classList.replace('scale-0', 'scale-100');
badge?.classList.replace('opacity-0', 'opacity-100');
if (stats) stats.innerText = `${val.split('\n').length} Lines`;
} else {
el.classList.remove('textarea-success', 'bg-success/5');
badge?.classList.replace('scale-100', 'scale-0');
badge?.classList.replace('opacity-100', 'opacity-0');
if (stats) stats.innerText = "Empty";
}
// 觸發解析
triggerFullProcess();
});
});
}
// 建議提取一個統一的解析 function
function triggerFullProcess() {
// 2. 觸發解析邏輯
try {
// 確保 auditor 的 ARP map 是同步的
const arpContent = document.getElementById('arp-editor').value;
if (arpContent.trim()) {
auditor.parseArpData(arpContent);
}
const result = auditor.processData(
document.getElementById('config-editor').value,
document.getElementById('vlan-editor').value,
document.getElementById('switch-editor').value,
document.getElementById('topo-editor').value
);
const jsonStr = JSON.stringify(result, null, 2);
// 檢查資料是否真的有變動且非空
if (viewer.value !== jsonStr && jsonStr !== "{}") {
viewer.value = jsonStr;
// --- Result 反應式 UI 邏輯 ---
const resultCard = document.getElementById('card-result');
const resultBadge = document.getElementById('badge-result');
const resultStats = document.getElementById('stats-result');
// 1. 觸發外框脈衝動畫
resultCard.classList.remove('animate-update');
void resultCard.offsetWidth; // 強制重繪觸發動畫
resultCard.classList.add('animate-update');
// 2. 顯示 READY 標籤
resultBadge.classList.remove('scale-0', 'opacity-0');
resultBadge.classList.add('scale-100', 'opacity-100');
// 3. 更新統計 (計算節點與連線數)
const nodeCount = result.nodes?.length || 0;
resultStats.innerText = `${nodeCount} Nodes Analyzed`;
resultStats.classList.add('text-primary');
}
} catch (err) {
console.error("解析錯誤:", err);
}
}
function clearEditor(id) {
const el = document.getElementById(id);
el.value = '';
el.dispatchEvent(new Event('input'));
}
function handleDownloadJSON() {
if (!viewer.value) return alert("無資料");
const blob = new Blob([viewer.value], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `audit_${new Date().getTime()}.json`;
a.click();
}
// 下載 CSV
function handleDownloadCSV() {
const csv = auditor.generateCSV();
if (!csv) return alert("請先輸入資料");
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `audit_${new Date().getTime()}.csv`;
a.click();
}
// 在 vlan2.html 中的 handleDownloadExcel 函式加入防錯
function handleDownloadExcel() {
// 1. 檢查是否有資料
if (!viewer.value || viewer.value === "" || viewer.value === "{}") {
return alert("尚無資料可導出,請先貼入 Config 並確保解析成功。");
}
// 2. 檢查 XLSX 函式庫是否成功載入
if (typeof XLSX === 'undefined') {
alert("Excel 函式庫載入失敗,請檢查網路連線或 CDN 連結。");
return;
}
try {
auditor.exportToExcel();
} catch (err) {
console.error("Excel 匯出失敗:", err);
alert("匯出失敗,請查看控制台錯誤。");
}
}
function isStorageExpired() {
const expiry = localStorage.getItem(CONFIG.STORAGE.EXPIRY);
if (!expiry) return true;
return Date.now() > parseInt(expiry);
}
window.addEventListener('load', init);
</script>
</body>
</html>