better formatting
This commit is contained in:
@ -2,7 +2,7 @@
|
||||
FROM python:3.10-slim
|
||||
|
||||
# Install Python dependencies
|
||||
RUN pip install Flask requests tabulate texttable
|
||||
RUN pip install Flask requests tabulate
|
||||
|
||||
WORKDIR /app/static
|
||||
RUN mkdir fonts
|
||||
|
||||
120
frontend/main.py
120
frontend/main.py
@ -5,8 +5,9 @@ import json
|
||||
from flask import Flask, render_template_string, jsonify
|
||||
from socket import gethostbyname_ex
|
||||
from datetime import datetime
|
||||
from texttable import Texttable # For ASCII tables
|
||||
from tabulate import tabulate # For ASCII tables
|
||||
import time # For timestamp conversion
|
||||
import uuid # For report IDs
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@ -99,37 +100,50 @@ HTML_TEMPLATE = """<!DOCTYPE html>
|
||||
<body>
|
||||
<pre>
|
||||
$> ./dws_ntp_report
|
||||
<b>**INFO**</b>: INITIALIZING DWS NTP MONITORING SYSTEM
|
||||
<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>
|
||||
DATE: <span id="clock-date">----------</span>
|
||||
STATUS: <span id="clock-status">Syncing...</span>
|
||||
CLOCK OFFSET: <span id="clock-offset">---</span>
|
||||
════════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
<b>**INFO**</b>: DETAILED METRICS:
|
||||
<b>**INFO**</b>: COLLECTING TRACKING STATUS METRICS:
|
||||
<b>SECTION 2: NODE TRACKING STATUS METRICS</b>
|
||||
════════════════════════════════════════════════════════════════════════════════
|
||||
<b>**INFO**</b>: COLLECTING TRACKING STATUS METRICS FROM ALL NODES
|
||||
|
||||
|
||||
<b>TRACKING STATUS</b>
|
||||
{{ 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 }}
|
||||
|
||||
<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
|
||||
|
||||
|
||||
<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 NTP REPORT COMPLETE {{ gen_time_utc }}
|
||||
|
||||
════════════════════════════════════════════════════════════════════════════════
|
||||
<b>**INFO**</b>: REPORT GENERATION COMPLETE {{ gen_time_utc }}
|
||||
<b>**INFO**</b>: END OF REPORT
|
||||
════════════════════════════════════════════════════════════════════════════════
|
||||
</pre>
|
||||
|
||||
<script>
|
||||
@ -220,6 +234,23 @@ USE DWS AS YOUR NTP POOL BY SETTING time.dws.rip AS YOUR NTP SOURCE
|
||||
</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):
|
||||
try: _, _, ips = gethostbyname_ex(service_name); return ips
|
||||
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"))
|
||||
nodes_list = [f.get("node_id", "unknown") for f in fragments]
|
||||
|
||||
# 2. Generate ASCII Tracking Table
|
||||
track_table = Texttable(max_width=0)
|
||||
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))
|
||||
|
||||
# 2. Generate ASCII Tracking Table with tabulate
|
||||
tracking_rows = []
|
||||
for metric in TRACKING_METRICS_ORDER:
|
||||
row = [metric]
|
||||
for node_id in nodes_list:
|
||||
@ -312,8 +337,19 @@ def homepage():
|
||||
elif metric == "Update interval": value = format_float(raw_value, 1)
|
||||
else: value = format_value(raw_value) # Use generic formatter
|
||||
row.append(value)
|
||||
track_table.add_row(row)
|
||||
tracking_table_ascii = track_table.draw()
|
||||
tracking_rows.append(row)
|
||||
|
||||
# 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
|
||||
valid_offset_count = 0
|
||||
@ -347,22 +383,20 @@ def homepage():
|
||||
meta_leap_status = "Mixed"
|
||||
# else remains "Unknown" if no valid status found
|
||||
|
||||
# 3. Generate ASCII Sources Table (Rotated/Concatenated)
|
||||
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
|
||||
|
||||
# 3. Generate ASCII Sources Table with tabulate
|
||||
if not fragments:
|
||||
sources_table_ascii = "ERROR: Could not fetch data from any reporter pods."
|
||||
else:
|
||||
sources_rows = []
|
||||
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:
|
||||
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:
|
||||
for source in sources:
|
||||
row = [
|
||||
@ -376,14 +410,32 @@ def homepage():
|
||||
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
|
||||
]
|
||||
source_table.add_row(row)
|
||||
sources_table_ascii = source_table.draw()
|
||||
sources_rows.append(row)
|
||||
|
||||
# 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()
|
||||
|
||||
# Generate report header
|
||||
report_id = str(uuid.uuid4())[:8].upper()
|
||||
report_header_text = report_header(report_id, gen_time)
|
||||
|
||||
return render_template_string(
|
||||
HTML_TEMPLATE,
|
||||
gen_time_utc=gen_time,
|
||||
report_header=report_header_text,
|
||||
tracking_table_ascii=tracking_table_ascii,
|
||||
sources_table_ascii=sources_table_ascii,
|
||||
meta_description=f"DWS NTP Pool: {meta_leap_status}. Avg Offset: {meta_offset_ms}.",
|
||||
|
||||
Reference in New Issue
Block a user