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
This commit is contained in:
1 parent
10824ab851
commit
a201d5d7ac
34 files changed
+9453
No files matched your search
@@ -0,0 +1,174 @@
|
||||
<!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>
|
||||
Reference in new issue
Block a user