o
    „jéQ  ã                	   @   sx  d dl Z d dlZej d d¡ d dlmZmZ e  ¡ ZedƒZ	e	 
e¡\ZZZerGedƒ er3eeƒ edƒ er>edƒ nedƒ e d ¡ er\ed	ƒ edƒ eed
ƒƒ e d ¡ d dlZd dlZd dlZd dlZd dlmZ d dlmZ ej ej ej ej e¡¡¡¡ d dlmZmZmZ dZdZdd„ Z dd„ Z!ddd„Z"dd„ Z#dd„ Z$dd„ Z%e&dkrºe%ƒ  dS dS )é    Nz/opt/ngon/apps)ÚAuthManagerÚgenerate_login_page_htmlÚinventory_checkzContent-Type: application/jsonÚ z6{"success": false, "error": "Authentication required"}z{"success": true}zContent-Type: text/htmlzInventory Comparison)Údefaultdict)Údatetime)Úgenerate_dropdown_htmlÚgenerate_dropdown_cssÚgenerate_dropdown_jsz$/opt/ngon/data/live/miner_status.csvz'/opt/ngon/data/live/miner_inventory.csvc              
   C   s    t j | ¡sdd| › �fS z&t| ddd��}t |¡}t|ƒdfW  d  ƒ W S 1 s,w   Y  W dS  tyO } zdd| › d|› �fW  Y d}~S d}~ww )zCReads a CSV file and returns its content as a list of dictionaries.NzError: File not found at Úrúutf-8)ÚmodeÚencodingzError reading or parsing z: )ÚosÚpathÚexistsÚopenÚcsvÚ
DictReaderÚlistÚ	Exception)Ú	file_pathÚinfileÚreaderÚe© r   ú//var/www/html/ngon/inventory/inventory_check.pyÚread_csv_file,   s   

(þ €ÿr   c                 C   s,   | rt | ƒdkr| dd… | dd…  S dS )z{
    Creates a partial serial key from the first 6 and last 6 characters.
    Returns None if the serial is too short.
    é   Né   iúÿÿÿ)Úlen)Úserialr   r   r   Úget_partial_serial7   s   r"   Fc                 C   s$  dd„ |D ƒ}d| › d�}|rB|rBdd„ |D ƒ}dd  |¡ }dd	l}| d
¡}	| |	¡ d
¡}
d|
› �}|d|› dt|ƒ› d�7 }|d7 }|sM|d7 }n?|d7 }|D ]}|d| dd¡ ¡ › d�7 }qS|d7 }|D ]}|d7 }|D ]}| |d¡}|d|› d�7 }qr|d7 }qj|d7 }|d7 }|S )z:Generates an HTML table string for a given set of results.c                 S   s   g | ]	}|  d ¡s|‘qS )Ú__)Ú
startswith)Ú.0Úhr   r   r   Ú
<listcomp>C   s    z*generate_results_table.<locals>.<listcomp>zÅ
    <div class="content-panel">
        <div class="panel-header">
            <div style="display: flex; justify-content: space-between; align-items: center; width: 100%;">
                <span>z</span>
    c                 S   s(   g | ]}|  d d¡ ¡ r|  d d¡‘qS )Úipr   )ÚgetÚstrip©r%   Úitemr   r   r   r'   O   ó   ( zIP Address
Ú
r   Nr   zdata:text/csv;base64,z
                <a href="aõ  " 
                   download="unmatched_miners_ips.csv"
                   class="download-btn">
                    <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
                    </svg>
                    Download IP List (z# IPs)
                </a>
        zK
            </div>
        </div>
        <div class="panel-content">
    zC<div class="no-data">No discrepancies found in this category.</div>z+<table class="comparison-table"><thead><tr>z<th>Ú_ú z</th>z</tr></thead><tbody>z<tr>r   z<td>z</td>z</tr>z</tbody></table>z</div></div>)	ÚjoinÚbase64ÚencodeÚ	b64encodeÚdecoder    ÚreplaceÚtitler)   )r7   ÚdataÚheadersÚinclude_download_buttonÚdisplay_headersÚ
table_htmlÚip_listÚcsv_contentr2   Ú	csv_bytesÚcsv_b64Údata_urlÚheaderr,   Úvaluer   r   r   Úgenerate_results_table@   s@   ü

ÿ
ù

rD   c               
   C   sÔ   zQt j t¡s
W dS t j t¡} t | ¡}t ¡ | }| d¡}| 	¡ dk r0t
| 	¡ ƒ› d�}n| 	¡ dk rBt
| 	¡ d ƒ› d�}nt
| 	¡ d ƒ› d�}||fW S  tyi } zdt|ƒfW  Y d	}~S d	}~ww )
z0Get the inventory.csv file timestamp information)zN/AzFile not foundz%m/%d %H:%M:%Sé<   zs agoi  zm agozh agoÚErrorN)r   r   r   ÚINVENTORY_CSV_PATHÚgetmtimer   ÚfromtimestampÚnowÚstrftimeÚtotal_secondsÚintr   Ústr)Ú
file_mtimeÚ	file_timeÚtime_agoÚformatted_timeÚtime_ago_strr   r   r   r   Úget_inventory_file_timestamp{   s"   


€ÿrT   c                 C   s   d| › d�S )z3Generates an HTML page to display a critical error.a"  
    <div class="container mx-auto p-4 md:p-8">
        <div class="bg-red-500/20 backdrop-blur-md p-6 rounded-lg shadow-lg">
            <div class="text-white">
                <strong class="font-bold text-lg">A critical error occurred:</strong>
                <span class="block mt-2">z9</span>
            </div>
        </div>
    </div>
    r   )Úmessager   r   r   Úgenerate_error_page–   s   ûrV   c                  C   s¼  d} t ƒ \}}ttƒ\}}ttƒ\}}|r| t|ƒ7 } |r#| t|ƒ7 } | �s-dd„ |D ƒ}dd„ |D ƒ}g }	g }
g }g }tƒ }|D ]Á}| dd¡ ¡ }| dd¡ ¡  ¡ }|rY|sŠ| 	| dd¡| dd¡| dd¡| dd¡| d	d¡|sxd
nd|s|sdn|sƒdnd dœ¡ qAd}|r½||v r½|| }| dd¡ ¡ }d}||kr­|	 	|||dœ¡ | 
| d
¡¡ | 
| d¡¡ |sú||v rú|| }| d
¡|vrú| d¡|vrú| d
d¡}d}||krê|
 	|||dœ¡ | 
| d
¡¡ | 
| d¡¡ |�s| 	|¡ qA| td|	g d¢ƒ7 } | td|
g d¢ƒ7 } | td|g d¢dd�7 } | td|g d¢ƒ7 } tdƒ tdtƒ › dtƒ › dtt ¡ ƒ› dtj t¡› dtj t¡› d|› d|› d | › d!�ƒ d"S )#z6Main function to perform comparison and generate HTML.r   c                 S   s$   i | ]}|  d ¡r|  d d¡|“qS )ÚSerialr   )r)   r+   r   r   r   Ú
<dictcomp>³   s   $ zmain.<locals>.<dictcomp>c                 S   s(   i | ]}|  d ¡r|  d d¡ ¡ |“qS )ÚMACr   )r)   Úupperr+   r   r   r   rX   ´   r-   r!   Úmacr(   ÚpodÚ
miner_typerW   z & MACrY   )r!   r[   r(   r\   r]   Úmissing_dataFT)zFull Serial (Matched)z
Online MACzInventory MAC)zMAC Address (Matched)zOnline SerialzInventory Serialz&MAC Mismatch (Full Serial Match Found)z&Full Serial Mismatch (MAC Match Found)z)Miners Found Online Only (No Match Found))r!   r[   r(   r\   r]   )r:   z0Miners with Incomplete Data (Missing Serial/MAC)zContent-type: text/html
aú  
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Inventory Comparison Tool</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: #1a1a1a;
            color: #e0e0e0;
            min-height: 100vh;
            padding: 20px;
            line-height: 1.4;
        }
        .container { max-width: none; margin: 0 auto; }
        .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;
            display: flex !important;
            justify-content: space-between !important;
            align-items: flex-start !important;
        }
        .header h1.dropdown-title {
            font-size: 2.2em !important;
            color: #00ff00 !important;
            margin: 3px 0 0 0 !important;
            line-height: 1.2 !important;
            font-weight: 600 !important;
        }
        .subtitle {
            color: #888;
            font-size: 14px;
            margin-bottom: 20px;
        }
        .stats-bar { 
            display: flex; 
            gap: 20px; 
            align-items: center; 
            flex-wrap: wrap; 
        }
        .stat-item { 
            background-color: rgba(255,255,255,0.05); 
            color: #ffffff; 
            padding: 15px 25px; 
            border-radius: 6px; 
            font-weight: 600; 
            border: 1px solid rgba(255,255,255,0.1); 
        }
        .refresh-btn {
            background: #00ff00;
            color: #000;
            border: none;
            padding: 12px 24px;
            border-radius: 6px;
            cursor: pointer;
            font-weight: 600;
            display: flex;
            align-items: center;
            gap: 8px;
            transition: background-color 0.2s;
        }
        .refresh-btn:hover {
            background: #00cc00;
        }
        .refresh-btn:disabled {
            background: #666;
            color: #aaa;
            cursor: not-allowed;
        }
        .content-panel { 
            background-color: rgba(255,255,255,0.02); 
            border-radius: 6px; 
            border: 1px solid rgba(255,255,255,0.1); 
            margin-bottom: 30px;
            overflow: hidden;
        }
        .panel-header { 
            background-color: rgba(255,255,255,0.05); 
            color: #ffffff; 
            padding: 20px; 
            font-size: 1.3em; 
            font-weight: 600; 
            border-bottom: 1px solid rgba(255,255,255,0.1); 
        }
        .panel-content { 
            padding: 20px; 
        }
        .comparison-table {
            width: 100%;
            border-collapse: collapse;
            background-color: rgba(255,255,255,0.02);
            border-radius: 6px;
            overflow: hidden;
        }
        .comparison-table th {
            background-color: rgba(255,255,255,0.1);
            color: #ffffff;
            padding: 15px;
            text-align: left;
            font-weight: 600;
            border-bottom: 1px solid rgba(255,255,255,0.1);
        }
        .comparison-table td {
            padding: 12px 15px;
            border-bottom: 1px solid rgba(255,255,255,0.05);
            color: #ffffff;
        }
        .comparison-table tr:nth-child(even) {
            background-color: rgba(255,255,255,0.02);
        }
        .comparison-table tr:hover {
            background-color: rgba(255,255,255,0.05);
        }
        .no-data {
            text-align: center;
            padding: 40px;
            color: #888;
            font-style: italic;
        }
        .download-btn {
            background: #4a9eff;
            color: #fff;
            border: none;
            padding: 8px 16px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
            display: inline-flex;
            align-items: center;
            gap: 6px;
            transition: background-color 0.2s;
        }
        .download-btn:hover {
            background: #3a8eef;
        }
        .spinner {
            width: 16px;
            height: 16px;
            border: 2px solid transparent;
            border-top: 2px solid currentColor;
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        
        a  
    </style>
    <script>
        function downloadIPs(ipList) {
            if (ipList.length === 0) {
                alert('No IP addresses found to download.');
                return;
            }
            
            // Create CSV content
            let csvContent = "IP Address\n";
            ipList.forEach(ip => {
                if (ip && ip.trim()) {
                    csvContent += ip.trim() + "\n";
                }
            });
            
            // Create and download file
            const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
            const link = document.createElement("a");
            const url = URL.createObjectURL(blob);
            link.setAttribute("href", url);
            link.setAttribute("download", "unmatched_miners_ips.csv");
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            URL.revokeObjectURL(url);
        }
        
        function refreshInventory() {
            const button = document.getElementById('refresh-btn');
            const spinner = document.getElementById('refresh-spinner');
            const buttonText = document.getElementById('refresh-text');
            
            // Disable button and show loading state
            button.disabled = true;
            spinner.style.display = 'block';
            buttonText.textContent = 'Refreshing...';
            
            // Make AJAX call to refresh script
            fetch('./refresh_inventory.py', {
                method: 'GET'
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    // Reload the page after successful refresh
                    window.location.reload();
                } else {
                    alert('Error refreshing inventory: ' + data.error);
                    // Re-enable button on error
                    button.disabled = false;
                    spinner.style.display = 'none';
                    buttonText.textContent = 'Refresh Inventory';
                }
            })
            .catch(error => {
                alert('Error refreshing inventory: ' + error);
                // Re-enable button on error
                button.disabled = false;
                spinner.style.display = 'none';
                buttonText.textContent = 'Refresh Inventory';
            });
        }
        // 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');
                    }
                });
            }
        });
        
        // Add dropdown navigation JavaScript
        zÐ
    </script>
</head>
<body>
<div class="header">
    <div>
        <div class="dropdown">
            <h1 class="dropdown-title">NGON Mining - Inventory Check</h1>
            <div class="dropdown-content">zB</div>
        </div>
        <p class="subtitle">Comparing <code>z</code> against <code>aÉ  </code> using full serial numbers</p>
        <div class="stats-bar">
            <button id="refresh-btn" onclick="refreshInventory()" class="refresh-btn">
                <div id="refresh-spinner" class="spinner" style="display: none;"></div>
                <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
                </svg>
                <span id="refresh-text">Refresh Inventory</span>
            </button>
            <div class="stat-item">Last updated: z+</div>
            <div class="stat-item">(zZ)</div>
        </div>
    </div>
    <div></div>
</div>
<div class="container">
    
    z
</div>
</body>
</html>
    N)rT   r   ÚMINERS_CSV_PATHrG   rV   Úsetr)   r*   rZ   ÚappendÚaddrD   Úprintr	   r
   r   r   Úget_user_accessr   r   Úbasename)Úresults_htmlÚinventory_timestampÚinventory_ageÚminers_dataÚminers_errorÚinventory_dataÚinventory_errorÚinventory_by_full_serialÚinventory_by_macÚmac_mismatchesÚserial_mismatchesÚunmatched_minersÚincomplete_minersÚused_inventory_itemsÚminerÚonline_full_serialÚ
online_macÚmatchedÚinventory_itemÚinventory_macÚinventory_full_serialr   r   r   Úmain£   sô   







þú
ýý
€þþýþ  �ã | �„  
 � �û  
 � �ù  
 � �ù   � �ð   � �ï   � �èr{   Ú__main__)F)'ÚcgiÚsysr   ÚinsertÚmanagers.auth_managerr   r   ÚFieldStorageÚ_formÚ_authÚrequire_authÚ_auth_requiredÚ_should_exitÚ_headersrc   Úexitr   r   ÚjsonÚcollectionsr   r   ra   ÚdirnameÚabspathÚ__file__Úlinksr   r	   r
   r_   rG   r   r"   rD   rT   rV   r{   Ú__name__r   r   r   r   Ú<module>   sT   


$
	;   
ÿ