o
    »­ºjµb  ã                   @   sT  d dl Z d dlZd dlZzdejvrej d d¡ d dlmZmZ W n e	y0   dZe
ƒ ZY nw h d£ZddddœiZd	d
dddœdddddœdddddœdddddœdddddœgdddd dœd!d"d#d$dœd%d&d'd(dœd)d*d+d,dœd-d.d/d0dœd1d2d3d4dœd5d6d7d8dœgd9d:d;d<dœd=d>d?d@dœdAdBdCdDdœdEdFdGdHdœdIdJdKdLdœdMdNdKdOdœdPdQdRdSdœdTdUdVdWdœdXdYdZd[dœd\d]d^d_dœd`dadbdcdœgdddedfdgdœdhdidjdkdœdldmdndodœdpdqdrdsdœdtdudvdwdxdyœgdzd{d|d}dœd~dd€d�dœd‚dƒd„d…dœd†d‡dˆd‰dœdŠd‹dŒd�dœgdŽœZg Ze ¡ D ]$\ZZeD ]Zed� ed� ed‘œZe d’¡�r-dxed’< e e¡ �q�qe d“d”d•dxd–œ¡ e d—d˜d™d‘œ¡ e dšd›d•d‘œ¡ e ddœd�d‘œ¡ e dždŸd™d‘œ¡ e d d¡d™d‘œ¡ dad¢d£„ Zd¤d¥„ Zd·d¦d§„Zd¨d©„ Zd¸dªd«„Zd¬d­„ Zd®d¯„ Zd¹d±d²„Zd³eƒ  d´ Ze dµk�r¨e!d¶ƒ e!eƒ dS dS )ºé    Nz/opt/ngon/apps)ÚAuthManagerÚRESTRICTED_PAGES>   ÚmapÚnmtÚcamsÚdeadÚtodoÚconnexÚhow_toÚ	inventoryÚ
group_textÚgen_managerÚbridge_statusÚstatus_mobileÚmesa_gen_trackerÚhow_to_site_issuesÚhow_to_status_pageÚhow_to_remote_startÚinventory_managementÚstatusz/status/m.pyr   )ÚurlÚpage_keyzStatus Pagez/status/zReal-time status dashboard)Útitler   Údescriptionr   z	Sites Mapz/status/map.pyz(Interactive map showing all mining sitesr   zPerformance Pagez/status/performance.pyzHistorical hashrate monitoringÚperformanceÚCamsz/cams/z/Blink camera viewer (on-demand fresh snapshots)r   zWifi Bridge Statusz/status/bridge_status.pyzIGen wifi bridges: which bridge is on which gen, its IP, and which peplinkr   z	Todo Listz/todo/zShared company todo listr   zNGON Miner Tool (NMT)z/nmm/nmt.pyz(Comprehensive miner management interfacer   zGenerator Managerz/status/gen_manager.pyz1Generator notes, status, and maintenance trackingr   zGenerator Service Historyz/status/gen_service.pyz*Service invoice tracking and cost analysisÚgen_servicezGroup Text Managerz/status/group_text.pyz:Shared SMS threads with gas providers, processors and Mesar   zMesa Gen Trackerz/status/mesa_gen_tracker.pyz,Mesa-facing gen status, run state, and notesr   zDead Miner Hunterz/nmm/dead.pyz$Find broken miners, build pull listsr   zEcons by Site Report Builderz/status/report_builder.pyzMBuild custom cross-source econ reports (hashrate, revenue, gas, service, G&A)Úreport_builderzSite Hashrate & Uptimez/status/site_hashrate_uptime.pyzAPer-site average hashrate vs spec (utilisation) over 7/30/90 daysÚsite_hashrate_uptimezMARA Reportz/status/mara_report.pyz7Comprehensive generator and miner performance reportingÚmara_reportzZero Hash Analysisz/nmm/zero_hash.pyz&Analysis tool for zero hashrate minersÚ	zero_hashzMiner Trendsz/status/miner_trends.pyz#Historical analysis and performanceÚminer_trendszGenerator Trendsz/status/gen_trends.pyÚ
gen_trendszGen Outage Reportz/gen_outages.pyz1Repeat-offender gens that initiate group cascadesÚgen_outageszSite Non-Zero Hash based Uptimez/status/site_uptime.pyz:Historical site uptime analysis based on non-zero hashrateÚsite_uptimezMiner Pod Reportz/status/miner_report.pyzHourly mining operation statusÚminer_reportzDead M60 Reportz/dead_m60.pyz,Dead M60 miner analysis and hashboard healthÚdead_m60zFlowmeter Dataz/status/flowmeter_data.pyz<Gas flowmeter daily usage history and live readings per siteÚ	flowmeterzInventory Managementzhttps://inventory.ngon.usz8Put/pull miners and manage inventory (inventory.ngon.us)r   zInventory Displayz/inventory/z0Search and filter miners (concept, not complete)r   zInventory Comparisonz/inventory/inventory_check.pyz1Compare inventory data with live data from minersÚinventory_checkzAsset Inventoryz/connex/z0What gear is stored where, by site and containerr	   zSite Developmentz+https://inventory.ngon.us/site-development/z8Plan a site move or a new site build (inventory.ngon.us)Úsite_developmentT)r   r   r   r   Ú
restrictedzHow-To: Power Managementz/status/how_to.pyz+Field guide to AS, AW, gen disable, and EMSr
   zHow-To: Remote Gen Startz/status/how_to_remote_start.pyz8Remote gen control: manual, clear alarms, start, on-loadr   zHow-To: Using the Status Pagez/status/how_to_status_page.pyz9New-hire orientation to the status dashboard and its datar   zData Flow Mapz/status/dataflow.pyz?Animated map of how data moves: field, VPN, APIs, stores, pagesÚdataflowzHow-To: Site Issuesz/status/how_to_site_issues.pyz9What the Site Issues panel flags, and the rules behind itr   )úStatus & MonitoringúManagement ToolsúReports & AnalyticszInventory & AssetsÚHelpr   r   )Úkeyr   Úcategoryr*   Úsite_managerzSite Managerr-   )r0   r   r1   r*   Úperformance_30zPerformance 30-Dayr.   Úgen_controlz$Gen Control (gen start/manual/clear)zStatus Page (mobile)r,   ÚresearchzResearch (research.ngon.us)ÚmoneyzMoney (BTC earnings)c                     sp   t du r6tƒ } tdur4tr4zt ¡ }|r&t ¡  |¡pi ‰ ‡ fdd„tD ƒ} W n ty3   tƒ } Y nw | a t S )a–  Restricted page keys the CURRENT user (per cookie) has been granted.

    Restricted keys live as standalone booleans on the user record, not in the
    access list, so the access value the nav callers hand us can't answer this.
    Resolved from the cookie here instead of changing 36 call sites. Computed
    once per process; every importer of this module is a CGI, so that is once
    per request.
    Nc                    s   h | ]	}ˆ   |¡r|’qS © )Úget)Ú.0Úk©Úrecordr7   ú/var/www/html/ngon/links.pyÚ	<setcomp>V  s    z%_restricted_grants.<locals>.<setcomp>)Ú_RESTRICTED_GRANTSÚsetÚ_AuthManagerr   Úget_current_userÚ_load_usersr8   Ú	Exception)ÚgrantsÚusernamer7   r;   r=   Ú_restricted_grantsE  s   
€
ÿrG   c                 C   s<   | t v r	| tƒ v S |du s|dkrdS t|tƒr| |v S dS )z-Check if access list permits a given page_keyNÚallTF)r   rG   Ú
isinstanceÚlist)r   Úaccessr7   r7   r=   Ú_has_access]  s   

rL   c                    s†   d}t  ¡ D ]:\}}‡ fdd„|D ƒ}|sq|d|› d�7 }|D ]}|d|d › d|d	 › d
|d › d|d › d�	7 }q |d7 }q|S )NÚ c                    s   g | ]}t |d  ˆ ƒr|‘qS )r   ©rL   )r9   Úl©rK   r7   r=   Ú
<listcomp>m  s    z'generate_links_html.<locals>.<listcomp>z5
        <div class="links-section">
            <h2>z*</h2>
            <div class="links-grid">z
                <a href="r   zQ" class="link-card" target="_blank">
                    <div class="link-title">r   z9</div>
                    <div class="link-description">r   z1</div>
                    <div class="link-url">z</div>
                </a>z"
            </div>
        </div>)Ú
LINKS_DATAÚitems)rK   Úhtml_contentr1   ÚlinksÚfilteredÚlinkr7   rP   r=   Úgenerate_links_htmlj  s(   
þÿþýü
rX   c                  C   s"   t j dd¡} tt d| tj¡ƒS )a¾  True when the request came from a phone, per the CGI User-Agent.

    Sniffing a UA is crude, but the alternative is worse: without it the nav is
    only phone-shaped on the pages that remember to ask for it, so a field guy who
    taps through to the Todo list gets handed the desktop menu -- and the "Status
    Page" link there drops him on the three-column desktop page. The menu should
    follow the device, not the page he happens to be standing on.

    Deliberately narrow: "Mobile" covers iOS and Android browsers, and iPad is
    listed because iPadOS reports a desktop UA in some modes but not all. A false
    negative just means the full menu, which is the current behaviour anyway.
    ÚHTTP_USER_AGENTrM   z3Android|iPhone|iPad|iPod|Mobile|Opera Mini|IEMobile)ÚosÚenvironr8   ÚboolÚreÚsearchÚI)Úuar7   r7   r=   Ú_is_mobile_client…  s   ra   c              
      s0  |du rt ƒ }ˆ du pˆ dk}d}t ¡ D ]€\}}g }|D ]2}|d |d }}	|r/t |¡nd}
|
rG|
 d|¡}t|ˆ ƒrG||
 d|	¡}}	| |||	f¡ q‡ fdd„|D ƒ}|rd|rddd„ |D ƒ}|sgq|d	7 }|d
|› d�7 }|D ]\}}}	|r~dnd}|d|	› d|› d|d › d�7 }qu|d7 }q|S )a�  Nav dropdown, filtered by what the user may see.

    mobile=None (the default) auto-detects from the User-Agent, so every page
    gets a phone-shaped menu on a phone without each of the 36 callers having to
    opt in. Pass True/False to force it.

    In mobile mode links lose target="_blank" -- on a phone that piles up tabs
    with no obvious way back -- and MOBILE_URLS swaps in any phone-specific page.

    MOBILE_PAGE_KEYS is applied ONLY to users whose access is "all". If someone
    has an explicit page list, a human already decided exactly what they should
    see, and second-guessing that would hide a page you deliberately granted.
    NrH   rM   r   r   c                    s&   g | ]\}}}t |ˆ ƒr|||f‘qS r7   rN   ©r9   rO   r:   ÚurP   r7   r=   rQ   º  s   & z*generate_dropdown_html.<locals>.<listcomp>c                 S   s$   g | ]\}}}|t v r|||f‘qS r7   )ÚMOBILE_PAGE_KEYSrb   r7   r7   r=   rQ   ¼  s   $ z<div class="dropdown-section">z<div class="dropdown-category">z</div>z target="_blank"z	<a href="z" class="dropdown-link"ú>r   z</a>)ra   rR   rS   ÚMOBILE_PAGESr8   rL   Úappend)rK   ÚmobileÚunrestrictedÚdropdown_htmlr1   rU   ÚresolvedrW   r0   r   ÚswapÚmkeyrV   Ú_keyÚtargetr7   rP   r=   Úgenerate_dropdown_html–  s4   
"
rp   c                   C   ó   dS )Nas	  
    /* Universal Navigation Dropdown Styles */
    .dropdown {
        position: relative;
        display: inline-block;
    }

    .dropdown-title {
        cursor: pointer;
        transition: color 0.2s ease;
        user-select: none;
        color: #00ff00;
        font-size: 2.2em;
        font-weight: 600;
        margin: 0;
    }

    .dropdown-title:hover {
        color: #4a9eff;
    }

    .dropdown-content {
        display: none;
        position: absolute;
        background-color: #1a1a1a;
        min-width: 350px;
        box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.4);
        z-index: 1000;
        border: 1px solid #2a2a2a;
        border-radius: 8px;
        padding: 10px 0;
        top: 100%;
        left: 0;
    }

    .dropdown:hover .dropdown-content {
        display: block;
    }

    .dropdown-section {
        padding: 5px 0;
    }

    .dropdown-category {
        color: #00ff00;
        font-weight: bold;
        padding: 8px 15px 5px 15px;
        font-size: 14px;
        border-bottom: 1px solid #2a2a2a;
        margin-bottom: 5px;
    }

    .dropdown-link {
        color: #e0e0e0;
        padding: 6px 25px;
        text-decoration: none;
        display: block;
        transition: background-color 0.2s ease;
        font-size: 13px;
    }

    .dropdown-link:hover {
        background-color: #2a2a2a;
        color: #4a9eff;
        text-decoration: none;
    }

    /* Mobile dropdown handling */
    @media (max-width: 768px) {
        .dropdown-content {
            position: fixed;
            top: 0;
            left: 0;
            right: auto;
            width: 75vw;
            min-width: 0;
            z-index: 2000;
            max-height: 100vh;
            overflow-y: auto;
            -webkit-overflow-scrolling: touch;
            border-radius: 0 0 8px 0;
            border: none;
            border-right: 1px solid #2a2a2a;
            border-bottom: 1px solid #2a2a2a;
        }

        .dropdown:hover .dropdown-content {
            display: none; /* Disable hover on mobile */
        }

        .dropdown-title {
            position: relative;
        }

        /* Add click functionality for mobile */
        .dropdown-content.show {
            display: block !important;
        }
    }

    /* Header styles */
    .header {
        background: #1a1a1a;
        padding: 20px;
        border-bottom: 2px solid #2a2a2a;
    }
    r7   r7   r7   r7   r=   Úgenerate_dropdown_cssÉ  ó   rr   c                   C   rq   )Na}  
    // Mobile dropdown functionality
    document.addEventListener('DOMContentLoaded', function() {
        const dropdownTitle = document.querySelector('.dropdown-title');
        const dropdownContent = document.querySelector('.dropdown-content');

        if (dropdownTitle && dropdownContent) {
            dropdownTitle.addEventListener('click', function(e) {
                if (window.innerWidth <= 768) {
                    e.preventDefault();
                    dropdownContent.classList.toggle('show');
                }
            });

            // Close dropdown when clicking outside
            document.addEventListener('click', function(e) {
                if (!e.target.closest('.dropdown') && dropdownContent.classList.contains('show')) {
                    dropdownContent.classList.remove('show');
                }
            });

            // Close dropdown when window is resized to desktop
            window.addEventListener('resize', function() {
                if (window.innerWidth > 768) {
                    dropdownContent.classList.remove('show');
                }
            });
        }
    });
    r7   r7   r7   r7   r=   Úgenerate_dropdown_js7  rs   rt   rM   c                 C   s   d| › dt |ƒ› d|› d�S )Nzn
    <div class="header">
        <div class="dropdown">
            <h1 class="dropdown-title">NGON Mining - z0</h1>
            <div class="dropdown-content">z</div>
        </div>
        z
    </div>
    )rp   )Ú
page_titleÚadditional_controlsrK   r7   r7   r=   Úgenerate_header_htmlX  s   ýüúrw   a!  <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>NGON Links</title>
    <link rel="stylesheet" href="status/styles.css">
    <style>
        .links-container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }

        .links-section {
            background: #1a1a1a;
            border: 1px solid #2a2a2a;
            border-radius: 12px;
            padding: 20px;
            margin-bottom: 20px;
        }

        .links-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 15px;
            margin-top: 15px;
        }

        .link-card {
            background: #2a2a2a;
            border: 1px solid #3a3a3a;
            border-radius: 8px;
            padding: 15px;
            transition: all 0.2s ease;
            display: block;
            text-decoration: none;
            color: #e0e0e0;
        }

        .link-card:hover {
            border-color: #00ff00;
            transform: translateY(-2px);
            box-shadow: 0 4px 12px rgba(0, 255, 0, 0.1);
            text-decoration: none;
            color: #fff;
        }

        .link-title {
            color: #00ff00;
            font-weight: 600;
            font-size: 16px;
            margin-bottom: 8px;
        }

        .link-description {
            color: #888;
            font-size: 14px;
            line-height: 1.4;
        }

        .link-url {
            color: #4a9eff;
            font-size: 12px;
            margin-top: 8px;
            font-family: monospace;
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>NGON Links</h1>
    </div>

    <div class="links-container">z
    </div>
</body>
</html>Ú__main__zContent-Type: text/html
)N)NN)rM   N)"rZ   r]   ÚsysÚpathÚinsertÚmanagers.auth_managerr   rA   r   rD   r@   rd   rf   rR   Ú	PAGE_KEYSrS   Ú_catÚ_linksÚ_linkÚ_entryr8   rg   r?   rG   rL   rX   ra   rp   rr   rt   rw   ÚhtmlÚ__name__Úprintr7   r7   r7   r=   Ú<module>   s¾  

þ
ÿ	üüüüüç"üüüüüüüÛ.üüüüüüüüüüüÃFüüüüøç&üüüüüç �Ê Yýö

3n
!J¶J¶
Oþ