chore: better json and ui

This commit is contained in:
skidoodle 2024-06-01 21:55:05 +02:00
parent 55cef1457c
commit da96cec13e
4 changed files with 66 additions and 31 deletions

28
main.go
View file

@ -19,6 +19,11 @@ const (
retryDelay = 10 * time.Second retryDelay = 10 * time.Second
) )
type IPRecord struct {
Timestamp string `json:"timestamp"`
IPAddress string `json:"ip_address"`
}
func getPublicIP() (string, error) { func getPublicIP() (string, error) {
var ip string var ip string
var err error var err error
@ -54,24 +59,31 @@ func fetchIP() (string, error) {
return "", fmt.Errorf("IP address not found") return "", fmt.Errorf("IP address not found")
} }
func readHistory() ([]string, error) { func readHistory() ([]IPRecord, error) {
file, err := os.Open(ipHistoryPath) file, err := os.Open(ipHistoryPath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
return []string{}, nil return []IPRecord{}, nil
} else if err != nil { } else if err != nil {
return nil, err return nil, err
} }
defer file.Close() defer file.Close()
var history []string var history []IPRecord
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
history = append(history, scanner.Text()) parts := strings.Split(scanner.Text(), " - ")
if len(parts) == 2 {
record := IPRecord{
Timestamp: parts[0],
IPAddress: parts[1],
}
history = append(history, record)
}
} }
return history, scanner.Err() return history, scanner.Err()
} }
func writeHistory(history []string) error { func writeHistory(history []IPRecord) error {
file, err := os.Create(ipHistoryPath) file, err := os.Create(ipHistoryPath)
if err != nil { if err != nil {
return err return err
@ -80,7 +92,7 @@ func writeHistory(history []string) error {
writer := bufio.NewWriter(file) writer := bufio.NewWriter(file)
for _, entry := range history { for _, entry := range history {
_, err := writer.WriteString(entry + "\n") _, err := writer.WriteString(fmt.Sprintf("%s - %s\n", entry.Timestamp, entry.IPAddress))
if err != nil { if err != nil {
return err return err
} }
@ -98,8 +110,8 @@ func trackIP(ipChan <-chan string) {
} }
entry := fmt.Sprintf("%s - %s", time.Now().Format("2006-01-02 15:04:05 MST"), ip) entry := fmt.Sprintf("%s - %s", time.Now().Format("2006-01-02 15:04:05 MST"), ip)
if len(history) == 0 || !strings.Contains(history[0], ip) { if len(history) == 0 || !strings.Contains(history[0].IPAddress, ip) {
history = append([]string{entry}, history...) history = append([]IPRecord{{Timestamp: time.Now().Format("2006-01-02 15:04:05 MST"), IPAddress: ip}}, history...)
if err := writeHistory(history); err != nil { if err := writeHistory(history); err != nil {
fmt.Println("Error writing IP history:", err) fmt.Println("Error writing IP history:", err)
} }

View file

@ -6,9 +6,11 @@
<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"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="script.js"></script>
<link rel="stylesheet" href="style.css">
</head> </head>
<body> <body>
<h1>IP History</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" onclick="exportToCSV()">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">
@ -26,6 +28,5 @@
<button onclick="prevPage()" id="prevBtn" disabled>&laquo; Prev</button> <button onclick="prevPage()" id="prevBtn" disabled>&laquo; Prev</button>
<button onclick="nextPage()" id="nextBtn" disabled>Next &raquo;</button> <button onclick="nextPage()" id="nextBtn" disabled>Next &raquo;</button>
</div> </div>
<script src="script.js"></script>
</body> </body>
</html> </html>

View file

@ -1,8 +1,11 @@
let ipHistory = [], filteredHistory = [], currentPage = 1, entriesPerPage = 10; let ipHistory = [],
filteredHistory = [],
currentPage = 1,
entriesPerPage = 10;
$(document).ready(function() { $(document).ready(function() {
$.getJSON('/history', function(data) { $.getJSON('/history', function(data) {
ipHistory = data.map(entry => entry.split(" - ")); ipHistory = data;
filteredHistory = ipHistory; filteredHistory = ipHistory;
displayTable(); displayTable();
}).fail(function() { }).fail(function() {
@ -11,7 +14,7 @@ $(document).ready(function() {
$('#searchBar').on('input', function() { $('#searchBar').on('input', function() {
const query = $(this).val().toLowerCase(); const query = $(this).val().toLowerCase();
filteredHistory = ipHistory.filter(row => row.some(cell => cell.toLowerCase().includes(query))); filteredHistory = ipHistory.filter(entry => entry.timestamp.toLowerCase().includes(query) || entry.ip_address.toLowerCase().includes(query));
currentPage = 1; currentPage = 1;
displayTable(); displayTable();
}); });
@ -23,11 +26,10 @@ function displayTable() {
const end = start + entriesPerPage; const end = start + entriesPerPage;
const currentEntries = filteredHistory.slice(start, end); const currentEntries = filteredHistory.slice(start, end);
currentEntries.forEach(row => { currentEntries.forEach(entry => {
const tr = $('<tr>'); const tr = $('<tr>');
row.forEach(cell => { tr.append($('<td>').text(entry.timestamp));
tr.append($('<td>').text(cell)); tr.append($('<td>').text(entry.ip_address));
});
tbody.append(tr); tbody.append(tr);
}); });
updatePaginationButtons(); updatePaginationButtons();
@ -53,21 +55,27 @@ function prevPage() {
} }
function sortTable() { function sortTable() {
const th = $('#ipTable th').eq(0); const isAscending = $('#ipTable th').eq(0).hasClass('sort-asc');
const isAscending = !th.hasClass('sort-asc');
const orderModifier = isAscending ? 1 : -1; const orderModifier = isAscending ? 1 : -1;
filteredHistory.sort((a, b) => a[0].localeCompare(b[0]) * orderModifier); filteredHistory.sort((a, b) => (a.timestamp.localeCompare(b.timestamp)) * orderModifier);
th.toggleClass('sort-asc', isAscending).toggleClass('sort-desc', !isAscending);
$('#ipTable th').removeClass('sort-asc sort-desc');
$('#ipTable th').eq(0).addClass(isAscending ? 'sort-desc' : 'sort-asc');
displayTable(); displayTable();
} }
function exportToCSV() { function exportToCSV() {
const rows = [['Timestamp', 'IP Address'], ...filteredHistory]; const rows = [
['Timestamp', 'IP Address'], ...filteredHistory.map(entry => [entry.timestamp, entry.ip_address])
];
const csvContent = "data:text/csv;charset=utf-8," + rows.map(e => e.join(",")).join("\n"); const csvContent = "data:text/csv;charset=utf-8," + rows.map(e => e.join(",")).join("\n");
const encodedUri = encodeURI(csvContent); const encodedUri = encodeURI(csvContent);
const link = $('<a>').attr({href: encodedUri, download: 'ip_history.csv'}); const link = $('<a>').attr({
href: encodedUri,
download: 'ip_history.csv'
});
$('body').append(link); $('body').append(link);
link[0].click(); link[0].click();

View file

@ -12,6 +12,13 @@ h1 {
text-align: center; text-align: center;
margin: 20px 0; margin: 20px 0;
} }
h1 a {
color: #007bff;
text-decoration: none;
}
h1 a:hover {
text-decoration: underline;
}
.controls { .controls {
width: 90%; width: 90%;
max-width: 800px; max-width: 800px;
@ -20,14 +27,20 @@ h1 {
align-items: center; align-items: center;
margin: 20px 0; margin: 20px 0;
} }
.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;
color: #fff; color: #fff;
border: none;
padding: 10px 20px;
cursor: pointer; cursor: pointer;
border-radius: 5px;
flex-shrink: 0; flex-shrink: 0;
text-align: center;
border: none;
} }
.export:hover { .export:hover {
background-color: #0056b3; background-color: #0056b3;
@ -35,13 +48,13 @@ h1 {
.search-bar { .search-bar {
flex-grow: 1; flex-grow: 1;
margin-left: 20px; margin-left: 20px;
padding: 10px;
border-radius: 5px;
border: 1px solid #444;
background-color: #333; background-color: #333;
color: #fff; color: #fff;
font-size: 16px; border-color: #444;
max-width: 100%; }
.search-bar:focus {
outline: none;
border-color: #007bff;
} }
table { table {
width: 90%; width: 90%;
@ -62,6 +75,7 @@ th {
cursor: pointer; cursor: pointer;
background-color: #007bff; background-color: #007bff;
color: #fff; color: #fff;
border: none;
} }
th.sort-asc::after { th.sort-asc::after {
content: " \u2191"; content: " \u2191";