Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/provider/providers/porkbun/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (p *Provider) HTML() models.HTMLRow {
return models.HTMLRow{
Domain: fmt.Sprintf("<a href=\"http://%s\">%s</a>", p.BuildDomainName(), p.BuildDomainName()),
Owner: p.Owner(),
Provider: "<a href=\"https://www.porkbun.com/\">Porkbun DNS</a>",
Provider: "<a href=\"https://www.porkbun.com/\">Porkbun</a>",
IPVersion: p.ipVersion.String(),
}
}
Expand Down
57 changes: 43 additions & 14 deletions internal/records/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,34 @@ func (r *Record) HTML(now time.Time) models.HTMLRow {
row := r.Provider.HTML()
message := r.Message
if r.Status == constants.UPTODATE {
message = "no IP change for " + r.History.GetDurationSinceSuccess(now)
}
if message != "" {
message = fmt.Sprintf("(%s)", message)
message = "No changes needed - IP stable for " + r.History.GetDurationSinceSuccess(now)
}
if r.Status == "" {
row.Status = NotAvailable
} else {
row.Status = fmt.Sprintf("%s %s, %s",
convertStatus(r.Status),
message,
time.Since(r.Time).Round(time.Second).String()+" ago")
timeSince := time.Since(r.Time).Round(time.Second)
var timeDisplay string
if timeSince < time.Minute {
timeDisplay = "Just now"
} else if timeSince < time.Hour {
timeDisplay = fmt.Sprintf("%d min ago", int(timeSince.Minutes()))
} else if timeSince < 24*time.Hour {
timeDisplay = fmt.Sprintf("%d hrs ago", int(timeSince.Hours()))
} else {
timeDisplay = fmt.Sprintf("%d days ago", int(timeSince.Hours()/24))
}

statusBadge := convertStatus(r.Status)
if message != "" {
statusBadge = convertStatusWithTooltip(r.Status, message)
}

statusHTML := fmt.Sprintf(`%s <span class="status-timestamp">%s</span>`, statusBadge, timeDisplay)
row.Status = statusHTML
}
currentIP := r.History.GetCurrentIP()
if currentIP.IsValid() {
row.CurrentIP = `<a href="https://ipinfo.io/` + currentIP.String() + `">` + currentIP.String() + "</a>"
row.CurrentIP = `<a href="https://ipinfo.io/` + currentIP.String() + `" class="ip-link" target="_blank" rel="noopener noreferrer">` + currentIP.String() + ` <svg class="ipinfo-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3m-2 16H5V5h7V3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7z" fill="currentColor"/></svg></a>`
} else {
row.CurrentIP = NotAvailable
}
Expand All @@ -53,15 +65,32 @@ func (r *Record) HTML(now time.Time) models.HTMLRow {
func convertStatus(status models.Status) string {
switch status {
case constants.SUCCESS:
return `<span class="success">Success</span>`
return `<span class="success">Updated</span>`
case constants.FAIL:
return `<span class="error">Failed</span>`
case constants.UPTODATE:
return `<span class="uptodate">Current</span>`
case constants.UPDATING:
return `<span class="updating">Syncing</span>`
case constants.UNSET:
return `<span class="unset">Pending</span>`
default:
return "Unknown status"
}
}

func convertStatusWithTooltip(status models.Status, message string) string {
switch status {
case constants.SUCCESS:
return fmt.Sprintf(`<span class="success" data-tooltip="%s">Updated</span>`, message)
case constants.FAIL:
return `<span class="error">Failure</span>`
return fmt.Sprintf(`<span class="error" data-tooltip="%s">Failed</span>`, message)
case constants.UPTODATE:
return `<span class="uptodate">Up to date</span>`
return fmt.Sprintf(`<span class="uptodate" data-tooltip="%s">Current</span>`, message)
case constants.UPDATING:
return `<span class="updating">Updating</span>`
return fmt.Sprintf(`<span class="updating" data-tooltip="%s">Syncing</span>`, message)
case constants.UNSET:
return `<span class="unset">Unset</span>`
return fmt.Sprintf(`<span class="unset" data-tooltip="%s">Pending</span>`, message)
default:
return "Unknown status"
}
Expand Down
4 changes: 3 additions & 1 deletion internal/server/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import (
"github.com/qdm12/ddns-updater/internal/models"
)

func (h *handlers) index(w http.ResponseWriter, _ *http.Request) {
func (h *handlers) index(w http.ResponseWriter, r *http.Request) {
var htmlData models.HTMLData
for _, record := range h.db.SelectAll() {
row := record.HTML(h.timeNow())
htmlData.Rows = append(htmlData.Rows, row)
}


err := h.indexTemplate.ExecuteTemplate(w, "index.html", htmlData)
if err != nil {
httpError(w, http.StatusInternalServerError, "failed generating webpage: "+err.Error())
Expand Down
159 changes: 119 additions & 40 deletions internal/server/ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,125 @@
</head>

<body>
<table role="table">
<thead>
<tr>
<th>Domain</th>
<th>Owner</th>
<th>Provider</th>
<th>IP Version</th>
<th>Update Status</th>
<th>Current IP</th>
<th>Previous IPs<small> (reverse chronological order)</small></th>
</tr>
</thead>
<tbody>
{{range .Rows}}
<tr>
<td data-label="Domain">{{.Domain}}</td>
<td data-label="Owner">{{.Owner}}</td>
<td data-label="Provider">{{.Provider}}</td>
<td data-label="IP Version">{{.IPVersion}}</td>
<td data-label="Update Status">{{.Status}}</td>
<td data-label="Current IP">{{.CurrentIP}}</td>
<td data-label="Previous IPs">{{.PreviousIPs}}</td>
</tr>
{{end}}
</tbody>
</table>
<footer>
<div>
<a href="https://github.com/qdm12/ddns-updater" class="text-big">
<svg class="github-icon" height="1em" aria-hidden="true" viewBox="0 0 16 16" version="1.1"
data-view-component="true">
<path
d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z">
</path>
</svg>
</a>
</div>
<div>by <a href="https://github.com/qdm12">Quentin McGaw</a> / UI reworked by <a
href="https://github.com/fuse314">Gottfried Mayer</a></div>
</footer>
<div class="container">
<header class="header">
<div class="header-content">
<div class="title-section">
<h1 class="title">
<svg class="logo-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L3 7L12 12L21 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
<path d="M3 17L12 22L21 17" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
<path d="M3 12L12 17L21 12" stroke="currentColor" stroke-width="2" stroke-linejoin="round" />
</svg>
DDNS Updater
</h1>
<p class="subtitle">Dynamic DNS Management Dashboard</p>
</div>
<div class="header-controls">
<button class="theme-toggle" id="themeToggle" aria-label="Toggle dark mode">
<svg class="theme-icon sun-icon" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="5" stroke="currentColor" stroke-width="2" />
<path
d="m12 1 0 2m0 18 0 2M4.22 4.22l1.42 1.42m12.72 12.72 1.42 1.42M1 12l2 0m18 0 2 0M4.22 19.78l1.42-1.42m12.72-12.72 1.42-1.42"
stroke="currentColor" stroke-width="2" />
</svg>
<svg class="theme-icon moon-icon" viewBox="0 0 24 24" fill="none">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" stroke="currentColor" stroke-width="2" />
</svg>
</button>
<button class="refresh-btn" onclick="window.location.reload()" aria-label="Refresh page">
<svg viewBox="0 0 24 24" fill="none">
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" />
<path d="M21 3v5h-5" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" />
<path d="M3 21v-5h5" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" />
</svg>
</button>
</div>
</div>
</header>

<main class="main-content">
<div class="table-container">
<table class="modern-table" role="table" aria-label="DDNS Records Status">
<thead>
<tr>
<th scope="col">Domain</th>
<th scope="col">Owner</th>
<th scope="col">Provider</th>
<th scope="col">IP Version</th>
<th scope="col">Status</th>
<th scope="col">Current IP</th>
<th scope="col">Previous IPs</th>
</tr>
</thead>
<tbody>
{{range .Rows}}
<tr>
<td data-label="Domain">{{.Domain}}</td>
<td data-label="Owner">{{.Owner}}</td>
<td data-label="Provider">{{.Provider}}</td>
<td data-label="IP Version">
{{if eq .IPVersion "ipv4 or ipv6"}}
<span class="ip-version-badge" role="img" aria-label="IPv4 supported">ipv4</span>
<span class="ip-version-badge ipv6" role="img" aria-label="IPv6 supported">ipv6</span>
{{else}}
<span class="ip-version-badge{{if eq .IPVersion "ipv6"}} ipv6{{end}}" role="img" aria-label="{{.IPVersion}} supported">{{.IPVersion}}</span>
{{end}}
</td>
<td data-label="Status">{{.Status}}</td>
<td data-label="Current IP">{{.CurrentIP}}</td>
<td data-label="Previous IPs">{{.PreviousIPs}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</main>
<footer class="footer">
<div class="footer-content">
<div class="footer-links">
<a href="https://github.com/qdm12/ddns-updater" class="footer-link">
<svg class="github-icon" viewBox="0 0 16 16" aria-hidden="true">
<path
d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z">
</path>
</svg>
GitHub
</a>
</div>
</div>
</footer>
</div>

<script>
// Dark mode toggle functionality
const themeToggle = document.getElementById('themeToggle');
const body = document.body;

// Check for saved theme preference or default to system preference
const savedTheme = localStorage.getItem('theme');
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;

if (savedTheme) {
body.setAttribute('data-theme', savedTheme);
} else if (systemPrefersDark) {
body.setAttribute('data-theme', 'dark');
}

themeToggle.addEventListener('click', () => {
const currentTheme = body.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';

body.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});

</script>
</body>

</html>
Loading