better formatting

This commit is contained in:
2025-10-22 19:00:56 -04:00
parent 5160a87290
commit 8bce752a19
2 changed files with 88 additions and 36 deletions

View File

@ -2,7 +2,7 @@
FROM python:3.10-slim FROM python:3.10-slim
# Install Python dependencies # Install Python dependencies
RUN pip install Flask requests tabulate texttable RUN pip install Flask requests tabulate
WORKDIR /app/static WORKDIR /app/static
RUN mkdir fonts RUN mkdir fonts

View File

@ -5,8 +5,9 @@ import json
from flask import Flask, render_template_string, jsonify from flask import Flask, render_template_string, jsonify
from socket import gethostbyname_ex from socket import gethostbyname_ex
from datetime import datetime from datetime import datetime
from texttable import Texttable # For ASCII tables from tabulate import tabulate # For ASCII tables
import time # For timestamp conversion import time # For timestamp conversion
import uuid # For report IDs
app = Flask(__name__) app = Flask(__name__)
@ -99,37 +100,50 @@ HTML_TEMPLATE = """<!DOCTYPE html>
<body> <body>
<pre> <pre>
$> ./dws_ntp_report $> ./dws_ntp_report
<b>**INFO**</b>: INITIALIZING DWS NTP MONITORING SYSTEM
<b>**INFO**</b>: COLLECTING DWS NTP POOL INFORMATION <b>**INFO**</b>: COLLECTING DWS NTP POOL INFORMATION
CURRENT TIME
================================= {{ report_header }}
<b>SECTION 1: CURRENT TIME SYNCHRONIZATION</b>
════════════════════════════════════════════════════════════════════════════════
TIME: <span id="clock-time">--:--:--</span> TIME: <span id="clock-time">--:--:--</span>
DATE: <span id="clock-date">----------</span> DATE: <span id="clock-date">----------</span>
STATUS: <span id="clock-status">Syncing...</span> STATUS: <span id="clock-status">Syncing...</span>
CLOCK OFFSET: <span id="clock-offset">---</span> CLOCK OFFSET: <span id="clock-offset">---</span>
════════════════════════════════════════════════════════════════════════════════
<b>**INFO**</b>: DETAILED METRICS: <b>SECTION 2: NODE TRACKING STATUS METRICS</b>
<b>**INFO**</b>: COLLECTING TRACKING STATUS METRICS: ════════════════════════════════════════════════════════════════════════════════
<b>**INFO**</b>: COLLECTING TRACKING STATUS METRICS FROM ALL NODES
<b>TRACKING STATUS</b>
{{ tracking_table_ascii }} {{ tracking_table_ascii }}
<b>**INFO**</b>: COLLECTING UPSTREAM SOURCES METRICS: ════════════════════════════════════════════════════════════════════════════════
<b>SECTION 3: UPSTREAM NTP SOURCES</b>
════════════════════════════════════════════════════════════════════════════════
<b>**INFO**</b>: COLLECTING UPSTREAM SOURCES METRICS FROM ALL NODES
<b>UPSTREAM SOURCES</b>
{{ sources_table_ascii }} {{ sources_table_ascii }}
<b>**INFO**</b>: REPORT COMPLETE ════════════════════════════════════════════════════════════════════════════════
<b>**INFO**</b>: DEVELOPER INFO
<b>SECTION 4: DEVELOPER INFORMATION</b>
════════════════════════════════════════════════════════════════════════════════
USE DWS AS YOUR NTP POOL BY SETTING time.dws.rip AS YOUR NTP SOURCE USE DWS AS YOUR NTP POOL BY SETTING time.dws.rip AS YOUR NTP SOURCE
<b>**INFO**</b>: DWS LLC // "IT'S YOUR INTERNET, TAKE IT BACK" // https://dws.rip <b>**INFO**</b>: DWS LLC // "IT'S YOUR INTERNET, TAKE IT BACK" // https://dws.rip
<b>**INFO**</b>: DWS LLC // UNITED STATES OF AMERICA // 2025 <b>**INFO**</b>: DWS LLC // UNITED STATES OF AMERICA // 2025
<b>**INFO**</b>: DWS NTP REPORT COMPLETE {{ gen_time_utc }}
════════════════════════════════════════════════════════════════════════════════
<b>**INFO**</b>: REPORT GENERATION COMPLETE {{ gen_time_utc }}
<b>**INFO**</b>: END OF REPORT
════════════════════════════════════════════════════════════════════════════════
</pre> </pre>
<script> <script>
@ -220,6 +234,23 @@ USE DWS AS YOUR NTP POOL BY SETTING time.dws.rip AS YOUR NTP SOURCE
</html> </html>
""" """
# --- US Graphics Company Style Helpers ---
def form_feed_separator(width=80):
"""Generate a form feed separator line like vintage computer printouts."""
return "" * width
def report_header(report_id, timestamp):
"""Generate a vintage-style report header."""
header = []
header.append(form_feed_separator(80))
header.append(f"REPORT ID: {report_id}".ljust(40) + f"GENERATED: {timestamp}".rjust(40))
header.append(form_feed_separator(80))
return "\n".join(header)
def section_separator(title, width=80):
"""Generate a section separator with title."""
return f"\n{title}\n{form_feed_separator(width)}\n"
def get_reporter_ips(service_name): def get_reporter_ips(service_name):
try: _, _, ips = gethostbyname_ex(service_name); return ips try: _, _, ips = gethostbyname_ex(service_name); return ips
except Exception as e: print(f"Error resolving service IPs: {e}"); return [] except Exception as e: print(f"Error resolving service IPs: {e}"); return []
@ -290,14 +321,8 @@ def homepage():
fragments.sort(key=lambda x: x.get("node_id", "z")) fragments.sort(key=lambda x: x.get("node_id", "z"))
nodes_list = [f.get("node_id", "unknown") for f in fragments] nodes_list = [f.get("node_id", "unknown") for f in fragments]
# 2. Generate ASCII Tracking Table # 2. Generate ASCII Tracking Table with tabulate
track_table = Texttable(max_width=0) tracking_rows = []
track_table.set_deco(Texttable.BORDER | Texttable.HEADER | Texttable.VLINES)
track_table.set_chars(['', '', '', '', '', '', '', '', '', '', ''])
track_table.set_cols_width(TRACKING_COL_WIDTHS[:len(nodes_list)+1]) # Dynamic width based on nodes found
track_table.header(["Metric"] + nodes_list)
track_table.set_cols_align(["l"] + ["r"] * len(nodes_list))
for metric in TRACKING_METRICS_ORDER: for metric in TRACKING_METRICS_ORDER:
row = [metric] row = [metric]
for node_id in nodes_list: for node_id in nodes_list:
@ -312,8 +337,19 @@ def homepage():
elif metric == "Update interval": value = format_float(raw_value, 1) elif metric == "Update interval": value = format_float(raw_value, 1)
else: value = format_value(raw_value) # Use generic formatter else: value = format_value(raw_value) # Use generic formatter
row.append(value) row.append(value)
track_table.add_row(row) tracking_rows.append(row)
tracking_table_ascii = track_table.draw()
# Add summary row
tracking_rows.append([form_feed_separator(18)] + [form_feed_separator(24)] * len(nodes_list))
tracking_rows.append([f"TOTAL NODES: {len(nodes_list)}"] + [""] * len(nodes_list))
tracking_table_ascii = tabulate(
tracking_rows,
headers=["Metric"] + nodes_list,
tablefmt="fancy_grid",
stralign="left",
numalign="right"
)
total_offset_seconds = 0.0 total_offset_seconds = 0.0
valid_offset_count = 0 valid_offset_count = 0
@ -347,22 +383,20 @@ def homepage():
meta_leap_status = "Mixed" meta_leap_status = "Mixed"
# else remains "Unknown" if no valid status found # else remains "Unknown" if no valid status found
# 3. Generate ASCII Sources Table (Rotated/Concatenated) # 3. Generate ASCII Sources Table with tabulate
source_table = Texttable(max_width=0)
source_table.set_deco(Texttable.BORDER | Texttable.HEADER | Texttable.VLINES)
source_table.set_chars(['', '', '', '', '', '', '', '', '', '', ''])
source_table.set_cols_width(SOURCES_COL_WIDTHS)
source_table.header(SOURCES_COLUMNS_ORDER)
source_table.set_cols_align(["l"] * 3 + ["r"] * 6) # Left align text, right align numbers
if not fragments: if not fragments:
sources_table_ascii = "ERROR: Could not fetch data from any reporter pods." sources_table_ascii = "ERROR: Could not fetch data from any reporter pods."
else: else:
sources_rows = []
node_source_counts = {}
for f in fragments: for f in fragments:
node_id = f.get("node_id", "unknown") node_id = f.get("node_id", "unknown")
sources = f.get("sources", []) sources = f.get("sources", [])
node_source_counts[node_id] = len(sources) if sources else 0
if not sources: if not sources:
source_table.add_row([node_id, "N/A", "No sources reported", "N/A", "N/A", "N/A", "N/A", "N/A", "N/A"]) sources_rows.append([node_id, "N/A", "No sources reported", "N/A", "N/A", "N/A", "N/A", "N/A", "N/A"])
else: else:
for source in sources: for source in sources:
row = [ row = [
@ -376,14 +410,32 @@ def homepage():
format_float(source.get("Last sample", "N/A"), 6), # Format sample offset format_float(source.get("Last sample", "N/A"), 6), # Format sample offset
format_float(source.get("Std Dev", "N/A"), 3) # Format Std Dev/Jitter format_float(source.get("Std Dev", "N/A"), 3) # Format Std Dev/Jitter
] ]
source_table.add_row(row) sources_rows.append(row)
sources_table_ascii = source_table.draw()
# Add summary section
sources_rows.append([form_feed_separator(SOURCES_COL_WIDTHS[0]-2)] + [form_feed_separator(w-2) for w in SOURCES_COL_WIDTHS[1:]])
total_sources = sum(node_source_counts.values())
summary_text = f"TOTAL SOURCES: {total_sources} | NODES REPORTING: {len(node_source_counts)}"
sources_rows.append([summary_text] + [""] * (len(SOURCES_COLUMNS_ORDER) - 1))
sources_table_ascii = tabulate(
sources_rows,
headers=SOURCES_COLUMNS_ORDER,
tablefmt="fancy_grid",
stralign="left",
numalign="right"
)
gen_time = subprocess.run(["date", "-u", "+%Y-%m-%dT%H:%M:%SZ"], capture_output=True, text=True).stdout.strip() gen_time = subprocess.run(["date", "-u", "+%Y-%m-%dT%H:%M:%SZ"], capture_output=True, text=True).stdout.strip()
# Generate report header
report_id = str(uuid.uuid4())[:8].upper()
report_header_text = report_header(report_id, gen_time)
return render_template_string( return render_template_string(
HTML_TEMPLATE, HTML_TEMPLATE,
gen_time_utc=gen_time, gen_time_utc=gen_time,
report_header=report_header_text,
tracking_table_ascii=tracking_table_ascii, tracking_table_ascii=tracking_table_ascii,
sources_table_ascii=sources_table_ascii, sources_table_ascii=sources_table_ascii,
meta_description=f"DWS NTP Pool: {meta_leap_status}. Avg Offset: {meta_offset_ms}.", meta_description=f"DWS NTP Pool: {meta_leap_status}. Avg Offset: {meta_offset_ms}.",