diff --git a/genhistory.py b/genhistory.py index f50fe5b..830c29b 100644 --- a/genhistory.py +++ b/genhistory.py @@ -1,27 +1,60 @@ import random -from datetime import datetime, timedelta +import json +from datetime import datetime +import os import sys -def generate_example_data(num_entries): - data = [] - for _ in range(num_entries): - timestamp = datetime.now() - timedelta(days=random.randint(0, 365), - hours=random.randint(0, 23), - minutes=random.randint(0, 59), - seconds=random.randint(0, 59)) - ip_address = f"{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}" - entry = f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} CEST - {ip_address}" - data.append(entry) - return data +IP_HISTORY_FILE = 'history.json' + +def generate_ipv4(): + return '.'.join(str(random.randint(0, 255)) for _ in range(4)) + +def generate_ipv6(): + return ':'.join('{:x}'.format(random.randint(0, 65535)) for _ in range(8)) + +def generate_timestamp(): + return datetime.now().strftime("%Y-%m-%d %H:%M:%S %Z") + +def generate_ip_record(): + return { + "timestamp": generate_timestamp(), + "ipv4": generate_ipv4(), + "ipv6": generate_ipv6() + } + +def load_ip_history(): + if os.path.exists(IP_HISTORY_FILE): + with open(IP_HISTORY_FILE, 'r') as file: + try: + return json.load(file) + except json.JSONDecodeError: + return [] + return [] + +def save_ip_history(ip_history): + with open(IP_HISTORY_FILE, 'w') as file: + json.dump(ip_history, file, indent=2) + +def generate_and_save_ip_records(num_records=1): + ip_history = load_ip_history() + new_records = [generate_ip_record() for _ in range(num_records)] + ip_history = new_records + ip_history # Append new records at the beginning + save_ip_history(ip_history) if __name__ == "__main__": if len(sys.argv) != 2: - print("Usage: python genhistory.py ") + print("Usage: python genhistory.py ") sys.exit(1) - num_entries = int(sys.argv[1]) - example_data = generate_example_data(num_entries) + try: + num_records = int(sys.argv[1]) + if num_records <= 0: + raise ValueError("Number of records must be a positive integer.") - with open('ip_history.txt', 'w') as file: - for entry in example_data: - file.write(entry + '\n') + generate_and_save_ip_records(num_records) + print(f"Successfully generated {num_records} IP records.") + + except ValueError as e: + print(f"Error: {e}") + print("Please provide a valid positive integer for the number of records.") + sys.exit(1) diff --git a/history.json b/history.json new file mode 100644 index 0000000..5127764 --- /dev/null +++ b/history.json @@ -0,0 +1,7 @@ +[ + { + "timestamp": "2024-08-24 01:00:44", + "ipv4": "78.131.51.69", + "ipv6": "2a01:36d:3200:441c:8120:3ab4:653d:58f4" + } +] diff --git a/main.go b/main.go index 4687feb..2c84664 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "bufio" "encoding/json" "fmt" "io" @@ -12,8 +11,9 @@ import ( ) const ( - traceURL = "https://one.one.one.one/cdn-cgi/trace" - ipHistoryPath = "ip_history.txt" + ipv4URL = "https://4.ident.me/" + ipv6URL = "https://6.ident.me/" + ipHistoryPath = "history.json" checkInterval = 30 * time.Second maxRetries = 3 retryDelay = 10 * time.Second @@ -21,26 +21,41 @@ const ( type IPRecord struct { Timestamp string `json:"timestamp"` - IPAddress string `json:"ip_address"` + IPv4 string `json:"ipv4"` + IPv6 string `json:"ipv6"` } -func getPublicIP() (string, error) { - var ip string +func getPublicIPs() (string, string, error) { + var ipv4, ipv6 string var err error for attempt := 1; attempt <= maxRetries; attempt++ { - ip, err = fetchIP() + ipv4, ipv6, err = fetchIPs() if err == nil { - return ip, nil + return ipv4, ipv6, nil } - fmt.Printf("Attempt %d: Error fetching IP: %v\n", attempt, err) + fmt.Printf("Attempt %d: Error fetching IPs: %v\n", attempt, err) time.Sleep(retryDelay) } - return "", err + return "", "", err } -func fetchIP() (string, error) { - resp, err := http.Get(traceURL) +func fetchIPs() (string, string, error) { + ipv4, err := fetchIP(ipv4URL) + if err != nil { + return "", "", err + } + + ipv6, err := fetchIP(ipv6URL) + if err != nil { + fmt.Println("Warning: Could not fetch IPv6 address:", err) + } + + return ipv4, ipv6, nil +} + +func fetchIP(url string) (string, error) { + resp, err := http.Get(url) if err != nil { return "", err } @@ -51,12 +66,7 @@ func fetchIP() (string, error) { return "", err } - for _, line := range strings.Split(string(body), "\n") { - if strings.HasPrefix(line, "ip=") { - return strings.TrimPrefix(line, "ip="), nil - } - } - return "", fmt.Errorf("IP address not found") + return strings.TrimSpace(string(body)), nil } func readHistory() ([]IPRecord, error) { @@ -68,19 +78,22 @@ func readHistory() ([]IPRecord, error) { } defer file.Close() - var history []IPRecord - scanner := bufio.NewScanner(file) - for scanner.Scan() { - parts := strings.Split(scanner.Text(), " - ") - if len(parts) == 2 { - record := IPRecord{ - Timestamp: parts[0], - IPAddress: parts[1], - } - history = append(history, record) - } + fileInfo, err := file.Stat() + if err != nil { + return nil, err } - return history, scanner.Err() + + // Check if the file is empty + if fileInfo.Size() == 0 { + return []IPRecord{}, nil + } + + var history []IPRecord + err = json.NewDecoder(file).Decode(&history) + if err != nil { + return nil, err + } + return history, nil } func writeHistory(history []IPRecord) error { @@ -90,42 +103,54 @@ func writeHistory(history []IPRecord) error { } defer file.Close() - writer := bufio.NewWriter(file) - for _, entry := range history { - _, err := writer.WriteString(fmt.Sprintf("%s - %s\n", entry.Timestamp, entry.IPAddress)) - if err != nil { - return err - } + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") // Pretty-print with indentation + err = encoder.Encode(history) + if err != nil { + return err } - return writer.Flush() + return nil } -func trackIP(ipChan <-chan string) { - var lastLoggedIP string - for ip := range ipChan { +func trackIP(ipChan <-chan IPRecord) { + var lastLoggedIP IPRecord + hasNotifiedUpToDate := false + + for ipRecord := range ipChan { history, err := readHistory() if err != nil { fmt.Println("Error reading IP history:", err) continue } - entry := fmt.Sprintf("%s - %s", time.Now().Format("2006-01-02 15:04:05 MST"), ip) - if len(history) == 0 || !strings.Contains(history[0].IPAddress, ip) { - history = append([]IPRecord{{Timestamp: time.Now().Format("2006-01-02 15:04:05 MST"), IPAddress: ip}}, history...) - if err := writeHistory(history); err != nil { - fmt.Println("Error writing IP history:", err) - } - fmt.Printf("📄 New IP logged: %s\n", entry) - lastLoggedIP = ip - } else { - if lastLoggedIP != ip { - fmt.Println("🤷 IP is already up to date") - lastLoggedIP = ip - } + if len(history) > 0 { + lastLoggedIP = history[0] } + + // Check if IPs are the same as the last logged one + if ipRecord.IPv4 == lastLoggedIP.IPv4 && ipRecord.IPv6 == lastLoggedIP.IPv6 { + if !hasNotifiedUpToDate { + fmt.Println("🤷 IPs are already up to date") + hasNotifiedUpToDate = true + } + continue + } + + // Reset the notification flag when IP changes + hasNotifiedUpToDate = false + + ipRecord.Timestamp = time.Now().Format("2006-01-02 15:04:05") + history = append([]IPRecord{ipRecord}, history...) + if err := writeHistory(history); err != nil { + fmt.Println("Error writing IP history:", err) + continue + } + + fmt.Printf("📄 New IP logged: {Timestamp: %s, IPv4: %s, IPv6: %s}\n", ipRecord.Timestamp, ipRecord.IPv4, ipRecord.IPv6) } } + func serveWeb() { http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("web")))) @@ -151,15 +176,15 @@ func serveWeb() { } func main() { - ipChan := make(chan string) + ipChan := make(chan IPRecord) go func() { for { - ip, err := getPublicIP() + ipv4, ipv6, err := getPublicIPs() if err != nil { - fmt.Println("Error fetching public IP:", err) + fmt.Println("Error fetching public IPs:", err) } else { - ipChan <- ip + ipChan <- IPRecord{IPv4: ipv4, IPv6: ipv6} } time.Sleep(checkInterval) } diff --git a/web/index.html b/web/index.html index ef5c38f..e05f57b 100644 --- a/web/index.html +++ b/web/index.html @@ -2,12 +2,11 @@ - + IP History -

IP History

@@ -18,8 +17,9 @@ - - + + + diff --git a/web/script.js b/web/script.js index 58d6fa5..d943438 100644 --- a/web/script.js +++ b/web/script.js @@ -15,7 +15,11 @@ $(document).ready(function() { $('#searchBar').on('input', function() { const query = $(this).val().toLowerCase(); - filteredHistory = ipHistory.filter(entry => entry.timestamp.toLowerCase().includes(query) || entry.ip_address.toLowerCase().includes(query)); + filteredHistory = ipHistory.filter(entry => + entry.timestamp.toLowerCase().includes(query) || + (entry.ipv4 && entry.ipv4.toLowerCase().includes(query)) || + (entry.ipv6 && entry.ipv6.toLowerCase().includes(query)) + ); currentPage = 1; displayTable(); updateTotalIPs(); @@ -31,7 +35,8 @@ function displayTable() { currentEntries.forEach(entry => { const tr = $(''); tr.append($('
TimestampIP AddressTimestampIPv4 AddressIPv6 Address
').text(entry.timestamp)); - tr.append($('').text(entry.ip_address)); + tr.append($('').text(entry.ipv4 || 'N/A').css('min-width', '120px')); // Set static width + tr.append($('').text(entry.ipv6 || 'N/A').css('min-width', '180px')); // Set static width tbody.append(tr); }); updatePaginationButtons(); @@ -62,21 +67,27 @@ function prevPage() { } } -function sortTable() { - const isAscending = $('#ipTable th').eq(0).hasClass('sort-asc'); +function sortTable(field) { + const isAscending = $(`#ipTable th:contains(${field.charAt(0).toUpperCase() + field.slice(1)})`).hasClass('sort-asc'); const orderModifier = isAscending ? 1 : -1; - filteredHistory.sort((a, b) => (a.timestamp.localeCompare(b.timestamp)) * orderModifier); + filteredHistory.sort((a, b) => { + if (a[field] && b[field]) { + return (a[field].localeCompare(b[field])) * orderModifier; + } + return 0; + }); $('#ipTable th').removeClass('sort-asc sort-desc'); - $('#ipTable th').eq(0).addClass(isAscending ? 'sort-desc' : 'sort-asc'); + $(`#ipTable th:contains(${field.charAt(0).toUpperCase() + field.slice(1)})`).addClass(isAscending ? 'sort-desc' : 'sort-asc'); displayTable(); } function exportToCSV() { const rows = [ - ['Timestamp', 'IP Address'], ...filteredHistory.map(entry => [entry.timestamp, entry.ip_address]) + ['Timestamp', 'IPv4 Address', 'IPv6 Address'], + ...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 encodedUri = encodeURI(csvContent); diff --git a/web/style.css b/web/style.css index f079966..44e45e9 100644 --- a/web/style.css +++ b/web/style.css @@ -1,3 +1,9 @@ +* { + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; + touch-action: manipulation; +} + body { font-family: Arial, sans-serif; background-color: #222; @@ -58,18 +64,21 @@ h1 a:hover { } table { width: 90%; - max-width: 800px; + max-width: 1200px; 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; + table-layout: fixed; } th, td { border: 1px solid #444; padding: 12px; text-align: left; + word-wrap: break-word; + overflow-wrap: break-word; } th { cursor: pointer; @@ -107,9 +116,14 @@ tbody tr:nth-child(even) { background-color: #555; cursor: not-allowed; } + @media (max-width: 600px) { th, td { padding: 8px; + font-size: 14px; + } + table { + font-size: 14px; } .export, .pagination button { padding: 8px 12px; @@ -119,4 +133,16 @@ tbody tr:nth-child(even) { padding: 8px; font-size: 14px; } + .controls { + flex-direction: column; + align-items: stretch; + } + .export { + width: 100%; + margin-bottom: 10px; + } + .search-bar { + width: 100%; + margin-left: 0; + } }