chore(web): improve ui

This commit is contained in:
skidoodle 2024-09-05 16:56:08 +02:00
parent 9a29dca3b7
commit f16459af29
3 changed files with 222 additions and 137 deletions

View file

@ -5,28 +5,27 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>IP History</title> <title>IP History</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script defer src="script.js"></script>
<script src="script.js"></script>
</head> </head>
<body> <body>
<h1><a href="/">IP History</a></h1> <h1><a href="/">IP History</a></h1>
<div class="controls"> <div class="controls">
<button class="export" onclick="exportToCSV()">Export to CSV</button> <button class="export" id="exportBtn">Export to CSV</button>
<input type="text" id="searchBar" class="search-bar" placeholder="Search IP history..." autocomplete="off"> <input type="text" id="searchBar" class="search-bar" placeholder="Search IP history..." autocomplete="off">
</div> </div>
<table id="ipTable"> <table id="ipTable">
<thead> <thead>
<tr> <tr>
<th onclick="sortTable('timestamp')">Timestamp</th> <th id="sortTimestamp">Timestamp</th>
<th onclick="sortTable('ipv4')">IPv4 Address</th> <th id="sortIPv4">IPv4 Address</th>
<th onclick="sortTable('ipv6')">IPv6 Address</th> <th id="sortIPv6">IPv6 Address</th>
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody></tbody>
</table> </table>
<div class="pagination" id="pagination"> <div class="pagination" id="pagination">
<button onclick="prevPage()" id="prevBtn" disabled>&laquo; Prev</button> <button id="prevBtn" disabled>&laquo; Prev</button>
<button onclick="nextPage()" id="nextBtn" disabled>Next &raquo;</button> <button id="nextBtn" disabled>Next &raquo;</button>
</div> </div>
</body> </body>
</html> </html>

View file

@ -1,20 +1,33 @@
let ipHistory = [], let ipHistory = [];
filteredHistory = [], let filteredHistory = [];
currentPage = 1, let currentPage = 1;
entriesPerPage = 10; let entriesPerPage = 10;
$(document).ready(function() { document.addEventListener('DOMContentLoaded', () => {
$.getJSON('/history', function(data) { fetch('/history')
.then(response => response.json())
.then(data => {
ipHistory = data; ipHistory = data;
filteredHistory = ipHistory; filteredHistory = ipHistory;
displayTable(); displayTable();
updateTotalIPs(); updateTotalIPs();
}).fail(function() { })
console.error('Error fetching IP history'); .catch(error => console.error('Error fetching IP history:', error));
});
$('#searchBar').on('input', function() { const searchBar = document.getElementById('searchBar');
const query = $(this).val().toLowerCase(); searchBar.addEventListener('input', handleSearch);
document.getElementById('prevBtn').addEventListener('click', prevPage);
document.getElementById('nextBtn').addEventListener('click', nextPage);
document.getElementById('exportBtn').addEventListener('click', exportToCSV);
document.getElementById('sortTimestamp').addEventListener('click', () => sortTable('timestamp'));
document.getElementById('sortIPv4').addEventListener('click', () => sortTable('ipv4'));
document.getElementById('sortIPv6').addEventListener('click', () => sortTable('ipv6'));
});
function handleSearch() {
const query = this.value.toLowerCase();
filteredHistory = ipHistory.filter(entry => filteredHistory = ipHistory.filter(entry =>
entry.timestamp.toLowerCase().includes(query) || entry.timestamp.toLowerCase().includes(query) ||
(entry.ipv4 && entry.ipv4.toLowerCase().includes(query)) || (entry.ipv4 && entry.ipv4.toLowerCase().includes(query)) ||
@ -23,21 +36,24 @@ $(document).ready(function() {
currentPage = 1; currentPage = 1;
displayTable(); displayTable();
updateTotalIPs(); updateTotalIPs();
}); }
});
function displayTable() { function displayTable() {
const tbody = $('#ipTable tbody').empty(); const tbody = document.querySelector('#ipTable tbody');
tbody.innerHTML = '';
const start = (currentPage - 1) * entriesPerPage; const start = (currentPage - 1) * entriesPerPage;
const end = start + entriesPerPage; const end = start + entriesPerPage;
const currentEntries = filteredHistory.slice(start, end); const currentEntries = filteredHistory.slice(start, end);
currentEntries.forEach(entry => { currentEntries.forEach(entry => {
const tr = $('<tr>'); const row = document.createElement('tr');
tr.append($('<td>').text(entry.timestamp)); row.innerHTML = `
tr.append($('<td>').text(entry.ipv4 || 'N/A').css('min-width', '120px')); // Set static width <td>${entry.timestamp}</td>
tr.append($('<td>').text(entry.ipv6 || 'N/A').css('min-width', '180px')); // Set static width <td>${entry.ipv4 || 'N/A'}</td>
tbody.append(tr); <td>${entry.ipv6 || 'N/A'}</td>
`;
tbody.appendChild(row);
}); });
updatePaginationButtons(); updatePaginationButtons();
updateTotalIPs(); updateTotalIPs();
@ -45,12 +61,15 @@ function displayTable() {
function updateTotalIPs() { function updateTotalIPs() {
const totalIPs = filteredHistory.length; const totalIPs = filteredHistory.length;
$('#searchBar').attr('placeholder', `Search IP history... (Total IPs: ${totalIPs})`); const searchBar = document.getElementById('searchBar');
searchBar.placeholder = `Search IP history... (Total IPs: ${totalIPs})`;
} }
function updatePaginationButtons() { function updatePaginationButtons() {
$('#prevBtn').prop('disabled', currentPage === 1); const prevBtn = document.getElementById('prevBtn');
$('#nextBtn').prop('disabled', currentPage === Math.ceil(filteredHistory.length / entriesPerPage)); const nextBtn = document.getElementById('nextBtn');
prevBtn.disabled = currentPage === 1;
nextBtn.disabled = currentPage === Math.ceil(filteredHistory.length / entriesPerPage);
} }
function nextPage() { function nextPage() {
@ -68,18 +87,12 @@ function prevPage() {
} }
function sortTable(field) { function sortTable(field) {
const isAscending = $(`#ipTable th:contains(${field.charAt(0).toUpperCase() + field.slice(1)})`).hasClass('sort-asc'); const isAscending = document.getElementById(`sort${capitalizeFirstLetter(field)}`).classList.contains('sort-asc');
const orderModifier = isAscending ? 1 : -1; filteredHistory.sort((a, b) => (a[field] && b[field]) ? a[field].localeCompare(b[field]) * (isAscending ? 1 : -1) : 0);
filteredHistory.sort((a, b) => { // Toggle the sort direction class
if (a[field] && b[field]) { document.querySelectorAll('th').forEach(th => th.classList.remove('sort-asc', 'sort-desc'));
return (a[field].localeCompare(b[field])) * orderModifier; document.getElementById(`sort${capitalizeFirstLetter(field)}`).classList.add(isAscending ? 'sort-desc' : 'sort-asc');
}
return 0;
});
$('#ipTable th').removeClass('sort-asc sort-desc');
$(`#ipTable th:contains(${field.charAt(0).toUpperCase() + field.slice(1)})`).addClass(isAscending ? 'sort-desc' : 'sort-asc');
displayTable(); displayTable();
} }
@ -89,14 +102,14 @@ function exportToCSV() {
['Timestamp', 'IPv4 Address', 'IPv6 Address'], ['Timestamp', 'IPv4 Address', 'IPv6 Address'],
...filteredHistory.map(entry => [entry.timestamp, entry.ipv4 || '', entry.ipv6 || '']) ...filteredHistory.map(entry => [entry.timestamp, entry.ipv4 || '', entry.ipv6 || ''])
]; ];
const csvContent = "data:text/csv;charset=utf-8," + rows.map(e => e.join(",")).join("\n"); const csvContent = rows.map(row => row.join(',')).join('\n');
const encodedUri = encodeURI(csvContent); const blob = new Blob([csvContent], { type: 'text/csv' });
const link = $('<a>').attr({ const link = document.createElement('a');
href: encodedUri, link.href = URL.createObjectURL(blob);
download: 'ip_history.csv' link.download = 'ip_history.csv';
}); link.click();
}
$('body').append(link);
link[0].click(); function capitalizeFirstLetter(string) {
link.remove(); return string.charAt(0).toUpperCase() + string.slice(1);
} }

View file

@ -1,148 +1,221 @@
/* General */
* { * {
-webkit-tap-highlight-color: transparent; margin: 0;
-webkit-touch-callout: none; padding: 0;
touch-action: manipulation; box-sizing: border-box;
font-family: 'Poppins', Arial, sans-serif;
transition: all 0.3s ease;
} }
body { body {
font-family: Arial, sans-serif; background-color: #121212;
background-color: #222; color: #e0e0e0;
color: #fff;
margin: 0;
padding: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 20px;
min-height: 100vh;
} }
/* Typography */
h1 { h1 {
text-align: center; font-size: 2.5rem;
margin: 20px 0; margin-bottom: 20px;
}
h1 a {
color: #007bff; color: #007bff;
text-align: center;
font-weight: 700;
}
h1 a {
color: inherit;
text-decoration: none; text-decoration: none;
} }
h1 a:hover { h1 a:hover {
text-decoration: underline; color: #0056b3;
} }
/* Controls */
.controls { .controls {
width: 90%; width: 100%;
max-width: 800px; max-width: 900px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin: 20px 0; margin-bottom: 20px;
} flex-wrap: wrap;
.export, .search-bar {
padding: 10px 20px;
border-radius: 5px;
border: 1px solid #444;
font-size: 16px;
line-height: 24px;
} }
.export { .export {
background-color: #007bff; background-color: #007bff;
border: 1px solid #007bff;
color: #fff; color: #fff;
padding: 12px 24px;
border-radius: 5px;
font-size: 16px;
cursor: pointer; cursor: pointer;
flex-shrink: 0; flex: 0 1 30%;
text-align: center; text-align: center;
border: none; transition: background-color 0.2s;
} }
.export:hover { .export:hover {
background-color: #0056b3; background-color: #0056b3;
border-color: #0056b3;
} }
.search-bar { .search-bar {
flex-grow: 1; flex: 0 1 65%;
margin-left: 20px; padding: 12px;
font-size: 16px;
border-radius: 5px;
background-color: #333; background-color: #333;
color: #fff; color: #e0e0e0;
border-color: #444; border: 1px solid #444;
} }
.search-bar:focus { .search-bar:focus {
outline: none; outline: none;
border-color: #007bff; border-color: #007bff;
} }
/* Table */
table { table {
width: 90%; width: 100%;
max-width: 1200px; max-width: 900px;
border-collapse: collapse; border-collapse: collapse;
margin: 0 auto 20px; margin: 20px auto;
background-color: #1e1e1e;
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
background-color: #333;
table-layout: fixed;
} }
th, td { th, td {
border: 1px solid #444; padding: 16px 20px;
padding: 12px;
text-align: left; text-align: left;
word-wrap: break-word; border-bottom: 1px solid #333;
overflow-wrap: break-word; font-size: 16px;
word-break: break-all;
} }
th { th {
cursor: pointer;
background-color: #007bff; background-color: #007bff;
color: #fff; color: #fff;
border: none; font-weight: 600;
} cursor: pointer;
th.sort-asc::after { position: relative;
content: " (newest first)";
color: #fff;
} }
th.sort-asc::after,
th.sort-desc::after { th.sort-desc::after {
content: " (oldest first)"; content: " ▼";
font-size: 12px;
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
color: #fff; color: #fff;
} }
tbody tr:nth-child(even) {
background-color: #444; th.sort-asc::after {
content: " ▲";
} }
tr:nth-child(even) {
background-color: #292929;
}
tr:hover {
background-color: #333;
transition: background-color 0.2s ease;
}
/* Pagination */
.pagination { .pagination {
margin: 20px; display: flex;
justify-content: center;
margin: 20px 0;
} }
.pagination button { .pagination button {
background-color: #007bff; background-color: #007bff;
color: #fff; color: #fff;
border: none; border: none;
padding: 8px 16px; padding: 10px 20px;
cursor: pointer;
border-radius: 5px;
margin: 0 5px; margin: 0 5px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
} }
.pagination button:hover {
background-color: #0056b3;
}
.pagination button:disabled { .pagination button:disabled {
background-color: #555; background-color: #555;
cursor: not-allowed; cursor: not-allowed;
} }
@media (max-width: 600px) { .pagination button:hover:not(:disabled) {
th, td { background-color: #0056b3;
padding: 8px; }
font-size: 14px;
} /* Responsive Design */
table { @media (max-width: 768px) {
font-size: 14px; h1 {
} font-size: 2rem;
.export, .pagination button {
padding: 8px 12px;
}
.search-bar {
margin-left: 10px;
padding: 8px;
font-size: 14px;
} }
.controls { .controls {
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: center;
} }
.export {
.export, .search-bar {
width: 100%; width: 100%;
margin-bottom: 10px; margin-bottom: 10px;
} }
.search-bar {
width: 100%; th, td {
margin-left: 0; padding: 10px;
font-size: 14px;
}
table {
font-size: 14px;
}
}
@media (max-width: 480px) {
h1 {
font-size: 1.8rem;
}
.controls {
width: 100%;
text-align: center;
}
.export, .search-bar {
width: 100%;
font-size: 14px;
padding: 10px;
margin-bottom: 10px;
}
table {
font-size: 12px;
}
th, td {
font-size: 12px;
padding: 8px 10px;
}
th, td {
word-wrap: break-word;
word-break: break-all;
}
.pagination button {
padding: 8px 12px;
} }
} }