Files
iphistory/ui.templ
T
2026-01-11 06:35:23 +01:00

181 lines
4.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"fmt"
"time"
)
func pathBuilder(page int, query string) string {
base := "/"
if page > 1 {
base = fmt.Sprintf("/p/%d", page)
}
if query != "" {
return fmt.Sprintf("%s?q=%s", base, query)
}
return base
}
func humanize(t time.Time) string {
duration := time.Since(t)
switch {
case duration < time.Minute:
return "just now"
case duration < time.Hour:
return fmt.Sprintf("%dm ago", int(duration.Minutes()))
case duration < time.Hour*24:
return fmt.Sprintf("%dh ago", int(duration.Hours()))
default:
return t.Format("Jan 02")
}
}
templ navLink(text string, page int, query string, active bool) {
if active {
<a
href={ templ.SafeURL(pathBuilder(page, query)) }
hx-get={ pathBuilder(page, query) }
hx-target="#main-content"
hx-push-url="true"
class="nav-link"
>{ text }</a>
} else {
<a class="nav-link disabled">{ text }</a>
}
}
templ MainContent(records []Record, query string, page int, hasMore bool) {
<title>
if query != "" {
IP History - { query }
} else if page > 1 {
IP History - Page { fmt.Sprint(page) }
} else {
IP History
}
</title>
<div id="main-content">
if query != "" {
<div class="search-meta">
<span>Showing results for "<strong>{ query }</strong>"</span>
<a
href="/"
hx-get="/"
hx-target="#main-content"
hx-push-url="true"
hx-on:click="document.querySelector('.search-input').value = ''"
>Clear Search</a>
</div>
}
if page == 1 && query == "" && len(records) > 0 {
<section class="current-ip-card">
<div>Current Public IP</div>
<div class="ip-display" onclick="copyToClipboard(this.innerText, this)">{ records[0].IP }</div>
<div>{ records[0].Timestamp.Format("Jan 02, 15:04:05 MST") } ({ humanize(records[0].Timestamp) })</div>
</section>
}
if len(records) > 0 {
<table class="history-table">
<thead>
<tr>
<th scope="col">Timestamp</th>
<th scope="col">IP Address</th>
</tr>
</thead>
<tbody>
for _, r := range records {
<tr>
<td>
{ r.Timestamp.Format("2006-01-02 15:04:05") }
<span class="time-relative">{ humanize(r.Timestamp) }</span>
</td>
<td class="copyable" onclick="copyToClipboard(this.innerText, this)">{ r.IP }</td>
</tr>
}
</tbody>
</table>
} else {
<div class="empty-state">
<p>No IP found matching your search.</p>
if query != "" {
<button
type="button"
class="btn-ghost"
hx-get="/"
hx-target="#main-content"
hx-push-url="true"
hx-on:click="document.querySelector('.search-input').value = ''"
>Return to Home</button>
}
</div>
}
<div class="pagination">
@navLink("← Previous", page-1, query, page > 1)
<span>Page { fmt.Sprint(page) }</span>
@navLink("Next →", page+1, query, hasMore)
</div>
</div>
}
templ Page(records []Record, query string, page int, hasMore bool) {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>IP History</title>
<link rel="icon" href="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"/>
<script src="/assets/htmx.min.js" defer></script>
<link rel="stylesheet" type="text/css" href="/assets/style.css"/>
<script>
function copyToClipboard(text, el) {
navigator.clipboard.writeText(text);
el.classList.add("copied");
setTimeout(() => {
el.classList.remove("copied");
}, 1000);
}
document.addEventListener('keydown', function(e) {
if (e.key === '/' && document.activeElement.tagName !== 'INPUT') {
e.preventDefault();
document.querySelector('.search-input').focus();
}
});
</script>
</head>
<body>
<main class="container">
<header>
<a href="/" class="title-link"><h1>IP History</h1></a>
<p>A simple timeline of your public IP changes</p>
</header>
<form class="search-form" hx-get="/" hx-target="#main-content" hx-push-url="true" hx-sync="closest form:abort">
<div class="search-wrapper">
<input
type="text"
name="q"
class="search-input"
placeholder="Search IPs... (Press '/' to focus)"
value={ query }
autocomplete="off"
hx-get="/"
hx-trigger="keyup changed delay:300ms, search"
hx-target="#main-content"
/>
<button
type="button"
class="clear-input"
hx-get="/"
hx-target="#main-content"
hx-push-url="true"
onclick="this.previousElementSibling.value = ''; this.previousElementSibling.focus();"
>×</button>
</div>
</form>
@MainContent(records, query, page, hasMore)
</main>
</body>
</html>
}