mirror of
https://github.com/skidoodle/iphistory.git
synced 2025-02-15 08:29:16 +01:00
chore: better json and ui
This commit is contained in:
parent
55cef1457c
commit
da96cec13e
4 changed files with 66 additions and 31 deletions
28
main.go
28
main.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>« Prev</button>
|
<button onclick="prevPage()" id="prevBtn" disabled>« Prev</button>
|
||||||
<button onclick="nextPage()" id="nextBtn" disabled>Next »</button>
|
<button onclick="nextPage()" id="nextBtn" disabled>Next »</button>
|
||||||
</div>
|
</div>
|
||||||
<script src="script.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue