#!/usr/bin/python3
"""Read-only, no-login site view. For temps and partners on site who need to see
what is up and what is down, and nothing else.

Gate is a token in the URL, checked against master_config misc.public_view:

    /status/view.py?k=<token>

  enabled=false  -> dead immediately, no restart, whatever the token says
  rotate         -> replace the token; old links stop working on the next request

This is NOT the mobile operator page with buttons removed. It loads no modal
files, renders no element carrying an action handler, and binds no click handler
beyond site navigation -- the action surface is absent, not hidden.

On what it exposes: /api/status is already public and unauthenticated by design
(api_guard keeps status_api_read/status_api_ws in audit_only while enforcing
/api/miner/*), so this page shows data anyone could already fetch. It makes that
data legible, it does not make it reachable. Actions stay guarded regardless of
who holds this URL.

No nav, no user identity, no page-key grant -- nothing here is tied to an account.
"""
import cgi
import cgitb
import hmac
import json
import sys

sys.path.insert(0, '/opt/ngon/apps')
from managers.config_manager import config

cgitb.enable(display=0, logdir='/opt/ngon/logs')


def _deny():
    """Same response for disabled, missing token and wrong token.

    404 rather than 403 on purpose: a wrong key should not confirm that a valid
    one exists, and a disabled view should look like nothing was ever here.
    """
    print("Status: 404 Not Found")
    print("Content-Type: text/html; charset=utf-8")
    print("X-Robots-Tag: noindex, nofollow")
    print("")
    print("<!DOCTYPE html><html><head><title>Not found</title></head>"
          "<body style='background:#1a1a1a;color:#888;font-family:sans-serif;"
          "padding:40px;text-align:center'>Not found</body></html>")
    sys.exit(0)


cfg = ((config._master_config or {}).get('misc', {}) or {}).get('public_view', {}) or {}
views = cfg.get('views') or []
supplied = str(cgi.FieldStorage().getvalue('k') or '')

# Walk every view rather than breaking at the first hit, so the work is the same
# whichever token was supplied, and compare each with compare_digest so a wrong
# key cannot be narrowed down a character at a time.
matched = None
for v in views:
    tok = str((v or {}).get('token') or '')
    if tok and hmac.compare_digest(supplied, tok) and (v or {}).get('enabled'):
        matched = v
if matched is None:
    _deny()

# sites=[] means every site; otherwise this view sees only what is listed.
allowed_sites = [str(x) for x in (matched.get('sites') or [])]
view_name = str(matched.get('name') or '')

print("Content-Type: text/html; charset=utf-8")
print("X-Robots-Tag: noindex, nofollow")
# The token sits in the URL, so keep it out of the Referer of anything this page
# links to, and out of any intermediary cache.
print("Referrer-Policy: no-referrer")
print("Cache-Control: no-store")
print("")

# Asset versions are stamped from file mtimes below rather than hand-written,
# matching index.py and m.py. This page loads the same StatusWidgets ->
# StatusData -> StatusView bundle, joined by `extends`, so a stale copy of a
# base class does not degrade it -- it kills it outright. This file carried
# status_widgets.js?v=2 across the commit that added genServicing() to that
# file, which is the bug that stranded phones on "Loading...".
_parts = []
def _w(chunk):
    _parts.append(str(chunk))

_w('''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="robots" content="noindex, nofollow">
<meta name="theme-color" content="#141414">
<title>NGON Site View</title>
<link rel="stylesheet" href="mobile.css?v=16">
<style>
/* Read-only: no menu, so the title takes the space the hamburger held. */
.mhead .title { margin-left: 4px; }
.msec > h2 { margin-top: 4px; }
</style>
</head>
<body>

<div class="mhead">
    <button class="mback" id="back" onclick="history.back()" aria-label="Back" style="display:none">&lsaquo;</button>
    <div class="title" id="title">Sites</div>
    <div class="mconn" id="conn" title="Live connection"></div>
</div>

<div id="app"><div class="empty">Loading&hellip;</div></div>

<script>
// [] means every site. StatusView renders these and nothing else.
window.NGON_VIEW_SITES = ''' + json.dumps(allowed_sites) + ''';
window.NGON_VIEW_NAME = ''' + json.dumps(view_name) + ''';
</script>
<script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
<!-- Order is mandatory: `extends` resolves at class-definition time.
     StatusWidgets -> StatusData -> StatusView.
     No modal files, no config controls, no nav: nothing that can act. -->
<script src="status_widgets.js?v=3"></script>
<script src="status_data.js?v=10"></script>
<script src="status_view.js?v=8"></script>
</body>
</html>''')

import os as _os, re as _re
_dir = _os.path.dirname(_os.path.abspath(__file__))
_v = int(max(_os.path.getmtime(_os.path.join(_dir, f)) for f in _os.listdir(_dir)
             if f.endswith(('.css', '.js')) and not f.startswith('.')))
_html = "\n".join(_parts)
_html = _re.sub(r'href="((?!https?://)[^"]+\.css)(?:\?v=\d+)?"', 'href="\\1?v=%d"' % _v, _html)
_html = _re.sub(r'src="((?!https?://)[^"]+\.js)(?:\?v=\d+)?"',  'src="\\1?v=%d"' % _v, _html)
print(_html)
