Mr-vabs/itsam-tool-normalization-engine-31l34
0
1<!DOCTYPE html>2<html lang="en">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <title>ITSAM Tool - Normalization Engine</title>7 <script src="https://cdn.tailwindcss.com"></script>8 <script src="https://unpkg.com/lucide@latest"></script>9 <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>10 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">11 <style>12 * { font-family: 'Inter', sans-serif; }13 .drop-zone { transition: all 0.3s ease; }14 .drop-zone.dragover { border-color: #3b82f6; background: #eff6ff; transform: scale(1.02); }15 .data-table th { position: sticky; top: 0; background: #f8fafc; z-index: 10; }16 .data-table { font-size: 0.85rem; }17 .data-table th, .data-table td { padding: 0.5rem 0.75rem; white-space: nowrap; }18 .tab-active { border-bottom: 2px solid #3b82f6; color: #3b82f6; }19 .modal { backdrop-filter: blur(4px); }20 .scrollbar-thin::-webkit-scrollbar { width: 6px; height: 6px; }21 .scrollbar-thin::-webkit-scrollbar-track { background: #f1f5f9; }22 .scrollbar-thin::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 3px; }23 .badge { padding: 2px 8px; border-radius: 12px; font-size: 0.75rem; font-weight: 500; }24 .badge-freeware { background: #dcfce7; color: #166534; }25 .badge-standard { background: #dbeafe; color: #1e40af; }26 .badge-blacklisted { background: #fee2e2; color: #991b1b; }27 .badge-unknown { background: #f1f5f9; color: #64748b; }28 </style>29</head>30<body class="bg-slate-50 min-h-screen">31 <!-- Header -->32 <header class="bg-white border-b border-slate-200 shadow-sm">33 <div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">34 <div class="flex items-center gap-3">35 <div class="w-10 h-10 bg-gradient-to-br from-blue-600 to-indigo-700 rounded-lg flex items-center justify-center">36 <i data-lucide="database" class="text-white w-5 h-5"></i>37 </div>38 <div>39 <h1 class="text-xl font-bold text-slate-800">ITSAM Tool</h1>40 <p class="text-xs text-slate-500">Normalization Engine</p>41 </div>42 </div>43 <div class="flex items-center gap-4">44 <button onclick="resetAllData()" class="px-4 py-2 text-sm text-slate-600 hover:text-red-600 hover:bg-red-50 rounded-lg transition flex items-center gap-2">45 <i data-lucide="refresh-cw" class="w-4 h-4"></i>46 Reset47 </button>48 <button onclick="exportAllData()" class="px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition flex items-center gap-2">49 <i data-lucide="download" class="w-4 h-4"></i>50 Export All51 </button>52 </div>53 </div>54 </header>55 56 <!-- Main Content -->57 <main class="max-w-7xl mx-auto px-4 py-6">58 <!-- Tabs -->59 <div class="bg-white rounded-t-xl border border-slate-200 border-b-0">60 <div class="flex overflow-x-auto">61 <button onclick="switchTab('upload')" id="tab-upload" class="tab-btn tab-active px-6 py-3 text-sm font-medium text-slate-600 hover:text-blue-600 transition">62 <i data-lucide="upload-cloud" class="w-4 h-4 inline mr-2"></i>Upload Data63 </button>64 <button onclick="switchTab('dashboard')" id="tab-dashboard" class="tab-btn px-6 py-3 text-sm font-medium text-slate-600 hover:text-blue-600 transition">65 <i data-lucide="layout-dashboard" class="w-4 h-4 inline mr-2"></i>Dashboard66 </button>67 <button onclick="switchTab('normalized')" id="tab-normalized" class="tab-btn px-6 py-3 text-sm font-medium text-slate-600 hover:text-blue-600 transition">68 <i data-lucide="link" class="w-4 h-4 inline mr-2"></i>Normalized Data69 </button>70 <button onclick="switchTab('relations')" id="tab-relations" class="tab-btn px-6 py-3 text-sm font-medium text-slate-600 hover:text-blue-600 transition">71 <i data-lucide="network" class="w-4 h-4 inline mr-2"></i>Relational Views72 </button>73 <button onclick="switchTab('sr')" id="tab-sr" class="tab-btn px-6 py-3 text-sm font-medium text-slate-600 hover:text-blue-600 transition">74 <i data-lucide="file-spreadsheet" class="w-4 h-4 inline mr-2"></i>SR Expansion75 </button>76 </div>77 </div>78 79 <!-- Upload Tab -->80 <div id="view-upload" class="view-section">81 <div class="bg-white border border-slate-200 rounded-b-xl p-6">82 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">83 <!-- Upload Zones -->84 <div class="space-y-2">85 <h3 class="text-sm font-semibold text-slate-700 flex items-center gap-2">86 <i data-lucide="monitor" class="w-4 h-4 text-blue-500"></i>SCCM Discovery87 </h3>88 <div class="drop-zone border-2 border-dashed border-slate-300 rounded-lg p-4 text-center cursor-pointer hover:border-blue-400" 89 data-type="sccm" ondrop="handleDrop(event, 'sccm')" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" onclick="triggerFileInput('sccm')">90 <input type="file" id="file-sccm" class="hidden" accept=".csv,.xlsx,.xls" onchange="handleFileSelect(event, 'sccm')">91 <i data-lucide="file-spreadsheet" class="w-8 h-8 mx-auto text-slate-400 mb-2"></i>92 <p class="text-sm text-slate-600">Drop CSV/Excel here</p>93 <p id="status-sccm" class="text-xs text-green-600 mt-1 hidden"></p>94 </div>95 </div>96 <div class="space-y-2">97 <h3 class="text-sm font-semibold text-slate-700 flex items-center gap-2">98 <i data-lucide="hard-drive" class="w-4 h-4 text-green-500"></i>Asset Owner99 </h3>100 <div class="drop-zone border-2 border-dashed border-slate-300 rounded-lg p-4 text-center cursor-pointer hover:border-green-400" 101 data-type="asset" ondrop="handleDrop(event, 'asset')" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" onclick="triggerFileInput('asset')">102 <input type="file" id="file-asset" class="hidden" accept=".csv,.xlsx,.xls" onchange="handleFileSelect(event, 'asset')">103 <i data-lucide="file-spreadsheet" class="w-8 h-8 mx-auto text-slate-400 mb-2"></i>104 <p class="text-sm text-slate-600">Drop CSV/Excel here</p>105 <p id="status-asset" class="text-xs text-green-600 mt-1 hidden"></p>106 </div>107 </div>108 <div class="space-y-2">109 <h3 class="text-sm font-semibold text-slate-700 flex items-center gap-2">110 <i data-lucide="users" class="w-4 h-4 text-purple-500"></i>User Master111 </h3>112 <div class="drop-zone border-2 border-dashed border-slate-300 rounded-lg p-4 text-center cursor-pointer hover:border-purple-400" 113 data-type="user" ondrop="handleDrop(event, 'user')" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" onclick="triggerFileInput('user')">114 <input type="file" id="file-user" class="hidden" accept=".csv,.xlsx,.xls" onchange="handleFileSelect(event, 'user')">115 <i data-lucide="file-spreadsheet" class="w-8 h-8 mx-auto text-slate-400 mb-2"></i>116 <p class="text-sm text-slate-600">Drop CSV/Excel here</p>117 <p id="status-user" class="text-xs text-green-600 mt-1 hidden"></p>118 </div>119 </div>120 <div class="space-y-2">121 <h3 class="text-sm font-semibold text-slate-700 flex items-center gap-2">122 <i data-lucide="package" class="w-4 h-4 text-orange-500"></i>Master Software List123 </h3>124 <div class="drop-zone border-2 border-dashed border-slate-300 rounded-lg p-4 text-center cursor-pointer hover:border-orange-400" 125 data-type="software" ondrop="handleDrop(event, 'software')" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" onclick="triggerFileInput('software')">126 <input type="file" id="file-software" class="hidden" accept=".csv,.xlsx,.xls" onchange="handleFileSelect(event, 'software')">127 <i data-lucide="file-spreadsheet" class="w-8 h-8 mx-auto text-slate-400 mb-2"></i>128 <p class="text-sm text-slate-600">Drop CSV/Excel here</p>129 <p id="status-software" class="text-xs text-green-600 mt-1 hidden"></p>130 </div>131 </div>132 <div class="space-y-2">133 <h3 class="text-sm font-semibold text-slate-700 flex items-center gap-2">134 <i data-lucide="clipboard-check" class="w-4 h-4 text-red-500"></i>Approved SR Data135 </h3>136 <div class="drop-zone border-2 border-dashed border-slate-300 rounded-lg p-4 text-center cursor-pointer hover:border-red-400" 137 data-type="sr" ondrop="handleDrop(event, 'sr')" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)" onclick="triggerFileInput('sr')">138 <input type="file" id="file-sr" class="hidden" accept=".csv,.xlsx,.xls" onchange="handleFileSelect(event, 'sr')">139 <i data-lucide="file-spreadsheet" class="w-8 h-8 mx-auto text-slate-400 mb-2"></i>140 <p class="text-sm text-slate-600">Drop CSV/Excel here</p>141 <p id="status-sr" class="text-xs text-green-600 mt-1 hidden"></p>142 </div>143 </div>144 </div>145 146 <button onclick="processAllData()" class="w-full py-3 bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-semibold rounded-lg hover:from-blue-700 hover:to-indigo-700 transition flex items-center justify-center gap-2">147 <i data-lucide="zap" class="w-5 h-5"></i>148 Process & Normalize Data149 </button>150 </div>151 </div>152 153 <!-- Dashboard Tab -->154 <div id="view-dashboard" class="view-section hidden">155 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">156 <!-- Stats Cards -->157 <div class="bg-white rounded-xl border border-slate-200 p-5 shadow-sm">158 <div class="flex items-center justify-between mb-3">159 <span class="text-sm text-slate-500">SCCM Records</span>160 <div class="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center">161 <i data-lucide="monitor" class="w-4 h-4 text-blue-600"></i>162 </div>163 </div>164 <p id="stat-sccm" class="text-2xl font-bold text-slate-800">0</p>165 </div>166 <div class="bg-white rounded-xl border border-slate-200 p-5 shadow-sm">167 <div class="flex items-center justify-between mb-3">168 <span class="text-sm text-slate-500">Total Assets</span>169 <div class="w-8 h-8 bg-green-100 rounded-lg flex items-center justify-center">170 <i data-lucide="hard-drive" class="w-4 h-4 text-green-600"></i>171 </div>172 </div>173 <p id="stat-assets" class="text-2xl font-bold text-slate-800">0</p>174 </div>175 <div class="bg-white rounded-xl border border-slate-200 p-5 shadow-sm">176 <div class="flex items-center justify-between mb-3">177 <span class="text-sm text-slate-500">Users</span>178 <div class="w-8 h-8 bg-purple-100 rounded-lg flex items-center justify-center">179 <i data-lucide="users" class="w-4 h-4 text-purple-600"></i>180 </div>181 </div>182 <p id="stat-users" class="text-2xl font-bold text-slate-800">0</p>183 </div>184 <div class="bg-white rounded-xl border border-slate-200 p-5 shadow-sm">185 <div class="flex items-center justify-between mb-3">186 <span class="text-sm text-slate-500">Normalization Rate</span>187 <div class="w-8 h-8 bg-orange-100 rounded-lg flex items-center justify-center">188 <i data-lucide="percent" class="w-4 h-4 text-orange-600"></i>189 </div>190 </div>191 <p id="stat-norm-rate" class="text-2xl font-bold text-slate-800">0%</p>192 </div>193 </div>194 195 <!-- Warning Cards -->196 <div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">197 <div class="bg-white rounded-xl border border-slate-200 p-5 shadow-sm">198 <h3 class="text-sm font-semibold text-slate-700 mb-3 flex items-center gap-2">199 <i data-lucide="alert-triangle" class="w-4 h-4 text-red-500"></i>200 Blacklisted Software Found201 </h3>202 <div id="blacklisted-list" class="space-y-2 max-h-48 overflow-y-auto scrollbar-thin">203 <p class="text-sm text-slate-400 italic">No blacklisted software detected</p>204 </div>205 </div>206 <div class="bg-white rounded-xl border border-slate-200 p-5 shadow-sm">207 <h3 class="text-sm font-semibold text-slate-700 mb-3 flex items-center gap-2">208 <i data-lucide="check-circle" class="w-4 h-4 text-green-500"></i>209 Normalization Summary210 </h3>211 <div id="norm-summary" class="space-y-2 max-h-48 overflow-y-auto scrollbar-thin">212 <p class="text-sm text-slate-400 italic">Upload data to see summary</p>213 </div>214 </div>215 </div>216 217 <!-- Upload Status -->218 <div class="bg-white rounded-xl border border-slate-200 p-5 shadow-sm">219 <h3 class="text-sm font-semibold text-slate-700 mb-3 flex items-center gap-2">220 <i data-lucide="folder-open" class="w-4 h-4 text-blue-500"></i>221 Uploaded Data Files222 </h3>223 <div class="grid grid-cols-2 md:grid-cols-5 gap-3" id="upload-status">224 <div class="p-3 bg-slate-50 rounded-lg text-center">225 <p class="text-xs text-slate-500">SCCM</p>226 <p id="upload-sccm" class="text-sm font-medium text-slate-400">Pending</p>227 </div>228 <div class="p-3 bg-slate-50 rounded-lg text-center">229 <p class="text-xs text-slate-500">Assets</p>230 <p id="upload-asset" class="text-sm font-medium text-slate-400">Pending</p>231 </div>232 <div class="p-3 bg-slate-50 rounded-lg text-center">233 <p class="text-xs text-slate-500">Users</p>234 <p id="upload-user" class="text-sm font-medium text-slate-400">Pending</p>235 </div>236 <div class="p-3 bg-slate-50 rounded-lg text-center">237 <p class="text-xs text-slate-500">Software</p>238 <p id="upload-software" class="text-sm font-medium text-slate-400">Pending</p>239 </div>240 <div class="p-3 bg-slate-50 rounded-lg text-center">241 <p class="text-xs text-slate-500">SR Data</p>242 <p id="upload-sr" class="text-sm font-medium text-slate-400">Pending</p>243 </div>244 </div>245 </div>246 </div>247 248 <!-- Normalized Data Tab -->249 <div id="view-normalized" class="view-section hidden">250 <div class="bg-white border border-slate-200 rounded-xl">251 <!-- Toolbar -->252 <div class="p-4 border-b border-slate-200 flex flex-wrap items-center gap-3">253 <div class="relative flex-1 min-w-64">254 <i data-lucide="search" class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>255 <input type="text" id="search-normalized" placeholder="Search normalized data..." 256 class="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"257 oninput="filterNormalizedData()">258 </div>259 <select id="filter-license" class="px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500"260 onchange="filterNormalizedData()">261 <option value="">All License Types</option>262 <option value="Freeware">Freeware</option>263 <option value="License Standard">License Standard</option>264 <option value="Blacklisted">Blacklisted</option>265 <option value="Unknown">Unknown</option>266 </select>267 <button onclick="exportData('normalized')" class="px-4 py-2 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition flex items-center gap-2">268 <i data-lucide="download" class="w-4 h-4"></i>Export269 </button>270 </div>271 <!-- Table -->272 <div class="overflow-auto max-h-[600px] scrollbar-thin">273 <table class="data-table w-full border-collapse">274 <thead class="bg-slate-50">275 <tr>276 <th class="text-left cursor-pointer hover:bg-slate-100" onclick="sortTable('normalized', 'computerName')">277 <span class="flex items-center gap-1">Computer <i data-lucide="arrow-up-down" class="w-3 h-3"></i></span>278 </th>279 <th class="text-left cursor-pointer hover:bg-slate-100" onclick="sortTable('normalized', 'rawSoftware')">280 <span class="flex items-center gap-1">Raw Software <i data-lucide="arrow-up-down" class="w-3 h-3"></i></span>281 </th>282 <th class="text-left cursor-pointer hover:bg-slate-100" onclick="sortTable('normalized', 'standardTitle')">283 <span class="flex items-center gap-1">Standard Title <i data-lucide="arrow-up-down" class="w-3 h-3"></i></span>284 </th>285 <th class="text-left cursor-pointer hover:bg-slate-100" onclick="sortTable('normalized', 'publisher')">286 <span class="flex items-center gap-1">Publisher <i data-lucide="arrow-up-down" class="w-3 h-3"></i></span>287 </th>288 <th class="text-left cursor-pointer hover:bg-slate-100" onclick="sortTable('normalized', 'version')">289 <span class="flex items-center gap-1">Version <i data-lucide="arrow-up-down" class="w-3 h-3"></i></span>290 </th>291 <th class="text-left cursor-pointer hover:bg-slate-100" onclick="sortTable('normalized', 'licenseType')">292 <span class="flex items-center gap-1">License Type <i data-lucide="arrow-up-down" class="w-3 h-3"></i></span>293 </th>294 <th class="text-left">Status</th>295 </tr>296 </thead>297 <tbody id="normalized-table-body">298 <tr><td colspan="7" class="text-center py-8 text-slate-400">Upload and process data to see normalized results</td></tr>299 </tbody>300 </table>301 </div>302 <div class="p-3 border-t border-slate-200 text-sm text-slate-500">303 <span id="normalized-count">0</span> records304 </div>305 </div>306 </div>307 308 <!-- Relational Views Tab -->309 <div id="view-relations" class="view-section hidden">310 <div class="bg-white border border-slate-200 rounded-xl">311 <div class="p-4 border-b border-slate-200">312 <h3 class="text-lg font-semibold text-slate-800 mb-3">Relational Data Views</h3>313 <div class="flex gap-2 flex-wrap">314 <button onclick="switchRelationView('software-per-user')" class="relation-btn px-4 py-2 text-sm rounded-lg bg-blue-100 text-blue-700 font-medium" data-view="software-per-user">315 <i data-lucide="user" class="w-4 h-4 inline mr-1"></i>Software per User316 </button>317 <button onclick="switchRelationView('software-per-asset')" class="relation-btn px-4 py-2 text-sm rounded-lg bg-slate-100 text-slate-600" data-view="software-per-asset">318 <i data-lucide="hard-drive" class="w-4 h-4 inline mr-1"></i>Software per Asset319 </button>320 <button onclick="switchRelationView('user-asset-link')" class="relation-btn px-4 py-2 text-sm rounded-lg bg-slate-100 text-slate-600" data-view="user-asset-link">321 <i data-lucide="link" class="w-4 h-4 inline mr-1"></i>User-Asset Links322 </button>323 </div>324 </div>325 326 <!-- Software per User View -->327 <div id="view-software-per-user" class="relation-view">328 <div class="p-4 border-b border-slate-200 flex flex-wrap items-center gap-3">329 <div class="relative flex-1 min-w-64">330 <i data-lucide="search" class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>331 <input type="text" id="search-relations-user" placeholder="Search by user or software..." 332 class="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg text-sm"333 oninput="filterRelationData('software-per-user')">334 </div>335 <button onclick="exportData('software-per-user')" class="px-4 py-2 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition flex items-center gap-2">336 <i data-lucide="download" class="w-4 h-4"></i>Export337 </button>338 </div>339 <div class="overflow-auto max-h-[500px] scrollbar-thin">340 <table class="data-table w-full border-collapse">341 <thead class="bg-slate-50">342 <tr>343 <th class="text-left">User ID</th>344 <th class="text-left">Full Name</th>345 <th class="text-left">Department</th>346 <th class="text-left">Email</th>347 <th class="text-left">Software Title</th>348 <th class="text-left">Standard Title</th>349 <th class="text-left">License Type</th>350 </tr>351 </thead>352 <tbody id="software-per-user-body">353 <tr><td colspan="7" class="text-center py-8 text-slate-400">Process data to see relations</td></tr>354 </tbody>355 </table>356 </div>357 </div>358 359 <!-- Software per Asset View -->360 <div id="view-software-per-asset" class="relation-view hidden">361 <div class="p-4 border-b border-slate-200 flex flex-wrap items-center gap-3">362 <div class="relative flex-1 min-w-64">363 <i data-lucide="search" class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>364 <input type="text" id="search-relations-asset" placeholder="Search by asset or software..." 365 class="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg text-sm"366 oninput="filterRelationData('software-per-asset')">367 </div>368 <button onclick="exportData('software-per-asset')" class="px-4 py-2 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition flex items-center gap-2">369 <i data-lucide="download" class="w-4 h-4"></i>Export370 </button>371 </div>372 <div class="overflow-auto max-h-[500px] scrollbar-thin">373 <table class="data-table w-full border-collapse">374 <thead class="bg-slate-50">375 <tr>376 <th class="text-left">Asset ID</th>377 <th class="text-left">Serial Number</th>378 <th class="text-left">Computer Name</th>379 <th class="text-left">Location</th>380 <th class="text-left">Software Title</th>381 <th class="text-left">Standard Title</th>382 <th class="text-left">Version</th>383 <th class="text-left">License Type</th>384 </tr>385 </thead>386 <tbody id="software-per-asset-body">387 <tr><td colspan="8" class="text-center py-8 text-slate-400">Process data to see relations</td></tr>388 </tbody>389 </table>390 </div>391 </div>392 393 <!-- User-Asset Link View -->394 <div id="view-user-asset-link" class="relation-view hidden">395 <div class="p-4 border-b border-slate-200 flex flex-wrap items-center gap-3">396 <div class="relative flex-1 min-w-64">397 <i data-lucide="search" class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>398 <input type="text" id="search-relations-link" placeholder="Search links..." 399 class="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg text-sm"400 oninput="filterRelationData('user-asset-link')">401 </div>402 <button onclick="exportData('user-asset-link')" class="px-4 py-2 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition flex items-center gap-2">403 <i data-lucide="download" class="w-4 h-4"></i>Export404 </button>405 </div>406 <div class="overflow-auto max-h-[500px] scrollbar-thin">407 <table class="data-table w-full border-collapse">408 <thead class="bg-slate-50">409 <tr>410 <th class="text-left">User ID</th>411 <th class="text-left">Full Name</th>412 <th class="text-left">Department</th>413 <th class="text-left">Asset ID</th>414 <th class="text-left">Serial Number</th>415 <th class="text-left">Computer Name</th>416 <th class="text-left">Location</th>417 <th class="text-left">Asset Status</th>418 </tr>419 </thead>420 <tbody id="user-asset-link-body">421 <tr><td colspan="8" class="text-center py-8 text-slate-400">Process data to see relations</td></tr>422 </tbody>423 </table>424 </div>425 </div>426 </div>427 </div>428 429 <!-- SR Expansion Tab -->430 <div id="view-sr" class="view-section hidden">431 <div class="bg-white border border-slate-200 rounded-xl">432 <div class="p-4 border-b border-slate-200">433 <h3 class="text-lg font-semibold text-slate-800 mb-2">Service Request Expansion (Cartesian Product)</h3>434 <p class="text-sm text-slate-500">Cross-join between Asset IDs and Approved Software in each SR</p>435 </div>436 <div class="p-4 border-b border-slate-200 flex flex-wrap items-center gap-3">437 <div class="relative flex-1 min-w-64">438 <i data-lucide="search" class="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>439 <input type="text" id="search-sr" placeholder="Search SR expansions..." 440 class="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg text-sm"441 oninput="filterSRData()">442 </div>443 <button onclick="exportData('sr-expansion')" class="px-4 py-2 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition flex items-center gap-2">444 <i data-lucide="download" class="w-4 h-4"></i>Export445 </button>446 </div>447 <div class="overflow-auto max-h-[500px] scrollbar-thin">448 <table class="data-table w-full border-collapse">449 <thead class="bg-slate-50">450 <tr>451 <th class="text-left">SR Number</th>452 <th class="text-left">Asset ID</th>453 <th class="text-left">Approved Software</th>454 <th class="text-left">Approval Date</th>455 <th class="text-left">Approver Email</th>456 <th class="text-left">Validity</th>457 </tr>458 </thead>459 <tbody id="sr-expansion-body">460 <tr><td colspan="6" class="text-center py-8 text-slate-400">Process data to see SR expansions</td></tr>461 </tbody>462 </table>463 </div>464 <div class="p-3 border-t border-slate-200 text-sm text-slate-500">465 <span id="sr-expansion-count">0</span> expanded records (Cartesian product applied)466 </div>467 </div>468 </div>469 </main>470 471 <!-- Processing Modal -->472 <div id="processing-modal" class="modal fixed inset-0 bg-black/50 hidden flex items-center justify-center z-50">473 <div class="bg-white rounded-xl p-6 w-96 shadow-xl">474 <div class="flex items-center gap-3 mb-4">475 <div class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center animate-pulse">476 <i data-lucide="loader-2" class="w-5 h-5 text-blue-600"></i>477 </div>478 <div>479 <h3 class="font-semibold text-slate-800">Processing Data</h3>480 <p class="text-sm text-slate-500">Normalizing and linking...</p>481 </div>482 </div>483 <div class="w-full bg-slate-200 rounded-full h-2">484 <div id="processing-progress" class="bg-blue-600 h-2 rounded-full transition-all duration-300" style="width: 0%"></div>485 </div>486 <p id="processing-status" class="text-sm text-slate-500 mt-3 text-center">Starting...</p>487 </div>488 </div>489 490 <script>491 // Data Store492 let dataStore = {493 sccm: [],494 asset: [],495 user: [],496 software: [],497 sr: [],498 normalized: [],499 softwarePerUser: [],500 softwarePerAsset: [],501 userAssetLink: [],502 srExpansion: []503 };504 505 let sortState = { column: null, direction: 'asc' };506 507 // File Handling508 function handleDragOver(e) {509 e.preventDefault();510 e.currentTarget.classList.add('dragover');511 }512 513 function handleDragLeave(e) {514 e.currentTarget.classList.remove('dragover');515 }516 517 function handleDrop(e, type) {518 e.preventDefault();519 e.currentTarget.classList.remove('dragover');520 const files = e.dataTransfer.files;521 if (files.length > 0) {522 processFile(files[0], type);523 }524 }525 526 function triggerFileInput(type) {527 document.getElementById(`file-${type}`).click();528 }529 530 function handleFileSelect(e, type) {531 const file = e.target.files[0];532 if (file) {533 processFile(file, type);534 }535 }536 537 function processFile(file, type) {538 const reader = new FileReader();539 reader.onload = (e) => {540 const data = new Uint8Array(e.target.result);541 const workbook = XLSX.read(data, { type: 'array' });542 const firstSheet = workbook.Sheets[workbook.SheetNames[0]];543 const jsonData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 });544 545 if (jsonData.length > 0) {546 const headers = jsonData[0].map(h => h ? h.toString().trim() : '');547 const rows = jsonData.slice(1).map(row => {548 const obj = {};549 headers.forEach((h, i) => obj[h] = row[i] ? row[i].toString().trim() : '');550 return obj;551 });552 553 dataStore[type] = rows;554 document.getElementById(`status-${type}`).textContent = `${rows.length} records loaded`;555 document.getElementById(`status-${type}`).classList.remove('hidden');556 updateUploadStatus(type, rows.length);557 }558 };559 reader.readAsArrayBuffer(file);560 }561 562 function updateUploadStatus(type, count) {563 const labels = { sccm: 'SCCM', asset: 'Assets', user: 'Users', software: 'Software', sr: 'SR Data' };564 const el = document.getElementById(`upload-${type}`);565 el.textContent = count > 0 ? `${count} records` : 'Pending';566 el.className = count > 0 ? 'text-sm font-medium text-green-600' : 'text-sm font-medium text-slate-400';567 }568 569 // Data Processing570 async function processAllData() {571 if (dataStore.sccm.length === 0) {572 alert('Please upload at least the SCCM Discovery data');573 return;574 }575 576 const modal = document.getElementById('processing-modal');577 const progress = document.getElementById('processing-progress');578 const status = document.getElementById('processing-status');579 modal.classList.remove('hidden');580 581 // Simulate processing steps582 progress.style.width = '20%';583 status.textContent = 'Normalizing software names...';584 await sleep(300);585 586 normalizeData();587 progress.style.width = '50%';588 status.textContent = 'Building relations...';589 await sleep(300);590 591 buildRelations();592 progress.style.width = '75%';593 status.textContent = 'Expanding SR data...';594 await sleep(300);595 596 expandSRData();597 progress.style.width = '100%';598 status.textContent = 'Complete!';599 await sleep(500);600 601 modal.classList.add('hidden');602 updateDashboard();603 renderNormalizedTable();604 renderRelationTables();605 renderSRTable();606 switchTab('dashboard');607 }608 609 function normalizeData() {610 const softwareMap = {};611 dataStore.software.forEach(s => {612 const title = s['Software Title'] || s['Software Title '];613 const classification = s['Classification'] || s['Classification '];614 const label = s['Status_Label'] || s['Status_Label '] || s['Status Label'] || 'Unknown';615 if (title) {616 softwareMap[title.toLowerCase()] = {617 standardTitle: title,618 classification: classification || 'Unclassified',619 label: label620 };621 }622 });623 624 // Also create fuzzy matching map625 Object.keys(softwareMap).forEach(key => {626 const words = key.split(/[\s\-\_]+/);627 words.forEach(word => {628 if (word.length > 3 && !softwareMap[word]) {629 softwareMap[word] = softwareMap[key];630 }631 });632 });633 634 dataStore.normalized = dataStore.sccm.map(item => {635 const rawSoftware = item['Software Name'] || item['Software Name '] || '';636 const rawLower = rawSoftware.toLowerCase();637 638 let match = softwareMap[rawLower];639 640 // Fuzzy match641 if (!match) {642 const words = rawLower.split(/[\s\-\_]+/);643 for (let word of words) {644 if (word.length > 3 && softwareMap[word]) {645 match = softwareMap[word];646 break;647 }648 }649 }650 651 return {652 computerName: item['Computer Name'] || item['Computer Name '] || '',653 rawSoftware: rawSoftware,654 standardTitle: match ? match.standardTitle : rawSoftware,655 publisher: item['Publisher'] || item['Publisher '] || '',656 version: item['Version'] || item['Version '] || '',657 installDate: item['Install Date'] || item['Install Date '] || '',658 lastInventory: item['Last Inventory Date'] || item['Last Inventory Date '] || '',659 executablePath: item['Executable Path'] || item['Executable Path '] || '',660 classification: match ? match.classification : 'Unclassified',661 licenseType: match ? match.label : 'Unknown',662 normalized: !!match663 };664 });665 }666 667 function buildRelations() {668 // Link 1: SCCM to Asset via Computer Name669 const assetMap = {};670 dataStore.asset.forEach(a => {671 const compName = a['Computer Name'] || a['Computer Name '] || '';672 if (compName) assetMap[compName.toLowerCase()] = a;673 });674 675 // Link 2: Asset to User (Asset should have User ID)676 const userMap = {};677 dataStore.user.forEach(u => {678 const userId = u['User ID'] || u['User ID '] || '';679 if (userId) userMap[userId.toLowerCase()] = u;680 });681 682 // Build Software per User683 dataStore.softwarePerUser = [];684 dataStore.normalized.forEach(norm => {685 const asset = assetMap[norm.computerName.toLowerCase()];686 if (asset) {687 const userId = asset['User ID'] || asset['User ID '] || '';688 const user = userMap[userId.toLowerCase()];689 if (user) {690 dataStore.softwarePerUser.push({691 userId: user['User ID'] || user['User ID '] || '',692 fullName: user['Full Name'] || user['Full Name '] || '',693 department: user['Department'] || user['Department '] || '',694 email: user['Email'] || user['Email '] || '',695 softwareTitle: norm.rawSoftware,696 standardTitle: norm.standardTitle,697 licenseType: norm.licenseType698 });699 }700 }701 });702 703 // Build Software per Asset704 dataStore.softwarePerAsset = dataStore.normalized.map(norm => {705 const asset = assetMap[norm.computerName.toLowerCase()] || {};706 return {707 assetId: asset['Asset ID'] || asset['Asset ID '] || '',708 serialNumber: asset['Serial Number'] || asset['Serial Number '] || '',709 computerName: norm.computerName,710 location: asset['Location'] || asset['Location '] || '',711 softwareTitle: norm.rawSoftware,712 standardTitle: norm.standardTitle,713 version: norm.version,714 licenseType: norm.licenseType715 };716 }).filter(item => item.assetId);717 718 // Build User-Asset Links719 dataStore.userAssetLink = [];720 dataStore.asset.forEach(asset => {721 const compName = asset['Computer Name'] || asset['Computer Name '] || '';722 const userId = asset['User ID'] || asset['User ID '] || '';723 const user = userMap[userId.toLowerCase()];724 725 if (user) {726 dataStore.userAssetLink.push({727 userId: user['User ID'] || user['User ID '] || '',728 fullName: user['Full Name'] || user['Full Name '] || '',729 department: user['Department'] || user['Department '] || '',730 assetId: asset['Asset ID'] || asset['Asset ID '] || '',731 serialNumber: asset['Serial Number'] || asset['Serial Number '] || '',732 computerName: compName,733 location: asset['Location'] || asset['Location '] || '',734 assetStatus: asset['Asset Status'] || asset['Asset Status '] || ''735 });736 }737 });738 }739 740 function expandSRData() {741 // SR Expansion: Cartesian Product for each SR742 dataStore.srExpansion = [];743 744 dataStore.sr.forEach(sr => {745 const assetIds = (sr['Requested Asset ID'] || sr['Requested Asset ID '] || '').split(/[,;]/).map(s => s.trim()).filter(s => s);746 const softwareList = (sr['Approved Software'] || sr['Approved Software '] || '').split(/[,;]/).map(s => s.trim()).filter(s => s);747 748 // Cartesian product749 assetIds.forEach(assetId => {750 softwareList.forEach(software => {751 dataStore.srExpansion.push({752 srNumber: sr['SR Number'] || sr['SR Number '] || '',753 assetId: assetId,754 approvedSoftware: software,755 approvalDate: sr['Approval Date'] || sr['Approval Date '] || '',756 approverEmail: sr['Approver Email'] || sr['Approver Email '] || '',757 validity: sr['Validity'] || sr['Validity '] || ''758 });759 });760 });761 });762 }763 764 // UI Updates765 function updateDashboard() {766 document.getElementById('stat-sccm').textContent = dataStore.sccm.length;767 document.getElementById('stat-assets').textContent = dataStore.asset.length;768 document.getElementById('stat-users').textContent = dataStore.user.length;769 770 const normalized = dataStore.normalized.filter(n => n.normalized).length;771 const total = dataStore.normalized.length;772 const rate = total > 0 ? Math.round((normalized / total) * 100) : 0;773 document.getElementById('stat-norm-rate').textContent = rate + '%';774 775 // Blacklisted776 const blacklisted = dataStore.normalized.filter(n => n.licenseType === 'Blacklisted');777 const blacklistedEl = document.getElementById('blacklisted-list');778 if (blacklisted.length > 0) {779 const unique = [...new Set(blacklisted.map(b => b.standardTitle))];780 blacklistedEl.innerHTML = unique.map(s => 781 `<div class="flex items-center gap-2 p-2 bg-red-50 rounded-lg">782 <i data-lucide="ban" class="w-4 h-4 text-red-500"></i>783 <span class="text-sm text-red-700">${s}</span>784 </div>`785 ).join('');786 }787 788 // Summary789 const summaryEl = document.getElementById('norm-summary');790 const licenseCounts = {};791 dataStore.normalized.forEach(n => {792 licenseCounts[n.licenseType] = (licenseCounts[n.licenseType] || 0) + 1;793 });794 summaryEl.innerHTML = Object.entries(licenseCounts).map(([type, count]) => 795 `<div class="flex items-center justify-between p-2 bg-slate-50 rounded-lg">796 <span class="text-sm text-slate-600">${type}</span>797 <span class="text-sm font-semibold text-slate-800">${count}</span>798 </div>`799 ).join('');800 801 lucide.createIcons();802 }803 804 function renderNormalizedTable() {805 const tbody = document.getElementById('normalized-table-body');806 const data = dataStore.normalized;807 808 if (data.length === 0) {809 tbody.innerHTML = '<tr><td colspan="7" class="text-center py-8 text-slate-400">No data available</td></tr>';810 return;811 }812 813 tbody.innerHTML = data.map(row => `814 <tr class="hover:bg-slate-50 border-b border-slate-100">815 <td class="font-medium text-slate-800">${row.computerName}</td>816 <td class="text-slate-600">${row.rawSoftware}</td>817 <td class="font-medium text-slate-800">${row.standardTitle}</td>818 <td class="text-slate-600">${row.publisher}</td>819 <td class="text-slate-600">${row.version}</td>820 <td>${getLicenseBadge(row.licenseType)}</td>821 <td>${row.normalized ? '<span class="text-green-600"><i data-lucide="check-circle" class="w-4 h-4"></i></span>' : '<span class="text-amber-600"><i data-lucide="alert-circle" class="w-4 h-4"></i></span>'}</td>822 </tr>823 `).join('');824 825 document.getElementById('normalized-count').textContent = data.length;826 lucide.createIcons();827 }828 829 function getLicenseBadge(type) {830 const classes = {831 'Freeware': 'badge-freeware',832 'License Standard': 'badge-standard',833 'Blacklisted': 'badge-blacklisted',834 'Unknown': 'badge-unknown'835 };836 return `<span class="badge ${classes[type] || 'badge-unknown'}">${type}</span>`;837 }838 839 function renderRelationTables() {840 // Software per User841 const spuBody = document.getElementById('software-per-user-body');842 if (dataStore.softwarePerUser.length > 0) {843 spuBody.innerHTML = dataStore.softwarePerUser.map(row => `844 <tr class="hover:bg-slate-50 border-b border-slate-100">845 <td class="font-medium text-slate-800">${row.userId}</td>846 <td class="text-slate-600">${row.fullName}</td>847 <td class="text-slate-600">${row.department}</td>848 <td class="text-slate-600">${row.email}</td>849 <td class="text-slate-600">${row.softwareTitle}</td>850 <td class="font-medium text-slate-800">${row.standardTitle}</td>851 <td>${getLicenseBadge(row.licenseType)}</td>852 </tr>853 `).join('');854 }855 856 // Software per Asset857 const spaBody = document.getElementById('software-per-asset-body');858 if (dataStore.softwarePerAsset.length > 0) {859 spaBody.innerHTML = dataStore.softwarePerAsset.map(row => `860 <tr class="hover:bg-slate-50 border-b border-slate-100">861 <td class="font-medium text-slate-800">${row.assetId}</td>862 <td class="text-slate-600">${row.serialNumber}</td>863 <td class="text-slate-600">${row.computerName}</td>864 <td class="text-slate-600">${row.location}</td>865 <td class="text-slate-600">${row.softwareTitle}</td>866 <td class="font-medium text-slate-800">${row.standardTitle}</td>867 <td class="text-slate-600">${row.version}</td>868 <td>${getLicenseBadge(row.licenseType)}</td>869 </tr>870 `).join('');871 }872 873 // User-Asset Link874 const ualBody = document.getElementById('user-asset-link-body');875 if (dataStore.userAssetLink.length > 0) {876 ualBody.innerHTML = dataStore.userAssetLink.map(row => `877 <tr class="hover:bg-slate-50 border-b border-slate-100">878 <td class="font-medium text-slate-800">${row.userId}</td>879 <td class="text-slate-600">${row.fullName}</td>880 <td class="text-slate-600">${row.department}</td>881 <td class="font-medium text-slate-800">${row.assetId}</td>882 <td class="text-slate-600">${row.serialNumber}</td>883 <td class="text-slate-600">${row.computerName}</td>884 <td class="text-slate-600">${row.location}</td>885 <td><span class="badge ${row.assetStatus === 'Active' ? 'badge-freeware' : 'badge-unknown'}">${row.assetStatus}</span></td>886 </tr>887 `).join('');888 }889 }890 891 function renderSRTable() {892 const tbody = document.getElementById('sr-expansion-body');893 if (dataStore.srExpansion.length > 0) {894 tbody.innerHTML = dataStore.srExpansion.map(row => `895 <tr class="hover:bg-slate-50 border-b border-slate-100">896 <td class="font-medium text-slate-800">${row.srNumber}</td>897 <td class="text-slate-600">${row.assetId}</td>898 <td class="font-medium text-slate-800">${row.approvedSoftware}</td>899 <td class="text-slate-600">${row.approvalDate}</td>900 <td class="text-slate-600">${row.approverEmail}</td>901 <td>${getLicenseBadge(row.validity === 'Valid' ? 'License Standard' : 'Unknown')}</td>902 </tr>903 `).join('');904 }905 document.getElementById('sr-expansion-count').textContent = dataStore.srExpansion.length;906 }907 908 // Filtering & Sorting909 function filterNormalizedData() {910 const search = document.getElementById('search-normalized').value.toLowerCase();911 const licenseFilter = document.getElementById('filter-license').value;912 913 let filtered = dataStore.normalized;914 if (search) {915 filtered = filtered.filter(row => 916 row.computerName.toLowerCase().includes(search) ||917 row.rawSoftware.toLowerCase().includes(search) ||918 row.standardTitle.toLowerCase().includes(search) ||919 row.publisher.toLowerCase().includes(search)920 );921 }922 if (licenseFilter) {923 filtered = filtered.filter(row => row.licenseType === licenseFilter);924 }925 926 const tbody = document.getElementById('normalized-table-body');927 tbody.innerHTML = filtered.map(row => `928 <tr class="hover:bg-slate-50 border-b border-slate-100">929 <td class="font-medium text-slate-800">${row.computerName}</td>930 <td class="text-slate-600">${row.rawSoftware}</td>931 <td class="font-medium text-slate-800">${row.standardTitle}</td>932 <td class="text-slate-600">${row.publisher}</td>933 <td class="text-slate-600">${row.version}</td>934 <td>${getLicenseBadge(row.licenseType)}</td>935 <td>${row.normalized ? '<span class="text-green-600"><i data-lucide="check-circle" class="w-4 h-4"></i></span>' : '<span class="text-amber-600"><i data-lucide="alert-circle" class="w-4 h-4"></i></span>'}</td>936 </tr>937 `).join('');938 document.getElementById('normalized-count').textContent = filtered.length;939 lucide.createIcons();940 }941 942 function filterRelationData(view) {943 const searchInputs = {944 'software-per-user': 'search-relations-user',945 'software-per-asset': 'search-relations-asset',946 'user-asset-link': 'search-relations-link'947 };948 949 const search = document.getElementById(searchInputs[view]).value.toLowerCase();950 let data, keys;951 952 if (view === 'software-per-user') {953 data = dataStore.softwarePerUser;954 keys = ['userId', 'fullName', 'department', 'email', 'softwareTitle', 'standardTitle'];955 } else if (view === 'software-per-asset') {956 data = dataStore.softwarePerAsset;957 keys = ['assetId', 'serialNumber', 'computerName', 'softwareTitle', 'standardTitle'];958 } else {959 data = dataStore.userAssetLink;960 keys = ['userId', 'fullName', 'department', 'assetId', 'serialNumber', 'computerName'];961 }962 963 const filtered = search ? data.filter(row => 964 keys.some(k => row[k] && row[k].toLowerCase().includes(search))965 ) : data;966 967 renderFilteredRelation(view, filtered);968 }969 970 function renderFilteredRelation(view, data) {971 const tbodyId = {972 'software-per-user': 'software-per-user-body',973 'software-per-asset': 'software-per-asset-body',974 'user-asset-link': 'user-asset-link-body'975 }[view];976 977 const tbody = document.getElementById(tbodyId);978 979 if (view === 'software-per-user') {980 tbody.innerHTML = data.map(row => `981 <tr class="hover:bg-slate-50 border-b border-slate-100">982 <td class="font-medium text-slate-800">${row.userId}</td>983 <td class="text-slate-600">${row.fullName}</td>984 <td class="text-slate-600">${row.department}</td>985 <td class="text-slate-600">${row.email}</td>986 <td class="text-slate-600">${row.softwareTitle}</td>987 <td class="font-medium text-slate-800">${row.standardTitle}</td>988 <td>${getLicenseBadge(row.licenseType)}</td>989 </tr>990 `).join('');991 } else if (view === 'software-per-asset') {992 tbody.innerHTML = data.map(row => `993 <tr class="hover:bg-slate-50 border-b border-slate-100">994 <td class="font-medium text-slate-800">${row.assetId}</td>995 <td class="text-slate-600">${row.serialNumber}</td>996 <td class="text-slate-600">${row.computerName}</td>997 <td class="text-slate-600">${row.location}</td>998 <td class="text-slate-600">${row.softwareTitle}</td>999 <td class="font-medium text-slate-800">${row.standardTitle}</td>1000 <td class="text-slate-600">${row.version}</td>1001 <td>${getLicenseBadge(row.licenseType)}</td>1002 </tr>1003 `).join('');1004 } else {1005 tbody.innerHTML = data.map(row => `1006 <tr class="hover:bg-slate-50 border-b border-slate-100">1007 <td class="font-medium text-slate-800">${row.userId}</td>1008 <td class="text-slate-600">${row.fullName}</td>1009 <td class="text-slate-600">${row.department}</td>1010 <td class="font-medium text-slate-800">${row.assetId}</td>1011 <td class="text-slate-600">${row.serialNumber}</td>1012 <td class="text-slate-600">${row.computerName}</td>1013 <td class="text-slate-600">${row.location}</td>1014 <td><span class="badge ${row.assetStatus === 'Active' ? 'badge-freeware' : 'badge-unknown'}">${row.assetStatus}</span></td>1015 </tr>1016 `).join('');1017 }1018 }1019 1020 function filterSRData() {1021 const search = document.getElementById('search-sr').value.toLowerCase();1022 const filtered = search ? dataStore.srExpansion.filter(row => 1023 row.srNumber.toLowerCase().includes(search) ||1024 row.assetId.toLowerCase().includes(search) ||1025 row.approvedSoftware.toLowerCase().includes(search)1026 ) : dataStore.srExpansion;1027 1028 const tbody = document.getElementById('sr-expansion-body');1029 tbody.innerHTML = filtered.map(row => `1030 <tr class="hover:bg-slate-50 border-b border-slate-100">1031 <td class="font-medium text-slate-800">${row.srNumber}</td>1032 <td class="text-slate-600">${row.assetId}</td>1033 <td class="font-medium text-slate-800">${row.approvedSoftware}</td>1034 <td class="text-slate-600">${row.approvalDate}</td>1035 <td class="text-slate-600">${row.approverEmail}</td>1036 <td>${getLicenseBadge(row.validity === 'Valid' ? 'License Standard' : 'Unknown')}</td>1037 </tr>1038 `).join('');1039 document.getElementById('sr-expansion-count').textContent = filtered.length;1040 }1041 1042 function sortTable(tableType, column) {1043 if (sortState.column === column) {1044 sortState.direction = sortState.direction === 'asc' ? 'desc' : 'asc';1045 } else {1046 sortState.column = column;1047 sortState.direction = 'asc';1048 }1049 1050 const data = [...dataStore[tableType === 'normalized' ? 'normalized' : tableType]];1051 data.sort((a, b) => {1052 const valA = a[column] || '';1053 const valB = b[column] || '';1054 if (valA < valB) return sortState.direction === 'asc' ? -1 : 1;1055 if (valA > valB) return sortState.direction === 'asc' ? 1 : -1;1056 return 0;1057 });1058 1059 if (tableType === 'normalized') {1060 dataStore.normalized = data;1061 renderNormalizedTable();1062 }1063 }1064 1065 // Tab Switching1066 function switchTab(tab) {1067 document.querySelectorAll('.view-section').forEach(el => el.classList.add('hidden'));1068 document.getElementById(`view-${tab}`).classList.remove('hidden');1069 1070 document.querySelectorAll('.tab-btn').forEach(el => {1071 el.classList.remove('tab-active');1072 el.classList.add('text-slate-600');1073 });1074 document.getElementById(`tab-${tab}`).classList.add('tab-active');1075 document.getElementById(`tab-${tab}`).classList.remove('text-slate-600');1076 }1077 1078 function switchRelationView(view) {1079 document.querySelectorAll('.relation-view').forEach(el => el.classList.add('hidden'));1080 document.getElementById(`view-${view}`).classList.remove('hidden');1081 1082 document.querySelectorAll('.relation-btn').forEach(el => {1083 el.classList.remove('bg-blue-100', 'text-blue-700', 'font-medium');1084 el.classList.add('bg-slate-100', 'text-slate-600');1085 });1086 document.querySelector(`[data-view="${view}"]`).classList.add('bg-blue-100', 'text-blue-700', 'font-medium');1087 document.querySelector(`[data-view="${view}"]`).classList.remove('bg-slate-100', 'text-slate-600');1088 }1089 1090 // Export1091 function exportData(type) {1092 let data, filename;1093 1094 if (type === 'normalized') {1095 data = dataStore.normalized;1096 filename = 'normalized_data.xlsx';1097 } else if (type === 'software-per-user') {1098 data = dataStore.softwarePerUser;1099 filename = 'software_per_user.xlsx';1100 } else if (type === 'software-per-asset') {1101 data = dataStore.softwarePerAsset;1102 filename = 'software_per_asset.xlsx';1103 } else if (type === 'user-asset-link') {1104 data = dataStore.userAssetLink;1105 filename = 'user_asset_links.xlsx';1106 } else if (type === 'sr-expansion') {1107 data = dataStore.srExpansion;1108 filename = 'sr_expansion.xlsx';1109 }1110 1111 if (data.length === 0) {1112 alert('No data to export');1113 return;1114 }1115 1116 const ws = XLSX.utils.json_to_sheet(data);1117 const wb = XLSX.utils.book_new();1118 XLSX.utils.book_append_sheet(wb, ws, 'Data');1119 XLSX.writeFile(wb, filename);1120 }1121 1122 function exportAllData() {1123 if (dataStore.normalized.length === 0) {1124 alert('No processed data to export. Please upload and process data first.');1125 return;1126 }1127 1128 const wb = XLSX.utils.book_new();1129 1130 if (dataStore.normalized.length > 0) {1131 XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(dataStore.normalized), 'Normalized');1132 }1133 if (dataStore.softwarePerUser.length > 0) {1134 XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(dataStore.softwarePerUser), 'Software per User');1135 }1136 if (dataStore.softwarePerAsset.length > 0) {1137 XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(dataStore.softwarePerAsset), 'Software per Asset');1138 }1139 if (dataStore.userAssetLink.length > 0) {1140 XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(dataStore.userAssetLink), 'User Asset Links');1141 }1142 if (dataStore.srExpansion.length > 0) {1143 XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(dataStore.srExpansion), 'SR Expansion');1144 }1145 1146 XLSX.writeFile(wb, 'ITSAM_Export_All.xlsx');1147 }1148 1149 function resetAllData() {1150 if (!confirm('Are you sure you want to reset all data?')) return;1151 1152 dataStore = {1153 sccm: [],1154 asset: [],1155 user: [],1156 software: [],1157 sr: [],1158 normalized: [],1159 softwarePerUser: [],1160 softwarePerAsset: [],1161 userAssetLink: [],1162 srExpansion: []1163 };1164 1165 ['sccm', 'asset', 'user', 'software', 'sr'].forEach(type => {1166 document.getElementById(`status-${type}`).classList.add('hidden');1167 document.getElementById(`upload-${type}`).textContent = 'Pending';1168 document.getElementById(`upload-${type}`).className = 'text-sm font-medium text-slate-400';1169 });1170 1171 document.getElementById('stat-sccm').textContent = '0';1172 document.getElementById('stat-assets').textContent = '0';1173 document.getElementById('stat-users').textContent = '0';1174 document.getElementById('stat-norm-rate').textContent = '0%';1175 document.getElementById('blacklisted-list').innerHTML = '<p class="text-sm text-slate-400 italic">No blacklisted software detected</p>';1176 document.getElementById('norm-summary').innerHTML = '<p class="text-sm text-slate-400 italic">Upload data to see summary</p>';1177 1178 document.getElementById('normalized-table-body').innerHTML = '<tr><td colspan="7" class="text-center py-8 text-slate-400">Upload and process data to see normalized results</td></tr>';1179 document.getElementById('software-per-user-body').innerHTML = '<tr><td colspan="7" class="text-center py-8 text-slate-400">Process data to see relations</td></tr>';1180 document.getElementById('software-per-asset-body').innerHTML = '<tr><td colspan="8" class="text-center py-8 text-slate-400">Process data to see relations</td></tr>';1181 document.getElementById('user-asset-link-body').innerHTML = '<tr><td colspan="8" class="text-center py-8 text-slate-400">Process data to see relations</td></tr>';1182 document.getElementById('sr-expansion-body').innerHTML = '<tr><td colspan="6" class="text-center py-8 text-slate-400">Process data to see SR expansions</td></tr>';1183 1184 document.getElementById('normalized-count').textContent = '0';1185 document.getElementById('sr-expansion-count').textContent = '0';1186 1187 switchTab('upload');1188 }1189 1190 function sleep(ms) {1191 return new Promise(resolve => setTimeout(resolve, ms));1192 }1193 1194 // Initialize1195 lucide.createIcons();1196 </script>1197<script src="https://deepsite.hf.co/deepsite-badge.js"></script>1198</body>1199</html>