o
    €‹i½^  ã                	   @   s´   d Z ddlZddlZddlZddlZddlZddlmZ ej	 
ej	 ej	 ej	 e¡¡¡¡ ddlmZmZmZ G dd„ dƒZdd„ Zd	d
„ Zdd„ Zdd„ ZedkrXeƒ  dS dS )a  
Inventory Display Page
File: /var/www/html/ngon/inventory/index.py

This script displays inventory data with a three-column layout:
- Left: All miners from inventory
- Middle: Container filters (Site Pod combinations)
- Right: Floaters (placeholder for unassigned miners)
é    N)Úescape)Úgenerate_dropdown_htmlÚgenerate_dropdown_cssÚgenerate_dropdown_jsc                   @   s8   e Zd ZdZdd„ Zdd„ Zdd„ Zdd	„ Zd
d„ ZdS )ÚInventoryManagerz1Handles loading and processing of inventory data.c                 C   s   g | _ tƒ | _|  ¡  d S )N)Úinventory_dataÚsetÚ
containersÚ	load_data©Úself© r   ú%/var/www/html/ngon/inventory/index.pyÚ__init__   s   zInventoryManager.__init__c                 C   s  z„d}t j |¡rt |¡}| ¡ D ]o\}}t|d ƒ ¡ }t|d ƒ ¡ }t|d ƒ ¡ }t|d ƒ ¡ }t|d ƒ ¡ }	|dksF|dkrGq|› d	|	› �}
| j 	|
¡ |dks\|dkoc|dkpc|dk}| j
 ||dkrn|nd
|dkru|nd
||	|
|dœ¡ qW dS W dS  tyŽ   Y dS w )z#Loads inventory data from CSV file.z'/opt/ngon/data/live/miner_inventory.csvÚPositionÚSerialÚMACÚSiteÚPodÚnanÚ ú ÚUnknown)ÚpositionÚserialÚmacÚsiteÚpodÚ	containerÚis_emptyN)ÚosÚpathÚexistsÚpdÚread_csvÚiterrowsÚstrÚstripr	   Úaddr   ÚappendÚ	Exception)r   Úinventory_fileÚdfÚ_Úrowr   r   r   r   r   r   r   r   r   r   r
      s:   
 
ùìþzInventoryManager.load_datac                 C   s   t t| jƒƒS )z"Returns sorted list of containers.)ÚsortedÚlistr	   r   r   r   r   Úget_containersE   s   zInventoryManager.get_containersc                    s   ‡ fdd„| j D ƒS )z(Returns miners for a specific container.c                    s   g | ]
}|d  ˆ kr|‘qS ©r   r   )Ú.0Úminerr2   r   r   Ú
<listcomp>K   s    z<InventoryManager.get_miners_by_container.<locals>.<listcomp>)r   )r   r   r   r2   r   Úget_miners_by_containerI   s   z(InventoryManager.get_miners_by_containerc                 C   sF   d}d}| j D ]}|d dvr|d7 }|d dvr|d7 }qt||ƒS )zCReturns count of installed miners (higher of MAC or Serial counts).r   r   )r   r   r   é   r   )r   Úmax)r   Ú	mac_countÚserial_countr4   r   r   r   Úget_installed_miners_countM   s   
€
z+InventoryManager.get_installed_miners_countN)	Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r
   r1   r6   r;   r   r   r   r   r      s    'r   c                 C   s  g }| j s| d¡ nO| j D ]K}t|d ƒ}t|d ƒ}t|d ƒ}t|d ƒ}t|d ƒ}t|d ƒ}| dd	¡r<d
nd}	| d|› d|	› d|› d|› d|› d|› d|› d�¡ q|  ¡ }
g }|
D ] }t|dd�}t|  |¡ƒ}| d|› dt|ƒ› d|› d�¡ qbd |¡d |¡fS )z1Generates the dynamic HTML sections for the page.z4<div class="no-miners">No inventory data found</div>r   r   r   r   r   r   r   FÚtrueÚfalsez9
                <div class="miner-item" data-container="z" data-empty="zd">
                    <div class="miner-info">
                        <div class="miner-position">zn</div>
                        <div class="miner-details">
                            <div class="miner-mac">zE</div>
                            <div class="miner-serial">Serial: z?</div>
                            <div class="miner-location">z Pod zd</div>
                        </div>
                    </div>
                </div>
            T)ÚquotezI
            <div class="container-filter-item selected" data-container="zÛ">
                <label style="display: flex; align-items: center; cursor: pointer; width: 100%;">
                    <input type="checkbox" class="checkbox" checked>
                    <span class="container-name">zO</span>
                </label>
                <span class="container-count">z#</span>
            </div>
        r   )r   r)   r   Úgetr1   Úlenr6   Újoin)Úinventory_managerÚminers_htmlr4   r   r   r   r   r   r   Úis_empty_attrr	   Úcontainer_filters_htmlÚcontainer_safeÚminer_countr   r   r   Úgenerate_html_content^   sN   
ÿÿýûúùùÿüú
rL   c                  C   s”  g g dœ} d}d}d|fd|ffD ]ª\}}z�t j |¡r±t|dƒ�ˆ}t |¡}t|dƒ |D ]s}|r>|d r>|d d  ¡ s?q.|dkrkt|ƒd	krk| d  	|d |d
 |d |d t|ƒd	kre|d	  
¡ nddœ¡ q.|dkr¡t|ƒd	kr¡| d  	|d |d
 |d |d t|ƒdkr�|d ndt|ƒdkrœ|d  
¡ nddœ¡ q.W d  ƒ n1 s¬w   Y  W q ty»   Y qw | d  ¡  | d  ¡  | S )z6Load put and pull CSV data for the miner moves viewer.)ÚputÚpullz"/opt/ngon/data/miner_moves/put.csvz#/opt/ngon/data/miner_moves/pull.csvrM   rN   ÚrNr   é   r7   é   é   r   )Údater   r   r   Úuseré   é   )rS   r   r   r   r   rT   )r    r!   r"   ÚopenÚcsvÚreaderÚnextÚisdigitrD   r)   r'   r*   Úreverse)ÚmovesÚput_csvÚpull_csvÚ	move_typeÚcsv_pathÚfrY   r.   r   r   r   Úload_miner_moves‘   sF   



ýü€öý€ÿrc   c                 C   s´   t | ƒ\}}t |  ¡ ¡}tƒ }t |¡}t|d ƒ}t|d ƒ}dtƒ › dtƒ › dt| jƒ› d|  	¡ › dt|  ¡ ƒ› dt| jƒ› d	|› d
|› d|› d|› d|› d|› dt
ƒ › d�S )zRenders the complete HTML page.rM   rN   aÖ  <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Inventory Display</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #1a1a1a; color: #ffffff; min-height: 100vh; padding: 20px; }
        .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; 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: 0 !important; font-weight: 600 !important; }
        .subtitle { color: #ccc; font-size: 1.1rem; }
        .stats-bar { display: flex; gap: 20px; margin-top: 20px; 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); }
        .main-content { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 15px; align-items: start; }
        .panel { background-color: rgba(255,255,255,0.02); border-radius: 6px; border: 1px solid rgba(255,255,255,0.1); overflow: hidden; display: flex; flex-direction: column; height: 800px; }
        .panel-header { display: flex; justify-content: space-between; align-items: center; background-color: rgba(255,255,255,0.05); color: #ffffff; padding: 15px 20px; font-size: 1.1em; font-weight: bold; border-bottom: 1px solid rgba(255,255,255,0.1); }
        .content-area { padding: 15px; flex: 1; overflow-y: auto; }
        
        /* Miner Item Styles */
        .miner-item { background-color: #222; border: 1px solid #333; border-radius: 6px; padding: 15px; margin-bottom: 8px; }
        .miner-info { display: flex; justify-content: space-between; align-items: flex-start; }
        .miner-position { font-weight: 600; color: #4CAF50; font-size: 1rem; margin-bottom: 8px; }
        .miner-mac { font-family: 'Courier New', monospace; font-weight: 600; color: #ffffff; font-size: 0.9rem; }
        .miner-serial { color: #ccc; font-size: 0.85rem; margin-top: 4px; }
        .miner-location { color: #aaa; font-size: 0.8rem; margin-top: 2px; }
        
        /* Container Filter Styles */
        .container-filter-item { background-color: #222; border: 1px solid #333; border-radius: 4px; padding: 12px; margin-bottom: 8px; cursor: pointer; display: flex; align-items: center; justify-content: space-between; }
        .container-filter-item.selected { border-color: #555; background-color: rgba(255,255,255,0.05); }
        .container-name { font-weight: 600; margin-left: 8px; }
        .container-count { background-color: rgba(255,255,255,0.1); padding: 3px 8px; border-radius: 4px; font-size: 0.75rem; }
        
        /* Search Box */
        .search-box { padding: 15px; border-bottom: 1px solid rgba(255,255,255,0.1); }
        .search-input { width: 100%; padding: 10px 15px; border: 1px solid #333; border-radius: 4px; font-size: 0.9rem; background: #222; color: #fff; }
        .search-input:focus { outline: none; border-color: #555; }
        
        /* Filter Controls */
        .filter-controls { padding: 15px; border-bottom: 1px solid rgba(255,255,255,0.1); }
        .toggle-all-btn { width: 100%; padding: 10px; background-color: rgba(255,255,255,0.05); color: #ffffff; border: 1px solid rgba(255,255,255,0.1); border-radius: 4px; cursor: pointer; }
        .toggle-all-btn:hover { background-color: rgba(255,255,255,0.08); }
        
        /* Empty Positions Toggle */
        .empty-positions-toggle { font-size: 0.8rem; font-weight: normal; display: flex; align-items: center; gap: 8px; }
        
        /* Utility */
        .no-miners { text-align: center; padding: 40px 20px; color: #ccc; }
        .hidden { display: none; }
        
        /* Miner Moves Viewer */
        .moves-toggle { display: flex; gap: 4px; }
        .moves-btn { background: rgba(255,255,255,0.05); color: #888; border: 1px solid rgba(255,255,255,0.1); border-radius: 4px; padding: 4px 12px; cursor: pointer; font-size: 0.8rem; transition: all 0.15s; }
        .moves-btn.active { background: rgba(0,255,0,0.15); color: #00ff00; border-color: rgba(0,255,0,0.3); }
        .moves-btn:hover { background: rgba(255,255,255,0.08); }
        .moves-search { padding: 10px 15px; border-bottom: 1px solid rgba(255,255,255,0.1); }
        .moves-table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }
        .moves-table th { text-align: left; color: #888; padding: 6px 8px; border-bottom: 1px solid rgba(255,255,255,0.15); position: sticky; top: 0; background: #1e1e1e; z-index: 1; white-space: nowrap; }
        .moves-table td { padding: 5px 8px; border-bottom: 1px solid rgba(255,255,255,0.05); color: #ccc; }
        .moves-table tr:hover td { background: rgba(255,255,255,0.03); }
        .moves-table .col-serial { font-family: 'Courier New', monospace; font-size: 0.72rem; max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .moves-table .col-mac { font-family: 'Courier New', monospace; font-size: 0.72rem; white-space: nowrap; }
        .moves-table .col-date { white-space: nowrap; color: #888; }
        .moves-table .col-user { color: #4a9eff; font-weight: 600; }
        .moves-count { font-size: 0.75rem; color: #888; padding: 8px 15px; border-bottom: 1px solid rgba(255,255,255,0.05); }

        /* Responsive */
        @media (max-width: 1400px) { .main-content { grid-template-columns: 1fr; } }
        
        zÉ
    </style>
</head>
<body>
<div class="header">
    <div>
        <div class="dropdown">
            <h1 class="dropdown-title">NGON Mining - Inventory</h1>
            <div class="dropdown-content">zµ</div>
        </div>
        <p class="subtitle">View and filter mining equipment inventory</p>
        <div class="stats-bar">
            <div class="stat-item">Total Positions: z?</div>
            <div class="stat-item">Miners in Inventory: z6</div>
            <div class="stat-item">Containers: aw  </div>
        </div>
    </div>
    <div></div>
</div>
<div class="container">
    <div class="main-content">
        <!-- Left Column: Miners List -->
        <div class="panel">
            <div class="panel-header">
                <span>All Miners</span>
                <div style="display: flex; align-items: center; gap: 15px;">
                    <div class="empty-positions-toggle">
                        <label for="empty-positions-filter">Only Empty Positions</label>
                        <input type="checkbox" id="empty-positions-filter">
                    </div>
                    <span id="visible-count">zr items</span>
                </div>
            </div>
            <div class="content-area" id="miners-content">a{  </div>
        </div>
        
        <!-- Middle Column: Search and Container Filters -->
        <div class="panel">
            <div class="panel-header"><span>Search</span></div>
            <div class="search-box">
                <input type="text" id="search-input" class="search-input" placeholder="Search MAC or Serial...">
            </div>
            <div class="panel-header"><span>Container Filters</span></div>
            <div class="filter-controls">
                <button class="toggle-all-btn" id="toggle-all">Deselect All</button>
            </div>
            <div class="content-area" id="container-filters">aL  </div>
        </div>
        
        <!-- Right Column: Miner Moves Viewer -->
        <div class="panel">
            <div class="panel-header">
                <span>Miner Moves</span>
                <div class="moves-toggle">
                    <button class="moves-btn active" id="btn-put" onclick="switchMoves('put')">Put (zk)</button>
                    <button class="moves-btn" id="btn-pull" onclick="switchMoves('pull')">Pull (a­  )</button>
                </div>
            </div>
            <div class="moves-search">
                <input type="text" id="moves-search" class="search-input" placeholder="Search serial, pod, MAC...">
            </div>
            <div class="moves-count" id="moves-count"></div>
            <div class="content-area" id="moves-content"></div>
        </div>
    </div>
</div>

<script>
    const allContainers = new Set(aõ  );
    let selectedContainers = new Set(allContainers);
    let filterEmptyPositions = false;
    let searchTerm = '';
    
    function updateToggleButtonText() {
        const button = document.getElementById('toggle-all');
        const allSelected = selectedContainers.size === allContainers.size;
        button.textContent = allSelected ? 'Deselect All' : 'Select All';
    }
    
    function matchesSearch(miner) {
        if (!searchTerm) return true;
        
        const mac = miner.querySelector('.miner-mac').textContent.toLowerCase();
        const serial = miner.querySelector('.miner-serial').textContent.toLowerCase();
        const searchLower = searchTerm.toLowerCase();
        
        return mac.includes(searchLower) || serial.includes(searchLower);
    }
    
    function updateDisplay() {
        // Update visual state of container filters
        document.querySelectorAll('.container-filter-item').forEach(item => {
            const checkbox = item.querySelector('input[type="checkbox"]');
            const isSelected = selectedContainers.has(item.dataset.container);
            item.classList.toggle('selected', isSelected);
            checkbox.checked = isSelected;
        });
        
        // Filter miners based on selected containers, empty positions filter, and search
        const miners = document.querySelectorAll('.miner-item');
        let visibleCount = 0;
        
        miners.forEach(miner => {
            const minerContainer = miner.dataset.container;
            const isEmpty = miner.dataset.empty === 'true';
            
            const containerMatch = selectedContainers.has(minerContainer);
            const emptyMatch = !filterEmptyPositions || isEmpty;
            const searchMatch = matchesSearch(miner);
            
            const shouldShow = containerMatch && emptyMatch && searchMatch;
            miner.classList.toggle('hidden', !shouldShow);
            if (shouldShow) visibleCount++;
        });
        
        // Update visible count
        document.getElementById('visible-count').textContent = `${visibleCount} items`;
        
        updateToggleButtonText();
    }
    
    // Event Listeners
    document.getElementById('search-input').addEventListener('input', (e) => {
        searchTerm = e.target.value;
        updateDisplay();
    });
    
    document.getElementById('empty-positions-filter').addEventListener('change', (e) => {
        filterEmptyPositions = e.target.checked;
        updateDisplay();
    });
    
    document.getElementById('toggle-all').addEventListener('click', () => {
        const allSelected = selectedContainers.size === allContainers.size;
        
        if (allSelected) {
            selectedContainers.clear();
        } else {
            selectedContainers = new Set(allContainers);
        }
        
        updateDisplay();
    });
    
    document.querySelectorAll('.container-filter-item').forEach(item => {
        item.addEventListener('click', (e) => {
            const checkbox = item.querySelector('input[type="checkbox"]');
            if (e.target.tagName !== 'INPUT') {
                checkbox.checked = !checkbox.checked;
            }
            
            const container = item.dataset.container;
            if (checkbox.checked) {
                selectedContainers.add(container);
            } else {
                selectedContainers.delete(container);
            }
            
            updateDisplay();
        });
    });
    
    // Initialize display
    document.addEventListener('DOMContentLoaded', () => {
        updateDisplay();
    });
    
    // 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');
                }
            });
        }
    });
    
    // Miner Moves Viewer
    const movesData = ad	  ;
    let currentMovesType = 'put';
    let movesSearchTerm = '';

    function renderMoves() {
        const data = movesData[currentMovesType];
        const container = document.getElementById('moves-content');
        const countEl = document.getElementById('moves-count');
        const search = movesSearchTerm.toLowerCase();

        const filtered = search ? data.filter(r =>
            r.serial.toLowerCase().includes(search) ||
            r.pod.toLowerCase().includes(search) ||
            (r.mac && r.mac.toLowerCase().includes(search)) ||
            r.user.toLowerCase().includes(search) ||
            r.position.toLowerCase().includes(search)
        ) : data;

        countEl.textContent = search
            ? `Showing ${filtered.length} of ${data.length} records`
            : `${data.length} records`;

        if (filtered.length === 0) {
            container.innerHTML = '<div class="no-miners">No records found</div>';
            return;
        }

        const isPull = currentMovesType === 'pull';
        let html = '<table class="moves-table"><thead><tr>';
        html += '<th>Date</th><th>Serial</th><th>Pod</th><th>Pos</th>';
        if (isPull) html += '<th>MAC</th>';
        html += '<th>User</th></tr></thead><tbody>';

        for (const row of filtered) {
            html += `<tr>`;
            html += `<td class="col-date">${row.date}</td>`;
            html += `<td class="col-serial" title="${row.serial}">${row.serial}</td>`;
            html += `<td>${row.pod}</td>`;
            html += `<td>${row.position}</td>`;
            if (isPull) html += `<td class="col-mac">${row.mac || ''}</td>`;
            html += `<td class="col-user">${row.user}</td>`;
            html += `</tr>`;
        }

        html += '</tbody></table>';
        container.innerHTML = html;
    }

    function switchMoves(type) {
        currentMovesType = type;
        document.getElementById('btn-put').classList.toggle('active', type === 'put');
        document.getElementById('btn-pull').classList.toggle('active', type === 'pull');
        document.getElementById('moves-search').value = '';
        movesSearchTerm = '';
        renderMoves();
    }

    document.getElementById('moves-search').addEventListener('input', (e) => {
        movesSearchTerm = e.target.value;
        renderMoves();
    });

    renderMoves();

    // Add dropdown navigation JavaScript
    z
</script>
</body>
</html>)rL   ÚjsonÚdumpsr1   rc   rD   r   r   r   r;   r   )rF   rG   rI   Úall_containers_jsonÚ
moves_dataÚ
moves_jsonÚ	put_countÚ
pull_countr   r   r   Úrender_pageµ   sV   
FºN²R®S­
T¬dœg™tŒ|„}ƒ  �õ   � �ó  N � �²rk   c                  C   s   t ƒ } tdƒ tt| ƒƒ dS )zMain execution function.zContent-Type: text/html
N)r   Úprintrk   )rF   r   r   r   Úmain  s   rm   Ú__main__)r?   Úpandasr#   rd   rX   r    ÚsysÚhtmlr   r!   r)   ÚdirnameÚabspathÚ__file__Úlinksr   r   r   r   rL   rc   rk   rm   r<   r   r   r   r   Ú<module>   s&   
$G3$  \
ÿ