Files
ntp/frontend/main.py
2025-10-23 01:42:03 -04:00

1270 lines
47 KiB
Python

import os
import requests
import subprocess
from flask import Flask, render_template_string, jsonify, request, send_from_directory
from socket import gethostbyname_ex
from datetime import datetime
app = Flask(__name__)
# Config
REPORTER_SERVICE = os.environ.get("REPORTER_SERVICE", "ntp-reporter-svc.default.svc.cluster.local")
BASE_URL = os.environ.get("BASE_URL", "https://time.dws.rip")
# Tracking table config
TRACKING_METRICS_ORDER = [
"Reference ID", "Ref Source IP", "Stratum", "Ref time (UTC)", "System time",
"Last offset", "RMS offset", "Frequency", "Residual freq", "Skew",
"Root delay", "Root dispersion", "Update interval", "Leap status"
]
# Sources table config
SOURCES_COLUMNS_ORDER = [
"DWS PEER", "ModeState", "Name/IP address", "Stratum", "Poll", "Reach",
"LastRx", "Last sample", "Std Dev"
]
# Metric Definitions
TRACKING_METRICS_DEFS = {
"Reference ID": "Identifier of current time source (IP or refclock ID)",
"Ref Source IP": "IP address of the reference time source",
"Stratum": "Distance from primary time source (lower is better, 1-16)",
"Ref time (UTC)": "Last time the reference was updated",
"System time": "Offset between system clock and reference time (seconds)",
"Last offset": "Offset of last clock update (seconds)",
"RMS offset": "Root mean square of recent offset values (long-term average)",
"Frequency": "Rate of system clock drift (ppm - parts per million)",
"Residual freq": "Residual frequency error not yet corrected",
"Skew": "Estimated error bound of frequency (accuracy metric)",
"Root delay": "Total network delay to stratum-1 server (seconds)",
"Root dispersion": "Total dispersion accumulated to stratum-1 server",
"Update interval": "Time between clock updates (seconds)",
"Leap status": "Leap second indicator (Normal, Insert, Delete, or Not synced)"
}
SOURCES_METRICS_DEFS = {
"DWS PEER": "Node identifier for this NTP daemon instance",
"ModeState": "Source mode (^=server, ==peer) & state (*=current sync)",
"Name/IP address": "Hostname or IP address of the NTP source",
"Stratum": "Stratum level of the source (1=primary reference)",
"Poll": "Polling interval to source (log2 seconds, e.g., 6 = 64s)",
"Reach": "Reachability register (377 octal = all 8 recent polls OK)",
"LastRx": "Time since last successful response from source",
"Last sample": "Offset measurement from last valid sample (seconds)",
"Std Dev": "Standard deviation of offset (jitter measurement)"
}
#
# HTML Template - DWS Design System Compliant
#
HTML_TEMPLATE = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DWS LLC NTP STATUS</title>
<style>
/* ===================================================================
DWS DESIGN SYSTEM - PRAGMATIC FUTURISM
Color Palette, Typography, Grid System
=================================================================== */
/* Font Faces - DWS Two-Typeface System */
/* Primary Typeface: Inter (Workhorse Grotesk) */
/* Using system Inter or CDN fallback - for production, serve from /static/fonts/ */
@import url('https://rsms.me/inter/inter.css');
/* Secondary Typeface: Berkeley Mono (Code Monospace) */
@font-face {
font-family: 'BerkeleyMono';
src: url('/static/fonts/BerkeleyMono-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'BerkeleyMono';
src: url('/static/fonts/BerkeleyMono-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
/* ===================================================================
DWS COLOR SYSTEM - CATHODE MODE (Dark)
Exact specifications from DWS Table 1
=================================================================== */
:root[data-mode="cathode"], :root {
/* Base Colors - Cathode Mode (Dark) */
--cathode-black: #101418;
--cathode-surface: #1A2026;
--cathode-primary: #E0E6EB;
--cathode-secondary: #98A3AF;
/* Functional Colors - Cathode Mode */
--signal-amber: #FFA800;
--engineering-orange: #F97316;
--warning-red: #EF4444;
--vector-green: #34D399;
--systems-blue: #3B82F6;
/* Semantic Mappings - Cathode Mode */
--dws-bg-primary: var(--cathode-black);
--dws-bg-secondary: var(--cathode-black);
--dws-bg-elevated: var(--cathode-surface);
--dws-text-primary: var(--cathode-primary);
--dws-text-secondary: var(--cathode-secondary);
--dws-text-tertiary: var(--cathode-secondary);
--dws-border-primary: var(--cathode-surface);
--dws-border-secondary: var(--cathode-secondary);
--dws-accent-primary: var(--signal-amber);
--dws-accent-cta: var(--engineering-orange);
--dws-success: var(--vector-green);
--dws-error: var(--warning-red);
--dws-info: var(--systems-blue);
}
/* ===================================================================
DWS COLOR SYSTEM - VELLUM MODE (Light)
Exact specifications from DWS Table 1
=================================================================== */
:root[data-mode="vellum"] {
/* Base Colors - Vellum Mode (Light) */
--vellum-white: #FAF8F5;
--vellum-surface: #F2F0ED;
--vellum-primary: #2D2A26;
--vellum-secondary: #78716A;
/* Functional Colors - Vellum Mode (same as Cathode) */
--signal-amber: #FFA800;
--engineering-orange: #F97316;
--warning-red: #EF4444;
--vector-green: #34D399;
--systems-blue: #3B82F6;
/* Semantic Mappings - Vellum Mode */
--dws-bg-primary: var(--vellum-white);
--dws-bg-secondary: var(--vellum-white);
--dws-bg-elevated: var(--vellum-surface);
--dws-text-primary: var(--vellum-primary);
--dws-text-secondary: var(--vellum-secondary);
--dws-text-tertiary: var(--vellum-secondary);
--dws-border-primary: var(--vellum-surface);
--dws-border-secondary: var(--vellum-secondary);
--dws-accent-primary: var(--signal-amber);
--dws-accent-cta: var(--engineering-orange);
--dws-success: var(--vector-green);
--dws-error: var(--warning-red);
--dws-info: var(--systems-blue);
}
/* ===================================================================
DWS SPACING SYSTEM - 8px Base Unit
=================================================================== */
:root {
--space-xxs: 4px; /* 0.5 unit */
--space-xs: 8px; /* 1 unit */
--space-sm: 16px; /* 2 units */
--space-md: 24px; /* 3 units */
--space-lg: 32px; /* 4 units */
--space-xl: 48px; /* 6 units */
--space-xxl: 64px; /* 8 units */
}
/* ===================================================================
DWS TYPOGRAPHY SCALE - Two-Typeface System
Primary: Inter (neo-grotesk) | Secondary: Berkeley Mono (monospace)
Based on DWS Table 2 - 1.25 Major Third scale, 16px base
=================================================================== */
/* Typography Variable Definitions */
:root {
/* Primary Typeface (Workhorse Grotesk) */
--font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
/* Secondary Typeface (Code Monospace) */
--font-mono: 'BerkeleyMono', 'IBM Plex Mono', 'Courier New', monospace;
/* Display & Headings */
--type-display-size: 4.883rem; /* 78.13px */
--type-display-height: 80px;
--type-h1-size: 3.906rem; /* 62.50px */
--type-h1-height: 64px;
--type-h2-size: 3.125rem; /* 50.00px */
--type-h2-height: 56px;
--type-h3-size: 2.5rem; /* 40.00px */
--type-h3-height: 48px;
--type-h4-size: 2rem; /* 32.00px */
--type-h4-height: 40px;
--type-h5-size: 1.6rem; /* 25.60px */
--type-h5-height: 32px;
/* Body Text */
--type-body-lg-size: 1.25rem; /* 20.00px */
--type-body-lg-height: 32px;
--type-body-size: 1rem; /* 16.00px */
--type-body-height: 24px;
--type-body-sm-size: 0.8rem; /* 12.80px */
--type-body-sm-height: 24px;
/* Labels (All Caps) */
--type-label-lg-size: 1rem; /* 16.00px */
--type-label-lg-height: 24px;
--type-label-size: 0.8rem; /* 12.80px */
--type-label-height: 16px;
--type-label-sm-size: 0.64rem; /* 10.24px */
--type-label-sm-height: 16px;
/* Code/Data */
--type-code-size: 0.875rem; /* 14.00px */
--type-code-height: 24px;
--type-code-sm-size: 0.75rem; /* 12.00px */
--type-code-sm-height: 16px;
}
/* Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 16px; /* Base size for rem calculations */
background-color: var(--dws-bg-primary);
transition: background-color 0.3s ease;
}
body {
font-family: var(--font-primary);
font-weight: 400;
font-size: var(--type-body-size);
line-height: var(--type-body-height);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: var(--dws-bg-primary);
color: var(--dws-text-primary);
margin: 0;
overflow-x: auto;
transition: background-color 0.3s ease, color 0.3s ease;
}
/* Typography Classes */
.display {
font-family: var(--font-primary);
font-size: var(--type-display-size);
line-height: var(--type-display-height);
font-weight: 700;
letter-spacing: -0.02em;
}
h1, .heading-01 {
font-family: var(--font-primary);
font-size: var(--type-h1-size);
line-height: var(--type-h1-height);
font-weight: 700;
letter-spacing: -0.02em;
}
h2, .heading-02 {
font-family: var(--font-primary);
font-size: var(--type-h2-size);
line-height: var(--type-h2-height);
font-weight: 600;
letter-spacing: -0.01em;
}
h3, .heading-03 {
font-family: var(--font-primary);
font-size: var(--type-h3-size);
line-height: var(--type-h3-height);
font-weight: 600;
}
h4, .heading-04 {
font-family: var(--font-primary);
font-size: var(--type-h4-size);
line-height: var(--type-h4-height);
font-weight: 600;
}
h5, .heading-05 {
font-family: var(--font-primary);
font-size: var(--type-h5-size);
line-height: var(--type-h5-height);
font-weight: 600;
}
.body-large {
font-size: var(--type-body-lg-size);
line-height: var(--type-body-lg-height);
}
.body-small {
font-size: var(--type-body-sm-size);
line-height: var(--type-body-sm-height);
}
.label-large, .label-lg {
font-family: var(--font-primary);
font-size: var(--type-label-lg-size);
line-height: var(--type-label-lg-height);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.label-default, .label {
font-family: var(--font-primary);
font-size: var(--type-label-size);
line-height: var(--type-label-height);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.label-small, .label-sm {
font-family: var(--font-primary);
font-size: var(--type-label-sm-size);
line-height: var(--type-label-sm-height);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.code-default, code {
font-family: var(--font-mono);
font-size: var(--type-code-size);
line-height: var(--type-code-height);
font-weight: 400;
}
.code-small {
font-family: var(--font-mono);
font-size: var(--type-code-sm-size);
line-height: var(--type-code-sm-height);
font-weight: 400;
}
pre {
margin: 0;
padding: 0;
white-space: pre;
font-family: var(--font-mono);
font-size: var(--type-code-size);
line-height: var(--type-code-height);
color: var(--dws-text-primary);
}
/* Section Headers - Using DWS label styles */
b, .section-header {
font-family: var(--font-primary);
font-weight: 700;
font-size: var(--type-label-size);
line-height: var(--type-label-height);
color: var(--dws-accent-primary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Dynamic Clock Spans */
#clock-time {
font-family: var(--font-mono);
color: var(--dws-text-primary);
font-weight: 700;
font-size: var(--type-body-lg-size);
line-height: var(--type-body-lg-height);
}
#clock-date {
font-family: var(--font-mono);
color: var(--dws-text-secondary);
font-size: var(--type-code-size);
line-height: var(--type-code-height);
}
#clock-status {
font-family: var(--font-mono);
color: var(--dws-info);
font-weight: 700;
}
#clock-status.synced {
color: var(--dws-success);
}
#clock-status.error {
color: var(--dws-error);
}
#clock-offset {
font-family: var(--font-mono);
color: var(--dws-text-secondary);
}
/* Semantic Color Classes */
.status-normal { color: var(--dws-success); }
.status-warning { color: var(--dws-accent-primary); }
.status-error { color: var(--dws-error); }
.status-info { color: var(--dws-info); }
/* ===================================================================
MODE TOGGLE COMPONENT - DWS Style
=================================================================== */
.mode-toggle-container {
position: fixed;
top: var(--space-md);
right: var(--space-md);
z-index: 1000;
}
.mode-toggle {
display: flex;
align-items: center;
gap: var(--space-xs);
background: var(--dws-bg-elevated);
border: 1px solid var(--dws-border-primary);
border-radius: 0; /* DWS: 0px border-radius */
padding: var(--space-xs) var(--space-sm);
cursor: pointer;
transition: border-color 0.2s ease, background-color 0.2s ease;
user-select: none;
}
.mode-toggle:hover {
border-color: var(--dws-accent-primary);
background: var(--dws-bg-secondary);
}
.mode-toggle:active {
border-color: var(--dws-accent-cta);
}
.mode-toggle-label {
font-size: 12px;
line-height: 16px;
color: var(--dws-text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 700;
}
.mode-toggle-switch {
position: relative;
width: 48px;
height: 24px;
background: var(--dws-bg-secondary);
border: 1px solid var(--dws-border-primary);
border-radius: 0; /* DWS: 0px border-radius */
transition: background-color 0.3s ease;
}
.mode-toggle-switch::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 18px;
height: 18px;
background: var(--dws-accent-primary);
border-radius: 0; /* DWS: 0px border-radius */
transition: transform 0.3s ease, background-color 0.3s ease;
}
.mode-toggle.active .mode-toggle-switch {
background: var(--dws-bg-elevated);
}
.mode-toggle.active .mode-toggle-switch::after {
transform: translateX(24px);
background: var(--dws-accent-cta);
}
/* ===================================================================
SEPARATOR LINES - Hairline Borders
=================================================================== */
.separator {
border: 0;
border-top: 1px solid var(--dws-border-primary);
margin: var(--space-md) 0;
}
/* ===================================================================
DWS 12-COLUMN GRID SYSTEM - Section V
=================================================================== */
.dws-container {
width: 100%;
max-width: 1440px;
margin: 0 auto;
padding: 0 var(--space-xxl); /* 64px page margins */
}
.dws-grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 16px; /* 16px gutters */
margin-bottom: var(--space-xl);
}
/* Column Span Classes */
.col-1 { grid-column: span 1; }
.col-2 { grid-column: span 2; }
.col-3 { grid-column: span 3; }
.col-4 { grid-column: span 4; }
.col-5 { grid-column: span 5; }
.col-6 { grid-column: span 6; }
.col-7 { grid-column: span 7; }
.col-8 { grid-column: span 8; }
.col-9 { grid-column: span 9; }
.col-10 { grid-column: span 10; }
.col-11 { grid-column: span 11; }
.col-12 { grid-column: span 12; }
/* ===================================================================
DWS PANEL COMPONENT - Section VI
=================================================================== */
.dws-panel {
background: var(--dws-bg-elevated);
border: 1px solid var(--dws-border-primary);
border-radius: 0; /* DWS: 0px border-radius */
padding: var(--space-lg);
margin-bottom: var(--space-xl);
transition: background-color 0.3s ease, border-color 0.3s ease;
}
.dws-panel-header {
font-family: var(--font-primary);
font-size: var(--type-label-lg-size);
line-height: var(--type-label-lg-height);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--dws-accent-primary);
margin-bottom: var(--space-md);
padding-bottom: var(--space-sm);
border-bottom: 1px solid var(--dws-border-secondary);
}
.dws-panel-body {
color: var(--dws-text-primary);
}
/* ===================================================================
DWS TABLE COMPONENT - Section 6.3
=================================================================== */
.dws-table {
width: 100%;
border-collapse: collapse;
font-family: var(--font-mono);
font-size: var(--type-code-size);
line-height: var(--type-code-height);
margin-bottom: var(--space-lg);
}
.dws-table thead {
border-bottom: 1px solid var(--dws-border-primary);
}
.dws-table th {
font-family: var(--font-primary);
font-size: var(--type-label-size);
line-height: var(--type-label-height);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
text-align: left;
padding: var(--space-sm) var(--space-xs);
color: var(--dws-text-secondary);
border-bottom: 1px solid var(--dws-border-primary);
}
.dws-table td {
padding: var(--space-sm) var(--space-xs);
border-bottom: 1px solid var(--dws-border-secondary);
color: var(--dws-text-primary);
}
.dws-table tbody tr:last-child td {
border-bottom: none;
}
/* Table cell alignment per DWS spec */
.dws-table td:first-child,
.dws-table th:first-child {
text-align: left; /* Text data: left-aligned */
}
.dws-table td.numeric,
.dws-table th.numeric {
text-align: right; /* Numerical data: right-aligned */
}
.dws-table tr:hover {
background: var(--dws-bg-elevated);
}
/* ===================================================================
DWS DEFINITION LIST STYLING
=================================================================== */
dl {
margin: 0;
padding: 0;
}
dt {
font-family: var(--font-primary);
font-size: var(--type-label-sm-size);
line-height: var(--type-label-sm-height);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--dws-text-secondary);
margin-top: var(--space-sm);
margin-bottom: var(--space-xxs);
}
dt:first-child {
margin-top: 0;
}
dd {
font-family: var(--font-primary);
font-size: var(--type-body-sm-size);
line-height: var(--type-body-sm-height);
color: var(--dws-text-primary);
margin: 0;
margin-left: 0;
padding-left: var(--space-md);
border-left: 2px solid var(--dws-border-primary);
}
/* ===================================================================
DWS BUTTON COMPONENT LIBRARY - Section 6.2
=================================================================== */
.dws-button {
font-family: var(--font-primary);
font-size: var(--type-label-size);
line-height: var(--type-label-height);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: var(--space-sm) var(--space-lg);
border: 1px solid;
border-radius: 0; /* DWS: 0px border-radius */
cursor: pointer;
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
display: inline-block;
text-decoration: none;
user-select: none;
}
.dws-button-primary {
background: var(--dws-accent-cta);
border-color: var(--dws-accent-cta);
color: var(--cathode-black);
}
.dws-button-primary:hover {
background: var(--dws-accent-primary);
border-color: var(--dws-accent-primary);
}
.dws-button-primary:active {
opacity: 0.8;
}
.dws-button-secondary {
background: transparent;
border-color: var(--dws-accent-primary);
color: var(--dws-accent-primary);
}
.dws-button-secondary:hover {
background: var(--dws-bg-elevated);
border-color: var(--dws-accent-cta);
color: var(--dws-accent-cta);
}
.dws-button-secondary:active {
background: var(--dws-bg-secondary);
}
.dws-button-tertiary {
background: transparent;
border-color: transparent;
color: var(--dws-text-secondary);
}
.dws-button-tertiary:hover {
color: var(--dws-accent-primary);
border-color: var(--dws-border-primary);
}
.dws-button-tertiary:active {
color: var(--dws-accent-cta);
}
/* ===================================================================
RESPONSIVE - Mobile Adjustments
=================================================================== */
@media (max-width: 1024px) {
.dws-container {
padding: 0 var(--space-lg); /* 32px margins on tablet */
}
.dws-grid {
gap: 8px; /* 8px gutters on mobile per DWS spec */
}
}
@media (max-width: 768px) {
html {
font-size: 14px; /* Slightly smaller base for mobile */
}
.dws-container {
padding: 0 var(--space-sm); /* 16px margins on mobile */
}
.dws-grid {
grid-template-columns: 1fr; /* Single column on mobile */
}
.dws-panel {
padding: var(--space-md);
}
.mode-toggle-container {
top: var(--space-sm);
right: var(--space-sm);
}
.mode-toggle {
padding: var(--space-xxs) var(--space-xs);
}
.mode-toggle-label {
font-size: 10px;
}
}
/* ===================================================================
PRINT STYLES - Optimized for documentation
=================================================================== */
@media print {
.mode-toggle-container {
display: none;
}
body {
background: white;
color: black;
}
}
</style>
<meta name="description" content="{{ meta_description }}">
</head>
<body>
<!-- Mode Toggle Component -->
<div class="mode-toggle-container">
<div class="mode-toggle" id="modeToggle" role="button" aria-label="Toggle dark/light mode" tabindex="0">
<span class="mode-toggle-label" id="modeLabel">CATHODE</span>
<div class="mode-toggle-switch"></div>
</div>
</div>
<!-- DWS Container with Grid System -->
<div class="dws-container">
<!-- Report Header -->
<header style="margin: var(--space-xl) 0;">
<h1 class="heading-02" style="color: var(--dws-accent-primary); margin-bottom: var(--space-xs);">DWS LLC NTP STATUS</h1>
<div class="code-small" style="color: var(--dws-text-secondary);">
<span class="label-sm">GENERATED:</span> <span class="code-small">{{ gen_time_utc }}</span>
</div>
</header>
<!-- Section 1: Current Time Synchronization -->
<div class="dws-panel">
<div class="dws-panel-header">Section 1: Current Time Synchronization</div>
<div class="dws-panel-body">
<div style="display: grid; gap: var(--space-sm);">
<div>
<span class="label-sm" style="color: var(--dws-text-secondary);">TIME:</span>
<span id="clock-time" class="code-default" style="margin-left: var(--space-sm);">--:--:--</span>
</div>
<div>
<span class="label-sm" style="color: var(--dws-text-secondary);">DATE:</span>
<span id="clock-date" class="code-default" style="margin-left: var(--space-sm);">----------</span>
</div>
<div>
<span class="label-sm" style="color: var(--dws-text-secondary);">STATUS:</span>
<span id="clock-status" class="code-default" style="margin-left: var(--space-sm);">Syncing...</span>
</div>
<div>
<span class="label-sm" style="color: var(--dws-text-secondary);">CLOCK OFFSET:</span>
<span id="clock-offset" class="code-default" style="margin-left: var(--space-sm);">---</span>
</div>
</div>
</div>
</div>
<!-- Section 2: Node Tracking Status Metrics -->
<div class="dws-panel">
<div class="dws-panel-header">Section 2: Node Tracking Status Metrics</div>
<div class="dws-panel-body">
{{ tracking_table_html | safe }}
</div>
</div>
<!-- Section 3: Upstream NTP Sources -->
<div class="dws-panel">
<div class="dws-panel-header">Section 3: Upstream NTP Sources</div>
<div class="dws-panel-body">
{{ sources_table_html | safe }}
</div>
</div>
<!-- Section 4: Metric Definitions & Developer Information -->
<div class="dws-panel">
<div class="dws-panel-header">Section 4: Metric Definitions & Developer Information</div>
<div class="dws-panel-body">
<h4 class="heading-05" style="color: var(--dws-accent-primary); margin-bottom: var(--space-sm);">Tracking Metrics Glossary</h4>
<dl style="margin-bottom: var(--space-lg);">
{{ tracking_glossary_html | safe }}
</dl>
<h4 class="heading-05" style="color: var(--dws-accent-primary); margin-bottom: var(--space-sm);">Sources Metrics Glossary</h4>
<dl style="margin-bottom: var(--space-lg);">
{{ sources_glossary_html | safe }}
</dl>
<h4 class="heading-05" style="color: var(--dws-accent-primary); margin-bottom: var(--space-sm);">Usage Information</h4>
<p class="body-default" style="margin-bottom: var(--space-sm);">
Use DWS as your NTP pool by setting <code class="code-default" style="color: var(--dws-accent-cta);">time.dws.rip</code> as your NTP source.
</p>
<div class="body-small" style="color: var(--dws-text-secondary); margin-top: var(--space-lg);">
<div>DWS LLC // "IT'S YOUR INTERNET, TAKE IT BACK" // https://dws.rip</div>
<div>DWS LLC // UNITED STATES OF AMERICA // 2025</div>
</div>
</div>
</div>
<!-- Report Footer -->
<footer style="margin: var(--space-xl) 0; padding-top: var(--space-lg); border-top: 1px solid var(--dws-border-primary);">
<div class="label-sm" style="color: var(--dws-text-tertiary); text-align: left;">
REPORT GENERATION COMPLETE {{ gen_time_utc }}<br>
END OF REPORT
</div>
</footer>
</div>
<!-- End DWS Container -->
<script>
// =====================================================================
// MODE TOGGLE LOGIC - DWS Design System
// =====================================================================
const html = document.documentElement;
const modeToggle = document.getElementById('modeToggle');
const modeLabel = document.getElementById('modeLabel');
// Initialize mode from localStorage or default to cathode
const savedMode = localStorage.getItem('dws-mode') || 'cathode';
html.setAttribute('data-mode', savedMode);
updateToggleUI(savedMode);
modeToggle.addEventListener('click', toggleMode);
modeToggle.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleMode();
}
});
function toggleMode() {
const currentMode = html.getAttribute('data-mode');
const newMode = currentMode === 'cathode' ? 'vellum' : 'cathode';
html.setAttribute('data-mode', newMode);
localStorage.setItem('dws-mode', newMode);
updateToggleUI(newMode);
}
function updateToggleUI(mode) {
if (mode === 'vellum') {
modeToggle.classList.add('active');
modeLabel.textContent = 'VELLUM';
} else {
modeToggle.classList.remove('active');
modeLabel.textContent = 'CATHODE';
}
}
// =====================================================================
// CLOCK SYNC LOGIC
// =====================================================================
const clockTimeSpan = document.getElementById('clock-time');
const clockDateSpan = document.getElementById('clock-date');
const clockStatusSpan = document.getElementById('clock-status');
const clockOffsetSpan = document.getElementById('clock-offset');
const isHistorical = {{ 'true' if is_historical else 'false' }};
let serverTimeOffsetMs = null;
let clockUpdateInterval = null;
let syncInterval = null;
function updateClock() {
if (serverTimeOffsetMs === null) return;
const now = new Date(new Date().getTime() + serverTimeOffsetMs);
const hours = String(now.getUTCHours()).padStart(2, '0');
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
const seconds = String(now.getUTCSeconds()).padStart(2, '0');
const timeString = `${hours}:${minutes}:${seconds}`;
const dateString = now.toLocaleDateString('en-US', {
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC'
}) + " (UTC)";
clockTimeSpan.textContent = timeString;
clockDateSpan.textContent = dateString;
}
async function syncClockAndOffset() {
clockStatusSpan.textContent = "Syncing...";
clockStatusSpan.className = "";
clockOffsetSpan.textContent = "---";
try {
const timeResponse = await fetch('/api/time');
if (!timeResponse.ok) throw new Error(`Time API Error ${timeResponse.status}`);
const timeData = await timeResponse.json();
const serverTime = new Date(timeData.time_utc).getTime();
const clientTime = new Date().getTime();
serverTimeOffsetMs = serverTime - clientTime;
clockStatusSpan.textContent = "Synced";
clockStatusSpan.className = "synced";
clockOffsetSpan.textContent = `${Math.abs(serverTimeOffsetMs)}ms ${serverTimeOffsetMs >= 0 ? 'ahead' : 'behind'}`;
if (!clockUpdateInterval) {
updateClock();
clockUpdateInterval = setInterval(updateClock, 1000);
}
} catch (error) {
console.error('Error syncing time/offset:', error);
clockStatusSpan.textContent = `Sync Error`;
clockStatusSpan.className = "error";
clockOffsetSpan.textContent = `---`;
serverTimeOffsetMs = 0;
if (!clockUpdateInterval) {
updateClock();
clockUpdateInterval = setInterval(updateClock, 1000);
}
}
}
function displayHistoricalTime(timestampStr) {
try {
const timestamp = new Date(timestampStr);
const hours = String(timestamp.getUTCHours()).padStart(2, '0');
const minutes = String(timestamp.getUTCMinutes()).padStart(2, '0');
const seconds = String(timestamp.getUTCSeconds()).padStart(2, '0');
const timeString = `${hours}:${minutes}:${seconds}`;
const dateString = timestamp.toLocaleDateString('en-US', {
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC'
}) + " (UTC)";
clockTimeSpan.textContent = timeString;
clockDateSpan.textContent = dateString;
clockStatusSpan.textContent = "Historical Snapshot";
clockStatusSpan.className = "status-info";
clockOffsetSpan.textContent = "N/A";
} catch (error) {
console.error('Error parsing historical timestamp:', error);
clockTimeSpan.textContent = timestampStr;
clockDateSpan.textContent = "Historical";
clockStatusSpan.textContent = "Snapshot";
clockStatusSpan.className = "status-info";
clockOffsetSpan.textContent = "N/A";
}
}
document.addEventListener('DOMContentLoaded', () => {
if (isHistorical) {
displayHistoricalTime("{{ gen_time_utc }}");
} else {
syncClockAndOffset();
syncInterval = setInterval(syncClockAndOffset, 60 * 1000);
}
});
</script>
</body>
</html>
"""
def get_reporter_ips(service_name):
try: _, _, ips = gethostbyname_ex(service_name); return ips
except Exception as e: print(f"Error resolving service IPs: {e}"); return []
# --- NEW: Helper to convert Ref time (UTC) ---
def format_ref_time(timestamp_str):
try:
ts = float(timestamp_str)
dt = datetime.utcfromtimestamp(ts)
return dt.strftime('%a %b %d %H:%M:%S %Y') + " (UTC)"
except:
return timestamp_str
# --- NEW: Helper to format floats nicely ---
def format_float(value_str, precision=3):
try:
f_val = float(value_str)
return f"{f_val:.{precision}f}"
except:
return value_str
# --- Flask Static Files Route ---
@app.route('/static/fonts/<path:filename>')
def serve_font(filename):
"""Serve font files from the fonts directory."""
return send_from_directory('fonts', filename)
@app.route('/api/time')
def get_server_time():
return jsonify({"time_utc": datetime.utcnow().isoformat() + "Z"})
@app.route('/api/fragments')
def get_fragments_json():
fragments = []
ips = get_reporter_ips(REPORTER_SERVICE)
for ip in ips:
try:
res = requests.get(f"http://{ip}:9898/fragment.json", timeout=1)
if res.status_code == 200: fragments.append(res.json())
except: pass
fragments.sort(key=lambda x: x.get("node_id", "z"))
return jsonify(fragments)
def format_value(value, max_len=25):
"""Truncates long values for table display."""
if value is None: return "N/A"
s_val = str(value)
if len(s_val) > max_len:
return s_val[:max_len-3] + "..."
return s_val
def format_glossary(metrics_defs):
"""Format metric definitions as terminal-style glossary."""
lines = []
for metric, definition in metrics_defs.items():
lines.append(f" {metric.ljust(20)} - {definition}")
return "\n".join(lines)
def format_glossary_html(metrics_defs):
"""Format metric definitions as HTML definition list."""
html_parts = []
for metric, definition in metrics_defs.items():
html_parts.append(f"<dt>{metric}</dt>")
html_parts.append(f"<dd>{definition}</dd>")
return "\n".join(html_parts)
def generate_tracking_table_html(fragments, nodes_list):
"""Generate HTML table for tracking metrics."""
if not fragments:
return '<p class="body-default" style="color: var(--dws-error);">ERROR: Could not fetch data from any reporter pods.</p>'
html = ['<table class="dws-table">']
# Table header
html.append('<thead><tr>')
html.append('<th>Metric</th>')
for node_id in nodes_list:
html.append(f'<th class="numeric">{node_id}</th>')
html.append('</tr></thead>')
# Table body
html.append('<tbody>')
for metric in TRACKING_METRICS_ORDER:
html.append('<tr>')
html.append(f'<td>{metric}</td>')
for node_id in nodes_list:
node_data = next((f for f in fragments if f.get("node_id") == node_id), None)
value = "N/A"
if node_data and isinstance(node_data.get("tracking"), dict):
raw_value = node_data["tracking"].get(metric, "N/A")
if metric == "Ref time (UTC)":
value = format_ref_time(raw_value)
elif metric in ["System time", "Last offset", "RMS offset", "Residual freq", "Skew", "Root delay", "Root dispersion"]:
value = format_float(raw_value, 6)
elif metric == "Frequency":
value = format_float(raw_value, 3)
elif metric == "Update interval":
value = format_float(raw_value, 1)
else:
value = format_value(raw_value)
html.append(f'<td class="numeric">{value}</td>')
html.append('</tr>')
html.append('</tbody>')
# Table footer with summary
html.append('<tfoot>')
html.append('<tr>')
html.append(f'<td colspan="{len(nodes_list) + 1}" style="text-align: left; padding-top: var(--space-sm); border-top: 2px solid var(--dws-border-primary);">')
html.append(f'<span class="label-sm">TOTAL NODES:</span> <span class="code-default">{len(nodes_list)}</span>')
html.append('</td>')
html.append('</tr>')
html.append('</tfoot>')
html.append('</table>')
return '\n'.join(html)
def generate_sources_table_html(fragments):
"""Generate HTML table for NTP sources."""
if not fragments:
return '<p class="body-default" style="color: var(--dws-error);">ERROR: Could not fetch data from any reporter pods.</p>'
html = ['<table class="dws-table">']
# Table header
html.append('<thead><tr>')
for col in SOURCES_COLUMNS_ORDER:
if col in ["Stratum", "Poll", "Reach", "LastRx", "Last sample", "Std Dev"]:
html.append(f'<th class="numeric">{col}</th>')
else:
html.append(f'<th>{col}</th>')
html.append('</tr></thead>')
# Table body
html.append('<tbody>')
node_source_counts = {}
for f in fragments:
node_id = f.get("node_id", "unknown")
sources = f.get("sources", [])
node_source_counts[node_id] = len(sources) if sources else 0
if not sources:
html.append('<tr>')
html.append(f'<td>{node_id}</td>')
html.append('<td>N/A</td>')
html.append('<td colspan="7">No sources reported</td>')
html.append('</tr>')
else:
for source in sources:
html.append('<tr>')
html.append(f'<td>{format_value(node_id, 24)}</td>')
html.append(f'<td>{source.get("Mode", "?")}{source.get("State", "?")}</td>')
html.append(f'<td>{format_value(source.get("Name/IP address", "N/A"), 32)}</td>')
html.append(f'<td class="numeric">{format_value(source.get("Stratum", "N/A"))}</td>')
html.append(f'<td class="numeric">{format_value(source.get("Poll", "N/A"))}</td>')
html.append(f'<td class="numeric">{format_value(source.get("Reach", "N/A"))}</td>')
html.append(f'<td class="numeric">{format_value(source.get("LastRx", "N/A"))}</td>')
html.append(f'<td class="numeric">{format_float(source.get("Last sample", "N/A"), 6)}</td>')
html.append(f'<td class="numeric">{format_float(source.get("Std Dev", "N/A"), 3)}</td>')
html.append('</tr>')
html.append('</tbody>')
# Table footer with summary
total_sources = sum(node_source_counts.values())
html.append('<tfoot>')
html.append('<tr>')
html.append(f'<td colspan="{len(SOURCES_COLUMNS_ORDER)}" style="text-align: left; padding-top: var(--space-sm); border-top: 2px solid var(--dws-border-primary);">')
html.append(f'<span class="label-sm">TOTAL SOURCES:</span> <span class="code-default">{total_sources}</span> | ')
html.append(f'<span class="label-sm">NODES REPORTING:</span> <span class="code-default">{len(node_source_counts)}</span>')
html.append('</td>')
html.append('</tr>')
html.append('</tfoot>')
html.append('</table>')
return '\n'.join(html)
def render_report(fragments, gen_time, is_historical=False):
"""Render NTP report from fragments data."""
meta_offset_ms = "N/A"
meta_leap_status = "Unknown"
nodes_list = [f.get("node_id", "unknown") for f in fragments]
# Calculate metadata for meta description
total_offset_seconds = 0.0
valid_offset_count = 0
leap_statuses = set()
if fragments:
for frag in fragments:
tracking = frag.get("tracking", {})
if isinstance(tracking, dict) and "Error" not in tracking:
leap = tracking.get("Leap status")
if leap:
leap_statuses.add(leap)
offset_str = tracking.get("Last offset", 0.1)
try:
offset_seconds = float(offset_str)
total_offset_seconds += offset_seconds
valid_offset_count += 1
except (TypeError, ValueError):
pass
if valid_offset_count > 0:
avg_offset_seconds = total_offset_seconds / valid_offset_count
meta_offset_ms = f"~{(avg_offset_seconds * 1000):.1f}ms"
if len(leap_statuses) == 1:
meta_leap_status = leap_statuses.pop()
elif len(leap_statuses) > 1:
meta_leap_status = "Mixed"
# Generate HTML tables
tracking_table_html = generate_tracking_table_html(fragments, nodes_list)
sources_table_html = generate_sources_table_html(fragments)
# Generate HTML glossaries
tracking_glossary_html = format_glossary_html(TRACKING_METRICS_DEFS)
sources_glossary_html = format_glossary_html(SOURCES_METRICS_DEFS)
return render_template_string(
HTML_TEMPLATE,
gen_time_utc=gen_time,
tracking_table_html=tracking_table_html,
sources_table_html=sources_table_html,
tracking_glossary_html=tracking_glossary_html,
sources_glossary_html=sources_glossary_html,
is_historical=is_historical,
meta_description=f"DWS NTP Pool: {meta_leap_status}. Avg Offset: {meta_offset_ms}."
)
@app.route('/')
def homepage():
"""Live NTP status - fetches current data from all nodes."""
fragments = []
ips = get_reporter_ips(REPORTER_SERVICE)
for ip in ips:
try:
res = requests.get(f"http://{ip}:9898/fragment.json", timeout=2)
if res.status_code == 200:
fragments.append(res.json())
else:
print(f"Failed fetch from {ip}: Status {res.status_code}")
except Exception as e:
print(f"Failed connect to {ip}: {e}")
fragments.sort(key=lambda x: x.get("node_id", "z"))
gen_time = subprocess.run(["date", "-u", "+%Y-%m-%dT%H:%M:%SZ"], capture_output=True, text=True).stdout.strip()
return render_report(fragments, gen_time)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)