const { createApp } = Vue; const api = axios.create({ baseURL: '/api' }); const chartColors = [ '#38bdf8','#22d3ee','#818cf8','#c084fc','#f472b6','#fb7185','#34d399','#a3e635','#fbbf24','#f87171' ]; function destroyChart(chart) { if(chart) { chart.destroy(); } } function makeBarConfig(label, labels, data) { return { type: 'bar', data: { labels, datasets: [{ label, data, backgroundColor: chartColors, borderRadius: 4, }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, grid: { color: '#27303f' }, ticks: { color: '#94a3b8' } }, x: { grid: { display: false }, ticks: { color: '#94a3b8' } } } } }; } function makeLineConfig(labels, data) { return { type: 'line', data: { labels, datasets: [{ label: 'Events', data, borderColor: '#38bdf8', backgroundColor: 'rgba(56,189,248,0.15)', fill: true, tension: 0.3, pointRadius: 3, }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, grid: { color: '#27303f' }, ticks: { color: '#94a3b8' } }, x: { grid: { color: '#27303f' }, ticks: { color: '#94a3b8' } } } } }; } createApp({ data() { return { selectedFile: null, uploading: false, uploadMsg: '', uploadOk: false, stats: { overview: { total_entries: 0, firewall_entries: 0, proxy_entries: 0 }, top_sources: [], top_destinations: [], top_ports: [], top_urls: [], actions: [], timeline: [], unique_counts: { unique_sources: 0, unique_destinations: 0 } }, analyzeType: 'all', analyzeLimit: 100, analyzing: false, analysisResult: '', charts: {} }; }, mounted() { this.fetchStats(); setInterval(() => this.fetchStats(), 15000); }, methods: { handleFile(e) { this.selectedFile = e.target.files[0]; }, async uploadFile() { if (!this.selectedFile) return; this.uploading = true; this.uploadMsg = ''; const form = new FormData(); form.append('file', this.selectedFile); try { const res = await api.post('/upload', form, { headers: { 'Content-Type': 'multipart/form-data' } }); this.uploadOk = true; this.uploadMsg = res.data.message; this.selectedFile = null; this.$el.querySelector('input[type=file]').value = ''; this.fetchStats(); } catch (e) { this.uploadOk = false; this.uploadMsg = e.response?.data?.detail || 'Upload fehlgeschlagen.'; } finally { this.uploading = false; } }, async fetchStats() { try { const res = await api.get('/stats', { params: { limit: 20 } }); this.stats = res.data; this.$nextTick(() => this.renderCharts()); } catch (e) { console.error('Stats fetch error', e); } }, renderCharts() { const s = this.stats; destroyChart(this.charts.sources); if (s.top_sources.length) { const cfg = makeBarConfig('Hits', s.top_sources.map(x=>x.source_ip||'N/A'), s.top_sources.map(x=>x.count)); this.charts.sources = new Chart(document.getElementById('chartSources'), cfg); } destroyChart(this.charts.dests); if (s.top_destinations.length) { const cfg = makeBarConfig('Hits', s.top_destinations.map(x=>x.destination_ip||'N/A'), s.top_destinations.map(x=>x.count)); this.charts.dests = new Chart(document.getElementById('chartDestinations'), cfg); } destroyChart(this.charts.ports); if (s.top_ports.length) { const cfg = makeBarConfig('Hits', s.top_ports.map(x=>x.destination_port||'N/A'), s.top_ports.map(x=>x.count)); this.charts.ports = new Chart(document.getElementById('chartPorts'), cfg); } destroyChart(this.charts.timeline); if (s.timeline.length) { const cfg = makeLineConfig(s.timeline.map(x=>x.time_bucket), s.timeline.map(x=>x.count)); this.charts.timeline = new Chart(document.getElementById('chartTimeline'), cfg); } }, async runAnalysis() { this.analyzing = true; this.analysisResult = ''; try { const res = await api.post('/analyze', null, { params: { log_type: this.analyzeType, limit: this.analyzeLimit } }); this.analysisResult = res.data.analysis; } catch (e) { this.analysisResult = e.response?.data?.detail || 'Analyse fehlgeschlagen.'; } finally { this.analyzing = false; } } } }).mount('#app');