mirror of
https://github.com/skidoodle/iphistory.git
synced 2025-02-15 08:29:16 +01:00
chore(web): improve ui
This commit is contained in:
parent
9a29dca3b7
commit
f16459af29
3 changed files with 222 additions and 137 deletions
|
@ -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>« Prev</button>
|
<button id="prevBtn" disabled>« Prev</button>
|
||||||
<button onclick="nextPage()" id="nextBtn" disabled>Next »</button>
|
<button id="nextBtn" disabled>Next »</button>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
const searchBar = document.getElementById('searchBar');
|
||||||
|
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'));
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#searchBar').on('input', function() {
|
|
||||||
const query = $(this).val().toLowerCase();
|
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);
|
||||||
}
|
}
|
||||||
|
|
219
web/style.css
219
web/style.css
|
@ -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;
|
|
||||||
}
|
}
|
||||||
table {
|
|
||||||
font-size: 14px;
|
/* Responsive Design */
|
||||||
}
|
@media (max-width: 768px) {
|
||||||
.export, .pagination button {
|
h1 {
|
||||||
padding: 8px 12px;
|
font-size: 2rem;
|
||||||
}
|
|
||||||
.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 {
|
|
||||||
|
th, td {
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
h1 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-left: 0;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue