Files
thetool/public/js/pages/Radius/RadiusOntFinder.js

29 lines
5.8 KiB
JavaScript

/* ===== RadiusOntFinder.js ===== */
Vue.component('radius-ont-finder', {
template: `
<div class="radius-scope ont-card">
<div v-if="step===1">
<div class="block-head"><div class="h4"><i class="fa-duotone fa-file-spreadsheet"></i> Schritt 1 · Excel (XLSX) Upload</div><p class="muted small">Datei muss die Spalte <code>Serial</code> enthalten. Optional <code>MAC</code>.</p></div>
<radius-file-drop @file-selected="readXlsx" /><div v-if="uploadError" class="alert error mt-2">{{ uploadError }}</div>
</div>
<div v-if="step===2">
<div class="block-head"><div class="h4"><i class="fa-duotone fa-list-check"></i> Ergebnisse</div><div class="cluster"><button class="primary-btn" @click="downloadResults" :disabled="loading"><i class="fa-duotone fa-download"></i> Ergebnisse herunterladen</button><button class="ghost-btn" @click="resetComponent" :disabled="loading"><i class="fa-duotone fa-rotate-right"></i> Neue Datei</button></div></div>
<div class="results-container mt-between">
<radius-processing-indicator v-if="loading" :progress="progress" :current-row="currentRow" :total-rows="totalRows" :current-serial="currentSerial" />
<radius-table-view v-else :items="processedData" :has-searched="true" no-results-placeholder-text="Keine Daten verarbeitet.">
<template #head><thead><tr><th v-for="h in originalHeaders" :key="'h'+h">{{ h }}</th><th>Username</th><th>Kundennummer</th><th>Kundenname</th><th>Info</th></tr></thead></template>
<template #row="{ item }"><td v-for="h in originalHeaders" :key="h+item.Serial">{{ item[h] }}</td><td class="mono">{{ item.fetched_username }}</td><td class="mono">{{ item.fetched_customerNumber }}</td><td class="clamp-2">{{ item.fetched_customerName }}</td><td class="clamp-2 mono">{{ item.fetched_info }}</td></template>
</radius-table-view>
<div v-if="!loading && processedData.length" class="results-summary">{{ processedData.length }} Zeilen verarbeitet</div>
</div>
</div>
</div>
`,
data: () => ({ step: 1, parsedData: [], processedData: [], originalHeaders: [], loading: false, progress: 0, currentRow: 0, totalRows: 0, currentSerial: '', uploadError: null, serialColumnName: 'Serial', macColumnName: 'MAC', fetchedKeys: { username: 'fetched_username', customerNumber: 'fetched_customerNumber', customerName: 'fetched_customerName', info: 'fetched_info' }, apiBasePath: window.TT_CONFIG?.BASE_PATH }),
methods: {
resetComponent(){ Object.assign(this.$data, this.$options.data.call(this)); const i=this.$el.querySelector('input[type="file"]'); if (i) i.value=''; },
async readXlsx(file){ this.uploadError=null; try{ await window.RadiusUtils.loadScript('https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.0/xlsx.full.min.js'); const arr = await new Promise((res,rej)=>{ const r=new FileReader(); r.onload=e=>res(new Uint8Array(e.target.result)); r.onerror=()=>rej(new Error('Fehler beim Lesen.')); r.readAsArrayBuffer(file); }); const wb = XLSX.read(arr, {type:'array'}); const ws = wb.Sheets[wb.SheetNames[0]]; this.parsedData = XLSX.utils.sheet_to_json(ws, {defval:''}); if (!this.parsedData.length) throw new Error('Die Datei ist leer.'); this.originalHeaders = Object.keys(this.parsedData[0]); if (!this.originalHeaders.includes(this.serialColumnName)) throw new Error(`Erforderliche Spalte '${this.serialColumnName}' nicht gefunden.`); this.startProcessing(); } catch(e){ this.uploadError=e.message; this.step=1; } },
async startProcessing(){ this.step = 2; this.loading = true; this.totalRows=this.parsedData.length; this.processedData=[]; const setRow = (row, msg, data={})=>{ const d={username:`N/A - ${msg}`,customerNumber:'N/A',customerName:'N/A',info:'N/A'}; Object.keys(this.fetchedKeys).forEach(k=>row[this.fetchedKeys[k]]=data[k]||d[k]); }; for (const [i,row] of this.parsedData.entries()){ this.currentRow=i; const out={...row}; const sn=(''+(row[this.serialColumnName]||'')).trim(); this.currentSerial=`SN: ${sn||'—'}`; let found=false; if (sn){ try{ const r=await fetch(`${this.apiBasePath}/Radius/proxyUnsecureHTTPRequestToRadius?ont_sn=${encodeURIComponent(sn)}`); if(r.ok){ const j=await r.json(); if(Array.isArray(j)&&j.length>0){ setRow(out,'',j[0]); found=true; }}}catch{} } if (!found && this.originalHeaders.includes(this.macColumnName)){ const macRaw=(''+(row[this.macColumnName]||'')).trim(); if(macRaw&&macRaw.length===12){ const mac=macRaw.toUpperCase().match(/.{1,2}/g).join(':'); try{ const s=await fetch(`${this.apiBasePath}/Radius/proxyUnsecureHTTPRequestToRadius?action2=find_by_current_session&mac=${encodeURIComponent(mac)}`); if(s.ok){ const ses=await s.json(); if(Array.isArray(ses)&&ses.length>0){ const u=await fetch(`${this.apiBasePath}/Radius/proxyUnsecureHTTPRequestToRadius?username=${encodeURIComponent(ses[0])}&info=&custnum=`); if(u.ok){ const d=await u.json(); if(Array.isArray(d)&&d.length>0) {setRow(out,'',d[0]); found=true;}}}}}catch{}}} if(!found) setRow(out,'Keinen Benutzer gefunden'); this.processedData.push(out); this.progress=((i+1)/this.totalRows)*100; if ((i+1)%20===0) await new Promise(r=>setTimeout(r,20)); } this.loading=false; this.currentSerial=''; },
downloadResults(){ if (!this.processedData.length) return; try{ const data=this.processedData.map(r=>{ const o={}; this.originalHeaders.forEach(h=>o[h]=r[h]); Object.keys(this.fetchedKeys).forEach(k=>{const K=k.charAt(0).toUpperCase()+k.slice(1).replace('Number','nummer').replace('Name','name'); o[K]=r[this.fetchedKeys[k]];}); return o; }); const ws=XLSX.utils.json_to_sheet(data); const wb=XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb,ws,'ONT_Finder_Results'); XLSX.writeFile(wb, `ont_finder_results_${new Date().toISOString().replace(/[-:.]/g,'').slice(0,14)}.xlsx`); } catch { if(window.notify) window.notify('error', 'Fehler beim Erstellen der Excel-Datei.'); } }
}
});