diff --git a/Dockerfile b/Dockerfile index a4e436a..65ad5be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,6 @@ FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /app/iphistory . -COPY --from=builder /app/index.html . +COPY --from=builder /app/web ./web EXPOSE 8080 CMD ["./iphistory"] diff --git a/index.html b/index.html deleted file mode 100644 index 4669123..0000000 --- a/index.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - IP History - - - - -

IP History

- - - - - - - - -
TimestampIP Address
- - - - - - diff --git a/main.go b/main.go index aadae04..0e3086e 100644 --- a/main.go +++ b/main.go @@ -115,9 +115,7 @@ func trackIP(ipChan <-chan string) { } func serveWeb() { - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, "index.html") - }) + http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("web")))) http.HandleFunc("/history", func(w http.ResponseWriter, r *http.Request) { history, err := readHistory() diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..6a3b13d --- /dev/null +++ b/web/index.html @@ -0,0 +1,31 @@ + + + + + + IP History + + + + +

IP History

+
+ + +
+ + + + + + + + +
TimestampIP Address
+ + + + diff --git a/web/script.js b/web/script.js new file mode 100644 index 0000000..2e0f41b --- /dev/null +++ b/web/script.js @@ -0,0 +1,75 @@ +let ipHistory = [], filteredHistory = [], currentPage = 1, entriesPerPage = 10; + +$(document).ready(function() { + $.getJSON('/history', function(data) { + ipHistory = data.map(entry => entry.split(" - ")); + filteredHistory = ipHistory; + displayTable(); + }).fail(function() { + console.error('Error fetching IP history'); + }); + + $('#searchBar').on('input', function() { + const query = $(this).val().toLowerCase(); + filteredHistory = ipHistory.filter(row => row.some(cell => cell.toLowerCase().includes(query))); + currentPage = 1; + displayTable(); + }); +}); + +function displayTable() { + const tbody = $('#ipTable tbody').empty(); + const start = (currentPage - 1) * entriesPerPage; + const end = start + entriesPerPage; + const currentEntries = filteredHistory.slice(start, end); + + currentEntries.forEach(row => { + const tr = $(''); + row.forEach(cell => { + tr.append($('').text(cell)); + }); + tbody.append(tr); + }); + updatePaginationButtons(); +} + +function updatePaginationButtons() { + $('#prevBtn').prop('disabled', currentPage === 1); + $('#nextBtn').prop('disabled', currentPage === Math.ceil(filteredHistory.length / entriesPerPage)); +} + +function nextPage() { + if (currentPage < Math.ceil(filteredHistory.length / entriesPerPage)) { + currentPage++; + displayTable(); + } +} + +function prevPage() { + if (currentPage > 1) { + currentPage--; + displayTable(); + } +} + +function sortTable() { + const th = $('#ipTable th').eq(0); + const isAscending = !th.hasClass('sort-asc'); + const orderModifier = isAscending ? 1 : -1; + + filteredHistory.sort((a, b) => a[0].localeCompare(b[0]) * orderModifier); + th.toggleClass('sort-asc', isAscending).toggleClass('sort-desc', !isAscending); + + displayTable(); +} + +function exportToCSV() { + const rows = [['Timestamp', 'IP Address'], ...filteredHistory]; + const csvContent = "data:text/csv;charset=utf-8," + rows.map(e => e.join(",")).join("\n"); + const encodedUri = encodeURI(csvContent); + const link = $('').attr({href: encodedUri, download: 'ip_history.csv'}); + + $('body').append(link); + link[0].click(); + link.remove(); +} diff --git a/web/style.css b/web/style.css new file mode 100644 index 0000000..c5edc1f --- /dev/null +++ b/web/style.css @@ -0,0 +1,108 @@ +body { + font-family: Arial, sans-serif; + background-color: #222; + color: #fff; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + align-items: center; +} +h1 { + text-align: center; + margin: 20px 0; +} +.controls { + width: 90%; + max-width: 800px; + display: flex; + justify-content: space-between; + align-items: center; + margin: 20px 0; +} +.export { + background-color: #007bff; + color: #fff; + border: none; + padding: 10px 20px; + cursor: pointer; + border-radius: 5px; + flex-shrink: 0; +} +.export:hover { + background-color: #0056b3; +} +.search-bar { + flex-grow: 1; + margin-left: 20px; + padding: 10px; + border-radius: 5px; + border: 1px solid #444; + background-color: #333; + color: #fff; + font-size: 16px; + max-width: 100%; +} +table { + width: 90%; + max-width: 800px; + border-collapse: collapse; + margin: 0 auto 20px; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + background-color: #333; +} +th, td { + border: 1px solid #444; + padding: 12px; + text-align: left; +} +th { + cursor: pointer; + background-color: #007bff; + color: #fff; +} +th.sort-asc::after { + content: " \u2191"; + color: #fff; +} +th.sort-desc::after { + content: " \u2193"; + color: #fff; +} +tbody tr:nth-child(even) { + background-color: #444; +} +.pagination { + margin: 20px; +} +.pagination button { + background-color: #007bff; + color: #fff; + border: none; + padding: 8px 16px; + cursor: pointer; + border-radius: 5px; + margin: 0 5px; +} +.pagination button:hover { + background-color: #0056b3; +} +.pagination button:disabled { + background-color: #555; + cursor: not-allowed; +} +@media (max-width: 600px) { + th, td { + padding: 8px; + } + .export, .pagination button { + padding: 8px 12px; + } + .search-bar { + margin-left: 10px; + padding: 8px; + font-size: 14px; + } +}