Development
This commit is contained in:
parent
e98eb85c5a
commit
278995958a
6 changed files with 39 additions and 18 deletions
|
|
@ -4,7 +4,7 @@ import re
|
|||
|
||||
from flask import Blueprint, make_response, redirect, flash, request
|
||||
from auth import require_level
|
||||
from config_utils import load_core, save_core, verify_core_hash, queued_msg, CONFIGS_DIR, PRODUCT_DISPLAY_NAME
|
||||
from config_utils import load_core, save_core, verify_core_hash, queued_msg, CONFIGS_DIR, WEB_APP_DISPLAY_NAME
|
||||
import sanitize
|
||||
import validation as validate
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ def _build_client_conf(vlan, peer_name, peer_ip, private_key, server_pubkey):
|
|||
allowed_ips = f'{subnet}/{prefix}' if split_tunnel else '0.0.0.0/0'
|
||||
|
||||
lines = [
|
||||
f'# Generated by {PRODUCT_DISPLAY_NAME}',
|
||||
f'# Generated by {WEB_APP_DISPLAY_NAME}',
|
||||
'',
|
||||
'[Interface]',
|
||||
f'PrivateKey = {private_key}',
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import json, os, bcrypt, secrets, smtplib
|
|||
from datetime import datetime, timezone, timedelta
|
||||
from email.message import EmailMessage
|
||||
from auth import require_level
|
||||
from config_utils import PRODUCT_DISPLAY_NAME, ACCOUNTS_FILE
|
||||
from config_utils import WEB_APP_DISPLAY_NAME, ACCOUNTS_FILE
|
||||
import sanitize
|
||||
|
||||
bp = Blueprint('action_create_account', __name__)
|
||||
|
|
@ -30,7 +30,7 @@ def _send_verification_email(to_address, code):
|
|||
raise RuntimeError('SMTP_HOST is not configured.')
|
||||
|
||||
msg = EmailMessage()
|
||||
msg['Subject'] = f'{PRODUCT_DISPLAY_NAME} - Email Verification'
|
||||
msg['Subject'] = f'{WEB_APP_DISPLAY_NAME} - Email Verification'
|
||||
msg['From'] = from_addr
|
||||
msg['To'] = to_address
|
||||
msg.set_content(
|
||||
|
|
|
|||
|
|
@ -12,8 +12,10 @@ DASHBOARD_LAST_RUN = f'{CONFIGS_DIR}/.dashboard-last-run'
|
|||
DASHBOARD_LOCK = f'{CONFIGS_DIR}/.dashboard-lock'
|
||||
DASHBOARD_PENDING = f'{CONFIGS_DIR}/.dashboard-pending'
|
||||
STATUS_FILE = f'{CONFIGS_DIR}/.status'
|
||||
DASHB_TIMER_NAME = 'routlin-dashboard-queue'
|
||||
PRODUCT_DISPLAY_NAME = os.environ.get('PRODUCT_DISPLAY_NAME', 'Routlin Dashboard')
|
||||
PRODUCT_NAME = os.environ.get('PRODUCT_NAME', 'routlin')
|
||||
DASHB_TIMER_NAME = f'{PRODUCT_NAME}-dashboard-queue'
|
||||
DDNS_TIMER_NAME = f'{PRODUCT_NAME}-ddns-update'
|
||||
WEB_APP_DISPLAY_NAME = os.environ.get('WEB_APP_DISPLAY_NAME', f'{PRODUCT_NAME.capitalize()} Dashboard')
|
||||
DASHB_INTERVAL_SECS = 60
|
||||
QUEUE_MAX_LINES = 50
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import sanitize
|
|||
import validation as validate
|
||||
from datetime import datetime, timezone
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
from config_utils import core_hash, get_pending_entries, get_dashboard_pending, _seconds_until_next_run, _format_timing, _is_locked, _lock_mtime, PRODUCT_DISPLAY_NAME, CONFIGS_DIR, DATA_DIR
|
||||
from config_utils import core_hash, get_pending_entries, get_dashboard_pending, _seconds_until_next_run, _format_timing, _is_locked, _lock_mtime, WEB_APP_DISPLAY_NAME, CONFIGS_DIR, DATA_DIR, DDNS_TIMER_NAME
|
||||
|
||||
bp = Blueprint('view_page', __name__)
|
||||
|
||||
|
|
@ -540,7 +540,7 @@ def _public_ip_info(ddns_cfg):
|
|||
next_interval = '-'
|
||||
|
||||
def _last_obtained(mtime):
|
||||
return f'Last obtained: {_relative_time(mtime)}' if mtime else ''
|
||||
return f'Obtained: {_relative_time(mtime)}' if mtime else ''
|
||||
|
||||
# Path 1: timer healthy and within interval -> use cached IP
|
||||
if interval_secs and enabled_p:
|
||||
|
|
@ -565,6 +565,22 @@ def _public_ip_info(ddns_cfg):
|
|||
# Path 3: offline
|
||||
return 'DDNS Offline', domains_sub, next_interval, ''
|
||||
|
||||
def _ddns_last_checked():
|
||||
"""Return 'Last checked: X ago' based on when the DDNS timer last fired, or ''."""
|
||||
try:
|
||||
out = _run(f'systemctl show {DDNS_TIMER_NAME}.timer --property=LastTriggerUSec --timestamp=utc')
|
||||
val = out.split('=', 1)[1].strip() if '=' in out else ''
|
||||
if not val or val == '0' or val == 'n/a':
|
||||
return ''
|
||||
parts = val.split() # ['Mon', '2026-05-25', '04:28:00', 'UTC']
|
||||
if len(parts) >= 3:
|
||||
dt = datetime.strptime(f'{parts[1]} {parts[2]}', '%Y-%m-%d %H:%M:%S')
|
||||
mtime = dt.replace(tzinfo=timezone.utc).timestamp()
|
||||
return f'Last checked: {_relative_time(mtime)}'
|
||||
except Exception:
|
||||
pass
|
||||
return ''
|
||||
|
||||
def _vpn_info():
|
||||
for vlan in _load_core().get('vlans', []):
|
||||
if 'vpn_information' in vlan:
|
||||
|
|
@ -711,10 +727,11 @@ def collect_tokens():
|
|||
tokens['VPN_GATEWAY'] = ''
|
||||
|
||||
ip_str, sub_str, next_interval, last_obtained = _public_ip_info(ddns)
|
||||
tokens['STAT_PUBLIC_IP'] = ip_str
|
||||
tokens['STAT_DDNS_HOSTNAME'] = sub_str
|
||||
tokens['STAT_DDNS_NEXT_INTERVAL'] = next_interval
|
||||
tokens['STAT_PUBLIC_IP'] = ip_str
|
||||
tokens['STAT_DDNS_HOSTNAME'] = sub_str
|
||||
tokens['STAT_DDNS_NEXT_INTERVAL'] = next_interval
|
||||
tokens['STAT_PUBLIC_IP_LAST_OBTAINED'] = last_obtained
|
||||
tokens['STAT_PUBLIC_IP_LAST_CHECKED'] = _ddns_last_checked()
|
||||
tokens['DDNS_LOG_TAIL'], tokens['DDNS_LOG_SUMMARY'] = _ddns_log_tail()
|
||||
tokens['STAT_UPTIME'] = _run('uptime -p') or '-'
|
||||
tokens['STAT_NFTABLES_STATUS'] = 'Active' if _run('nft list tables 2>/dev/null').strip() else 'Inactive'
|
||||
|
|
@ -1451,9 +1468,9 @@ def _load_datasource(spec):
|
|||
def render_layout(view_id, content_html, tokens):
|
||||
css = _load_css()
|
||||
level = _client_level()
|
||||
titlebar_html = f'<div class="titlebar"><span class="titlebar-brand">{PRODUCT_DISPLAY_NAME}</span></div>'
|
||||
titlebar_html = f'<div class="titlebar"><span class="titlebar-brand">{WEB_APP_DISPLAY_NAME}</span></div>'
|
||||
navbar_html = _render_navbar(view_id, level, tokens)
|
||||
footer_html = f'<footer class="footer">{PRODUCT_DISPLAY_NAME}</footer>'
|
||||
footer_html = f'<footer class="footer">{WEB_APP_DISPLAY_NAME}</footer>'
|
||||
|
||||
page_hash = core_hash()
|
||||
lan_iface = e(tokens.get('GENERAL_LAN_INTERFACE', ''))
|
||||
|
|
@ -1535,8 +1552,10 @@ def render_layout(view_id, content_html, tokens):
|
|||
fix_items = ''.join(f'<li><code>{e(c)}</code></li>' for c in fix_cmds)
|
||||
fix_html = ('<div style="margin-top:0.5em">To fix:</div>'
|
||||
f'<ul style="margin:0.25em 0;padding-left:1.25em">{fix_items}</ul>')
|
||||
content = ('Health check — problems found:'
|
||||
+ problems_list + fix_html)
|
||||
content = ('<div style="width:100%">'
|
||||
'<div style="font-weight:600;margin-bottom:0.25em">Health check — problems found:</div>'
|
||||
+ problems_list + fix_html
|
||||
+ '</div>')
|
||||
problem_bars += f'<div class="info-bar {cls}">{content}</div>\n'
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -1544,7 +1563,7 @@ def render_layout(view_id, content_html, tokens):
|
|||
return (f'<!DOCTYPE html>\n<html lang="en">\n<head>\n'
|
||||
f' <meta charset="UTF-8"/>\n'
|
||||
f' <meta name="viewport" content="width=device-width, initial-scale=1.0"/>\n'
|
||||
f' <title>{PRODUCT_DISPLAY_NAME}</title>\n'
|
||||
f' <title>{WEB_APP_DISPLAY_NAME}</title>\n'
|
||||
f' <style>{css}</style>\n'
|
||||
f'</head>\n<body>\n'
|
||||
f'{titlebar_html}\n'
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@
|
|||
"type": "stat_card",
|
||||
"label": "Check Interval",
|
||||
"value": "%DDNS_TIMER_INTERVAL%",
|
||||
"sub": "next in %STAT_DDNS_NEXT_INTERVAL%"
|
||||
"sub": "%STAT_PUBLIC_IP_LAST_CHECKED%"
|
||||
},
|
||||
{
|
||||
"type": "stat_card",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ services:
|
|||
- /sys/devices:/sys/devices:ro
|
||||
environment:
|
||||
- PYTHONPATH=/routlin_location
|
||||
- PRODUCT_DISPLAY_NAME=Routlin Dashboard
|
||||
- WEB_APP_DISPLAY_NAME=Routlin Dashboard
|
||||
- INITIAL_MANAGER_EMAIL=mgrotke@gmail.com
|
||||
- SECRET_KEY=ey8hSQCCYE5kQXV8nOg1CB44LSd3AoUet2ZBc3aZlFrwBbazE7aHcxXWyuT97eAObet5jmOL0CjMg0rB1hE4d2SBVYHPfl8De55EiFv307r1QP3Mf5XgOSSCxD3TuD
|
||||
- SMTP_HOST=smtp.gmail.com
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue