From ea72efe2c7a764d6d70893db519a437239d0a1a9 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Sat, 28 Dec 2024 22:06:13 -0500 Subject: [PATCH 01/22] fix(provider): update provider link text for Porkbun --- internal/provider/providers/porkbun/provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/providers/porkbun/provider.go b/internal/provider/providers/porkbun/provider.go index 45d32534a..a2702fde4 100644 --- a/internal/provider/providers/porkbun/provider.go +++ b/internal/provider/providers/porkbun/provider.go @@ -103,7 +103,7 @@ func (p *Provider) HTML() models.HTMLRow { return models.HTMLRow{ Domain: fmt.Sprintf("%s", p.BuildDomainName(), p.BuildDomainName()), Owner: p.Owner(), - Provider: "Porkbun DNS", + Provider: "Porkbun", IPVersion: p.ipVersion.String(), } } From da5b9947fecfc2ed658eda496f5e0637fc4a921c Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Sat, 28 Dec 2024 22:30:02 -0500 Subject: [PATCH 02/22] feat(version): add version display and fetch latest release from GitHub --- internal/models/html.go | 3 ++- internal/server/ui/index.html | 1 + internal/server/version.go | 25 +++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 internal/server/version.go diff --git a/internal/models/html.go b/internal/models/html.go index fc8d8681f..ca796015f 100644 --- a/internal/models/html.go +++ b/internal/models/html.go @@ -4,6 +4,7 @@ package models // It is exported so that the HTML template engine can render it. type HTMLData struct { Rows []HTMLRow + Version string } // HTMLRow contains HTML fields to be rendered @@ -16,4 +17,4 @@ type HTMLRow struct { Status string CurrentIP string PreviousIPs string -} +} \ No newline at end of file diff --git a/internal/server/ui/index.html b/internal/server/ui/index.html index 92c88fbfb..3f181b2bf 100644 --- a/internal/server/ui/index.html +++ b/internal/server/ui/index.html @@ -46,6 +46,7 @@ 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"> +

Version: {{.Version}}

by Quentin McGaw / UI reworked by Date: Sun, 29 Dec 2024 04:30:22 +0000 Subject: [PATCH 03/22] add ddnupdater new version notification --- internal/models/html.go | 8 +++++--- internal/server/index.go | 15 ++++++++++++++- internal/server/ui/index.html | 2 +- internal/server/version.go | 34 ++++++++++++++++++++-------------- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/internal/models/html.go b/internal/models/html.go index ca796015f..41aa1d633 100644 --- a/internal/models/html.go +++ b/internal/models/html.go @@ -3,8 +3,10 @@ package models // HTMLData is a list of HTML fields to be rendered. // It is exported so that the HTML template engine can render it. type HTMLData struct { - Rows []HTMLRow - Version string + Rows []HTMLRow + Version string + UpdateAvailable bool + LatestVersion string } // HTMLRow contains HTML fields to be rendered @@ -17,4 +19,4 @@ type HTMLRow struct { Status string CurrentIP string PreviousIPs string -} \ No newline at end of file +} diff --git a/internal/server/index.go b/internal/server/index.go index 090721572..ca3c09a1f 100644 --- a/internal/server/index.go +++ b/internal/server/index.go @@ -12,7 +12,20 @@ func (h *handlers) index(w http.ResponseWriter, _ *http.Request) { row := record.HTML(h.timeNow()) htmlData.Rows = append(htmlData.Rows, row) } - err := h.indexTemplate.ExecuteTemplate(w, "index.html", htmlData) + + currentVersion := getCurrentVersion() + latestVersion, err := getLatestRelease() + if err != nil { + httpError(w, http.StatusInternalServerError, "failed getting latest release: "+err.Error()) + return + } + htmlData.Version = currentVersion + if currentVersion != latestVersion { + htmlData.UpdateAvailable = true + htmlData.LatestVersion = latestVersion + } + + err = h.indexTemplate.ExecuteTemplate(w, "index.html", htmlData) if err != nil { httpError(w, http.StatusInternalServerError, "failed generating webpage: "+err.Error()) } diff --git a/internal/server/ui/index.html b/internal/server/ui/index.html index 3f181b2bf..aa002e77a 100644 --- a/internal/server/ui/index.html +++ b/internal/server/ui/index.html @@ -46,9 +46,9 @@ 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"> -

Version: {{.Version}}

+
Current Version: {{.Version}} {{if .UpdateAvailable}} Update Available: {{.LatestVersion}}{{end}}
by Quentin McGaw / UI reworked by Gottfried Mayer
diff --git a/internal/server/version.go b/internal/server/version.go index b9b41d05e..359ad76bb 100644 --- a/internal/server/version.go +++ b/internal/server/version.go @@ -1,25 +1,31 @@ package server import ( - "encoding/json" - "net/http" + "encoding/json" + "net/http" ) type GitHubRelease struct { - TagName string `json:"tag_name"` + TagName string `json:"tag_name"` } func getLatestRelease() (string, error) { - resp, err := http.Get("https://api.github.com/repos/qdm12/ddns-updater/releases/latest") - if err != nil { - return "", err - } - defer resp.Body.Close() + resp, err := http.Get("https://api.github.com/repos/qdm12/ddns-updater/releases/latest") + if err != nil { + return "", err + } + defer resp.Body.Close() - var release GitHubRelease - if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { - return "", err - } + var release GitHubRelease + if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { + return "", err + } - return release.TagName, nil -} \ No newline at end of file + return release.TagName, nil +} + +// Add a function to get the current version +func getCurrentVersion() string { + // Replace with actual logic to get the current version + return "v1.0.0" +} From d12296815cd1518251a64fed78e1d2cb886621b9 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Sat, 28 Dec 2024 23:41:18 -0500 Subject: [PATCH 04/22] simpler version display --- internal/server/ui/index.html | 2 +- internal/server/version.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/server/ui/index.html b/internal/server/ui/index.html index aa002e77a..b8e5bddff 100644 --- a/internal/server/ui/index.html +++ b/internal/server/ui/index.html @@ -48,7 +48,7 @@ -
Current Version: {{.Version}} {{if .UpdateAvailable}} Update Available: {{.LatestVersion}}{{end}}
+
{{.Version}} {{if .UpdateAvailable}} Update Available: {{.LatestVersion}}{{end}}
by Quentin McGaw / UI reworked by Gottfried Mayer
diff --git a/internal/server/version.go b/internal/server/version.go index 359ad76bb..9ac4f8533 100644 --- a/internal/server/version.go +++ b/internal/server/version.go @@ -24,8 +24,7 @@ func getLatestRelease() (string, error) { return release.TagName, nil } -// Add a function to get the current version func getCurrentVersion() string { // Replace with actual logic to get the current version - return "v1.0.0" + return "v2.9.0" } From 27dffae8278859d9edc0a90db12d15b0885be6b3 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Sun, 29 Dec 2024 00:37:52 -0500 Subject: [PATCH 05/22] better update available display on ui --- internal/server/ui/index.html | 3 ++- internal/server/version.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/server/ui/index.html b/internal/server/ui/index.html index b8e5bddff..ccde52d7d 100644 --- a/internal/server/ui/index.html +++ b/internal/server/ui/index.html @@ -48,7 +48,8 @@ -
{{.Version}} {{if .UpdateAvailable}} Update Available: {{.LatestVersion}}{{end}}
+
{{.Version}} {{if .UpdateAvailable}} (Update Available: {{.LatestVersion}}){{end}}
by Quentin McGaw / UI reworked by Gottfried Mayer
diff --git a/internal/server/version.go b/internal/server/version.go index 9ac4f8533..2608cfcf1 100644 --- a/internal/server/version.go +++ b/internal/server/version.go @@ -24,6 +24,7 @@ func getLatestRelease() (string, error) { return release.TagName, nil } +// Add a function to get the current version func getCurrentVersion() string { // Replace with actual logic to get the current version return "v2.9.0" From 5529f71dd700fef955dea6afab48befdd161cab8 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 24 Jul 2025 04:52:05 +0000 Subject: [PATCH 06/22] update UI v2 --- internal/server/ui/index.html | 125 ++++- internal/server/ui/static/styles.css | 679 ++++++++++++++++++++++----- 2 files changed, 664 insertions(+), 140 deletions(-) diff --git a/internal/server/ui/index.html b/internal/server/ui/index.html index ccde52d7d..f33f28282 100644 --- a/internal/server/ui/index.html +++ b/internal/server/ui/index.html @@ -11,7 +11,45 @@ - +
+
+
+
+

+ + + + + + DDNS Updater +

+

Dynamic DNS Management Dashboard

+
+
+ + +
+
+
+ +
+
+
@@ -35,24 +73,73 @@ {{end}} - -
Domain{{.PreviousIPs}}
- + + + + + + + + \ No newline at end of file diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index 3f0eb5f54..8d278ab80 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -1,181 +1,618 @@ +/* CSS Custom Properties for Theming */ :root { - --page-background: #fff; - --table-background: #eee; - --background-zebra: #ddd; - --background-hover: #e9e9f9; - --text-color: #000; - --border-color: #666; - --link-color: #039; - --link-color-hover: #36c; - --success-color: #181; - --warn-color: #951; - --error-color: #811; - --progress-color: #818; - --footer-background: #eee; - --footer-link-color: #666; - --footer-link-color-hover: #777; -} - -@media screen and (prefers-color-scheme: dark) { - :root { - --page-background: #000; - --table-background: #111; - --background-zebra: #222; - --background-hover: #191929; - --text-color: #ddd; - --border-color: #aaa; - --link-color: #9cf; - --link-color-hover: #69c; - --success-color: #9f9; - --warn-color: #fb8; - --error-color: #f88; - --progress-color: #c7c; - --footer-background: #111; - --footer-link-color: #999; - --footer-link-color-hover: #888; - } -} - -html, -body { - font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, - Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + /* Light theme colors */ + --bg-primary: #ffffff; + --bg-secondary: #f8fafc; + --bg-tertiary: #f1f5f9; + --bg-elevated: #ffffff; + --bg-overlay: rgba(255, 255, 255, 0.95); + + --text-primary: #0f172a; + --text-secondary: #475569; + --text-tertiary: #64748b; + --text-muted: #94a3b8; + + --border-primary: #e2e8f0; + --border-secondary: #cbd5e1; + --border-accent: #3b82f6; + + --accent-primary: #3b82f6; + --accent-hover: #2563eb; + --accent-light: #dbeafe; + + --success: #10b981; + --success-light: #d1fae5; + --warning: #f59e0b; + --warning-light: #fef3c7; + --error: #ef4444; + --error-light: #fee2e2; + --info: #8b5cf6; + --info-light: #ede9fe; + + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; +} + +/* Dark theme colors */ +[data-theme="dark"] { + --bg-primary: #0f172a; + --bg-secondary: #1e293b; + --bg-tertiary: #334155; + --bg-elevated: #1e293b; + --bg-overlay: rgba(15, 23, 42, 0.95); + + --text-primary: #f8fafc; + --text-secondary: #cbd5e1; + --text-tertiary: #94a3b8; + --text-muted: #64748b; + + --border-primary: #334155; + --border-secondary: #475569; + --border-accent: #3b82f6; + + --accent-primary: #3b82f6; + --accent-hover: #60a5fa; + --accent-light: #1e3a8a; + + --success: #10b981; + --success-light: #064e3b; + --warning: #f59e0b; + --warning-light: #451a03; + --error: #ef4444; + --error-light: #450a0a; + --info: #8b5cf6; + --info-light: #3c1d5b; + + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.3), 0 2px 4px -2px rgb(0 0 0 / 0.3); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.3), 0 4px 6px -4px rgb(0 0 0 / 0.3); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.3), 0 8px 10px -6px rgb(0 0 0 / 0.3); +} + +/* Base Styles */ +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +html, body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 16px; - background-color: var(--page-background); - color: var(--text-color); + line-height: 1.6; + background-color: var(--bg-primary); + color: var(--text-primary); margin: 0; - padding: 0.5rem; + padding: 0; + min-height: 100vh; + transition: background-color 0.3s ease, color 0.3s ease; } -table { - border-collapse: collapse; - border-radius: 0.3rem; +.container { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +/* Header Styles */ +.header { + background: var(--bg-elevated); + border-bottom: 1px solid var(--border-primary); + box-shadow: var(--shadow-sm); + padding: 1.5rem 2rem; + position: sticky; + top: 0; + z-index: 100; + backdrop-filter: blur(10px); +} + +.header-content { + max-width: 1400px; + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; + gap: 2rem; +} + +.title-section { + flex: 1; +} + +.title { + margin: 0; + font-size: 2rem; + font-weight: 700; + color: var(--text-primary); + display: flex; + align-items: center; + gap: 0.75rem; + line-height: 1.2; +} + +.logo-icon { + width: 2.5rem; + height: 2.5rem; + color: var(--accent-primary); + flex-shrink: 0; +} + +.subtitle { + margin: 0.5rem 0 0 0; + font-size: 1rem; + color: var(--text-secondary); + font-weight: 400; +} + +.header-controls { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.theme-toggle, .refresh-btn { + display: flex; + align-items: center; + justify-content: center; + width: 2.75rem; + height: 2.75rem; + border: 1px solid var(--border-primary); + background: var(--bg-secondary); + border-radius: var(--radius-lg); + cursor: pointer; + transition: all 0.2s ease; + position: relative; overflow: hidden; - width: 100%; - max-width: 1900px; - background: var(--table-background); +} + +.theme-toggle:hover, .refresh-btn:hover { + background: var(--bg-tertiary); + border-color: var(--border-accent); + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.theme-toggle:active, .refresh-btn:active { + transform: translateY(0); + box-shadow: var(--shadow-sm); +} + +.theme-icon, .refresh-btn svg { + width: 1.25rem; + height: 1.25rem; + color: var(--text-secondary); + transition: all 0.2s ease; +} + +.theme-toggle:hover .theme-icon, .refresh-btn:hover svg { + color: var(--accent-primary); +} + +/* Theme toggle icon switching */ +.sun-icon { display: block; } +.moon-icon { display: none; } + +[data-theme="dark"] .sun-icon { display: none; } +[data-theme="dark"] .moon-icon { display: block; } + +/* Main Content */ +.main-content { + flex: 1; + padding: 2rem; + max-width: 1400px; margin: 0 auto; - position: center; + width: 100%; +} + +.table-container { + background: var(--bg-elevated); + border-radius: var(--radius-xl); + border: 1px solid var(--border-primary); + box-shadow: var(--shadow-lg); + overflow: hidden; + position: relative; +} + +.modern-table { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; +} + +.modern-table th { + background: var(--bg-secondary); + color: var(--text-primary); + font-weight: 600; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 1rem 1.5rem; + text-align: left; + border-bottom: 1px solid var(--border-primary); + position: sticky; + top: 0; + z-index: 10; +} + +.modern-table th:first-child { + border-top-left-radius: var(--radius-xl); +} + +.modern-table th:last-child { + border-top-right-radius: var(--radius-xl); +} + +.modern-table td { + padding: 1rem 1.5rem; + color: var(--text-primary); + border-bottom: 1px solid var(--border-primary); + transition: background-color 0.2s ease; + vertical-align: middle; +} + +.modern-table tbody tr { + transition: all 0.2s ease; +} + +.modern-table tbody tr:nth-child(even) { + background: var(--bg-secondary); +} + +.modern-table tbody tr:hover { + background: var(--accent-light); + transform: translateX(2px); + box-shadow: inset 4px 0 0 var(--accent-primary); +} + +.modern-table tbody tr:last-child td:first-child { + border-bottom-left-radius: var(--radius-xl); +} + +.modern-table tbody tr:last-child td:last-child { + border-bottom-right-radius: var(--radius-xl); +} + +.modern-table tbody tr:last-child td { + border-bottom: none; } -td, -th { +/* Status badges */ +.success, .error, .uptodate, .updating, .unset { + font-weight: 600; + font-size: 0.75rem; + padding: 0.375rem 0.75rem; + border-radius: var(--radius-md); + text-transform: uppercase; + letter-spacing: 0.025em; + display: inline-block; + min-width: 80px; text-align: center; - padding: 0.7rem; } -th { - border-bottom: 1px solid var(--border-color); +.success, .uptodate { + background: var(--success-light); + color: var(--success); + border: 1px solid var(--success); +} + +.error { + background: var(--error-light); + color: var(--error); + border: 1px solid var(--error); +} + +.updating { + background: var(--info-light); + color: var(--info); + border: 1px solid var(--info); + position: relative; + overflow: hidden; +} + +.updating::after { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); + animation: shimmer 1.5s infinite; } -tbody tr:nth-child(odd) { - background: var(--background-zebra); +@keyframes shimmer { + 0% { left: -100%; } + 100% { left: 100%; } } -tbody tr:hover { - background: var(--background-hover); +.unset { + background: var(--warning-light); + color: var(--warning); + border: 1px solid var(--warning); } +/* Links */ a { - color: var(--link-color); + color: var(--accent-primary); text-decoration: none; + transition: color 0.2s ease; + font-weight: 500; } a:hover { - color: var(--link-color-hover); + color: var(--accent-hover); text-decoration: underline; } -footer { - position: fixed; - bottom: 0; - left: 0; - width: 100%; - padding: 0.5rem; +/* Footer */ +.footer { + background: var(--bg-elevated); + border-top: 1px solid var(--border-primary); + padding: 2rem; + margin-top: auto; +} + +.footer-content { + max-width: 1400px; + margin: 0 auto; display: flex; - flex-direction: column; + justify-content: space-between; align-items: center; - background: var(--footer-background); - font-size: 0.9em; - line-height: 1.6em; + gap: 2rem; + flex-wrap: wrap; } -footer a { - color: var(--footer-link-color); + +.footer-links { + display: flex; + gap: 1.5rem; } -footer a:hover { - color: var(--footer-link-color-hover); + +.footer-link { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: var(--bg-secondary); + border: 1px solid var(--border-primary); + border-radius: var(--radius-md); + font-size: 0.875rem; + font-weight: 500; + transition: all 0.2s ease; } -.text-big { - font-size: 1.4em; +.footer-link:hover { + background: var(--accent-light); + border-color: var(--accent-primary); + transform: translateY(-1px); + box-shadow: var(--shadow-md); + text-decoration: none; } -.success, .error, .uptodate, .updating, .unset { - font-weight: bold; +.github-icon { + width: 1rem; + height: 1rem; + fill: currentColor; } -.success { - color: var(--success-color); +.version-info { + display: flex; + align-items: center; + gap: 0.75rem; } -.error { - color: var(--error-color); +.version-badge { + background: var(--accent-light); + color: var(--accent-primary); + padding: 0.25rem 0.75rem; + border-radius: var(--radius-md); + font-size: 0.75rem; + font-weight: 600; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + border: 1px solid var(--accent-primary); } -.uptodate { - color: var(--success-color); +.update-badge { + background: var(--warning-light); + color: var(--warning); + padding: 0.25rem 0.75rem; + border-radius: var(--radius-md); + font-size: 0.75rem; + font-weight: 600; + border: 1px solid var(--warning); + animation: pulse 2s infinite; } -.updating { - color: var(--progress-color); +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.7; } } -.unset { - color: var(--warn-color); +.credits { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + color: var(--text-secondary); } -.github-icon { - vertical-align: text-bottom; - fill: currentColor; - height: 1em; -} - -/* responsive table. source: https://css-tricks.com/responsive-data-tables/ */ -@media only screen and (max-width: 760px), - (min-device-width: 768px) and (max-device-width: 1024px) { - table, - thead, - tbody, - th, - td, - tr { - display: block; +.credits span:nth-child(2) { + color: var(--text-muted); +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .header { + padding: 1rem 1.5rem; + } + + .main-content { + padding: 1.5rem; } + + .title { + font-size: 1.75rem; + } + + .logo-icon { + width: 2rem; + height: 2rem; + } +} - thead tr { +@media (max-width: 768px) { + .header-content { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } + + .header-controls { + align-self: flex-end; + } + + .main-content { + padding: 1rem; + } + + .title { + font-size: 1.5rem; + } + + .footer-content { + flex-direction: column; + text-align: center; + gap: 1rem; + } + + .version-info { + order: -1; + } +} + +/* Mobile table styles */ +@media (max-width: 640px) { + .table-container { + border-radius: var(--radius-lg); + overflow: visible; + box-shadow: none; + background: transparent; + border: none; + } + + .modern-table, + .modern-table thead, + .modern-table tbody, + .modern-table th, + .modern-table td, + .modern-table tr { + display: block; + } + + .modern-table thead tr { position: absolute; top: -9999px; left: -9999px; } - - tr { - margin: 0 0 1rem 0; + + .modern-table tbody tr { + background: var(--bg-elevated); + border: 1px solid var(--border-primary); + border-radius: var(--radius-lg); + margin-bottom: 1rem; + padding: 1rem; + box-shadow: var(--shadow-md); + transform: none; } - - td { + + .modern-table tbody tr:hover { + transform: none; + box-shadow: var(--shadow-lg); + } + + .modern-table td { border: none; - border-bottom: 1px solid var(--border-color); + border-bottom: 1px solid var(--border-primary); position: relative; - padding-left: 50%; + padding: 0.75rem 0; + padding-left: 40%; + text-align: right; } - - td:before { + + .modern-table td:last-child { + border-bottom: none; + } + + .modern-table td:before { + content: attr(data-label); position: absolute; - left: 6px; - width: 45%; - padding-right: 10px; + left: 0; + width: 35%; + padding-right: 1rem; white-space: nowrap; - font-weight: bold; - content: attr(data-label); + font-weight: 600; + color: var(--text-secondary); + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; + text-align: left; + top: 0.75rem; } } + +/* Loading states and animations */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +.modern-table tbody tr { + animation: fadeIn 0.3s ease-out; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg-secondary); +} + +::-webkit-scrollbar-thumb { + background: var(--border-secondary); + border-radius: var(--radius-sm); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-muted); +} + +/* Focus states for accessibility */ +.theme-toggle:focus, +.refresh-btn:focus, +a:focus { + outline: 2px solid var(--accent-primary); + outline-offset: 2px; +} + +/* Reduced motion preferences */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} \ No newline at end of file From 4c5bee183eae62d7dc09a1a70b5d0dabde16832f Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 24 Jul 2025 12:34:25 -0400 Subject: [PATCH 07/22] fix minot issues --- internal/models/html.go | 5 +- internal/server/.DS_Store | Bin 0 -> 8196 bytes internal/server/handler.go | 5 +- internal/server/index.go | 15 +---- internal/server/ui/index.html | 97 +++++++++++++-------------- internal/server/ui/static/styles.css | 8 ++- internal/server/version.go | 31 --------- 7 files changed, 60 insertions(+), 101 deletions(-) create mode 100644 internal/server/.DS_Store delete mode 100644 internal/server/version.go diff --git a/internal/models/html.go b/internal/models/html.go index 41aa1d633..fc8d8681f 100644 --- a/internal/models/html.go +++ b/internal/models/html.go @@ -3,10 +3,7 @@ package models // HTMLData is a list of HTML fields to be rendered. // It is exported so that the HTML template engine can render it. type HTMLData struct { - Rows []HTMLRow - Version string - UpdateAvailable bool - LatestVersion string + Rows []HTMLRow } // HTMLRow contains HTML fields to be rendered diff --git a/internal/server/.DS_Store b/internal/server/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..632f23418373ec87c5f57e90a05c89c1e53285d1 GIT binary patch literal 8196 zcmeHMTWl0n7(U;$(3yeCDbP}O0&HmHQd%hm3IzW`d5l1ezypbZct0fR5i%9YNh!lu2W8xw_e=;;@ zNJ$yzF#<6HcOoFdM`0yq=PUli?e}*-=;*i(rKWby+&Z;Rou?j6pYST_AQzPU-dypp z&@!IqP>Z|pjEPE#9O)^8umo^-}t?|C_YMDGArKVv-?s*7>_a-UpG zO_TT-Ar|vpbupSg&p0@uAIkEDWt#E+iNN+;|DaX$NoM&1pH?QJcCGW{YP` z7}}-zjmuW9TGz38+s^*odk;+2KeT{1EY$Teg2xT)W42WsuGnU9;!w^nSgvWi$4APx zZ|5B=n8ha8d3J%l&Mva|*(dBO`+|MNZm{p!kL)M* zGy9F*WPhO+^;iIo1|+Z=Ytf2z*n|{%(2G4dfFY!j!BOO4!oo3lD4~MWID=>K94_Dm zyolHECf>ojcn_Cw8K2_{uHs94gX{PfKj0?*z@PYAsZ&%%Qx+=BEe=W2 zRe5$hdqc`jBYH^LaEE7bZAu1_zN{KxGoxvkxNek}I^iXL7g)z-vjP34fvR(+dBal~t-`l!BB6QRvns3&?8{VK%* zSN}UnKVYA+E9@GZB9i{Xer3P2f1naIn~=mRBB!8cJG!t1-Pnd57{D&<#vsu&gJFzd z6k{k5O^@L?Y!u-mKp9VClBjwf&k|8z!pnFCui_%!z*~4bqU?wGXa)%%pH4ztu9C2v zF1n8A9w)s@*y*ORb}ta9k)1Y+`~Se~-~aE$&~cwJ0x<&j2?D6?Pxp&o1hc(-#a%l| z&y)0sB

- - - + + + DDNS Updater

@@ -28,19 +28,25 @@

@@ -50,30 +56,30 @@

- - - - - - - - - - - - - {{range .Rows}} - - - - - - - - - - {{end}} - + + + + + + + + + + + + + {{range .Rows}} + + + + + + + + + + {{end}} +
DomainOwnerProviderIP VersionUpdate StatusCurrent IPPrevious IPs (reverse chronological order)
{{.Domain}}{{.Owner}}{{.Provider}}{{.IPVersion}}{{.Status}}{{.CurrentIP}}{{.PreviousIPs}}
DomainOwnerProviderIP VersionUpdate StatusCurrent IPPrevious IPs (reverse chronological order)
{{.Domain}}{{.Owner}}{{.Provider}}{{.IPVersion}}{{.Status}}{{.CurrentIP}}{{.PreviousIPs}}
@@ -82,19 +88,13 @@

-
- {{.Version}} - {{if .UpdateAvailable}} - - Update Available: {{.LatestVersion}} - - {{end}} -
@@ -103,25 +103,25 @@

// 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); }); - + // Auto-refresh every 30 seconds setInterval(() => { const lastUpdate = performance.now(); @@ -132,7 +132,6 @@

const newDoc = parser.parseFromString(html, 'text/html'); const newTable = newDoc.querySelector('.modern-table tbody'); const currentTable = document.querySelector('.modern-table tbody'); - if (newTable && currentTable && newTable.innerHTML !== currentTable.innerHTML) { currentTable.innerHTML = newTable.innerHTML; } diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index 8d278ab80..9d05b930f 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -218,7 +218,8 @@ html, body { border-radius: var(--radius-xl); border: 1px solid var(--border-primary); box-shadow: var(--shadow-lg); - overflow: hidden; + overflow-x: auto; + overflow-y: hidden; position: relative; } @@ -226,6 +227,7 @@ html, body { width: 100%; border-collapse: collapse; font-size: 0.875rem; + table-layout: auto; } .modern-table th { @@ -241,6 +243,7 @@ html, body { position: sticky; top: 0; z-index: 10; + white-space: nowrap; } .modern-table th:first-child { @@ -257,6 +260,9 @@ html, body { border-bottom: 1px solid var(--border-primary); transition: background-color 0.2s ease; vertical-align: middle; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .modern-table tbody tr { diff --git a/internal/server/version.go b/internal/server/version.go deleted file mode 100644 index 2608cfcf1..000000000 --- a/internal/server/version.go +++ /dev/null @@ -1,31 +0,0 @@ -package server - -import ( - "encoding/json" - "net/http" -) - -type GitHubRelease struct { - TagName string `json:"tag_name"` -} - -func getLatestRelease() (string, error) { - resp, err := http.Get("https://api.github.com/repos/qdm12/ddns-updater/releases/latest") - if err != nil { - return "", err - } - defer resp.Body.Close() - - var release GitHubRelease - if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { - return "", err - } - - return release.TagName, nil -} - -// Add a function to get the current version -func getCurrentVersion() string { - // Replace with actual logic to get the current version - return "v2.9.0" -} From 60f5faf5e3d29a538fc5eb0065dafa403a32d093 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 24 Jul 2025 12:36:27 -0400 Subject: [PATCH 08/22] Remove ds_store --- internal/server/.DS_Store | Bin 8196 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 internal/server/.DS_Store diff --git a/internal/server/.DS_Store b/internal/server/.DS_Store deleted file mode 100644 index 632f23418373ec87c5f57e90a05c89c1e53285d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMTWl0n7(U;$(3yeCDbP}O0&HmHQd%hm3IzW`d5l1ezypbZct0fR5i%9YNh!lu2W8xw_e=;;@ zNJ$yzF#<6HcOoFdM`0yq=PUli?e}*-=;*i(rKWby+&Z;Rou?j6pYST_AQzPU-dypp z&@!IqP>Z|pjEPE#9O)^8umo^-}t?|C_YMDGArKVv-?s*7>_a-UpG zO_TT-Ar|vpbupSg&p0@uAIkEDWt#E+iNN+;|DaX$NoM&1pH?QJcCGW{YP` z7}}-zjmuW9TGz38+s^*odk;+2KeT{1EY$Teg2xT)W42WsuGnU9;!w^nSgvWi$4APx zZ|5B=n8ha8d3J%l&Mva|*(dBO`+|MNZm{p!kL)M* zGy9F*WPhO+^;iIo1|+Z=Ytf2z*n|{%(2G4dfFY!j!BOO4!oo3lD4~MWID=>K94_Dm zyolHECf>ojcn_Cw8K2_{uHs94gX{PfKj0?*z@PYAsZ&%%Qx+=BEe=W2 zRe5$hdqc`jBYH^LaEE7bZAu1_zN{KxGoxvkxNek}I^iXL7g)z-vjP34fvR(+dBal~t-`l!BB6QRvns3&?8{VK%* zSN}UnKVYA+E9@GZB9i{Xer3P2f1naIn~=mRBB!8cJG!t1-Pnd57{D&<#vsu&gJFzd z6k{k5O^@L?Y!u-mKp9VClBjwf&k|8z!pnFCui_%!z*~4bqU?wGXa)%%pH4ztu9C2v zF1n8A9w)s@*y*ORb}ta9k)1Y+`~Se~-~aE$&~cwJ0x<&j2?D6?Pxp&o1hc(-#a%l| z&y)0sB Date: Thu, 24 Jul 2025 12:41:48 -0400 Subject: [PATCH 09/22] fix vertical scrollbar --- internal/server/ui/static/styles.css | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index 9d05b930f..c544c367b 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -218,8 +218,7 @@ html, body { border-radius: var(--radius-xl); border: 1px solid var(--border-primary); box-shadow: var(--shadow-lg); - overflow-x: auto; - overflow-y: hidden; + overflow: hidden; position: relative; } From 53648a036732e6b28d3a6849916eb82f45289dfb Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 24 Jul 2025 12:46:41 -0400 Subject: [PATCH 10/22] clean up --- internal/server/handler.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/server/handler.go b/internal/server/handler.go index b781e25a8..cff3fdacc 100644 --- a/internal/server/handler.go +++ b/internal/server/handler.go @@ -40,8 +40,9 @@ func newHandler(ctx context.Context, rootURL string, ctx: ctx, db: db, indexTemplate: indexTemplate, - timeNow: time.Now, - runner: runner, + // TODO build information + timeNow: time.Now, + runner: runner, } router := chi.NewRouter() From 0755ca13ae80874199f63da27dca0ca349c5c691 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 24 Jul 2025 13:06:47 -0400 Subject: [PATCH 11/22] clean up css file --- internal/server/ui/static/styles.css | 37 ---------------------------- 1 file changed, 37 deletions(-) diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index c544c367b..47e0d5946 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -408,39 +408,6 @@ a:hover { fill: currentColor; } -.version-info { - display: flex; - align-items: center; - gap: 0.75rem; -} - -.version-badge { - background: var(--accent-light); - color: var(--accent-primary); - padding: 0.25rem 0.75rem; - border-radius: var(--radius-md); - font-size: 0.75rem; - font-weight: 600; - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; - border: 1px solid var(--accent-primary); -} - -.update-badge { - background: var(--warning-light); - color: var(--warning); - padding: 0.25rem 0.75rem; - border-radius: var(--radius-md); - font-size: 0.75rem; - font-weight: 600; - border: 1px solid var(--warning); - animation: pulse 2s infinite; -} - -@keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.7; } -} - .credits { display: flex; align-items: center; @@ -449,10 +416,6 @@ a:hover { color: var(--text-secondary); } -.credits span:nth-child(2) { - color: var(--text-muted); -} - /* Responsive Design */ @media (max-width: 1024px) { .header { From 5a0b72ec56c1a9ac7066959d99c1bab0ae67fdd0 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 24 Jul 2025 13:15:14 -0400 Subject: [PATCH 12/22] make css more production ready --- internal/server/ui/static/styles.css | 533 ++++++++++++++++----------- 1 file changed, 322 insertions(+), 211 deletions(-) diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index 47e0d5946..91bab6a87 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -1,118 +1,180 @@ -/* CSS Custom Properties for Theming */ +/* ========================================================================== + CSS Custom Properties for Design System + ========================================================================== */ + :root { - /* Light theme colors */ - --bg-primary: #ffffff; - --bg-secondary: #f8fafc; - --bg-tertiary: #f1f5f9; - --bg-elevated: #ffffff; - --bg-overlay: rgba(255, 255, 255, 0.95); + /* Color Palette - Light Theme */ + --color-bg-primary: #ffffff; + --color-bg-secondary: #f8fafc; + --color-bg-tertiary: #f1f5f9; + --color-bg-elevated: #ffffff; + --color-bg-overlay: rgb(255 255 255 / 0.95); - --text-primary: #0f172a; - --text-secondary: #475569; - --text-tertiary: #64748b; - --text-muted: #94a3b8; + --color-text-primary: #0f172a; + --color-text-secondary: #475569; + --color-text-tertiary: #64748b; + --color-text-muted: #94a3b8; - --border-primary: #e2e8f0; - --border-secondary: #cbd5e1; - --border-accent: #3b82f6; + --color-border-primary: #e2e8f0; + --color-border-secondary: #cbd5e1; + --color-border-accent: #3b82f6; - --accent-primary: #3b82f6; - --accent-hover: #2563eb; - --accent-light: #dbeafe; + --color-accent-primary: #3b82f6; + --color-accent-hover: #2563eb; + --color-accent-light: #dbeafe; - --success: #10b981; - --success-light: #d1fae5; - --warning: #f59e0b; - --warning-light: #fef3c7; - --error: #ef4444; - --error-light: #fee2e2; - --info: #8b5cf6; - --info-light: #ede9fe; + --color-success: #10b981; + --color-success-light: #d1fae5; + --color-warning: #f59e0b; + --color-warning-light: #fef3c7; + --color-error: #ef4444; + --color-error-light: #fee2e2; + --color-info: #8b5cf6; + --color-info-light: #ede9fe; - --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + /* Elevation System */ + --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + /* Border Radius Scale */ + --radius-xs: 0.25rem; --radius-sm: 0.375rem; --radius-md: 0.5rem; --radius-lg: 0.75rem; --radius-xl: 1rem; + --radius-2xl: 1.5rem; + + /* Spacing Scale */ + --space-1: 0.25rem; + --space-2: 0.5rem; + --space-3: 0.75rem; + --space-4: 1rem; + --space-5: 1.25rem; + --space-6: 1.5rem; + --space-8: 2rem; + --space-10: 2.5rem; + --space-12: 3rem; + + /* Typography Scale */ + --font-size-xs: 0.75rem; + --font-size-sm: 0.875rem; + --font-size-base: 1rem; + --font-size-lg: 1.125rem; + --font-size-xl: 1.25rem; + --font-size-2xl: 1.5rem; + --font-size-3xl: 1.875rem; + --font-size-4xl: 2.25rem; + + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + + --line-height-tight: 1.25; + --line-height-normal: 1.5; + --line-height-relaxed: 1.625; + + /* Timing Functions */ + --transition-fast: 150ms; + --transition-normal: 200ms; + --transition-slow: 300ms; + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); } -/* Dark theme colors */ +/* Dark Theme Override */ [data-theme="dark"] { - --bg-primary: #0f172a; - --bg-secondary: #1e293b; - --bg-tertiary: #334155; - --bg-elevated: #1e293b; - --bg-overlay: rgba(15, 23, 42, 0.95); + --color-bg-primary: #0f172a; + --color-bg-secondary: #1e293b; + --color-bg-tertiary: #334155; + --color-bg-elevated: #1e293b; + --color-bg-overlay: rgb(15 23 42 / 0.95); - --text-primary: #f8fafc; - --text-secondary: #cbd5e1; - --text-tertiary: #94a3b8; - --text-muted: #64748b; + --color-text-primary: #f8fafc; + --color-text-secondary: #cbd5e1; + --color-text-tertiary: #94a3b8; + --color-text-muted: #64748b; - --border-primary: #334155; - --border-secondary: #475569; - --border-accent: #3b82f6; + --color-border-primary: #334155; + --color-border-secondary: #475569; - --accent-primary: #3b82f6; - --accent-hover: #60a5fa; - --accent-light: #1e3a8a; + --color-accent-hover: #60a5fa; + --color-accent-light: #1e3a8a; - --success: #10b981; - --success-light: #064e3b; - --warning: #f59e0b; - --warning-light: #451a03; - --error: #ef4444; - --error-light: #450a0a; - --info: #8b5cf6; - --info-light: #3c1d5b; + --color-success-light: #064e3b; + --color-warning-light: #451a03; + --color-error-light: #450a0a; + --color-info-light: #3c1d5b; - --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3); + --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.3); + --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.3), 0 1px 2px -1px rgb(0 0 0 / 0.3); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.3), 0 2px 4px -2px rgb(0 0 0 / 0.3); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.3), 0 4px 6px -4px rgb(0 0 0 / 0.3); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.3), 0 8px 10px -6px rgb(0 0 0 / 0.3); } -/* Base Styles */ -* { +/* ========================================================================== + Base Styles & CSS Reset + ========================================================================== */ + +*, +*::before, +*::after { box-sizing: border-box; + margin: 0; + padding: 0; } html { scroll-behavior: smooth; -} - -html, body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - font-size: 16px; - line-height: 1.6; - background-color: var(--bg-primary); - color: var(--text-primary); - margin: 0; - padding: 0; + font-size: 100%; /* Use browser default 16px */ + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +body { + font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + font-size: var(--font-size-base); + line-height: var(--line-height-normal); + font-weight: var(--font-weight-normal); + background-color: var(--color-bg-primary); + color: var(--color-text-primary); min-height: 100vh; - transition: background-color 0.3s ease, color 0.3s ease; + transition: background-color var(--transition-normal) var(--ease-out), + color var(--transition-normal) var(--ease-out); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } +/* ========================================================================== + Layout Components + ========================================================================== */ + .container { min-height: 100vh; display: flex; flex-direction: column; + isolation: isolate; } -/* Header Styles */ +/* ========================================================================== + Header Component + ========================================================================== */ + .header { - background: var(--bg-elevated); - border-bottom: 1px solid var(--border-primary); + background: var(--color-bg-elevated); + border-bottom: 1px solid var(--color-border-primary); box-shadow: var(--shadow-sm); - padding: 1.5rem 2rem; + padding: var(--space-6) var(--space-8); position: sticky; top: 0; z-index: 100; backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); } .header-content { @@ -121,7 +183,7 @@ html, body { display: flex; justify-content: space-between; align-items: center; - gap: 2rem; + gap: var(--space-8); } .title-section { @@ -129,72 +191,76 @@ html, body { } .title { - margin: 0; - font-size: 2rem; - font-weight: 700; - color: var(--text-primary); + font-size: var(--font-size-3xl); + font-weight: var(--font-weight-bold); + color: var(--color-text-primary); display: flex; align-items: center; - gap: 0.75rem; - line-height: 1.2; + gap: var(--space-3); + line-height: var(--line-height-tight); } .logo-icon { - width: 2.5rem; - height: 2.5rem; - color: var(--accent-primary); + width: var(--space-10); + height: var(--space-10); + color: var(--color-accent-primary); flex-shrink: 0; } .subtitle { - margin: 0.5rem 0 0 0; - font-size: 1rem; - color: var(--text-secondary); - font-weight: 400; + margin-top: var(--space-2); + font-size: var(--font-size-base); + color: var(--color-text-secondary); + font-weight: var(--font-weight-normal); } .header-controls { display: flex; align-items: center; - gap: 0.75rem; + gap: var(--space-3); } -.theme-toggle, .refresh-btn { +.theme-toggle, +.refresh-btn { display: flex; align-items: center; justify-content: center; width: 2.75rem; height: 2.75rem; - border: 1px solid var(--border-primary); - background: var(--bg-secondary); + border: 1px solid var(--color-border-primary); + background: var(--color-bg-secondary); border-radius: var(--radius-lg); cursor: pointer; - transition: all 0.2s ease; + transition: all var(--transition-normal) var(--ease-out); position: relative; overflow: hidden; } -.theme-toggle:hover, .refresh-btn:hover { - background: var(--bg-tertiary); - border-color: var(--border-accent); +.theme-toggle:hover, +.refresh-btn:hover { + background: var(--color-bg-tertiary); + border-color: var(--color-border-accent); transform: translateY(-1px); box-shadow: var(--shadow-md); } -.theme-toggle:active, .refresh-btn:active { +.theme-toggle:active, +.refresh-btn:active { transform: translateY(0); - box-shadow: var(--shadow-sm); + box-shadow: var(--shadow-xs); } -.theme-icon, .refresh-btn svg { - width: 1.25rem; - height: 1.25rem; - color: var(--text-secondary); - transition: all 0.2s ease; +.theme-icon, +.refresh-btn svg { + inline-size: var(--space-5); + block-size: var(--space-5); + color: var(--color-text-secondary); + transition: color var(--transition-normal) var(--ease-out); } -.theme-toggle:hover .theme-icon, .refresh-btn:hover svg { - color: var(--accent-primary); +.theme-toggle:hover .theme-icon, +.refresh-btn:hover svg { + color: var(--color-accent-primary); } /* Theme toggle icon switching */ @@ -204,41 +270,45 @@ html, body { [data-theme="dark"] .sun-icon { display: none; } [data-theme="dark"] .moon-icon { display: block; } -/* Main Content */ +/* ========================================================================== + Main Content Area + ========================================================================== */ + .main-content { flex: 1; - padding: 2rem; + padding: var(--space-8); max-width: 1400px; margin: 0 auto; - width: 100%; + inline-size: 100%; } .table-container { - background: var(--bg-elevated); + background: var(--color-bg-elevated); border-radius: var(--radius-xl); - border: 1px solid var(--border-primary); + border: 1px solid var(--color-border-primary); box-shadow: var(--shadow-lg); overflow: hidden; position: relative; + container-type: inline-size; } .modern-table { - width: 100%; + inline-size: 100%; border-collapse: collapse; - font-size: 0.875rem; + font-size: var(--font-size-sm); table-layout: auto; } .modern-table th { - background: var(--bg-secondary); - color: var(--text-primary); - font-weight: 600; - font-size: 0.75rem; + background: var(--color-bg-secondary); + color: var(--color-text-primary); + font-weight: var(--font-weight-semibold); + font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.05em; - padding: 1rem 1.5rem; - text-align: left; - border-bottom: 1px solid var(--border-primary); + padding: var(--space-4) var(--space-6); + text-align: start; + border-bottom: 1px solid var(--color-border-primary); position: sticky; top: 0; z-index: 10; @@ -254,10 +324,10 @@ html, body { } .modern-table td { - padding: 1rem 1.5rem; - color: var(--text-primary); - border-bottom: 1px solid var(--border-primary); - transition: background-color 0.2s ease; + padding: var(--space-4) var(--space-6); + color: var(--color-text-primary); + border-bottom: 1px solid var(--color-border-primary); + transition: background-color var(--transition-normal) var(--ease-out); vertical-align: middle; white-space: nowrap; overflow: hidden; @@ -265,17 +335,17 @@ html, body { } .modern-table tbody tr { - transition: all 0.2s ease; + transition: all var(--transition-normal) var(--ease-out); } .modern-table tbody tr:nth-child(even) { - background: var(--bg-secondary); + background: var(--color-bg-secondary); } .modern-table tbody tr:hover { - background: var(--accent-light); + background: var(--color-accent-light); transform: translateX(2px); - box-shadow: inset 4px 0 0 var(--accent-primary); + box-shadow: inset 4px 0 0 var(--color-accent-primary); } .modern-table tbody tr:last-child td:first-child { @@ -290,35 +360,45 @@ html, body { border-bottom: none; } -/* Status badges */ -.success, .error, .uptodate, .updating, .unset { - font-weight: 600; - font-size: 0.75rem; - padding: 0.375rem 0.75rem; +/* ========================================================================== + Status Badge Components + ========================================================================== */ + +.success, +.error, +.uptodate, +.updating, +.unset { + font-weight: var(--font-weight-semibold); + font-size: var(--font-size-xs); + padding: var(--space-1) var(--space-3); border-radius: var(--radius-md); text-transform: uppercase; letter-spacing: 0.025em; - display: inline-block; - min-width: 80px; + display: inline-flex; + align-items: center; + justify-content: center; + min-inline-size: 80px; text-align: center; } -.success, .uptodate { - background: var(--success-light); - color: var(--success); - border: 1px solid var(--success); +.success, +.uptodate { + background: var(--color-success-light); + color: var(--color-success); + border: 1px solid var(--color-success); } .error { - background: var(--error-light); - color: var(--error); - border: 1px solid var(--error); + background: var(--color-error-light); + color: var(--color-error); + border: 1px solid var(--color-error); } .updating { - background: var(--info-light); - color: var(--info); - border: 1px solid var(--info); + background: var(--color-info-light); + color: var(--color-info); + border: 1px solid var(--color-info); position: relative; overflow: hidden; } @@ -340,29 +420,37 @@ html, body { } .unset { - background: var(--warning-light); - color: var(--warning); - border: 1px solid var(--warning); + background: var(--color-warning-light); + color: var(--color-warning); + border: 1px solid var(--color-warning); } -/* Links */ +/* ========================================================================== + Link Components + ========================================================================== */ + a { - color: var(--accent-primary); + color: var(--color-accent-primary); text-decoration: none; - transition: color 0.2s ease; - font-weight: 500; + transition: color var(--transition-normal) var(--ease-out); + font-weight: var(--font-weight-medium); } a:hover { - color: var(--accent-hover); + color: var(--color-accent-hover); text-decoration: underline; + text-decoration-thickness: 2px; + text-underline-offset: 2px; } -/* Footer */ +/* ========================================================================== + Footer Component + ========================================================================== */ + .footer { - background: var(--bg-elevated); - border-top: 1px solid var(--border-primary); - padding: 2rem; + background: var(--color-bg-elevated); + border-top: 1px solid var(--color-border-primary); + padding: var(--space-8); margin-top: auto; } @@ -372,75 +460,78 @@ a:hover { display: flex; justify-content: space-between; align-items: center; - gap: 2rem; + gap: var(--space-8); flex-wrap: wrap; } .footer-links { display: flex; - gap: 1.5rem; + gap: var(--space-6); } .footer-link { display: flex; align-items: center; - gap: 0.5rem; - padding: 0.5rem 1rem; - background: var(--bg-secondary); - border: 1px solid var(--border-primary); + gap: var(--space-2); + padding: var(--space-2) var(--space-4); + background: var(--color-bg-secondary); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-md); - font-size: 0.875rem; - font-weight: 500; - transition: all 0.2s ease; + font-size: var(--font-size-sm); + font-weight: var(--font-weight-medium); + transition: all var(--transition-normal) var(--ease-out); } .footer-link:hover { - background: var(--accent-light); - border-color: var(--accent-primary); + background: var(--color-accent-light); + border-color: var(--color-accent-primary); transform: translateY(-1px); box-shadow: var(--shadow-md); text-decoration: none; } .github-icon { - width: 1rem; - height: 1rem; + inline-size: var(--space-4); + block-size: var(--space-4); fill: currentColor; } .credits { display: flex; align-items: center; - gap: 0.5rem; - font-size: 0.875rem; - color: var(--text-secondary); + gap: var(--space-2); + font-size: var(--font-size-sm); + color: var(--color-text-secondary); } -/* Responsive Design */ -@media (max-width: 1024px) { +/* ========================================================================== + Responsive Design + ========================================================================== */ + +@media (width <= 64rem) { .header { - padding: 1rem 1.5rem; + padding: var(--space-4) var(--space-6); } .main-content { - padding: 1.5rem; + padding: var(--space-6); } .title { - font-size: 1.75rem; + font-size: var(--font-size-2xl); } .logo-icon { - width: 2rem; - height: 2rem; + inline-size: var(--space-8); + block-size: var(--space-8); } } -@media (max-width: 768px) { +@media (width <= 48rem) { .header-content { flex-direction: column; align-items: flex-start; - gap: 1rem; + gap: var(--space-4); } .header-controls { @@ -448,26 +539,22 @@ a:hover { } .main-content { - padding: 1rem; + padding: var(--space-4); } .title { - font-size: 1.5rem; + font-size: var(--font-size-xl); } .footer-content { flex-direction: column; text-align: center; - gap: 1rem; - } - - .version-info { - order: -1; + gap: var(--space-4); } } /* Mobile table styles */ -@media (max-width: 640px) { +@media (width <= 40rem) { .table-container { border-radius: var(--radius-lg); overflow: visible; @@ -492,11 +579,11 @@ a:hover { } .modern-table tbody tr { - background: var(--bg-elevated); - border: 1px solid var(--border-primary); + background: var(--color-bg-elevated); + border: 1px solid var(--color-border-primary); border-radius: var(--radius-lg); - margin-bottom: 1rem; - padding: 1rem; + margin-bottom: var(--space-4); + padding: var(--space-4); box-shadow: var(--shadow-md); transform: none; } @@ -508,11 +595,11 @@ a:hover { .modern-table td { border: none; - border-bottom: 1px solid var(--border-primary); + border-bottom: 1px solid var(--color-border-primary); position: relative; - padding: 0.75rem 0; - padding-left: 40%; - text-align: right; + padding: var(--space-3) 0; + padding-inline-start: 40%; + text-align: end; } .modern-table td:last-child { @@ -522,17 +609,17 @@ a:hover { .modern-table td:before { content: attr(data-label); position: absolute; - left: 0; - width: 35%; - padding-right: 1rem; + inset-inline-start: 0; + inline-size: 35%; + padding-inline-end: var(--space-4); white-space: nowrap; - font-weight: 600; - color: var(--text-secondary); - font-size: 0.75rem; + font-weight: var(--font-weight-semibold); + color: var(--color-text-secondary); + font-size: var(--font-size-xs); text-transform: uppercase; letter-spacing: 0.05em; - text-align: left; - top: 0.75rem; + text-align: start; + top: var(--space-3); } } @@ -546,34 +633,51 @@ a:hover { animation: fadeIn 0.3s ease-out; } -/* Custom scrollbar */ +/* ========================================================================== + Custom Scrollbar + ========================================================================== */ + ::-webkit-scrollbar { - width: 8px; - height: 8px; + inline-size: 8px; + block-size: 8px; } ::-webkit-scrollbar-track { - background: var(--bg-secondary); + background: var(--color-bg-secondary); } ::-webkit-scrollbar-thumb { - background: var(--border-secondary); + background: var(--color-border-secondary); border-radius: var(--radius-sm); } ::-webkit-scrollbar-thumb:hover { - background: var(--text-muted); + background: var(--color-text-muted); } -/* Focus states for accessibility */ -.theme-toggle:focus, -.refresh-btn:focus, -a:focus { - outline: 2px solid var(--accent-primary); +/* ========================================================================== + Accessibility & Focus States + ========================================================================== */ + +.theme-toggle:focus-visible, +.refresh-btn:focus-visible, +a:focus-visible { + outline: 2px solid var(--color-accent-primary); outline-offset: 2px; + border-radius: var(--radius-sm); +} + +@media (forced-colors: active) { + .theme-toggle, + .refresh-btn { + border: 1px solid ButtonBorder; + } } -/* Reduced motion preferences */ +/* ========================================================================== + Motion & Animation Preferences + ========================================================================== */ + @media (prefers-reduced-motion: reduce) { *, *::before, @@ -583,4 +687,11 @@ a:focus { transition-duration: 0.01ms !important; scroll-behavior: auto !important; } +} + +@media (prefers-contrast: high) { + :root { + --color-border-primary: currentColor; + --color-border-secondary: currentColor; + } } \ No newline at end of file From 39b8a664b09061041ad2a82e3a4a91034eb61877 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 24 Jul 2025 13:25:34 -0400 Subject: [PATCH 13/22] remove auto refresh --- internal/server/ui/index.html | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/internal/server/ui/index.html b/internal/server/ui/index.html index de505bdf2..de997561d 100644 --- a/internal/server/ui/index.html +++ b/internal/server/ui/index.html @@ -122,22 +122,6 @@

localStorage.setItem('theme', newTheme); }); - // Auto-refresh every 30 seconds - setInterval(() => { - const lastUpdate = performance.now(); - fetch(window.location.href) - .then(response => response.text()) - .then(html => { - const parser = new DOMParser(); - const newDoc = parser.parseFromString(html, 'text/html'); - const newTable = newDoc.querySelector('.modern-table tbody'); - const currentTable = document.querySelector('.modern-table tbody'); - if (newTable && currentTable && newTable.innerHTML !== currentTable.innerHTML) { - currentTable.innerHTML = newTable.innerHTML; - } - }) - .catch(error => console.log('Refresh failed:', error)); - }, 30000); From ea5047ae164b7223a557aa51458174f2a4a11fb2 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 24 Jul 2025 14:40:13 -0400 Subject: [PATCH 14/22] add style for ip version --- internal/server/ui/index.html | 9 ++++++- internal/server/ui/static/styles.css | 38 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/internal/server/ui/index.html b/internal/server/ui/index.html index de997561d..2258c7c51 100644 --- a/internal/server/ui/index.html +++ b/internal/server/ui/index.html @@ -73,7 +73,14 @@

{{.Domain}} {{.Owner}} {{.Provider}} - {{.IPVersion}} + + {{if eq .IPVersion "ipv4 or ipv6"}} + ipv4 + ipv6 + {{else}} + {{.IPVersion}} + {{end}} + {{.Status}} {{.CurrentIP}} {{.PreviousIPs}} diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index 91bab6a87..b80830183 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -356,6 +356,44 @@ body { border-bottom-right-radius: var(--radius-xl); } +/* IP Version styling */ +.ip-version-badge { + display: inline-block; + padding: var(--space-1) var(--space-3); + border: 2px solid var(--color-accent-primary); + border-radius: var(--radius-lg); + background: linear-gradient(135deg, var(--color-accent-light), rgba(59, 130, 246, 0.1)); + color: var(--color-accent-primary); + font-weight: var(--font-weight-semibold); + font-size: var(--font-size-xs); + text-transform: uppercase; + letter-spacing: 0.05em; + box-shadow: var(--shadow-sm); + transition: all var(--transition-normal) var(--ease-out); +} + +.ip-version-badge:hover { + transform: translateY(-1px); + box-shadow: var(--shadow-md); + border-color: var(--color-accent-hover); + background: linear-gradient(135deg, var(--color-accent-light), rgba(37, 99, 235, 0.15)); +} + +.ip-version-badge.ipv6 { + border-color: var(--color-info); + color: var(--color-info); + background: linear-gradient(135deg, var(--color-info-light), rgba(139, 92, 246, 0.1)); +} + +.ip-version-badge.ipv6:hover { + border-color: #7c3aed; + background: linear-gradient(135deg, var(--color-info-light), rgba(124, 58, 237, 0.15)); +} + +.ip-version-badge + .ip-version-badge { + margin-left: var(--space-2); +} + .modern-table tbody tr:last-child td { border-bottom: none; } From 20667c65c58a8cd3b8e8b90f656667fcbba81549 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 24 Jul 2025 14:53:54 -0400 Subject: [PATCH 15/22] simplify the ip history column name --- internal/server/ui/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/server/ui/index.html b/internal/server/ui/index.html index 2258c7c51..a49e7d7c1 100644 --- a/internal/server/ui/index.html +++ b/internal/server/ui/index.html @@ -64,7 +64,7 @@

IP Version Update Status Current IP - Previous IPs (reverse chronological order) + Previous IPs From 430aed9cb32c9d9443de1e8c37368105df58c7ab Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 24 Jul 2025 14:57:53 -0400 Subject: [PATCH 16/22] more clean up --- internal/records/html.go | 9 +- internal/server/ui/index.html | 22 +-- internal/server/ui/static/styles.css | 192 +++++++++++++++++++++++++-- 3 files changed, 199 insertions(+), 24 deletions(-) diff --git a/internal/records/html.go b/internal/records/html.go index e0c6b033e..0485db4da 100644 --- a/internal/records/html.go +++ b/internal/records/html.go @@ -22,10 +22,13 @@ func (r *Record) HTML(now time.Time) models.HTMLRow { if r.Status == "" { row.Status = NotAvailable } else { - row.Status = fmt.Sprintf("%s %s, %s", - convertStatus(r.Status), - message, + statusHTML := convertStatus(r.Status) + if message != "" { + statusHTML += fmt.Sprintf(` %s`, message) + } + statusHTML += fmt.Sprintf(`, %s`, time.Since(r.Time).Round(time.Second).String()+" ago") + row.Status = statusHTML } currentIP := r.History.GetCurrentIP() if currentIP.IsValid() { diff --git a/internal/server/ui/index.html b/internal/server/ui/index.html index a49e7d7c1..28793021e 100644 --- a/internal/server/ui/index.html +++ b/internal/server/ui/index.html @@ -55,16 +55,16 @@

- +
- - - - - - - + + + + + + + @@ -75,10 +75,10 @@

diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index b80830183..67d72ed0e 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -356,7 +356,10 @@ body { border-bottom-right-radius: var(--radius-xl); } -/* IP Version styling */ +/* ========================================================================== + IP Version Badge Components - Production Ready + ========================================================================== */ + .ip-version-badge { display: inline-block; padding: var(--space-1) var(--space-3); @@ -370,28 +373,106 @@ body { letter-spacing: 0.05em; box-shadow: var(--shadow-sm); transition: all var(--transition-normal) var(--ease-out); + /* Accessibility improvements */ + cursor: default; + user-select: none; + /* Performance optimizations */ + will-change: transform, box-shadow; + backface-visibility: hidden; + /* Browser compatibility */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } -.ip-version-badge:hover { - transform: translateY(-1px); - box-shadow: var(--shadow-md); - border-color: var(--color-accent-hover); - background: linear-gradient(135deg, var(--color-accent-light), rgba(37, 99, 235, 0.15)); +/* Hover states with reduced motion support */ +@media (hover: hover) and (prefers-reduced-motion: no-preference) { + .ip-version-badge:hover { + transform: translateY(-1px); + box-shadow: var(--shadow-md); + border-color: var(--color-accent-hover); + background: linear-gradient(135deg, var(--color-accent-light), rgba(37, 99, 235, 0.15)); + } +} + +/* Reduced motion fallback */ +@media (prefers-reduced-motion: reduce) { + .ip-version-badge { + transition: color var(--transition-normal) var(--ease-out), + background-color var(--transition-normal) var(--ease-out), + border-color var(--transition-normal) var(--ease-out); + } + + .ip-version-badge:hover { + border-color: var(--color-accent-hover); + background: linear-gradient(135deg, var(--color-accent-light), rgba(37, 99, 235, 0.15)); + } } +/* IPv6 specific styling */ .ip-version-badge.ipv6 { border-color: var(--color-info); color: var(--color-info); background: linear-gradient(135deg, var(--color-info-light), rgba(139, 92, 246, 0.1)); } -.ip-version-badge.ipv6:hover { - border-color: #7c3aed; - background: linear-gradient(135deg, var(--color-info-light), rgba(124, 58, 237, 0.15)); +@media (hover: hover) and (prefers-reduced-motion: no-preference) { + .ip-version-badge.ipv6:hover { + border-color: #7c3aed; + background: linear-gradient(135deg, var(--color-info-light), rgba(124, 58, 237, 0.15)); + } +} + +@media (prefers-reduced-motion: reduce) { + .ip-version-badge.ipv6:hover { + border-color: #7c3aed; + background: linear-gradient(135deg, var(--color-info-light), rgba(124, 58, 237, 0.15)); + } } +/* Spacing between multiple badges */ .ip-version-badge + .ip-version-badge { - margin-left: var(--space-2); + margin-inline-start: var(--space-2); +} + +/* Focus states for accessibility */ +.ip-version-badge:focus-visible { + outline: 2px solid var(--color-accent-primary); + outline-offset: 2px; +} + +.ip-version-badge.ipv6:focus-visible { + outline-color: var(--color-info); +} + +/* ========================================================================== + Table Column Specific Styling - Production Ready + ========================================================================== */ + +/* Previous IPs column - optimized for content display */ +.modern-table td:last-child { + white-space: normal; + overflow: visible; + text-overflow: initial; + min-width: 12rem; + max-width: 20rem; + word-break: break-word; + overflow-wrap: break-word; + hyphens: auto; + line-height: 1.4; +} + +/* Better text breaking for IP addresses */ +@supports (word-break: break-word) { + .modern-table td:last-child { + word-break: break-word; + } +} + +/* Fallback for older browsers */ +@supports not (word-break: break-word) { + .modern-table td:last-child { + word-break: break-all; + } } .modern-table tbody tr:last-child td { @@ -463,6 +544,97 @@ body { border: 1px solid var(--color-warning); } +/* ========================================================================== + Status Details Components - Production Ready + ========================================================================== */ + +/* Status details styling (e.g., "no IP change for 22d") */ +.status-details { + display: inline-block; + margin-inline-start: var(--space-2); + padding: var(--space-1) var(--space-2); + background: var(--color-bg-tertiary); + color: var(--color-text-secondary); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + border: 1px solid var(--color-border-secondary); + border-radius: var(--radius-sm); + /* Performance optimizations */ + backface-visibility: hidden; + /* Accessibility */ + user-select: none; + /* Browser compatibility */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Status timestamp styling (e.g., "2s ago") */ +.status-timestamp { + display: inline-block; + margin-inline-start: var(--space-1); + color: var(--color-text-tertiary); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + font-style: italic; + /* Accessibility */ + user-select: none; + /* Browser compatibility */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Responsive adjustments for mobile */ +@media (width <= 48rem) { + .status-details, + .status-timestamp { + display: block; + margin-inline-start: 0; + margin-top: var(--space-1); + } + + /* Improve mobile table readability */ + .ip-version-badge + .ip-version-badge { + margin-inline-start: 0; + margin-top: var(--space-1); + display: block; + } +} + +/* High contrast mode support */ +@media (prefers-contrast: high) { + .ip-version-badge { + border-width: 3px; + font-weight: var(--font-weight-bold); + } + + .status-details { + border-width: 2px; + font-weight: var(--font-weight-semibold); + } +} + +/* Print styles */ +@media print { + .ip-version-badge, + .status-details { + background: transparent !important; + color: black !important; + border: 1px solid black !important; + box-shadow: none !important; + } + + .status-timestamp { + color: #666 !important; + } +} + +/* Performance: Contain layout shifts */ +.ip-version-badge, +.status-details, +.status-timestamp { + contain: layout style; +} + /* ========================================================================== Link Components ========================================================================== */ From 39f252a2b272da3147341a6575ab6850739508ec Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Fri, 25 Jul 2025 11:27:29 -0400 Subject: [PATCH 17/22] even more clean up --- internal/server/ui/static/styles.css | 201 +++++++++++---------------- 1 file changed, 79 insertions(+), 122 deletions(-) diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index 67d72ed0e..e1bd955cb 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -9,20 +9,20 @@ --color-bg-tertiary: #f1f5f9; --color-bg-elevated: #ffffff; --color-bg-overlay: rgb(255 255 255 / 0.95); - + --color-text-primary: #0f172a; --color-text-secondary: #475569; --color-text-tertiary: #64748b; --color-text-muted: #94a3b8; - + --color-border-primary: #e2e8f0; --color-border-secondary: #cbd5e1; --color-border-accent: #3b82f6; - + --color-accent-primary: #3b82f6; --color-accent-hover: #2563eb; --color-accent-light: #dbeafe; - + --color-success: #10b981; --color-success-light: #d1fae5; --color-warning: #f59e0b; @@ -31,14 +31,14 @@ --color-error-light: #fee2e2; --color-info: #8b5cf6; --color-info-light: #ede9fe; - + /* Elevation System */ --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); - + /* Border Radius Scale */ --radius-xs: 0.25rem; --radius-sm: 0.375rem; @@ -46,7 +46,7 @@ --radius-lg: 0.75rem; --radius-xl: 1rem; --radius-2xl: 1.5rem; - + /* Spacing Scale */ --space-1: 0.25rem; --space-2: 0.5rem; @@ -57,7 +57,7 @@ --space-8: 2rem; --space-10: 2.5rem; --space-12: 3rem; - + /* Typography Scale */ --font-size-xs: 0.75rem; --font-size-sm: 0.875rem; @@ -67,16 +67,16 @@ --font-size-2xl: 1.5rem; --font-size-3xl: 1.875rem; --font-size-4xl: 2.25rem; - + --font-weight-normal: 400; --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; - + --line-height-tight: 1.25; --line-height-normal: 1.5; --line-height-relaxed: 1.625; - + /* Timing Functions */ --transition-fast: 150ms; --transition-normal: 200ms; @@ -93,23 +93,23 @@ --color-bg-tertiary: #334155; --color-bg-elevated: #1e293b; --color-bg-overlay: rgb(15 23 42 / 0.95); - + --color-text-primary: #f8fafc; --color-text-secondary: #cbd5e1; --color-text-tertiary: #94a3b8; --color-text-muted: #64748b; - + --color-border-primary: #334155; --color-border-secondary: #475569; - + --color-accent-hover: #60a5fa; --color-accent-light: #1e3a8a; - + --color-success-light: #064e3b; --color-warning-light: #451a03; --color-error-light: #450a0a; --color-info-light: #3c1d5b; - + --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.3); --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.3), 0 1px 2px -1px rgb(0 0 0 / 0.3); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.3), 0 2px 4px -2px rgb(0 0 0 / 0.3); @@ -131,7 +131,8 @@ html { scroll-behavior: smooth; - font-size: 100%; /* Use browser default 16px */ + font-size: 100%; + /* Use browser default 16px */ -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } @@ -144,8 +145,8 @@ body { background-color: var(--color-bg-primary); color: var(--color-text-primary); min-height: 100vh; - transition: background-color var(--transition-normal) var(--ease-out), - color var(--transition-normal) var(--ease-out); + transition: background-color var(--transition-normal) var(--ease-out), + color var(--transition-normal) var(--ease-out); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } @@ -264,11 +265,21 @@ body { } /* Theme toggle icon switching */ -.sun-icon { display: block; } -.moon-icon { display: none; } +.sun-icon { + display: block; +} + +.moon-icon { + display: none; +} + +[data-theme="dark"] .sun-icon { + display: none; +} -[data-theme="dark"] .sun-icon { display: none; } -[data-theme="dark"] .moon-icon { display: block; } +[data-theme="dark"] .moon-icon { + display: block; +} /* ========================================================================== Main Content Area @@ -398,10 +409,10 @@ body { @media (prefers-reduced-motion: reduce) { .ip-version-badge { transition: color var(--transition-normal) var(--ease-out), - background-color var(--transition-normal) var(--ease-out), - border-color var(--transition-normal) var(--ease-out); + background-color var(--transition-normal) var(--ease-out), + border-color var(--transition-normal) var(--ease-out); } - + .ip-version-badge:hover { border-color: var(--color-accent-hover); background: linear-gradient(135deg, var(--color-accent-light), rgba(37, 99, 235, 0.15)); @@ -430,7 +441,7 @@ body { } /* Spacing between multiple badges */ -.ip-version-badge + .ip-version-badge { +.ip-version-badge+.ip-version-badge { margin-inline-start: var(--space-2); } @@ -457,23 +468,9 @@ body { max-width: 20rem; word-break: break-word; overflow-wrap: break-word; - hyphens: auto; line-height: 1.4; } -/* Better text breaking for IP addresses */ -@supports (word-break: break-word) { - .modern-table td:last-child { - word-break: break-word; - } -} - -/* Fallback for older browsers */ -@supports not (word-break: break-word) { - .modern-table td:last-child { - word-break: break-all; - } -} .modern-table tbody tr:last-child td { border-bottom: none; @@ -529,13 +526,18 @@ body { left: -100%; width: 100%; height: 100%; - background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); animation: shimmer 1.5s infinite; } @keyframes shimmer { - 0% { left: -100%; } - 100% { left: 100%; } + 0% { + left: -100%; + } + + 100% { + left: 100%; + } } .unset { @@ -545,55 +547,13 @@ body { } /* ========================================================================== - Status Details Components - Production Ready + Responsive Design - Mobile Adjustments ========================================================================== */ +@media (width <=48rem) { -/* Status details styling (e.g., "no IP change for 22d") */ -.status-details { - display: inline-block; - margin-inline-start: var(--space-2); - padding: var(--space-1) var(--space-2); - background: var(--color-bg-tertiary); - color: var(--color-text-secondary); - font-size: var(--font-size-xs); - font-weight: var(--font-weight-medium); - border: 1px solid var(--color-border-secondary); - border-radius: var(--radius-sm); - /* Performance optimizations */ - backface-visibility: hidden; - /* Accessibility */ - user-select: none; - /* Browser compatibility */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -/* Status timestamp styling (e.g., "2s ago") */ -.status-timestamp { - display: inline-block; - margin-inline-start: var(--space-1); - color: var(--color-text-tertiary); - font-size: var(--font-size-xs); - font-weight: var(--font-weight-medium); - font-style: italic; - /* Accessibility */ - user-select: none; - /* Browser compatibility */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} -/* Responsive adjustments for mobile */ -@media (width <= 48rem) { - .status-details, - .status-timestamp { - display: block; - margin-inline-start: 0; - margin-top: var(--space-1); - } - /* Improve mobile table readability */ - .ip-version-badge + .ip-version-badge { + .ip-version-badge+.ip-version-badge { margin-inline-start: 0; margin-top: var(--space-1); display: block; @@ -606,32 +566,20 @@ body { border-width: 3px; font-weight: var(--font-weight-bold); } - - .status-details { - border-width: 2px; - font-weight: var(--font-weight-semibold); - } } /* Print styles */ @media print { - .ip-version-badge, - .status-details { + .ip-version-badge { background: transparent !important; color: black !important; border: 1px solid black !important; box-shadow: none !important; } - - .status-timestamp { - color: #666 !important; - } } /* Performance: Contain layout shifts */ -.ip-version-badge, -.status-details, -.status-timestamp { +.ip-version-badge { contain: layout style; } @@ -718,44 +666,44 @@ a:hover { Responsive Design ========================================================================== */ -@media (width <= 64rem) { +@media (width <=64rem) { .header { padding: var(--space-4) var(--space-6); } - + .main-content { padding: var(--space-6); } - + .title { font-size: var(--font-size-2xl); } - + .logo-icon { inline-size: var(--space-8); block-size: var(--space-8); } } -@media (width <= 48rem) { +@media (width <=48rem) { .header-content { flex-direction: column; align-items: flex-start; gap: var(--space-4); } - + .header-controls { align-self: flex-end; } - + .main-content { padding: var(--space-4); } - + .title { font-size: var(--font-size-xl); } - + .footer-content { flex-direction: column; text-align: center; @@ -764,7 +712,7 @@ a:hover { } /* Mobile table styles */ -@media (width <= 40rem) { +@media (width <=40rem) { .table-container { border-radius: var(--radius-lg); overflow: visible; @@ -772,7 +720,7 @@ a:hover { background: transparent; border: none; } - + .modern-table, .modern-table thead, .modern-table tbody, @@ -781,13 +729,13 @@ a:hover { .modern-table tr { display: block; } - + .modern-table thead tr { position: absolute; top: -9999px; left: -9999px; } - + .modern-table tbody tr { background: var(--color-bg-elevated); border: 1px solid var(--color-border-primary); @@ -797,12 +745,12 @@ a:hover { box-shadow: var(--shadow-md); transform: none; } - + .modern-table tbody tr:hover { transform: none; box-shadow: var(--shadow-lg); } - + .modern-table td { border: none; border-bottom: 1px solid var(--color-border-primary); @@ -811,11 +759,11 @@ a:hover { padding-inline-start: 40%; text-align: end; } - + .modern-table td:last-child { border-bottom: none; } - + .modern-table td:before { content: attr(data-label); position: absolute; @@ -835,8 +783,15 @@ a:hover { /* Loading states and animations */ @keyframes fadeIn { - from { opacity: 0; transform: translateY(10px); } - to { opacity: 1; transform: translateY(0); } + from { + opacity: 0; + transform: translateY(10px); + } + + to { + opacity: 1; + transform: translateY(0); + } } .modern-table tbody tr { @@ -878,6 +833,7 @@ a:focus-visible { } @media (forced-colors: active) { + .theme-toggle, .refresh-btn { border: 1px solid ButtonBorder; @@ -889,6 +845,7 @@ a:focus-visible { ========================================================================== */ @media (prefers-reduced-motion: reduce) { + *, *::before, *::after { From 31a2a7d2e20dc7a6e0906d5572a05c667688272f Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 21 Aug 2025 21:26:22 -0400 Subject: [PATCH 18/22] fix hover effect --- internal/server/ui/static/styles.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index e1bd955cb..742506218 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -355,8 +355,6 @@ body { .modern-table tbody tr:hover { background: var(--color-accent-light); - transform: translateX(2px); - box-shadow: inset 4px 0 0 var(--color-accent-primary); } .modern-table tbody tr:last-child td:first-child { From a556a634a0a0de55727ddee3ada729cdcadda8ee Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 21 Aug 2025 21:46:10 -0400 Subject: [PATCH 19/22] update the 'update status' column format --- internal/records/html.go | 31 +++++++++------ internal/server/ui/static/styles.css | 58 ++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 12 deletions(-) diff --git a/internal/records/html.go b/internal/records/html.go index 0485db4da..0ff3064b0 100644 --- a/internal/records/html.go +++ b/internal/records/html.go @@ -14,20 +14,27 @@ 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 { - statusHTML := convertStatus(r.Status) + 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)) + } + + statusHTML := fmt.Sprintf(`%s %s`, convertStatus(r.Status), timeDisplay) if message != "" { statusHTML += fmt.Sprintf(` %s`, message) } - statusHTML += fmt.Sprintf(`, %s`, - time.Since(r.Time).Round(time.Second).String()+" ago") row.Status = statusHTML } currentIP := r.History.GetCurrentIP() @@ -56,15 +63,15 @@ func (r *Record) HTML(now time.Time) models.HTMLRow { func convertStatus(status models.Status) string { switch status { case constants.SUCCESS: - return `Success` + return `Updated` case constants.FAIL: - return `Failure` + return `Failed` case constants.UPTODATE: - return `Up to date` + return `Current` case constants.UPDATING: - return `Updating` + return `Syncing` case constants.UNSET: - return `Unset` + return `Pending` default: return "Unknown status" } diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index 742506218..7f0db7825 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -494,6 +494,7 @@ body { justify-content: center; min-inline-size: 80px; text-align: center; + white-space: nowrap; } .success, @@ -544,6 +545,63 @@ body { border: 1px solid var(--color-warning); } +/* ========================================================================== + Status Details and Timestamp Components + ========================================================================== */ + + +.status-timestamp { + display: inline-flex; + align-items: center; + margin-right: var(--space-2); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-medium); + color: var(--color-text-muted); + text-transform: none; + letter-spacing: normal; + background: var(--color-bg-tertiary); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-sm); + border: 1px solid var(--color-border-primary); + gap: var(--space-1); + vertical-align: middle; +} + +.status-timestamp::before { + content: "🕒"; + font-size: var(--font-size-xs); +} + +.status-details { + display: block; + margin-top: var(--space-2); + font-size: var(--font-size-xs); + font-weight: var(--font-weight-normal); + color: var(--color-text-secondary); + font-style: italic; + text-transform: none; + letter-spacing: normal; + background: var(--color-bg-secondary); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-sm); + border: 1px solid var(--color-border-primary); + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Update Status column layout */ +.modern-table td[data-label="Update Status"] { + min-width: 200px; + max-width: 300px; + white-space: normal; + overflow: visible; + text-overflow: initial; + line-height: 1.4; + padding: var(--space-3) var(--space-4); +} + /* ========================================================================== Responsive Design - Mobile Adjustments ========================================================================== */ From 5a0ca9d24bdd116ae49b9908c8b5145635f218a8 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 21 Aug 2025 22:10:37 -0400 Subject: [PATCH 20/22] show update/error messgae as tooltip --- internal/records/html.go | 23 +++++++- internal/server/ui/static/styles.css | 80 +++++++++++++++++++--------- 2 files changed, 76 insertions(+), 27 deletions(-) diff --git a/internal/records/html.go b/internal/records/html.go index 0ff3064b0..272a5821d 100644 --- a/internal/records/html.go +++ b/internal/records/html.go @@ -31,10 +31,12 @@ func (r *Record) HTML(now time.Time) models.HTMLRow { timeDisplay = fmt.Sprintf("%d days ago", int(timeSince.Hours()/24)) } - statusHTML := fmt.Sprintf(`%s %s`, convertStatus(r.Status), timeDisplay) + statusBadge := convertStatus(r.Status) if message != "" { - statusHTML += fmt.Sprintf(` %s`, message) + statusBadge = convertStatusWithTooltip(r.Status, message) } + + statusHTML := fmt.Sprintf(`%s %s`, statusBadge, timeDisplay) row.Status = statusHTML } currentIP := r.History.GetCurrentIP() @@ -76,3 +78,20 @@ func convertStatus(status models.Status) string { return "Unknown status" } } + +func convertStatusWithTooltip(status models.Status, message string) string { + switch status { + case constants.SUCCESS: + return fmt.Sprintf(`Updated`, message) + case constants.FAIL: + return fmt.Sprintf(`Failed`, message) + case constants.UPTODATE: + return fmt.Sprintf(`Current`, message) + case constants.UPDATING: + return fmt.Sprintf(`Syncing`, message) + case constants.UNSET: + return fmt.Sprintf(`Pending`, message) + default: + return "Unknown status" + } +} diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index 7f0db7825..0a55ee4b1 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -546,14 +546,13 @@ body { } /* ========================================================================== - Status Details and Timestamp Components + Status Timestamp Component ========================================================================== */ - .status-timestamp { display: inline-flex; align-items: center; - margin-right: var(--space-2); + margin-left: var(--space-2); font-size: var(--font-size-xs); font-weight: var(--font-weight-medium); color: var(--color-text-muted); @@ -572,36 +571,67 @@ body { font-size: var(--font-size-xs); } -.status-details { - display: block; - margin-top: var(--space-2); - font-size: var(--font-size-xs); - font-weight: var(--font-weight-normal); - color: var(--color-text-secondary); - font-style: italic; - text-transform: none; - letter-spacing: normal; - background: var(--color-bg-secondary); - padding: var(--space-1) var(--space-2); - border-radius: var(--radius-sm); - border: 1px solid var(--color-border-primary); - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - /* Update Status column layout */ .modern-table td[data-label="Update Status"] { - min-width: 200px; - max-width: 300px; - white-space: normal; + min-width: 160px; + max-width: 250px; + white-space: nowrap; overflow: visible; text-overflow: initial; line-height: 1.4; padding: var(--space-3) var(--space-4); } +/* Enhanced tooltip styling for status badges */ +.success[title], +.error[title], +.uptodate[title], +.updating[title], +.unset[title] { + cursor: help; + position: relative; +} + +.success[title]:hover::after, +.error[title]:hover::after, +.uptodate[title]:hover::after, +.updating[title]:hover::after, +.unset[title]:hover::after { + content: attr(title); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + background: var(--color-text-primary); + color: var(--color-bg-primary); + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-md); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-normal); + text-transform: none; + letter-spacing: normal; + white-space: nowrap; + z-index: 1000; + box-shadow: var(--shadow-lg); + margin-bottom: var(--space-2); + min-width: max-content; +} + +.success[title]:hover::before, +.error[title]:hover::before, +.uptodate[title]:hover::before, +.updating[title]:hover::before, +.unset[title]:hover::before { + content: ""; + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + border: 4px solid transparent; + border-top-color: var(--color-text-primary); + z-index: 1000; +} + /* ========================================================================== Responsive Design - Mobile Adjustments ========================================================================== */ From bbf63e4c460c158943e6a384abfa051bf87f9683 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 21 Aug 2025 23:33:16 -0400 Subject: [PATCH 21/22] add tootio for update.error messages and some fix in mobile mode --- internal/records/html.go | 10 +-- internal/server/ui/index.html | 4 +- internal/server/ui/static/styles.css | 117 +++++++++++++++++++++------ 3 files changed, 101 insertions(+), 30 deletions(-) diff --git a/internal/records/html.go b/internal/records/html.go index 272a5821d..967f40b17 100644 --- a/internal/records/html.go +++ b/internal/records/html.go @@ -82,15 +82,15 @@ func convertStatus(status models.Status) string { func convertStatusWithTooltip(status models.Status, message string) string { switch status { case constants.SUCCESS: - return fmt.Sprintf(`Updated`, message) + return fmt.Sprintf(`Updated`, message) case constants.FAIL: - return fmt.Sprintf(`Failed`, message) + return fmt.Sprintf(`Failed`, message) case constants.UPTODATE: - return fmt.Sprintf(`Current`, message) + return fmt.Sprintf(`Current`, message) case constants.UPDATING: - return fmt.Sprintf(`Syncing`, message) + return fmt.Sprintf(`Syncing`, message) case constants.UNSET: - return fmt.Sprintf(`Pending`, message) + return fmt.Sprintf(`Pending`, message) default: return "Unknown status" } diff --git a/internal/server/ui/index.html b/internal/server/ui/index.html index 28793021e..fba594b9c 100644 --- a/internal/server/ui/index.html +++ b/internal/server/ui/index.html @@ -62,7 +62,7 @@

- + @@ -81,7 +81,7 @@

{{.IPVersion}} {{end}} -

+ diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index 0a55ee4b1..ad20c9d34 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -457,15 +457,13 @@ body { Table Column Specific Styling - Production Ready ========================================================================== */ -/* Previous IPs column - optimized for content display */ +/* Previous IPs column - single line display without truncation */ .modern-table td:last-child { - white-space: normal; + white-space: nowrap; overflow: visible; text-overflow: initial; min-width: 12rem; - max-width: 20rem; - word-break: break-word; - overflow-wrap: break-word; + width: auto; line-height: 1.4; } @@ -571,8 +569,8 @@ body { font-size: var(--font-size-xs); } -/* Update Status column layout */ -.modern-table td[data-label="Update Status"] { +/* Status column layout */ +.modern-table td[data-label="Status"] { min-width: 160px; max-width: 250px; white-space: nowrap; @@ -583,21 +581,21 @@ body { } /* Enhanced tooltip styling for status badges */ -.success[title], -.error[title], -.uptodate[title], -.updating[title], -.unset[title] { +.success[data-tooltip], +.error[data-tooltip], +.uptodate[data-tooltip], +.updating[data-tooltip], +.unset[data-tooltip] { cursor: help; position: relative; } -.success[title]:hover::after, -.error[title]:hover::after, -.uptodate[title]:hover::after, -.updating[title]:hover::after, -.unset[title]:hover::after { - content: attr(title); +.success[data-tooltip]:hover::after, +.error[data-tooltip]:hover::after, +.uptodate[data-tooltip]:hover::after, +.updating[data-tooltip]:hover::after, +.unset[data-tooltip]:hover::after { + content: attr(data-tooltip); position: absolute; bottom: 100%; left: 50%; @@ -617,11 +615,11 @@ body { min-width: max-content; } -.success[title]:hover::before, -.error[title]:hover::before, -.uptodate[title]:hover::before, -.updating[title]:hover::before, -.unset[title]:hover::before { +.success[data-tooltip]:hover::before, +.error[data-tooltip]:hover::before, +.uptodate[data-tooltip]:hover::before, +.updating[data-tooltip]:hover::before, +.unset[data-tooltip]:hover::before { content: ""; position: absolute; bottom: 100%; @@ -844,10 +842,16 @@ a:hover { padding: var(--space-3) 0; padding-inline-start: 40%; text-align: end; + white-space: normal; + overflow: visible; + text-overflow: initial; } .modern-table td:last-child { border-bottom: none; + white-space: normal; + word-wrap: break-word; + overflow-wrap: break-word; } .modern-table td:before { @@ -865,6 +869,73 @@ a:hover { text-align: start; top: var(--space-3); } + + /* Fix IPv6 badge stacking on mobile */ + .ip-version-badge+.ip-version-badge { + margin-inline-start: var(--space-2); + margin-top: 0; + display: inline-flex; + } + + /* Fix status badges positioning on mobile */ + .modern-table td[data-label="Status"] { + white-space: nowrap; + position: relative; + z-index: 1; + padding-inline-start: 40%; + } + + .modern-table td[data-label="Status"]:before { + white-space: nowrap; + position: absolute; + inset-inline-start: 0; + inline-size: 35%; + padding-inline-end: var(--space-4); + font-weight: var(--font-weight-semibold); + color: var(--color-text-secondary); + font-size: var(--font-size-xs); + text-transform: uppercase; + letter-spacing: 0.05em; + text-align: start; + top: var(--space-3); + content: attr(data-label); + z-index: 0; + } + + .modern-table td[data-label="Status"] .status-timestamp { + margin-left: var(--space-2); + margin-top: 0; + display: inline-flex; + width: auto; + } + + /* Ensure tooltips work properly on mobile */ + .success[data-tooltip]:hover::after, + .error[data-tooltip]:hover::after, + .uptodate[data-tooltip]:hover::after, + .updating[data-tooltip]:hover::after, + .unset[data-tooltip]:hover::after { + position: fixed; + top: 50vh; + left: 50vw; + transform: translate(-50%, -50%); + z-index: 9999; + max-width: calc(100vw - 2rem); + min-width: 200px; + white-space: normal; + word-wrap: break-word; + text-align: center; + margin: 0; + box-sizing: border-box; + } + + .success[data-tooltip]:hover::before, + .error[data-tooltip]:hover::before, + .uptodate[data-tooltip]:hover::before, + .updating[data-tooltip]:hover::before, + .unset[data-tooltip]:hover::before { + display: none; + } } /* Loading states and animations */ From d1f45fee01f4c468aac58631ea085f24912138a8 Mon Sep 17 00:00:00 2001 From: Ehsan Shirvanian Date: Thu, 21 Aug 2025 23:45:50 -0400 Subject: [PATCH 22/22] clean up css: --- internal/records/html.go | 2 +- internal/server/ui/static/styles.css | 52 +++++++++++++++++----------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/internal/records/html.go b/internal/records/html.go index 967f40b17..f09072862 100644 --- a/internal/records/html.go +++ b/internal/records/html.go @@ -41,7 +41,7 @@ func (r *Record) HTML(now time.Time) models.HTMLRow { } currentIP := r.History.GetCurrentIP() if currentIP.IsValid() { - row.CurrentIP = `` + currentIP.String() + "" + row.CurrentIP = `` + currentIP.String() + ` ` } else { row.CurrentIP = NotAvailable } diff --git a/internal/server/ui/static/styles.css b/internal/server/ui/static/styles.css index ad20c9d34..dd9a71f47 100644 --- a/internal/server/ui/static/styles.css +++ b/internal/server/ui/static/styles.css @@ -467,7 +467,6 @@ body { line-height: 1.4; } - .modern-table tbody tr:last-child td { border-bottom: none; } @@ -630,20 +629,6 @@ body { z-index: 1000; } -/* ========================================================================== - Responsive Design - Mobile Adjustments - ========================================================================== */ -@media (width <=48rem) { - - - /* Improve mobile table readability */ - .ip-version-badge+.ip-version-badge { - margin-inline-start: 0; - margin-top: var(--space-1); - display: block; - } -} - /* High contrast mode support */ @media (prefers-contrast: high) { .ip-version-badge { @@ -662,11 +647,6 @@ body { } } -/* Performance: Contain layout shifts */ -.ip-version-badge { - contain: layout style; -} - /* ========================================================================== Link Components ========================================================================== */ @@ -685,6 +665,36 @@ a:hover { text-underline-offset: 2px; } +/* IP Info Link Styling */ +.ip-link { + display: inline-flex; + align-items: center; + gap: var(--space-1); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-sm); + transition: all var(--transition-normal) var(--ease-out); + text-decoration: none; +} + +.ip-link:hover { + background: var(--color-accent-light); + text-decoration: none; + transform: translateY(-1px); + box-shadow: var(--shadow-sm); +} + +.ipinfo-icon { + width: var(--space-4); + height: var(--space-4); + opacity: 0.7; + transition: opacity var(--transition-normal) var(--ease-out); + flex-shrink: 0; +} + +.ip-link:hover .ipinfo-icon { + opacity: 1; +} + /* ========================================================================== Footer Component ========================================================================== */ @@ -870,7 +880,7 @@ a:hover { top: var(--space-3); } - /* Fix IPv6 badge stacking on mobile */ + /* Override desktop IPv6 badge stacking for mobile inline display */ .ip-version-badge+.ip-version-badge { margin-inline-start: var(--space-2); margin-top: 0;
DomainOwnerProviderIP VersionUpdate StatusCurrent IPPrevious IPsDomainOwnerProviderIP VersionUpdate StatusCurrent IPPrevious IPs
{{.Provider}} {{if eq .IPVersion "ipv4 or ipv6"}} - ipv4 - ipv6 + ipv4 + ipv6 {{else}} - {{.IPVersion}} + {{.IPVersion}} {{end}} {{.Status}}Owner Provider IP VersionUpdate StatusStatus Current IP Previous IPs
{{.Status}}{{.Status}} {{.CurrentIP}} {{.PreviousIPs}}