Files
piclock/www/network-tracker.html
jpd a201d5d7ac Update 34 files
- /nfc/README.md
- /nfc/clean_old_logs.sh
- /nfc/clockin.py
- /nfc/emptycards.py
- /nfc/sync_clocks.sh
- /piservices/Dockerfile
- /piservices/Portainer-lamp-stack.env
- /piservices/Portainer-lamp-stack.yaml
- /piservices/README.md
- /piservices/avahi-alias.service
- /piservices/clockpy.service
- /piservices/emptycards.service
- /piservices/nfc-sync.service
- /piservices/test.sh
- /www/404
- /www/get-table-data.php
- /www/habiwan copy.png
- /www/habiwan.png
- /www/index.php
- /www/log_swipe.php
- /www/manage_data.php
- /www/manifest.json
- /www/names.csv
- /www/network_audit.csv
- /www/network-tracker.html
- /www/nfc_debug.log
- /www/scanner.html
- /www/staff_names.php
- /www/times.csv
- /www/check_update.php
- /www/dev.php
- /www/favicon.png
- /www/JsBarcode.all.min.js
- /www/README.md
2026-07-24 14:37:45 +01:00

174 lines
8.7 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Network Audit Tracker</title>
<link rel="icon" href="habiwan.png" type="image/png">
<style>
body { font-family: system-ui, sans-serif; background-color: #1e1e2e; color: #cdd6f4; max-width: 900px; margin: 2rem auto; padding: 0 1rem; }
h2 { border-bottom: 2px solid #313244; padding-bottom: 0.5rem; }
.controls { margin-top: 1rem; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.form-group { display: flex; gap: 10px; margin-bottom: 1.5rem; flex-wrap: wrap; }
input[type="text"], select, button { padding: 8px 12px; background: #313244; border: 1px solid #45475a; color: #cdd6f4; border-radius: 4px; }
input.edit-input { width: 90%; }
button { font-weight: bold; cursor: pointer; border: none; transition: 0.2s; }
button:hover { opacity: 0.8; }
button.action-btn { background: #89b4fa; color: #11111b; }
button.delete-btn { background: #f38ba8; color: #11111b; }
button.export-btn { background: #a6e3a1; color: #11111b; }
button.import-btn { background: #f9e2af; color: #11111b; }
table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
th, td { text-align: left; padding: 10px; border-bottom: 1px solid #313244; }
th { cursor: pointer; user-select: none; }
/* Disable cursor for the non-sortable # column */
th.static { cursor: default; }
.badge { padding: 4px 8px; border-radius: 12px; font-size: 0.8rem; font-weight: bold; }
.type-infrastructure { background: #f38ba8; color: #11111b; }
.type-service { background: #fab387; color: #11111b; }
.type-client { background: #a6e3a1; color: #11111b; }
.type-transient { background: #cba6f7; color: #11111b; }
</style>
</head>
<body>
<h1>Network Audit Tracker</h1>
<p>Device Count: <span id="deviceCount">0</span></p>
<form class="form-group" id="addForm">
<input type="text" id="hostname" placeholder="Hostname" required>
<input type="text" id="ip" placeholder="IP" required>
<input type="text" id="mac" placeholder="MAC Address" required>
<select id="type">
<option value="Infrastructure">Infrastructure</option>
<option value="Service">Service</option>
<option value="Client">Client</option>
<option value="Transient">Transient</option>
</select>
<button type="submit" class="action-btn">Add Device</button>
</form>
<table>
<thead>
<tr>
<th><input type="checkbox" id="selectAll" onclick="toggleSelectAll()"></th>
<th class="static">#</th>
<th onclick="sortData('hostname')">Hostname ↕️</th>
<th onclick="sortData('ip')">IP Address ↕️</th>
<th onclick="sortData('mac')">MAC Address ↕️</th>
<th onclick="sortData('type')">Type ↕️</th>
<th>Action</th>
</tr>
</thead>
<tbody id="tableBody"></tbody>
</table>
<div class="controls">
<button class="export-btn" onclick="exportToCSV()">Export CSV</button>
<button class="import-btn" onclick="document.getElementById('importFile').click()">Import CSV</button>
<button class="delete-btn" onclick="deleteSelected()">Delete Selected</button>
<input type="file" id="importFile" accept=".csv" style="display:none" onchange="importCSV(event)">
</div>
<script>
let devices = JSON.parse(localStorage.getItem('networkAuditData')) || [];
let sortAscending = true;
let currentSortColumn = '';
const tableBody = document.getElementById('tableBody');
const deviceCount = document.getElementById('deviceCount');
function ipToNumber(ip) {
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0) >>> 0;
}
function renderTable() {
tableBody.innerHTML = '';
devices.forEach((device, index) => {
const tr = document.createElement('tr');
const isEditing = device.isEditing;
tr.innerHTML = `
<td><input type="checkbox" ${device.selected ? 'checked' : ''} onchange="toggleSelect(${device.id})"></td>
<td>${index + 1}</td>
<td>${isEditing ? `<input type="text" class="edit-input" value="${device.hostname}" onchange="updateField(${device.id}, 'hostname', this.value)">` : device.hostname}</td>
<td>${isEditing ? `<input type="text" class="edit-input" value="${device.ip}" onchange="updateField(${device.id}, 'ip', this.value)">` : device.ip}</td>
<td>${isEditing ? `<input type="text" class="edit-input" value="${device.mac}" onchange="updateField(${device.id}, 'mac', this.value)">` : device.mac}</td>
<td>${isEditing ? `<select onchange="updateField(${device.id}, 'type', this.value)"><option value="Infrastructure" ${device.type==='Infrastructure'?'selected':''}>Infrastructure</option><option value="Service" ${device.type==='Service'?'selected':''}>Service</option><option value="Client" ${device.type==='Client'?'selected':''}>Client</option><option value="Transient" ${device.type==='Transient'?'selected':''}>Transient</option></select>` : `<span class="badge type-${device.type.toLowerCase()}">${device.type}</span>`}</td>
<td><button class="action-btn" onclick="toggleEdit(${device.id})">${isEditing ? '💾' : '✏️'}</button></td>
`;
tableBody.appendChild(tr);
});
deviceCount.innerText = devices.length;
localStorage.setItem('networkAuditData', JSON.stringify(devices));
}
function toggleEdit(id) {
const dev = devices.find(d => d.id === id);
dev.isEditing = !dev.isEditing;
renderTable();
}
function updateField(id, field, value) {
const dev = devices.find(d => d.id === id);
dev[field] = value;
localStorage.setItem('networkAuditData', JSON.stringify(devices));
}
function toggleSelect(id) {
const dev = devices.find(d => d.id === id);
dev.selected = !dev.selected;
}
function toggleSelectAll() {
const allChecked = document.getElementById('selectAll').checked;
devices.forEach(d => d.selected = allChecked);
renderTable();
}
function deleteSelected() {
devices = devices.filter(d => !d.selected);
renderTable();
}
function sortData(key) {
if (currentSortColumn === key) sortAscending = !sortAscending;
else { currentSortColumn = key; sortAscending = true; }
devices.sort((a, b) => {
let valA = key === 'ip' ? ipToNumber(a[key]) : a[key].toLowerCase();
let valB = key === 'ip' ? ipToNumber(b[key]) : b[key].toLowerCase();
return sortAscending ? (valA < valB ? -1 : 1) : (valA > valB ? -1 : 1);
});
renderTable();
}
function exportToCSV() {
let csvContent = "data:text/csv;charset=utf-8,Hostname,IP,MAC,Type\n" + devices.map(d => `${d.hostname},${d.ip},${d.mac},${d.type}`).join("\n");
const link = document.createElement("a");
link.setAttribute("href", encodeURI(csvContent));
link.setAttribute("download", "network_audit.csv");
link.click();
}
function importCSV(event) {
const file = event.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
e.target.result.split('\n').slice(1).forEach(line => {
const [hostname, ip, mac, type] = line.split(',');
if (hostname) devices.push({ id: Date.now() + Math.random(), hostname: hostname.trim(), ip: ip.trim(), mac: mac.trim(), type: type.trim(), isEditing: false, selected: false });
});
renderTable();
};
reader.readAsText(file);
}
document.getElementById('addForm').addEventListener('submit', (e) => {
e.preventDefault();
devices.push({ id: Date.now(), hostname: document.getElementById('hostname').value, ip: document.getElementById('ip').value, mac: document.getElementById('mac').value.toUpperCase(), type: document.getElementById('type').value, isEditing: false, selected: false });
renderTable();
document.getElementById('addForm').reset();
});
renderTable();
</script>
</body>
</html>