#!/usr/bin/python3
import cgi
import sys
import os
import time
sys.path.insert(0, '/opt/ngon/apps')
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from managers.auth_manager import AuthManager, generate_login_page_html, generate_access_denied_html
from links import generate_dropdown_html, generate_dropdown_css, generate_dropdown_js

# Authentication
form = cgi.FieldStorage()

auth = AuthManager('status')
auth_required, should_exit, headers = auth.require_auth(form)

if should_exit:
    print("Content-Type: application/json")
    if headers:
        print(headers)
    print("")
    if auth_required:
        print('{"success": false, "error": "Authentication required"}')
    else:
        print('{"success": true}')
    sys.exit(0)

# Handle logout (must come BEFORE the text/html headers below). Clears the
# domain-scoped session cookie, which also signs the user out of the other
# *.ngon.us apps — intended, since this exists so people can get off the shared
# 'ngon' account and back on as themselves.
if not auth_required and form.getvalue('logout'):
    print("Content-Type: application/json")
    print(AuthManager.logout_cookie_header())
    print("")
    print('{"success": true}')
    sys.exit(0)

# Handle password change request (must come BEFORE the text/html headers below)
if not auth_required and form.getvalue('change_password'):
    import json
    current_pw = form.getvalue('current_password', '')
    new_pw = form.getvalue('new_password', '')
    username = AuthManager.get_current_user()
    success, error, cookie_header = AuthManager.change_password(username, current_pw, new_pw)
    print("Content-Type: application/json")
    if cookie_header:
        print(cookie_header)
    print("")
    if success:
        print(json.dumps({"success": True}))
    else:
        print(json.dumps({"success": False, "error": error}))
    sys.exit(0)

# Cache-busting version based on most recent file modification
_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('.')))

print("Content-Type: text/html")
print("Cache-Control: no-cache, no-store, must-revalidate")
print("Pragma: no-cache")
print("Expires: 0")
print()

# If not authenticated, show login page
if auth_required:
    # Check if logged in but no access (vs not logged in at all)
    username = auth.is_authenticated()
    if username:
        print(generate_login_page_html("NGON Status Dashboard").replace(
            '</body>', generate_access_denied_html("NGON Status Dashboard") + '</body>'
        ).replace('display: block;', 'display: none;'))
    else:
        print(generate_login_page_html("NGON Status Dashboard"))
    sys.exit(0)

# Get user access for filtered nav
_user_access = AuthManager.get_user_access()
_current_user = AuthManager.get_current_user() or 'STATUS'

# Whether this user may restart services (Site Manager access)
_can_restart_services = AuthManager('site_manager').check_access(AuthManager.get_current_user() or '')

# Whether this user may issue generator control commands (Gen Control access)
_can_gen_control = AuthManager('gen_control').check_access(AuthManager.get_current_user() or '')

# Clean, minimal HTML - all logic in status_widgets.js / status_data.js / status_desktop.js
html = '''<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Expires" content="0">
    <title>NGON Status Dashboard</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
    <link rel="stylesheet" href="styles.css?v=2">
    <style>
        /* Override body background to match other pages */
        body {
            background: #1a1a1a !important;
        }
        
        /* Override header styling to match other pages */
        .header {
            background-color: rgba(255,255,255,0.05) !important;
            border-radius: 8px !important;
            padding: 30px !important;
            margin-bottom: 30px !important;
            border: 1px solid rgba(255,255,255,0.1) !important;
            border-bottom: none !important;
            margin: 20px !important;
            display: flex !important;
            justify-content: space-between !important;
            align-items: flex-start !important;
        }
        
        /* Override other backgrounds to use rgba and add borders */
        .sites-section {
            background-color: rgba(255,255,255,0.05) !important;
            border: 1px solid rgba(255,255,255,0.1) !important;
        }
        
        .column {
            background-color: rgba(255,255,255,0.05) !important;
            border: 1px solid rgba(255,255,255,0.1) !important;
        }
        
        .summary-box {
            background-color: rgba(255,255,255,0.08) !important;
            border: 1px solid rgba(255,255,255,0.1) !important;
        }
        
        .site-card {
            background-color: rgba(255,255,255,0.08) !important;
            border: 1px solid rgba(255,255,255,0.1) !important;
        }
        
        .group-box {
            background-color: rgba(255,255,255,0.08) !important;
            border: 1px solid rgba(255,255,255,0.1) !important;
        }
        
        /* Ensure title font size matches other pages */
        .header h1.dropdown-title {
            font-size: 2.2em !important;
            font-weight: 600 !important;
            margin: 3px 0 0 0 !important;
            line-height: 1.2 !important;
        }
    </style>
</head>
<body>
    <div class="header">
        <div>
            <div class="dropdown">
                <h1 class="dropdown-title">NGON Mining - Status</h1>
                <div class="dropdown-content">''' + generate_dropdown_html(_user_access) + '''
                </div>
            </div>
        </div>
        <div class="data-rate-info">
            <span class="gen-search-wrap">
                <span class="gen-search-field" id="genSearchField">
                    <i class="fas fa-search gen-search-icon"></i>
                    <input id="genSearch" class="gen-search" type="text" placeholder="Find gen &mdash; e.g. -315"
                           autocomplete="off" spellcheck="false"
                           title="Partial generator ID. Enter re-centres the first match, Esc clears.">
                    <button type="button" id="genSearchClear" class="gen-search-clear"
                            tabindex="-1" aria-label="Clear search" title="Clear (Esc)">&times;</button>
                </span>
                <span id="genSearchCount" class="gen-search-count"></span>
            </span>
            <a href="#" onclick="showPasswordModal(); return false;" style="color: #00ff00; text-decoration: none; margin-right: 15px;" title="Change password">''' + _current_user + '''</a>
            Data Rate: <span id="dataRate">-- KB/s</span>
            <span id="connectionStatus" style="margin-left: 15px;">🔴 Connecting...</span>
            <span id="scannerStatus" onclick="showScannerModal()" style="margin-left: 15px; cursor: pointer;" title="Click to view scanner details">🔴 Scanner</span>
            <span id="watcherStatus" onclick="showWatcherModal()" style="margin-left: 15px; cursor: pointer;" title="Click to view Foreman Pickaxe watcher details">🔴 Watcher</span>
            <span id="servicesStatus" onclick="showServicesModal()" style="margin-left: 15px; cursor: pointer;" title="Click to view backend service status">🔴 Services</span>
        </div>
    </div>
    
    <!-- Sites Section -->
    <div class="sites-section">
        <h2>Sites Overview</h2>
        <div id="sitesGrid" class="sites-grid"></div>
    </div>
    
    <!-- Main Content -->
    <div class="container">
        <!-- Peps/Pods Column - nth-child(1) -->
        <div class="column column-peps">
            <h2>Peps/Pods</h2>
            <div style="font-size: 12px; color: #888; margin-top: -10px; margin-bottom: 10px;">
                Hashing / Installed (Not Hashing) - Hashrate
            </div>
            <ul id="pepsPodsList"></ul>
        </div>
        
        <!-- Generators TX Column - nth-child(2) -->
        <div class="column column-gens">
            <h2>Generators-TX <span class="oos-wrench" title="Move OOS gens — TX" onclick="showOosGens('Out of Service - TX')"><i class="fas fa-wrench"></i> <span id="oosCount-TX"></span></span></h2>
            <ul></ul>
        </div>

        <!-- Generators NDS Column - nth-child(3) -->
        <div class="column column-gens">
            <h2>Generators-DNSW <span class="oos-wrench" title="Move OOS gens — ND" onclick="showOosGens('Out of Service - ND')"><i class="fas fa-wrench"></i> <span id="oosCount-NDS"></span></span></h2>
            <ul></ul>
        </div>

        <!-- Generators GW Column - nth-child(4) -->
        <div class="column column-gens">
            <h2>Generators-GW <span class="oos-wrench" title="Move OOS gens — ND" onclick="showOosGens('Out of Service - ND')"><i class="fas fa-wrench"></i> <span id="oosCount-GW"></span></span></h2>
            <ul></ul>
        </div>
        
        <!-- Summary Column - nth-child(5) -->
        <div class="column column-notes">
            <h2>Summary</h2>
            
            <div class="summary-box">
                <h3 onclick="showPerformanceModal()" style="cursor: pointer; transition: color 0.2s ease;" onmouseover="this.style.color='#4a9eff'" onmouseout="this.style.color='#00ff00'">Hashrate</h3>
                <div class="summary-value" id="totalHashrate">0 PH/s</div>
                <div id="hashrateMax" style="text-align: right; color: #00ff00; font-size: 11px; font-weight: bold; margin-bottom: 3px;">0 PH/s</div>
                <div class="progress-bar">
                    <div id="hashrateProgress" class="progress-fill" style="width: 0%"></div>
                </div>
                <div class="summary-detail" id="hashrateDetail">TX: 0 | ND: 0</div>
            </div>
            
            <div class="summary-box">
                <h3 onclick="showPowerModal()" style="cursor: pointer; transition: color 0.2s ease;" onmouseover="this.style.color='#4a9eff'" onmouseout="this.style.color='#00ff00'">Power</h3>
                <div class="summary-value" id="totalPower">0 MW</div>
                <div id="powerMax" style="text-align: right; color: #00ff00; font-size: 11px; font-weight: bold; margin-bottom: 3px;">0 MW</div>
                <div class="progress-bar">
                    <div id="powerProgress" class="progress-fill" style="width: 0%"></div>
                </div>
                <div class="summary-detail" id="powerDetail">Could be using 0 MW more</div>
            </div>
            
            <div class="summary-box">
                <h3 onclick="showGasModal()" style="cursor: pointer; transition: color 0.2s ease;" onmouseover="this.style.color='#4a9eff'" onmouseout="this.style.color='#00ff00'">Gas</h3>
                <div class="summary-value" id="totalGas">0 MCF/d</div>
                <div class="summary-detail" id="gasDetail">0 cuft/day</div>
            </div>

            <div class="summary-box">
                <h3 onclick="showMinersModal()" style="cursor: pointer; transition: color 0.2s ease;" onmouseover="this.style.color='#4a9eff'" onmouseout="this.style.color='#00ff00'">Miners</h3>
                <div class="summary-value" id="minersHashing">0</div>
                <div id="minersMax" style="text-align: right; color: #00ff00; font-size: 11px; font-weight: bold; margin-bottom: 3px;">0</div>
                <div class="progress-bar">
                    <div id="minersProgress" class="progress-fill" style="width: 0%"></div>
                </div>
                <div class="summary-detail" id="minersDetail">Zero Hash: 0 | Sleeping: 0 | Offline: 0</div>
            </div>
            
            <div class="summary-box">
                <h3>Site Issues
                    <a href="/status/how_to_site_issues.py" target="_blank" title="What this panel flags and the rules behind it"
                       style="float:right;font-size:12px;font-weight:normal;color:#4a9eff;text-decoration:none;">
                        <i class="fas fa-circle-question"></i></a>
                </h3>
                <div id="siteIssuesPanel">
                    <span style="color: #666; font-size: 13px;">Loading…</span>
                </div>
            </div>
            
            <div class="summary-box">
                <h3>Notes</h3>
                <div id="statusNotes" onclick="editStatusNotes()"
                     style="font-size: 13px; cursor: pointer; min-height: 40px; white-space: pre-wrap; color: #e0e0e0;"
                     title="Click to edit">
                    <span style="color: #666; font-style: italic;">Click to add notes...</span>
                </div>
            </div>

            <div class="summary-box">
                <h3>Weather</h3>
                <div id="weatherList" style="font-size: 13px;">
                    <!-- Weather data will be loaded here -->
                </div>
            </div>
            
            <div class="summary-box">
                <h3>Power Control Log</h3>
                <div id="powerControlLog" style="font-size: 12px;">
                    <!-- Power control log entries -->
                </div>
            </div>
            
            <div class="summary-box">
                <h3>Miner Monitor Log</h3>
                <div id="minerMonitorLog" style="font-size: 12px;">
                    <!-- Miner monitor log entries -->
                </div>
            </div>

            <div class="summary-box">
                <h3>Automated Sleep/Wake Log</h3>
                <div id="autoSleepWakeLog" style="font-size: 12px;">
                    <!-- Automated sleep/wake (AW/AS) log entries -->
                </div>
            </div>
        </div>
    </div>
    
    <!-- Scripts -->
    <script>window.ngonUser = "''' + _current_user + '''";</script>
    <script>window.canRestartServices = ''' + ('true' if _can_restart_services else 'false') + ''';</script>
    <script>window.canGenControl = ''' + ('true' if _can_gen_control else 'false') + ''';</script>
    <!-- Chart.js v3.9.1 (UMD format, no module issues) -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
    <!-- Chart.js date adapter for time scale -->
    <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
    <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
    <link rel="stylesheet" href="shutdown_history.css?v=2">
    <link rel="stylesheet" href="/shared/miner_view.css">
    <script src="/shared/miner_view.js"></script>
    <script src="constants_cgi.py"></script>
    <script src="status_widgets.js?v=3"></script>
    <script src="status_data.js?v=8"></script>
    <script src="status_desktop.js?v=7"></script>
    
    <!-- Modal Scripts - preloaded for reliability -->
    <script src="status_modal_utils.js"></script>
    <script src="status_performance_modal.js?v=3"></script>
    <script src="status_power_modal.js?v=4"></script>
    <script src="status_gas_modal.js?v=2"></script>
    <script src="status_miners_modal.js?v=2"></script>
    <script src="status_site_modal.js?v=4"></script>
    <script src="shutdown_history.js?v=1"></script>
    <script src="status_gen_modal.js?v=17"></script>
    <script src="status_pod_modal.js"></script>
    <script src="status_miner_actions_modal.js"></script>
    <script src="status_gen_group_modal.js"></script>
    <script src="status_scanner_modal.js"></script>
    <script src="status_watcher_modal.js"></script>
    <script src="status_services_modal.js"></script>
    <script src="status_config_controls.js?v=3"></script>

    <script>
        // App is instantiated and init()-ed at the bottom of status_desktop.js
        // Modal classes are auto-initialized by their respective scripts
        
        // Modal functions - modals are now preloaded
        function showPowerModal() {
            if (window.powerModal) {
                window.powerModal.show();
            } else {
                console.error('Power modal not available');
            }
        }

        function showGasModal() {
            if (window.gasModal) {
                window.gasModal.show();
            } else {
                console.error('Gas modal not available');
            }
        }
        
        function showMinersModal() {
            if (window.minersModal) {
                window.minersModal.show();
            } else {
                console.error('Miners modal not available');
            }
        }
        
        function showPerformanceModal() {
            if (window.performanceModal) {
                window.performanceModal.show();
            } else {
                console.error('Performance modal not available');
            }
        }

        function showSiteModal(siteName) {
            if (window.siteModal) {
                window.siteModal.show(siteName);
            } else {
                console.error('Site modal not available');
            }
        }

        function showGenModal(genId, siteName) {
            if (window.genModal) {
                window.genModal.show(genId, siteName);
            } else {
                console.error('Generator modal not available');
            }
        }

        function showPodModal(podName) {
            if (window.podModal) {
                window.podModal.show(podName);
            } else {
                console.error('Pod modal not available');
            }
        }

        function editStatusNotes() {
            const currentNotes = window.statusCore?.currentData?.status_notes || '';
            const escapedNotes = currentNotes.replace(/</g, '&lt;').replace(/>/g, '&gt;');

            const popupHTML = `
                <div id="statusNotesOverlay" onclick="closeStatusNotesPopup()" style="
                    position: fixed;
                    top: 0;
                    left: 0;
                    right: 0;
                    bottom: 0;
                    background: rgba(0,0,0,0.5);
                    z-index: 10000;
                "></div>
                <div id="statusNotesPopup" style="
                    position: fixed;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    background: #1a1a1a;
                    border: 1px solid #333;
                    border-radius: 8px;
                    padding: 20px;
                    z-index: 10001;
                    min-width: 500px;
                    max-width: 700px;
                    box-shadow: 0 4px 20px rgba(0,0,0,0.5);
                ">
                    <h3 style="color: #00ff00; margin: 0 0 15px 0;">Edit Status Notes</h3>
                    <textarea id="statusNotesText" aria-label="Status notes" style="
                        width: 100%;
                        height: 150px;
                        background: #2a2a2a;
                        border: 1px solid #444;
                        border-radius: 4px;
                        color: #e0e0e0;
                        padding: 10px;
                        font-family: inherit;
                        font-size: 14px;
                        resize: vertical;
                        box-sizing: border-box;
                    ">${escapedNotes}</textarea>
                    <div style="margin-top: 15px; display: flex; gap: 10px; justify-content: flex-end;">
                        <button onclick="closeStatusNotesPopup()" style="
                            padding: 8px 16px;
                            background: #333;
                            border: 1px solid #444;
                            border-radius: 4px;
                            color: #888;
                            cursor: pointer;
                        ">Cancel</button>
                        <button onclick="clearStatusNotes()" style="
                            padding: 8px 16px;
                            background: #442222;
                            border: 1px solid #663333;
                            border-radius: 4px;
                            color: #ff6666;
                            cursor: pointer;
                        ">Clear</button>
                        <button onclick="saveStatusNotes()" style="
                            padding: 8px 16px;
                            background: #224422;
                            border: 1px solid #336633;
                            border-radius: 4px;
                            color: #00ff00;
                            cursor: pointer;
                        ">Save</button>
                    </div>
                </div>
            `;

            document.body.insertAdjacentHTML('beforeend', popupHTML);
            document.getElementById('statusNotesText').focus();
        }

        function closeStatusNotesPopup() {
            const popup = document.getElementById('statusNotesPopup');
            const overlay = document.getElementById('statusNotesOverlay');
            if (popup) popup.remove();
            if (overlay) overlay.remove();
        }

        async function saveStatusNotes() {
            const noteText = document.getElementById('statusNotesText').value;

            try {
                const response = await fetch('/api/config/update', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({
                        path: ['status_notes'],
                        value: noteText || ''
                    })
                });

                const result = await response.json();

                if (result.success) {
                    closeStatusNotesPopup();
                    // WebSocket will broadcast the update, but update locally too
                    if (window.statusCore) {
                        window.statusCore.currentData.status_notes = noteText;
                        window.statusCore.updateStatusNotes();
                    }
                } else {
                    alert('Error saving notes: ' + (result.error || 'Unknown error'));
                }
            } catch (error) {
                alert('Error saving notes: ' + error.message);
            }
        }

        async function clearStatusNotes() {
            if (!confirm('Clear all status notes?')) return;
            document.getElementById('statusNotesText').value = '';
            await saveStatusNotes();
        }

        // Add debug info to console
        window.addEventListener('load', function() {
            console.log('Page loaded. Chart.js available:', typeof Chart !== 'undefined');
            console.log('Modals will be loaded on demand');
        });
        
        // Add dropdown navigation JavaScript
        ''' + generate_dropdown_js() + '''

        // Password change modal
        function showPasswordModal() {
            const modalHTML = `
                <div id="pwOverlay" onclick="closePasswordModal()" style="
                    position: fixed; top: 0; left: 0; right: 0; bottom: 0;
                    background: rgba(0,0,0,0.5); z-index: 10000;
                "></div>
                <div id="pwModal" style="
                    position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
                    background: #1a1a1a; border: 2px solid #00ff00; border-radius: 8px;
                    padding: 20px; z-index: 10001; min-width: 320px; max-width: 400px;
                    box-shadow: 0 4px 20px rgba(0,255,0,0.3);
                ">
                    <h3 style="color: #00ff00; margin: 0 0 15px 0;">Account</h3>
                    <p style="color: #888; font-size: 12px; margin: 0 0 15px 0;">Logged in as: <strong style="color: #00ff00;">''' + _current_user + '''</strong></p>
                    <label style="color: #00ff00; font-size: 12px;">Current Password:</label>
                    <input type="password" id="pwCurrent" aria-label="Current password" style="
                        width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #3a3a3a;
                        color: white; border-radius: 4px; font-size: 13px; box-sizing: border-box; margin-bottom: 10px;
                    " autocomplete="current-password">
                    <label style="color: #00ff00; font-size: 12px;">New Password:</label>
                    <input type="password" id="pwNew" aria-label="New password" style="
                        width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #3a3a3a;
                        color: white; border-radius: 4px; font-size: 13px; box-sizing: border-box; margin-bottom: 10px;
                    " autocomplete="new-password">
                    <label style="color: #00ff00; font-size: 12px;">Confirm New Password:</label>
                    <input type="password" id="pwConfirm" aria-label="Confirm new password" style="
                        width: 100%; padding: 8px; background: #2a2a2a; border: 1px solid #3a3a3a;
                        color: white; border-radius: 4px; font-size: 13px; box-sizing: border-box; margin-bottom: 10px;
                    " autocomplete="new-password">
                    <div id="pwError" style="color: #ff6b6b; font-size: 12px; display: none; margin-bottom: 10px;"></div>
                    <div id="pwSuccess" style="color: #00ff00; font-size: 12px; display: none; margin-bottom: 10px;"></div>
                    <div style="display: flex; gap: 8px; justify-content: flex-end; margin-top: 15px;">
                        <button onclick="closePasswordModal()" style="
                            padding: 8px 16px; background: #333; border: 1px solid #444;
                            border-radius: 4px; color: #888; cursor: pointer;
                        ">Cancel</button>
                        <button onclick="submitPasswordChange()" style="
                            padding: 8px 16px; background: #224422; border: 1px solid #336633;
                            border-radius: 4px; color: #00ff00; cursor: pointer;
                        ">Save</button>
                    </div>
                    <div style="border-top: 1px solid #3a3a3a; margin-top: 18px; padding-top: 14px;
                                display: flex; align-items: center; justify-content: space-between; gap: 10px;">
                        <span style="color: #666; font-size: 11px;">Signs you out of all ngon.us apps</span>
                        <button onclick="submitLogout()" style="
                            padding: 8px 16px; background: #3a2222; border: 1px solid #663333;
                            border-radius: 4px; color: #ff9a9a; cursor: pointer; white-space: nowrap;
                        ">Log out</button>
                    </div>
                </div>
            `;
            document.body.insertAdjacentHTML('beforeend', modalHTML);
            document.getElementById('pwCurrent').focus();

            // Enter key support
            document.getElementById('pwConfirm').addEventListener('keypress', function(e) {
                if (e.key === 'Enter') submitPasswordChange();
            });
        }

        function closePasswordModal() {
            const modal = document.getElementById('pwModal');
            const overlay = document.getElementById('pwOverlay');
            if (modal) modal.remove();
            if (overlay) overlay.remove();
        }

        function submitLogout() {
            const formData = new FormData();
            formData.append('logout', '1');
            // Reload either way: on success the cookie is gone and the reload lands
            // on the login page; on failure the page simply re-renders as-is.
            fetch(window.location.href, { method: 'POST', body: formData })
                .then(() => window.location.reload())
                .catch(() => window.location.reload());
        }

        function submitPasswordChange() {
            const current = document.getElementById('pwCurrent').value;
            const newPw = document.getElementById('pwNew').value;
            const confirm = document.getElementById('pwConfirm').value;
            const errorDiv = document.getElementById('pwError');
            const successDiv = document.getElementById('pwSuccess');

            errorDiv.style.display = 'none';
            successDiv.style.display = 'none';

            if (!current || !newPw || !confirm) {
                errorDiv.textContent = 'Please fill in all fields';
                errorDiv.style.display = 'block';
                return;
            }
            if (newPw !== confirm) {
                errorDiv.textContent = 'New passwords do not match';
                errorDiv.style.display = 'block';
                return;
            }

            const formData = new FormData();
            formData.append('change_password', '1');
            formData.append('current_password', current);
            formData.append('new_password', newPw);

            fetch(window.location.href, { method: 'POST', body: formData })
                .then(r => r.json())
                .then(data => {
                    if (data.success) {
                        successDiv.textContent = 'Password changed successfully!';
                        successDiv.style.display = 'block';
                        setTimeout(closePasswordModal, 1500);
                    } else {
                        errorDiv.textContent = data.error || 'Failed to change password';
                        errorDiv.style.display = 'block';
                    }
                })
                .catch(() => {
                    errorDiv.textContent = 'Network error';
                    errorDiv.style.display = 'block';
                });
        }
    </script>
</body>
</html>'''

# Add cache-busting to local CSS/JS files only (not CDN URLs).
# Both patterns must swallow an existing ?v=N. The CSS one did not, so every
# href that already carried a hand-written version (styles.css?v=2,
# shutdown_history.css?v=2) kept that literal string forever and was never
# busted -- browsers held a cached stylesheet across every CSS change we shipped,
# while the JS beside it updated normally. Symptom is new markup + new behaviour
# with old styling, which reads as a CSS bug rather than a caching one.
import re
html = re.sub(r'href="((?!https?://)[^"]+\.css)(?:\?v=\d+)?"', f'href="\\1?v={_v}"', html)
html = re.sub(r'src="((?!https?://)[^"]+\.js)(?:\?v=\d+)?"', f'src="\\1?v={_v}"', html)
print(html)