o
    ]š”jòd ã                   @   s)  d dl Z d dlZd dlZd dlZd dlZej d d¡ d dlmZm	Z	m
Z
 dZdZe  ¡ ZedƒZe e¡\ZZZerYedƒ erEeeƒ edƒ erPed	ƒ ned
ƒ e d ¡ dev r’e d¡r’edƒ d dlZej ¡ Zded< ded d< d ed d< ee ¡ ƒ edƒ edƒ e d ¡ dev �r7e d¡�r7e d¡Zedƒ znej e¡s¶edƒ e d ¡ eedƒ�Z e !e ¡Z"W d  ƒ n1 sËw   Y  e #e¡Z$de"vrÝi e"d< e$e"d d< d dl%m%Z% e% &¡  '¡ e"d< eedƒ�Z ej(e"e dd� W d  ƒ n	1 �s
w   Y  ed
ƒ W n e)�y1 Z* zede+e*ƒ› d�ƒ W Y dZ*[*ndZ*[*ww e d ¡ dev �rße d¡�rße d¡Z,edƒ zqej e¡�s\edƒ e d ¡ eedƒ�Z e !e ¡Z"W d  ƒ n	1 �srw   Y  e #e,¡Z-de"v�r…i e"d< e-e"d d < d dl%m%Z% e% &¡  '¡ e"d< eedƒ�Z ej(e"e dd� W d  ƒ n	1 �s²w   Y  ed
ƒ W n e)�yÙ Z* zed!e+e*ƒ› d�ƒ W Y dZ*[*ndZ*[*ww e d ¡ d"ev �r‡e d"¡�r‡e d"¡Z.edƒ zqej e¡�sedƒ e d ¡ eedƒ�Z e !e ¡Z"W d  ƒ n	1 �sw   Y  e #e.¡Z/de"v�r-i e"d< e/e"d d#< d dl%m%Z% e% &¡  '¡ e"d< eedƒ�Z ej(e"e dd� W d  ƒ n	1 �sZw   Y  ed
ƒ W n e)�y� Z* zed$e+e*ƒ› d�ƒ W Y dZ*[*ndZ*[*ww e d ¡ d%ev �r1e d%¡�r1e d%¡Z0edƒ zsej e¡�s¬edƒ e d ¡ eedƒ�Z e !e ¡Z"W d  ƒ n	1 �sÂw   Y  e #e0¡Z1e2e1 3d&¡ƒe" 4di ¡ 4d'i ¡d&< d dl%m%Z% e% &¡  '¡ e"d< eedƒ�Z ej(e"e dd� W d  ƒ n	1 �sw   Y  ed
ƒ W n e)�y+ Z* zed(e+e*ƒ› d�ƒ W Y dZ*[*ndZ*[*ww e d ¡ d)ev �r-e d)¡�r-e d)¡Z5edƒ zÅej e¡�sVedƒ e d ¡ eedƒ�Z e !e ¡Z"W d  ƒ n	1 �slw   Y  e #e5¡Z1d*d+„ Z6e1 3d,¡�p�g Z7e8e7e9ƒ�sŠg Z7e2e1 3d-¡ƒd.d/„ e7D ƒe6e1 3d0¡d1d2d3ƒe6e1 3d4¡d5d d6ƒe6e1 3d7¡d8d d6ƒe6e1 3d9¡d:d d6ƒe6e1 3d;¡d<d d3ƒe:e6e1 3d=¡dd>d?ƒƒd@œZ;e;e" 4di ¡dA< d dl%m%Z% e% &¡  '¡ e"d< eedƒ�Z ej(e"e dd� W d  ƒ n	1 �s w   Y  ed
ƒ W n e)�y' Z* zedBe+e*ƒ› d�ƒ W Y dZ*[*ndZ*[*ww e d ¡ dCev �rÀe dC¡�rÀe dC¡Z<edƒ �z[ej e¡�sSedƒ e d ¡ eedƒ�Z e !e ¡Z"W d  ƒ n	1 �siw   Y  e #e<¡Z1dDd+„ Z6dEdF„ Z=e1 3dG¡�p‚i Z>e1 3dH¡�pŠi Z?e1 3dI¡�p’i Z@e1 3dJ¡�pši ZAe1 3dK¡�p¢g ZBe8eBe9ƒ�s«g ZBdLd/„ eBD ƒZBe@ 3dM¡ZCzeCdNv�rÁeDeCƒndZCW n eEeFf�yÒ   dZCY nw e2e1 3dO¡ƒe=e> 3dP¡dQƒe=e> 3dR¡dSƒdTœe6e1 3dU¡d2d d5ƒe6e1 3dV¡dWdXd3ƒe+e? 3dY¡ƒdZk�rdZnd[e6e? 3d[¡d2d d1ƒe6e? 3d\¡d]d^d_ƒe6e? 3d`¡d<d>d1ƒdaœeBe2e@ 3db¡ƒeCdcœdde:e6e1 3de¡�p9i  3dd¡d<d>d5ƒƒie:e6eA 3df¡dgd>d?ƒƒe6eA 3dd¡d5d>dhƒe6eA 3di¡d5d dhƒdjœdkœ	ZGeGe" 4di ¡dl< d dl%m%Z% e% &¡  '¡ e"d< eedƒ�Z ej(e"e dd� W d  ƒ n	1 �s“w   Y  ed
ƒ W n e)�yº Z* zedme+e*ƒ› d�ƒ W Y dZ*[*ndZ*[*ww e d ¡ dnev �rœe dn¡�rœe dn¡ZHedƒ z¥ej e¡�såedƒ e d ¡ eedƒ�Z e !e ¡Z"W d  ƒ n	1 �sûw   Y  e #eH¡Z1e8e1eIƒ�sedoƒ e d ¡ i ZJe1 K¡ D ]\ZLZMe+eLƒ N¡ ZOeO�r-e2eMƒeJeO< �qeJe" 4di ¡dp< d dl%m%Z% e% &¡  '¡ e"d< eedƒ�Z ej(e"e dd� W d  ƒ n	1 �s^w   Y  ePdqdr„ eJ K¡ D ƒƒZQee RdseQdtœ¡ƒ W n e)�y– Z* zedue+e*ƒ› d�ƒ W Y dZ*[*ndZ*[*ww e d ¡ dvev �ræe dv¡�ræe dv¡ZSedƒ �zej e¡�sÂedƒ e d ¡ eedƒ�Z e !e ¡Z"W d  ƒ n	1 �sØw   Y  e #eS¡Z1e1 3dw¡�péi ZTdxdy„ ZUeUeT 3dz¡d{d>d|ƒZVeUeT 3d}¡d~d>d|ƒZWeUeT 3d¡d€d>d|ƒZXeYeWeVƒZWeYeXeWƒZXe2e1 3dO¡ƒeVeWeXeUeT 3d�¡d‚d dƒƒeUeT 3d„¡d…d>d†ƒe:eUeT 3d‡¡d>d>dˆƒƒe:eUeT 3d‰¡dd>dˆƒƒe:eUeT 3dŠ¡dd>dXƒƒe:eUeT 3d‹¡dd>dXƒƒeUeT 3dŒ¡d�d2d†ƒdŽœ
d�œe" 4di ¡d�< d dl%m%Z% e% &¡  '¡ e"d< eedƒ�Z ej(e"e dd� W d  ƒ n	1 �s˜w   Y  zd dlZZ[e[j\j]e[j\j^d‘d’d“�dgd”� _¡  W n
 e)�y½   Y nw ed
ƒ W n e)�yà Z* zed•e+e*ƒ› d�ƒ W Y dZ*[*ndZ*[*ww e d ¡ d–ev �	rBe d–¡�	rBe d–¡Z`edƒ �z!ej d d¡ d d—lambZc ej e¡�sedƒ e d ¡ eedƒ�Z e !e ¡Z"W d  ƒ n	1 �s/w   Y  e #e`¡Z1�d¤d˜d™„Zde1 3dš¡Zee8eee9ƒ�sNecdš Zed›d/„ eeD ƒZei dœeddœd d^ƒ“d�edd�d d^ƒ“džeddžd d^ƒ“dŸeddŸd d^ƒ“d edd d>d¡ƒ“d¢edd¢d>d^ƒ“d£edd£d d^ƒ“d¤edd¤d d¡ƒ“d¥edd¥d>d¦ƒ“dšee“d§edd§d>d¨ƒ“d©e2e1 3d©ds¡ƒ“dªeddªd«d‚d¬d­�“d®edd®d>d¯ƒ“d°edd°d>d|ƒ“d±edd±d1d²ƒ“d³edd³d>d¡ƒ“Zfefd£ efd¢ k�rÝefd¢ efd£< efe" 4di ¡d´< d dl%m%Z% e% &¡  '¡ e"d< eedƒ�Z ej(e"e dd� W d  ƒ n	1 �	sw   Y  ee Rdsefdµœ¡ƒ W n  e)�	y< Z* zee Rd¬d¶e*› �d·œ¡ƒ W Y dZ*[*ndZ*[*ww e d ¡ d¸ev �	rÇe d¸¡�	rÇe d¸¡Zgedƒ zLej d d¡ d d¹lambZcmhZhmiZj e #eg¡Z1ejƒ ZkeIekƒZle1 K¡ D ]\ZLZMeLecv �	rŠeMdu�	rŠeMeleL< �	qxehekdº�Zmeheldº�Znee Rdsenemd»œ¡ƒ W n e)�	yÁ Z* zee Rd¬e+e*ƒd·œ¡ƒ W Y dZ*[*ndZ*[*ww e d ¡ d¼ev �re d¼¡�re d¼¡Zoedƒ �ze #eo¡ZpdZqzeedƒ�Z e !e ¡ZqW d  ƒ n	1 �	sûw   Y  W n e)�
y   dZqY nw depv �
rQd½epd v �
rQeq�
r*eq�
p!i  3di ¡ 3d½i ¡ni Zrepd d½  K¡ D ]\ZsZtd¾etv�
sCet 3d¾¡�
sOer 3esi ¡ 3d¾d¿¡etd¾< �
q4eqdu�
r‘eq 3dÀi ¡Zuep 3dÀi ¡ K¡ D ],\ZvZweu 3evi ¡ 3dÁi ¡Zxew 3dÁi ¡ K¡ D ]\ZyZzeyexv �
r�exey  3dÂi ¡ezdÂ< �
qz�
qdep 3dÀ¡Z{e8e{eIƒ�
r½dÃd/„ e{D ƒZ|e|�
r½dÄdÅ„ e{ K¡ D ƒepdÀ< epdÀ  }dÆdÅ„ e|D ƒ¡ eedƒ�Z ej(epe dd� W d  ƒ n	1 �
sÖw   Y  ed
ƒ W n e)�
yý Z* zedÇe+e*ƒ› d�ƒ W Y dZ*[*ndZ*[*ww e d ¡ dÈev �rÆedƒ z•e dÉ¡�pd N¡ Z~e dÊ¡�pd N¡ Zve dË¡�p(d N¡ Zye~�r4ev�r4ey�s=edÌƒ e d ¡ d dÍlm€Z€ e€ƒ Z�dÀevdÁeydÂe~gZ‚d¬ZƒdÎD ]BZ„e„ev �r“e e„¡�p`d N¡ Z…e�j†e‚e„g e…�rne…ndd¬dÏ�Z‡e‡ 3dÐ¡�s‘e+e‡ 3dÑ¡ƒ ˆdÒdÓ¡Z‰edÔe„e‰f ƒ e d ¡ dsZƒ�qReƒ�rœe� Š¡  ed
ƒ W n e)�yÀ Z* zedÕe+e*ƒ ˆdÒdÓ¡ ƒ W Y dZ*[*ndZ*[*ww e d ¡ dÖev �rÞe dÖ¡�rÞedƒ ed
ƒ e d ¡ e ‹¡  ed×ƒ dØdÙ„ ZŒdÚdÛ„ Z�dÜdÝ„ ZŽdÞdß„ Z�e�ƒ Z"e"dÀ Z{e"dà Z�e"dá Z‘e"dâ e"dã e"dä e"d dåœZ’edæƒ e�r8e “¡ Z”e”�r)ee
dçƒƒ nee	dçƒƒ edèƒ e d ¡ edéƒ e{ K¡ D �]ƒ\ZvZwedêev› dë�ƒ edìƒ edíƒ edîev› dïev› dð�ƒ ew 3dñi ¡Z•e• 3dpd¡Z–e• 3dòd¡Z—e–�r{e–dód… ndZ˜e—�r†e—dód… ndZ™edôƒ e–�r›edõev› döe˜› d÷�ƒ nedõev› dø�ƒ e—�r²edõev› dùe™› d÷�ƒ nedõev› dú�ƒ edðƒ ew 3dûd¡Zšeš�rÖedüev› dýeš› dþeš› dð�ƒ nedüev› dÿ�ƒ ew 3�d d¡Z›ew 3�dd¡Zœew 3�dd¡Z�ew 3�dd¡Zžew 3�dd¡ZŸew 3�dd¡Z e�dƒ e›�r"e�dev› �de›› �d	e›› dð�ƒ n
e�dev› �d
�ƒ ež�rAe�dev› �dež› �dež› dð�ƒ n
e�dev› �d�ƒ edðƒ e�dƒ eœ�rie�dev› �deœ› �deœ› dð�ƒ n
e�dev› �d�ƒ eŸ�rˆe�dev› �deŸ› �deŸ› dð�ƒ n
e�dev› �d�ƒ edðƒ e�dƒ e��r°e�dev› �de�› �de�› dð�ƒ n
e�dev› �d�ƒ e �rÏe�dev› �de › �de › dð�ƒ n
e�dev› �d�ƒ edðƒ edðƒ e�dƒ ew 3�di ¡Z¡e¡ 3�d¡�rAe¡ 3�d¡�rAe¡ 3�d¡Z¢e¡ 3�d¡Z£e¡ 3�dd¡Z¤e+e¡ 3�dds¡ƒ ¥¡ Z¦e¤�p"e¢› de£› �Z§e�dev› �de¢› �d e£› �d!e¦› �d"e¤› �d#e§› d÷�ƒ n
e�d$ev› �d%�ƒ ew 3�d&d¡Z¨e¨�rfe�d'ev› dýe¨› �d(e¨› d÷�ƒ n
e�d)ev› �d*�ƒ ew 3�d+d¡Z©e©�r‹e�d,ev› dýe©› �d-e©› d÷�ƒ n
e�d.ev› �d/�ƒ edðƒ edðƒ e�d0ev› �d1�ƒ ewdÁ  K¡ D �]\ZyZzez�d2 ZªezdÂ Z«e�d3ey› �d4�ƒ e�d5ƒ e�d6ey› �d7ey› dð�ƒ e�d8ƒ ez 3�di ¡Z¬e¬ 3�d¡�rFe¬ 3�d¡�rFe¬ 3�d¡Z¢e¬ 3�d¡Z£e¬ 3�dd¡Z¤e+e¬ 3�dds¡ƒ ¥¡ Z¦e¤�pe¢› de£› �Z§e�d9ey› �de¢› �d e£› �d"ev› dýey› �de¦› �d"e¤› �d:e¢› �d;e£› �d<e§› d÷�ƒ ne�d=ey› �d>ev› dýey› �d?�ƒ ez 3�d&d¡Z­e­�r|e�d@ey› dýe­› dýev› dýey› �dAe­› �dBe­› d÷�ƒ ne�dCey› �dDev› dýey› �dE�ƒ ez 3�d+d¡Z®e®�r¯e�dFey› dýe®› dýev› �dGe®› �dHe®› d÷�ƒ ne�dIey› �dDev› �dJ�ƒ ez 3�dK�dL¡Z¯e�dMev› dýey› �de¯› �dNe¯› �dO�	ƒ edðƒ edðƒ e�dPƒ e�dQe°e«ƒ› �dR�ƒ e�dSƒ e« K¡ D ]B\Z~Z±e8e±eIƒ�re± 3�dTd¡ndZ²e² ˆdÒ�dU¡Z³e�dVe~› �dWe³› �dX�ƒ e�dYe~› dð�ƒ e²�r9e�dZe²› dð�ƒ edðƒ �qüedðƒ edðƒ e�d[ƒ eª K¡ D �]_\Z´Zµeµ 3�d\d ¡Z¶eµ 3�d]�d^¡Z·e�d_e´› �d`�ƒ e�daƒ e�dbe´› �dce´› �dd�ƒ e�deƒ eµ 3�di ¡Z¸e¸ 3�d¡�rèe¸ 3�d¡�rèe¸ 3�d¡Z¢e¸ 3�d¡Z£e¸ 3�dd¡Z¤e+e¸ 3�dds¡ƒ ¥¡ Z¦e¤�pÂe¢› de£› �Z§e�dfe´› �de¢› �d e£› �d"ev› dýey› �de¦› �d"e¤› �dge§› d÷�ƒ ne�dhe´› �d>ev› dýey› �di�ƒ eµ 3�d&d¡Z¹e¹�re�dje´› dýe¹› dýev› dýey› �dke¹› d÷�ƒ ne�dle´› �dDev› dýey› �dm�ƒ eµ 3�d+d¡Zºeº�rLe�dne´› dýeº› dýev› dýey› �doeº› d÷�ƒ ne�dpe´› �dDev› dýey› �dq�ƒ edðƒ edðƒ e�dre´› �de¶› �dse¶› �dt�ƒ e�due´› dýe·› �dve·› dð�ƒ eµ 3�dwd ¡Z»e»�r—e»› �dx�n�dyZ¼e�dze´› �de»› �d{e¼› dð�ƒ edðƒ �qPedðƒ edðƒ �q­edðƒ edðƒ �q@e�d|ƒ e��rËe�d}ƒ e�d~ƒ e�d[ƒ e� K¡ D ]â\Z´Zµeµ 3�d\d ¡Z¶eµ 3�d]�d^¡Z·e�d_e´› �d`�ƒ e�daƒ e�dbe´› �dce´› �dd�ƒ e�dƒ eµ 3�di ¡Z¸e¸ 3�d¡�r`e¸ 3�d¡�r`e¸ 3�d¡Z¢e¸ 3�d¡Z£e+e¸ 3�dds¡ƒ ¥¡ Z¦e�d€e´› �de¢› �d e£› �d!e¦› �d�e¢› �d;e£› �d‚�ƒ n
e�dhe´› �dƒ�ƒ eµ 3�d+d¡Zºeº�r‰e�dne´› dýeº› �d„eº› �d1eº› d÷�	ƒ n
e�dpe´› �d…�ƒ edðƒ edðƒ e�dre´› �de¶› �dse¶› �dt�ƒ e�due´› dýe·› �dve·› dð�ƒ edðƒ �qàedðƒ edðƒ g Z½z3d dlZm\Z¾ e¾j]�d†�d‡d”��Z¿e #e¿ _¡  À¡ ¡�péi  3�dˆg ¡Z½W d  ƒ n	1 �súw   Y  W n e)�y   g Z½Y nw e�d‰e Re½¡› �dŠ�ƒ e�d‹ƒ e{ K¡ D ]$\ZvZwev�dŒv�rFe9ewdÁ  Á¡ ƒZÂe�d�ev› �dŽe ReÂ¡› �d��ƒ �q#e�d�ƒ e{ K¡ D ]@\ZvZwev�dŒv�r�e�d�ev› �d‘�ƒ ewdÁ  K¡ D ]\ZyZze9ez�d2  Á¡ ƒZÃe�d’ey› �dŽe ReÃ¡› �d��ƒ �qke�d“ƒ �qQe�d”ƒ zNeedƒ�Z e !e ¡ZÄW d  ƒ n	1 �s®w   Y  deÄv �rÝd½eÄd v �rÝeÄd d½ D ]Zsd¾eÄd d½ es v �rÛeÄd d½ es d¾= �qÅee ReÄ¡ƒ W n	   e�d•ƒ Y ej d �d–¡ d �d—lÅmÆZÆ e�d˜e ReÆ¡› �d™�ƒ zd �dšlÇmÈZÉ e�d›e ReÉjÊ¡› �dœe ReÉjË¡› �d™�ƒ W n e)�y9   e�d�ƒ Y nw zd �džlÇmÌZÍ e�dŸe ReÍ Î¡ ¡› �d™�ƒ W n e)�ya   e�d ƒ Y nw e�d¡e+e:ej Ïej Ðej Ñej ÒeÓ¡¡�d¢¡¡ƒƒ �d£ ƒ dS (¥  é    Nz/opt/ngon/apps)ÚAuthManagerÚgenerate_login_modal_htmlÚgenerate_access_denied_htmlz#/opt/ngon/config/master_config.json)ÚSparesúOut of ServicezOut of Service - TXÚsite_managerzContent-Type: application/jsonÚ z6{"success": false, "error": "Authentication required"}z{"success": true}ÚlogoutzContent-Type: text/htmlÚ	ngon_authú/Úpathzmax-ageai  
    <html><body style="background: #1a1a1a; color: #fff; font-family: Arial, sans-serif; text-align: center; padding: 50px;">
        <h1 style="color: #00ff00;">Logged Out Successfully</h1>
        <p>Your authentication session has been cleared.</p>
        <a href="site_manager.py" style="color: #4a9eff;">Return to Site Manager</a>
    </body></html>
    Úajax_miner_typeszContent-Type: application/json
z4{"success": false, "error": "Config file not found"}ÚrÚmiscÚminer_types)ÚdatetimeÚlast_updatedÚwé   ©Úindentz7{"success": false, "error": "Error saving miner types: z"}Úajax_employeesÚ	employeesz5{"success": false, "error": "Error saving employees: Úajax_gen_warningsÚgen_warningsz8{"success": false, "error": "Error saving gen warnings: Úajax_gen_controlÚenable_engine_actionsÚgen_controlz7{"success": false, "error": "Error saving gen control: Úajax_power_mgmtc              	   C   ó8   zt | ƒ}W n ttfy   | Y S w t|t||ƒƒS ©N©ÚfloatÚ	TypeErrorÚ
ValueErrorÚmaxÚmin©ÚvÚdfltÚloÚhiÚn© r-   úsite_manager.pyÚ_clampé   ó   ÿr/   Úlive_control_groupsÚlive_control_enabledc                 C   s$   g | ]}t |ƒ ¡ rt |ƒ ¡ ‘qS r-   )ÚstrÚstrip©Ú.0Úgr-   r-   r.   Ú
<listcomp>ö   s   $ r8   Úlive_fresh_secé<   é   iX  Úlive_boot_wake_secéx   i  Úlive_wake_lockout_seci,  Úlive_wake_lockout_highload_seciÂ  Úlive_shed_lockout_secé   Údeadband_minersé   é   )r2   r1   r9   r<   r>   r?   r@   rB   Ú
power_mgmtz6{"success": false, "error": "Error saving power mgmt: Úajax_auto_restartc              	   C   r   r    r!   r'   r-   r-   r.   r/      r0   c                 C   s’   z>t | ƒ d¡\}}t|ƒt|ƒ}}d|  krdkr9n W |S d|  kr+dkr<n W |S |d›d|d›�W S W |S W |S  tyH   Y |S w )Nú:r   é   é;   Ú02d)r3   ÚsplitÚintÚ	Exception)r(   r)   ÚhÚmr-   r-   r.   Ú_hhmm'  s    üýÿüþþrP   ÚwindowÚwarmupÚgasÚcircuit_breakerÚexclude_categoriesc                 C   s<   g | ]}t |ƒ ¡ rt |ƒ ¡  ¡ d krt |ƒ ¡  ¡ ‘qS )Úoverload©r3   r4   Úlower©r6   Úcr-   r-   r.   r8   :  s   < Úflow_min_mcfd)Nr   ÚnullÚsystem_enabledÚstartz21:00Úendz05:00)r^   r_   Újust_ran_minutesÚconfirm_secondsé-   é
   ÚmodeÚtempÚminutesÚtarget_fé    éd   éÜ   Úmax_minutes)rd   rf   rg   rk   Úblock_on_category_gas)rl   r[   Úwindow_minutesÚretripÚmax_attemptsé   i   Úcooldown_minutes)ro   rm   rq   )	r]   rQ   r`   ra   rR   rU   rS   rn   rT   Úauto_restartz8{"success": false, "error": "Error saving auto-restart: Úajax_alertszE{"success": false, "error": "Expected an object of category -> bool"}Úalertsc                 c   s   � | ]	\}}|s|V  qd S r    r-   ©r6   Úkr(   r-   r-   r.   Ú	<genexpr>‘  s   € rw   T)ÚsuccessÚdisabledz:{"success": false, "error": "Error saving alert settings: Úajax_gas_holdÚdefaultsc              	   C   r   r    r!   r'   r-   r-   r.   Ú_gclamp®  r0   r|   Ú	watch_psig      2@éÈ   Ú	hold1_psig      0@Ú	hold2_psig      ,@Úrelease_margin_psig      ð?é2   Úrelease_minutesg      >@iÐ  Úhold1_reserveé   Úhold2_reserveÚconfirm_samplesÚmin_live_gensÚstale_minutesg     €F@)
r}   r   r€   r�   rƒ   r„   r†   r‡   rˆ   r‰   )r]   r{   Úgas_holdz*http://localhost:5050/api/config/broadcastÚPOST)Úmethod)Útimeoutz4{"success": false, "error": "Error saving gas hold: Úajax_dead_miner)ÚDEFAULTSc              	   C   s\   t |  }z
tt | |¡ƒ}W n ttfy   | Y S w t|t||ƒƒ}|r,tt	|ƒƒS |S r    )
ÚDM_DEFAULTSr"   ÚpayloadÚgetr#   r$   r%   r&   rL   Úround)Úkeyr*   r+   Úas_intr)   r,   r-   r-   r.   Ú	_dm_clampû  s   ÿr–   Úhw_error_catsc                 C   s(   g | ]}t |ƒ ¡ rt |ƒ ¡  ¡ ‘qS r-   rW   rY   r-   r-   r.   r8     s   ( Ú	w_rebootsÚ
w_hw_errorÚw_zero_hashÚ
w_unstableÚreboots_fulliô  Úpull_thresholdÚscore_floorÚnever_hashed_rebootsÚauto_min_daysé   Úno_hash_daysim  Úuse_pod_gateÚpod_active_fracg        F)r•   Úwindow_dayséZ   Úmin_samples_per_dayÚshort_uptimei€Q Úreal_hash_thÚ
dead_miner)rx   Úsavedz"Error saving dead-miner settings: )rx   ÚerrorÚajax_dead_preview)r�   Útier_countsÚload_config)Úcfg)rx   Úpreviewr«   Údirect_config_saveÚusersÚpasswordÚ6767ÚsitesÚgenerator_groupsÚ
generatorsc                 C   s   g | ]}|t v r|‘qS r-   )ÚTRAILING_SITES)r6   Úsr-   r-   r.   r8     s    c                 C   s   i | ]\}}|t vr||“qS r-   )Útailru   r-   r-   r.   Ú
<dictcomp>�  s    r¼   c                 C   s   i | ]}|t | “qS r-   )r¶   )r6   rv   r-   r-   r.   r¼   ‚  s    z*{"success": false, "error": "Save failed: Úsave_gen_fieldsÚgen_idÚ	site_nameÚ
group_namezE{"success": false, "error": "gen_id, site_name, group_name required"})ÚConfigManager)ÚmacÚ
bridge_mac)Ú	broadcastrx   r¬   ú"ú'z3{"success": false, "error": "update %s failed: %s"}z!{"success": false, "error": "%s"}Úsession_checkzContent-Type: text/html
c                  C   sª  i } i }t ƒ }tj t¡rÐttdƒ�}t |¡}W d  ƒ n1 s"w   Y  | di ¡ 	¡ D ] \}}| dd¡| dd¡| dd¡| dd¡| d	d¡| d
d¡| dd¡| di ¡| dd¡i | di ¡| dd¡| ddddœ¡dœ| |< | di ¡ 	¡ D ]N\}}i | di ¡| dd¡| di ¡| dd¡| dd¡dœ| | d |< | di ¡D ]}	| 
|	¡ q­| di ¡ 	¡ D ]\}
}|| | d | d |
< q½q€q/| ||fS )zNLoad sites with their generator groups, pods and generators from master configr   Nr¶   Úsite_idr   Úpool1Úpool2Úpool3Úworker_name1Úworker_name2Úworker_name3ÚlocationÚnetworkÚpeplinkÚ	switch_ipÚwebhooks)rt   Úchat)rÈ   rÉ   rÊ   rË   rÌ   rÍ   rÎ   rÏ   rÐ   r·   rÑ   rÒ   rÓ   r·   r¸   Ú
max_gen_kwéJ  )Úpodsr¸   rÐ   rÑ   rÒ   rÕ   r×   )ÚsetÚosr   ÚexistsÚMASTER_CONFIG_FILEÚopenÚjsonÚloadr’   ÚitemsÚadd)r¶   Úunassigned_podsÚall_generatorsÚfÚconfigr¿   Ú	site_datarÀ   Ú
group_datar¾   Úpod_nameÚpod_settingsr-   r-   r.   Úload_sites_with_groupsÄ  sJ   ÿ











ó




ú
ÿñ
ré   c            	      C   s  i i i i i dœ} t j t¡r�ttdƒ�}t |¡}W d  ƒ n1 s#w   Y  | di ¡ ¡ D ]P\}}| di ¡ ¡ D ]C\}}| di ¡ ¡ D ]6\}}| dd¡| d	 |< | d
d¡| d |< | dd¡| d |< | dd¡| d |< | dd¡| d |< qHq<q0| S )z^Load pod settings (sleep, reboot, miner counts, miner types, sleep targets) from master config)Úsleep_eligibleÚreboot_eligibleÚwake_eligibleÚminer_countsr   r   Nr¶   r·   r×   Úauto_sleep_enabledFrê   Úauto_reboot_enabledrë   Úauto_wake_enabledrì   Úminer_countr   rí   Ú
miner_typeÚM60r   )	rÙ   r   rÚ   rÛ   rÜ   rÝ   rÞ   r’   rß   )	Úsettingsrã   rä   r¿   rå   rÀ   ræ   rç   rè   r-   r-   r.   Úload_settingsõ  s*   ûÿûÿrõ   c           $      C   sØ  t j t¡sdS �z@g }i }| r{|  d¡D ]e}d|v rz| dd¡\}}| |¡ g ||< |rz| d¡D ]F}d|v ry| dd¡\}}d|v rb| dd¡\}	}
dd	„ |	 d
¡D ƒ}dd	„ |
 d
¡D ƒ}ndd	„ | d
¡D ƒ}g }||  |||dœ¡ q3qttdƒ�}t |¡}W d  ƒ n1 s�w   Y  i }|D ]}||d v r©|d | ||< q™|d D ]}||vr¼|d | ||< q®||d< | 	¡ D �]J\}}||d v �r|d | }i }|D �]/}|d }|| 
di ¡v �r•|d | }i }|d D ]_}d|v �r| dd¡\}}d|i||< q÷|}d}|d  ¡ D ]7}| 
di ¡ ¡ D ]&}|| 
di ¡v �rF|d | }t|tƒ�rF| 
d¡�rF|||< d} n�q!|�rM n�q|�sVi ||< q÷||d< t| 
di ¡ ¡ ƒ}t|d ƒ}|| D ]}|d |= �qo|| D ]}dddddddtidœ|d |< �q||||< qÛi }|d D ]a}d|v �r²| dd¡\}}d|i||< �q›|}d}|d  ¡ D ]7}| 
di ¡ ¡ D ]&}|| 
di ¡v �rë|d | }t|tƒ�rë| 
d¡�rë|||< d} n�qÆ|�rò n�q¼|�sûi ||< �q›dd„ |d D ƒ|dœ||< qÛ||d< qÅddlm}m}  | | j¡ ¡  ¡ }!|!|d < ttd!ƒ�}tj||d"d#� W d  ƒ W dS 1 �sBw   Y  W dS  t�yk }" zddl}#td$|"› �|#jd%� W Y d}"~"dS d}"~"ww )&z1Parse and save groups data from the web interfaceNz||z::rC   ú|ú@ú;c                 S   ó   g | ]
}|  ¡ r|  ¡ ‘qS r-   ©r4   ©r6   Úpr-   r-   r.   r8   *  ó    z$save_groups_data.<locals>.<listcomp>ú,c                 S   rù   r-   rú   r5   r-   r-   r.   r8   +  rý   c                 S   rù   r-   rú   rû   r-   r-   r.   r8   -  rý   )Únamer×   r¸   r   r¶   rÿ   r·   r¸   rG   ÚnotesFTr×   r   ró   Údisplay_children)rñ   rî   rï   rð   Úpower_targetrò   rÑ   c              
   S   s    i | ]}|d dddd ddœ“qS )r   TFró   )rñ   rî   rï   rð   r  rò   r-   )r6   Úpodr-   r-   r.   r¼   £  s    ÿz$save_groups_data.<locals>.<dictcomp>)r×   r¸   )r   Útimezoner   r   r   r   zError saving groups data: )Úfile)rÙ   r   rÚ   rÛ   rK   ÚappendrÜ   rÝ   rÞ   rß   r’   ÚvaluesÚ
isinstanceÚdictrØ   ÚkeysÚtruer   r  ÚnowÚutcÚ
astimezoneÚ	isoformatÚdumprM   ÚsysÚprintÚstderr)$Úgroups_dataÚsites_orderÚ
sites_inforå   r¿   Úgroups_partræ   rÀ   ÚrestÚ	pods_partÚ	gens_partr×   r¸   rã   rä   Ú	new_sitesÚgroups_listÚnew_generator_groupsÚ
group_infoÚexisting_groupÚnew_generatorsÚ	gen_entryr¾   Úgen_noteÚfound_notesÚ
check_siteÚcheck_groupÚgen_dataÚcurrent_podsÚnew_podsrç   r   r  Únew_timestampÚer  r-   r-   r.   Úsave_groups_data  sü   
ý€ÿ€€

€ÿ€ÿù

€ÿ€ÿý€(ÿ"€ýr+  c                  C   s>   t ƒ \} }}tƒ }| |t|ƒ|d |d |d |d dœ}|S )z%Read all existing configuration filesrê   rë   rí   r   )r¶   rá   râ   rê   rë   rí   r   )ré   rõ   Úlist)r¶   rá   râ   rô   rä   r-   r-   r.   Úread_config_filesã  s   ù	r-  rá   râ   rê   rë   rí   )rê   rë   rí   r   ar  
<!DOCTYPE html>
<html>
<head>
    <title>NGON Site Manager</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="ngon-modals.css">
    <link rel="stylesheet" href="site_manager.css">
    <style>
        .config-changed-overlay {
            display: none;
            position: fixed;
            top: 0; left: 0; right: 0; bottom: 0;
            background: rgba(0, 0, 0, 0.85);
            z-index: 20000;
            justify-content: center;
            align-items: center;
        }
        .config-changed-modal {
            background: #1a1a1a;
            border: 2px solid #ff6600;
            border-radius: 12px;
            padding: 40px;
            text-align: center;
            max-width: 400px;
        }
        .config-changed-modal h2 { color: #ff6600; margin: 0 0 20px 0; }
        .config-changed-modal p  { color: #ccc; margin: 0 0 30px 0; font-size: 16px; }
        .config-changed-modal button {
            background: #ff6600;
            color: white;
            border: none;
            padding: 15px 40px;
            font-size: 16px;
            border-radius: 6px;
            cursor: pointer;
        }
        .config-changed-modal button:hover { background: #ff8833; }
    </style>
    <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
</head>
<body>

<!-- Config Changed Modal: blocks interaction when another user has modified
     the config, prompting a reload so we don't overwrite their changes. -->
<div id="configChangedOverlay" class="config-changed-overlay">
    <div class="config-changed-modal">
        <h2>Page Data Changed</h2>
        <p>Another user has made changes to the configuration. Please reload to see the latest data before saving.</p>
        <button onclick="location.reload()">Reload Page</button>
    </div>
</div>
zSite Managerz</body></html>zÂ
<!-- Main content -->
<div id="mainContent">
<h1>NGON Site Manager</h1>

    <div class="main-area" ondrop="dropInMainArea(event)" ondragover="allowDrop(event)" ondragleave="leaveDrop(event)">
z'<div class="site-container" data-site="z¤" draggable="true" ondragstart="dragSite(event)" ondragend="endDrag(event)" ondrop="dropInSite(event)" ondragover="allowDrop(event)" ondragleave="leaveDrop(event)">z<div class="site-header">z<div>z/<div class="site-title" onclick="editSiteName('zW'); event.stopPropagation();" style="cursor: pointer;" title="Click to edit site name">z</div>rÓ   rÔ   iìÿÿÿzX<div style="display: flex; gap: 10px; font-size: 11px; margin: 2px 0; flex-wrap: wrap;">z<span onclick="editWebhook('zõ', 'alerts'); event.stopPropagation();" style="cursor: pointer; color: #ff6600; padding: 2px 4px; background: rgba(255,102,0,0.1); border-radius: 3px;" title="Edit alerts webhook"><i class="fas fa-bell" style="margin-right: 3px;"></i>Alerts: ...z</span>zÊ', 'alerts'); event.stopPropagation();" style="cursor: pointer; color: #888; padding: 2px 4px;" title="Add alerts webhook"><i class="fas fa-bell" style="margin-right: 3px;"></i>Add Alerts Webhook</span>zó', 'chat'); event.stopPropagation();" style="cursor: pointer; color: #4a9eff; padding: 2px 4px; background: rgba(74,158,255,0.1); border-radius: 3px;" title="Edit chat webhook"><i class="fas fa-comment" style="margin-right: 3px;"></i>Chat: ...zÇ', 'chat'); event.stopPropagation();" style="cursor: pointer; color: #888; padding: 2px 4px;" title="Add chat webhook"><i class="fas fa-comment" style="margin-right: 3px;"></i>Add Chat Webhook</span>rÈ   z0<div class="site-location" onclick="editSiteId('z', 'z^'); event.stopPropagation();" style="cursor: pointer;" title="Click to edit site ID">Site ID: z', ''); event.stopPropagation();" style="cursor: pointer; color: #888;" title="Click to add site ID">Click to add Site ID</div>rÉ   rÊ   rË   rÌ   rÍ   rÎ   zG<div style="display: flex; gap: 10px; font-size: 12px; margin: 2px 0;">z<div onclick="editPool('z', 1, 'z�'); event.stopPropagation();" style="cursor: pointer; color: #666; white-space: nowrap; min-width: 300px;" title="Click to edit pool 1">Pool 1: z§', 1, ''); event.stopPropagation();" style="cursor: pointer; color: #888; white-space: nowrap; min-width: 300px;" title="Click to add pool 1">Click to add Pool 1</div>z<div onclick="editWorkerName('z‡'); event.stopPropagation();" style="cursor: pointer; color: #666; white-space: nowrap;" title="Click to edit worker name 1">Worker 1: zž', 1, ''); event.stopPropagation();" style="cursor: pointer; color: #888; white-space: nowrap;" title="Click to add worker name 1">Click to add Worker 1</div>z', 2, 'z�'); event.stopPropagation();" style="cursor: pointer; color: #666; white-space: nowrap; min-width: 300px;" title="Click to edit pool 2">Pool 2: z§', 2, ''); event.stopPropagation();" style="cursor: pointer; color: #888; white-space: nowrap; min-width: 300px;" title="Click to add pool 2">Click to add Pool 2</div>z‡'); event.stopPropagation();" style="cursor: pointer; color: #666; white-space: nowrap;" title="Click to edit worker name 2">Worker 2: zž', 2, ''); event.stopPropagation();" style="cursor: pointer; color: #888; white-space: nowrap;" title="Click to add worker name 2">Click to add Worker 2</div>z', 3, 'z�'); event.stopPropagation();" style="cursor: pointer; color: #666; white-space: nowrap; min-width: 300px;" title="Click to edit pool 3">Pool 3: z§', 3, ''); event.stopPropagation();" style="cursor: pointer; color: #888; white-space: nowrap; min-width: 300px;" title="Click to add pool 3">Click to add Pool 3</div>z‡'); event.stopPropagation();" style="cursor: pointer; color: #666; white-space: nowrap;" title="Click to edit worker name 3">Worker 3: zž', 3, ''); event.stopPropagation();" style="cursor: pointer; color: #888; white-space: nowrap;" title="Click to add worker name 3">Click to add Worker 3</div>z<div class="site-controls">rÑ   Úgroup_idÚ	device_idÚ	router_ipr  z$<span onclick="editPeplink('site', 'z', z, z
, '', '', z, 'zè'); event.stopPropagation();" style="cursor: pointer; color: #00ff00; font-size: 11px; padding: 2px 4px; background: rgba(0,255,0,0.1); border-radius: 3px;" title="Edit peplink"><i class="fas fa-wifi" style="margin-right: 3px;"></i>z#<div onclick="editPeplink('site', 'zÀ', 0, 0, '', '', true, ''); event.stopPropagation();" style="cursor: pointer; width: 16px; height: 16px; border: 2px dashed #00ff00; display: inline-block;" title="Click to add peplink"></div>rÒ   z#<span onclick="editSwitch('site', 'zû', '', ''); event.stopPropagation();" style="cursor: pointer; color: #8B5CF6; font-size: 11px; padding: 2px 4px; background: rgba(139,92,246,0.1); border-radius: 3px;" title="Edit switch"><i class="fas fa-network-wired" style="margin-right: 3px;"></i>z"<div onclick="editSwitch('site', 'z³', '', '', ''); event.stopPropagation();" style="cursor: pointer; width: 16px; height: 16px; border: 2px dashed #8B5CF6; display: inline-block;" title="Click to add switch"></div>rÐ   z$<span onclick="editNetwork('site', 'zõ', '', ''); event.stopPropagation();" style="cursor: pointer; color: #0080ff; font-size: 11px; padding: 2px 4px; background: rgba(0,128,255,0.1); border-radius: 3px;" title="Edit network"><i class="fas fa-sitemap" style="margin-right: 3px;"></i>z#<div onclick="editNetwork('site', 'z´', '', '', ''); event.stopPropagation();" style="cursor: pointer; width: 16px; height: 16px; border: 2px dashed #0080ff; display: inline-block;" title="Click to add network"></div>z'<div class="groups-wrapper" data-site="z">r×   z)<div class="group-container" data-group="z¦" draggable="true" ondragstart="dragGroup(event)" ondragend="endDrag(event)" ondrop="dropInGroup(event)" ondragover="allowDrop(event)" ondragleave="leaveDrop(event)">z<div class="group-header">z1<div class="group-title" onclick="editGroupName('z')">z<div class="group-controls">z%<span onclick="editPeplink('group', 'z¹'); event.stopPropagation();" style="cursor: pointer; color: #00ff00; font-size: 10px; padding: 2px 4px; background: rgba(0,255,0,0.1); border-radius: 3px;" title="Edit peplink (Group: z
, Device: z9)"><i class="fas fa-wifi" style="margin-right: 3px;"></i>z$<div onclick="editPeplink('group', 'z
', 0, 0, 'z²', true, ''); event.stopPropagation();" style="cursor: pointer; width: 14px; height: 14px; border: 2px dashed #00ff00; display: inline-block;" title="Click to add peplink"></div>z$<span onclick="editSwitch('group', 'z¿'); event.stopPropagation();" style="cursor: pointer; color: #8B5CF6; font-size: 10px; padding: 2px 4px; background: rgba(139,92,246,0.1); border-radius: 3px;" title="Edit master switch (IP: zB)"><i class="fas fa-network-wired" style="margin-right: 3px;"></i>z#<div onclick="editSwitch('group', 'z', '', 'z®'); event.stopPropagation();" style="cursor: pointer; width: 14px; height: 14px; border: 2px dashed #8B5CF6; display: inline-block;" title="Click to add master switch"></div>z%<span onclick="editNetwork('group', 'z¸', ''); event.stopPropagation();" style="cursor: pointer; color: #0080ff; font-size: 10px; padding: 2px 4px; background: rgba(0,128,255,0.1); border-radius: 3px;" title="Edit network: z;"><i class="fas fa-sitemap" style="margin-right: 3px;"></i>z$<div onclick="editNetwork('group', 'z¬', ''); event.stopPropagation();" style="cursor: pointer; width: 14px; height: 14px; border: 2px dashed #0080ff; display: inline-block;" title="Click to add network"></div>rÕ   rÖ   z<span onclick="editMaxGenKw('a  ); event.stopPropagation();" style="cursor: pointer; color: #ffaa00; font-size: 10px; padding: 2px 4px; background: rgba(255,170,0,0.1); border-radius: 3px; white-space: nowrap;" title="Click to edit max kW per generator"><i class="fas fa-bolt" style="margin-right: 3px;"></i>z kW/gen</span>z <div class="generators-section">zF<div class="generators-title"><i class="fas fa-bolt"></i> Generators (z)</div>z<div class="generators-grid" ondrop="dropInGeneratorsGrid(event)" ondragover="allowDrop(event)" ondragleave="leaveDrop(event)">r   z&quot;zÁ<div class="gen" draggable="true" ondragstart="dragGen(event)" ondragend="endDrag(event)" ondrop="dropOnGenerator(event)" ondragover="allowDrop(event)" ondragleave="leaveDrop(event)" data-gen="z" data-note="z„" onclick="editGeneratorNoteFromData(this); event.stopPropagation();" style="cursor: pointer;" title="Click to edit generator note">z<div class="gen-id">z<div class="gen-note">z<div class="pods-section">rñ   rò   ró   z<div class="pod" data-pod="zK" draggable="true" ondragstart="dragPod(event)" ondragend="endDrag(event)">zQ<div style="display: flex; justify-content: space-between; align-items: center;">z<h4 onclick="editPodName('za'); event.stopPropagation();" style="cursor: pointer; margin: 0;" title="Click to edit pod name">z</h4>zL<div style="display: flex; align-items: center; gap: 4px; flex-wrap: wrap;">z#<span onclick="editPeplink('pod', 'zç'); event.stopPropagation();" style="cursor: pointer; color: #00ff00; font-size: 9px; padding: 1px 3px; background: rgba(0,255,0,0.1); border-radius: 3px;" title="Edit peplink"><i class="fas fa-wifi" style="margin-right: 2px;"></i>z"<div onclick="editPeplink('pod', 'z²', true, ''); event.stopPropagation();" style="cursor: pointer; width: 12px; height: 12px; border: 2px dashed #00ff00; display: inline-block;" title="Click to add peplink"></div>z"<span onclick="editSwitch('pod', 'zò'); event.stopPropagation();" style="cursor: pointer; color: #8B5CF6; font-size: 9px; padding: 1px 3px; background: rgba(139,92,246,0.1); border-radius: 3px;" title="Edit switch"><i class="fas fa-network-wired" style="margin-right: 2px;"></i>z!<div onclick="editSwitch('pod', 'z§'); event.stopPropagation();" style="cursor: pointer; width: 12px; height: 12px; border: 2px dashed #8B5CF6; display: inline-block;" title="Click to add switch"></div>z#<span onclick="editNetwork('pod', 'zì'); event.stopPropagation();" style="cursor: pointer; color: #0080ff; font-size: 9px; padding: 1px 3px; background: rgba(0,128,255,0.1); border-radius: 3px;" title="Edit network"><i class="fas fa-sitemap" style="margin-right: 2px;"></i>z"<div onclick="editNetwork('pod', 'z¨'); event.stopPropagation();" style="cursor: pointer; width: 12px; height: 12px; border: 2px dashed #0080ff; display: inline-block;" title="Click to add network"></div>z/<div class="pod-info" onclick="editMinerCount('zy); event.stopPropagation();" style="cursor: pointer;" title="Click to edit miner count"><i class="fas fa-microchip"></i> z miners</div>z.<div class="pod-info" onclick="editMinerType('zy'); event.stopPropagation();" style="cursor: pointer;" title="Click to edit miner type"><i class="fas fa-cog"></i> Type: r  ÚWÚOffz0<div class="pod-info" onclick="editPowerTarget('zº); event.stopPropagation();" style="cursor: pointer;" title="Click to edit power target (watts per miner, 0=unmanaged)"><i class="fas fa-bolt" style="color: #ffaa00;"></i> Power Target: z
    </div>
z<div class="unassigned-pods" ondrop="dropInUnassignedPods(event)" ondragover="allowDrop(event)" ondragleave="leaveDrop(event)">z3<div class="unassigned-title">Unassigned Pods</div>z;<div style="display: flex; align-items: center; gap: 8px;">z4<i class="fas fa-wifi" onclick="editPeplink('pod', 'zÃ); event.stopPropagation();" style="cursor: pointer; color: #00ff00; font-size: 12px; padding: 4px; margin-right: 8px; vertical-align: middle; display: inline-block;" title="Edit peplink (Group: z)"></i>zç', 0, 0, '', '', true); event.stopPropagation();" style="cursor: pointer; width: 12px; height: 12px; border: 2px dashed #00ff00; margin-right: 8px; display: inline-block; vertical-align: middle;" title="Click to add peplink"></div>zâ', '', ''); event.stopPropagation();" style="cursor: pointer; color: #0080ff; font-size: 10px; padding: 2px; background: rgba(0,128,255,0.1); border-radius: 3px; margin-left: 4px; vertical-align: middle;" title="Edit network: zÞ', '', '', ''); event.stopPropagation();" style="cursor: pointer; width: 12px; height: 12px; border: 2px dashed #0080ff; display: inline-block; margin-left: 4px; vertical-align: middle;" title="Click to add network"></div>z$http://127.0.0.1:5005/alert_settingsé   Ú
categoriesz!<script>window.ALERT_CATALOGUE = z
;</script>uÎ›  
    </div>

<hr>

<!-- Creation buttons using in-memory system -->
<div class="forms-container">
    <div class="form-section">
        <h3>Create New Site</h3>
        <button type="button" onclick="createNewSite()" class="btn">Create Site</button>
    </div>
    
    <div class="form-section">
        <h3>Create New Group</h3>
        <button type="button" onclick="createNewGroup()" class="btn">Create Group</button>
    </div>
    
    <div class="form-section">
        <h3>Create New Pod</h3>
        <button type="button" onclick="createNewPod()" class="btn">Create Pod</button>
    </div>
    
    <div class="form-section">
        <h3>Create New Generator</h3>
        <button type="button" onclick="createNewGenerator()" class="btn">Create Generator</button>
        <p style="font-size: 12px; color: #888;">Generator will be added to the selected group</p>
    </div>
    
    <div class="form-section">
        <h3>Miner Type Specifications</h3>
        <div id="minerTypesContainer"></div>
        <div style="margin-top: 15px;">
            <button onclick="showAddMinerTypeForm()" style="background-color: #28a745; color: white; border: none; padding: 8px 16px; font-size: 14px; border-radius: 4px; cursor: pointer;">
                <i class="fas fa-plus"></i> Add New Miner Type
            </button>
        </div>
        <div id="addMinerTypeForm" style="display: none; margin-top: 15px; padding: 15px; background: rgba(0,0,0,0.3); border-radius: 5px;">
            <div style="display: flex; flex-wrap: wrap; gap: 10px; align-items: center;">
                <input type="text" id="newMinerTypeName" placeholder="Type Name (e.g., S21)" style="background: #333; border: 1px solid #555; color: white; padding: 8px; border-radius: 4px; flex: 1; min-width: 120px;">
                <input type="number" id="newMinerTypeSpec" placeholder="Spec TH/s (e.g., 200)" style="background: #333; border: 1px solid #555; color: white; padding: 8px; border-radius: 4px; flex: 1; min-width: 120px;">
                <input type="number" id="newMinerTypeWattage" placeholder="Spec Watts (e.g., 3400)" style="background: #333; border: 1px solid #555; color: white; padding: 8px; border-radius: 4px; flex: 1; min-width: 120px;">
                <button onclick="addMinerType()" style="background-color: #007bff; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; white-space: nowrap;">Add</button>
                <button onclick="hideAddMinerTypeForm()" style="background-color: #6c757d; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; white-space: nowrap;">Cancel</button>
            </div>
        </div>
    </div>
    
    </div>
</div>

<!-- User Management - full width below other sections -->
<div style="background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 10px; padding: 20px; margin-top: 20px;">
    <h3 style="color: #00ff00; margin-top: 0;">User Management</h3>
    <div id="employeesContainer"></div>
    <div style="margin-top: 15px;">
        <button onclick="showAddEmployeeForm()" style="background-color: #28a745; color: white; border: none; padding: 8px 16px; font-size: 14px; border-radius: 4px; cursor: pointer;">
            <i class="fas fa-plus"></i> Add New User
        </button>
    </div>
    <div id="addEmployeeForm" style="display: none; margin-top: 15px; padding: 15px; background: rgba(0,0,0,0.3); border-radius: 5px;">
        <div style="display: flex; flex-wrap: wrap; gap: 10px; align-items: center;">
            <input type="text" id="newEmployeeInitials" placeholder="Username (e.g., JD)" style="background: #333; border: 1px solid #555; color: white; padding: 8px; border-radius: 4px; flex: 1; min-width: 150px; text-transform: uppercase;">
            <button onclick="addEmployee()" style="background-color: #007bff; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; white-space: nowrap;">Add</button>
            <button onclick="hideAddEmployeeForm()" style="background-color: #6c757d; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; white-space: nowrap;">Cancel</button>
        </div>
    </div>
</div>

<!-- Gen Control: engine-action safety switch (START / ON LOAD) -->
<div style="background: rgba(255,102,0,0.06); border: 1px solid rgba(255,120,60,0.3); border-radius: 10px; padding: 16px; margin-top: 20px;">
    <div style="display:flex; align-items:center; gap:14px; flex-wrap:wrap;">
        <h3 style="color:#ff6600; margin:0;">Generator Control &mdash; Engine Actions</h3>
        <label style="color:#ddd; font-size:14px; display:flex; align-items:center; gap:8px; cursor:pointer;">
            <input type="checkbox" id="gcEngineActions" onchange="saveGenControl()"> Allow remote START / ON&nbsp;LOAD
        </label>
        <span id="gcSaveStatus" style="font-size:13px;"></span>
    </div>
    <div style="color:#d9a273; font-size:12px; margin-top:8px; line-height:1.5;">
        <i class="fas fa-exclamation-triangle" style="color:#ff6600;"></i> When ON, users with Gen Control access can remotely <b>start generators</b> and <b>close breakers (on&nbsp;load)</b> from the status page &mdash; each still gated by on-controller interlocks (manual&nbsp;+&nbsp;alarms-clear&nbsp;+&nbsp;stopped for start) and a type-&ldquo;yes&rdquo; confirm for start. <b>Leave OFF unless a field tech is on-site.</b> Manual-mode and Clear-alarms are always allowed regardless of this switch.
    </div>
</div>

<!-- Power Management: live gen-data miner control -->
<div style="background: rgba(0,204,102,0.06); border:1px solid rgba(0,204,102,0.3); border-radius:10px; padding:16px; margin-top:20px;">
    <div style="display:flex; align-items:center; gap:14px; flex-wrap:wrap;">
        <h3 style="color:#00cc66; margin:0;">Power Management &mdash; Live Gen Data Control</h3>
        <label style="color:#ddd; font-size:14px; display:flex; align-items:center; gap:8px; cursor:pointer;">
            <input type="checkbox" id="pmLiveEnabled" onchange="savePowerMgmt()"> Use live gen data for miner control
        </label>
        <span id="pmSaveStatus" style="font-size:13px;"></span>
    </div>
    <div style="color:#9bbfa8; font-size:12px; margin-top:8px; line-height:1.5;">
        When ON, any generator group where <b>every gen is reporting fresh live telemetry</b> is controlled from live data &mdash; fast instant-shed on overload, patient 30&nbsp;s-smoothed wake, with a boot-wake hold once gens come <b>on&nbsp;load</b> (running <i>and</i> breaker closed). Groups missing live data fall back to Mesa automatically. Live and Mesa control stay fully separate. Changes take effect within ~5&nbsp;s &mdash; no restart. Turning this OFF is an instant kill switch back to Mesa for every group.
    </div>
    <style>
      /* Label text at top, input box pinned to bottom, so boxes line up in rows
         no matter how tall each field's help blurb is. */
      #pmGrid > label > input, #pmGrid > label > select { margin-top: auto; }
    </style>
    <div id="pmGrid" style="display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:10px 22px; margin-top:14px; font-size:13px; color:#ccc; align-items:stretch;">
        <label style="display:flex; flex-direction:column; gap:3px;">Only these groups <span style="color:#777; font-size:11px;">comma-separated; blank = all live-capable groups</span>
            <input type="text" id="pmGroups" placeholder="e.g. GN 6, Will 1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Live freshness (s) <span style="color:#777; font-size:11px;">gen counts as live if seen within this</span>
            <input type="number" id="pmFresh" min="5" max="600" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Boot-wake hold (s) <span style="color:#777; font-size:11px;">on-load settle before waking</span>
            <input type="number" id="pmBoot" min="0" max="3600" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Wake lockout (s) <span style="color:#777; font-size:11px;">pace between wake batches (ramp ~90% @ 140s)</span>
            <input type="number" id="pmWakeLock" min="0" max="3600" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Wake lockout &mdash; high load (s) <span style="color:#777; font-size:11px;">longer pace when a gen is &ge;90% rated; instant-shed guards the top</span>
            <input type="number" id="pmWakeLockHi" min="0" max="3600" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Shed lockout (s) <span style="color:#777; font-size:11px;">pace between sheds (shed ~95% @ 4s)</span>
            <input type="number" id="pmShedLock" min="0" max="600" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Deadband (miners) <span style="color:#777; font-size:11px;">flap buffer; acts only past this many miners of drift</span>
            <input type="number" id="pmDeadband" min="1" max="20" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
    </div>
    <div style="margin-top:14px; display:flex; align-items:center; gap:12px;">
        <button onclick="savePowerMgmt()" style="background-color:#00994d; color:white; border:none; padding:8px 16px; font-size:14px; border-radius:4px; cursor:pointer;">
            <i class="fas fa-save"></i> Save Power Settings
        </button>
        <span style="color:#888; font-size:12px;">Fully-live groups today: GN 1&ndash;7, Will 1/3/4/5 (others auto-fall-back to Mesa)</span>
    </div>
</div>

<!-- Auto-Restart (AR) gate settings â€” org-wide defaults for the gen_autostart service -->
<div style="background: rgba(204,136,0,0.06); border:1px solid rgba(204,136,0,0.35); border-radius:10px; padding:16px; margin-top:20px;">
    <div style="display:flex; align-items:center; gap:14px; flex-wrap:wrap;">
        <h3 style="color:#cc8800; margin:0;">Auto-Restart &mdash; Gate Settings</h3>
        <label style="color:#ddd; font-size:14px; display:flex; align-items:center; gap:8px; cursor:pointer;">
            <input type="checkbox" id="arSystemEnabled" onchange="saveAutoRestart()"> <b>System ENABLED</b>
        </label>
        <span id="arSaveStatus" style="font-size:13px;"></span>
    </div>
    <div style="color:#cbb083; font-size:12px; margin-top:6px; line-height:1.5;">
        <i class="fas fa-power-off" style="color:#cc8800;"></i> Master kill switch. While OFF, the AR toggle on the status page is <b>locked</b> (no group can be armed) and the service actuates nothing. Turn ON only when you're ready to run auto-restart.
    </div>
    <div style="color:#cbb083; font-size:12px; margin-top:8px; line-height:1.5;">
        Org-wide defaults for the autonomous group auto-restart service. Per-group arming is the <b>AR</b> toggle on the status page (default OFF). On a full group-down, if all gates pass the service clears alarms, restarts the healthy gens, warms them, then asks for load &mdash; it does <b>not</b> close breakers. Requires <b>Generator Control &rarr; Engine Actions</b> (above) to be ON to actuate. Takes effect within one service cycle (~15&nbsp;s), no restart. <b>Window uses server-local time.</b>
    </div>
    <style>
      /* Keep every field's label text at the top of its grid cell and pin the
         input box to the bottom, so the boxes line up in neat rows regardless of
         how tall each field's help blurb is. */
      #arGrid > label > input:not([type=checkbox]),
      #arGrid > label > select { margin-top: auto; }
    </style>
    <div id="arGrid" style="display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:10px 22px; margin-top:14px; font-size:13px; color:#ccc; align-items:stretch;">
        <label style="display:flex; flex-direction:column; gap:3px;">Night window start <span style="color:#777; font-size:11px;">HH:MM, server-local</span>
            <input type="time" id="arWinStart" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Night window end <span style="color:#777; font-size:11px;">HH:MM, server-local (wraps midnight)</span>
            <input type="time" id="arWinEnd" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Just-ran window (min) <span style="color:#777; font-size:11px;">fresh trip restarts any time (nobody's on it yet)</span>
            <input type="number" id="arJustRan" min="0" max="120" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Confirm settle (s) <span style="color:#777; font-size:11px;">group must stay down this long before acting</span>
            <input type="number" id="arConfirm" min="10" max="600" step="5" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Warm-up mode
            <select id="arWarmMode" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;">
                <option value="minutes">Fixed minutes</option>
                <option value="temp">Coolant temperature</option>
            </select></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Warm-up minutes <span style="color:#777; font-size:11px;">used in 'Fixed minutes' mode</span>
            <input type="number" id="arWarmMin" min="0" max="60" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Warm-up target (&deg;F) <span style="color:#777; font-size:11px;">coolant target in 'temperature' mode (Mesa winter: 160)</span>
            <input type="number" id="arWarmTarget" min="100" max="220" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Warm-up cap (min) <span style="color:#777; font-size:11px;">max wait in temperature mode</span>
            <input type="number" id="arWarmMax" min="1" max="60" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <div style="grid-column:1/-1; display:flex; flex-direction:column; gap:4px;">
            <div style="color:#ddd;">Exclude causes <span style="color:#777; font-size:11px;">check a cause to hold a gen carrying it out of auto-restart. Each cause folds in the Mesa no-restart CODES that map to it &mdash; click <b>&#9656;</b> to see them. 'overload' is a cascade victim and is locked out; gas / manual / e-stop have their own gates.</span></div>
            <div id="arCatList" style="display:grid; grid-template-columns:repeat(auto-fill, minmax(230px, 1fr)); gap:2px 14px; background:#222; border:1px solid #444; border-radius:4px; padding:8px; max-height:260px; overflow:auto;"></div>
        </div>
        <label style="display:flex; align-items:center; gap:8px; color:#ddd; align-self:end; margin-bottom:6px;"><input type="checkbox" id="arGasBlock"> Block restart on a gas (Low NG) cause</label>
        <label style="display:flex; flex-direction:column; gap:3px;">Re-trip window (min) <span style="color:#777; font-size:11px;">if a restarted gen falls back down within this many minutes, AR disables it (kW modal) until repaired</span>
            <input type="number" id="arRetripWin" min="1" max="120" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Flow floor (MCF/day) <span style="color:#777; font-size:11px;">GN/Will only; blank = skip flowmeter check</span>
            <input type="number" id="arFlowMin" min="0" step="1" placeholder="(none)" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Retry limit (attempts) <span style="color:#777; font-size:11px;">anti-loop circuit breaker</span>
            <input type="number" id="arCbMax" min="1" max="20" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Retry window (min) <span style="color:#777; font-size:11px;">count attempts within this span</span>
            <input type="number" id="arCbWin" min="1" max="1440" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Cooldown (min) <span style="color:#777; font-size:11px;">hold after hitting the limit</span>
            <input type="number" id="arCbCool" min="0" max="1440" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
    </div>
    <div style="margin-top:14px;">
        <button onclick="saveAutoRestart()" style="background-color:#996600; color:white; border:none; padding:8px 16px; font-size:14px; border-radius:4px; cursor:pointer;">
            <i class="fas fa-save"></i> Save Auto-Restart Settings
        </button>
    </div>
</div>

<!-- Alert switches â€” which categories reach the chat rooms -->
<div style="background: rgba(74,158,255,0.06); border:1px solid rgba(74,158,255,0.35); border-radius:10px; padding:16px; margin-top:20px;">
    <div style="display:flex; align-items:center; gap:14px; flex-wrap:wrap;">
        <h3 style="color:#4a9eff; margin:0;">Alerts &mdash; What Reaches Chat</h3>
        <span id="alSaveStatus" style="font-size:13px;"></span>
    </div>
    <div style="color:#a8c7e8; font-size:12px; margin-top:6px; line-height:1.5;">
        Switch off any category that isn't worth an interruption. Turning one off suppresses the <b>live chat message</b> only &mdash; the event still feeds the summary digest and every subsystem keeps logging it, so nothing is lost, it just stops pinging. Takes effect on the very next alert; no restart.
    </div>
    <div id="alertCatList" style="display:grid; grid-template-columns:repeat(auto-fill,minmax(340px,1fr)); gap:8px 22px; margin-top:14px;">
        <span style="color:#888; font-size:13px;">Loading categories&hellip;</span>
    </div>
    <div style="margin-top:14px;">
        <button onclick="saveAlertSettings()" style="background-color:#2a6fb0; color:white; border:none; padding:8px 16px; font-size:14px; border-radius:4px; cursor:pointer;">
            <i class="fas fa-save"></i> Save Alert Settings
        </button>
    </div>
</div>

<!-- Gas-Pressure Hold â€” org-wide defaults for the gas capacity hold -->
<div style="background: rgba(255,102,0,0.06); border:1px solid rgba(255,102,0,0.35); border-radius:10px; padding:16px; margin-top:20px;">
    <div style="display:flex; align-items:center; gap:14px; flex-wrap:wrap;">
        <h3 style="color:#ff6600; margin:0;">Gas-Pressure Hold &mdash; Defaults</h3>
        <label style="color:#ddd; font-size:14px; display:flex; align-items:center; gap:8px; cursor:pointer;">
            <input type="checkbox" id="ghSystemEnabled" onchange="saveGasHold()"> <b>System ENABLED</b>
        </label>
        <span id="ghSaveStatus" style="font-size:13px;"></span>
    </div>
    <div style="color:#e0b48a; font-size:12px; margin-top:6px; line-height:1.5;">
        <i class="fas fa-power-off" style="color:#ff6600;"></i> Master kill switch. While OFF nothing is evaluated and no group can be armed. Per-group arming and per-group psi thresholds live on the <b>status page &rarr; gen group kW modal</b>, because normal NG pressure differs by site.
    </div>
    <div style="color:#e0b48a; font-size:12px; margin-top:8px; line-height:1.5;">
        Sheds mining load <b>before</b> a group's gens starve on gas, and gives it back when supply recovers &mdash; the gas analogue of the pre-emptive capacity hold. Thresholds are <b>absolute psi</b>, not percentages: controller trips cluster near 10&nbsp;psi while per-gen baselines run 19&ndash;44&nbsp;psi, so a relative rule fires constantly on a high-baseline gen and far too late on a low-baseline one. Takes effect within one monitor cycle (~15&nbsp;s), no restart.
    </div>
    <style>
      #ghGrid > label > input:not([type=checkbox]) { margin-top: auto; }
    </style>
    <div id="ghGrid" style="display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:10px 22px; margin-top:14px; font-size:13px; color:#ccc; align-items:stretch;">
        <label style="display:flex; flex-direction:column; gap:3px;">Watch below (psi) <span style="color:#777; font-size:11px;">pause auto-wake; reserves nothing</span>
            <input type="number" id="ghWatch" min="1" max="200" step="0.5" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Reserve 1 gen below (psi) <span style="color:#777; font-size:11px;">first stage that actually sheds load</span>
            <input type="number" id="ghHold1" min="1" max="200" step="0.5" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Reserve 2 gens below (psi) <span style="color:#777; font-size:11px;">deep stage; never empties a group</span>
            <input type="number" id="ghHold2" min="1" max="200" step="0.5" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Release margin (psi) <span style="color:#777; font-size:11px;">must recover this far above a step to leave it</span>
            <input type="number" id="ghRelMargin" min="0" max="50" step="0.5" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Release dwell (min) <span style="color:#777; font-size:11px;">how long recovery must hold before capacity returns</span>
            <input type="number" id="ghRelMin" min="1" max="720" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Reserve at stage 1 (gens) <span style="color:#777; font-size:11px;">capacity held back at the first shedding step</span>
            <input type="number" id="ghRes1" min="1" max="6" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Reserve at stage 2 (gens) <span style="color:#777; font-size:11px;">deep step; never leaves a group with nothing on load</span>
            <input type="number" id="ghRes2" min="1" max="6" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Confirm samples <span style="color:#777; font-size:11px;">consecutive readings before reserving (watch needs 1)</span>
            <input type="number" id="ghConfirm" min="1" max="10" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Min live sensors <span style="color:#777; font-size:11px;">usable gen readings needed to judge a group</span>
            <input type="number" id="ghMinGens" min="1" max="10" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Stale decay (min) <span style="color:#777; font-size:11px;">no usable reading this long &rarr; ease the hold one step</span>
            <input type="number" id="ghStale" min="5" max="720" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
    </div>
    <div style="margin-top:14px;">
        <button onclick="saveGasHold()" style="background-color:#a34f00; color:white; border:none; padding:8px 16px; font-size:14px; border-radius:4px; cursor:pointer;">
            <i class="fas fa-save"></i> Save Gas-Hold Settings
        </button>
    </div>
</div>

<!-- Dead-Miner Scoring â€” weights for the Dead Miner Hunter + inventory API -->
<div style="background: rgba(255,68,68,0.06); border:1px solid rgba(255,68,68,0.35); border-radius:10px; padding:16px; margin-top:20px;">
    <div style="display:flex; align-items:center; gap:14px; flex-wrap:wrap;">
        <h3 style="color:#ff6666; margin:0;">Dead-Miner Scoring &mdash; Confidence Weights</h3>
        <span id="dmSaveStatus" style="font-size:13px;"></span>
    </div>
    <div style="color:#e0b3b3; font-size:12px; margin-top:8px; line-height:1.5;">
        Scores every miner 0&ndash;100 for dead-confidence. Drives the <b>Dead Miner Hunter</b> page and the dead-miner feed the <b>inventory app</b> reads when a pod is opened. Signals are extracted from history on a cron; the score itself is computed <b>on every read</b>, so a change here re-scores the whole fleet immediately &mdash; no re-scan.
        <br><b>The dominant signal is reboots, not zero-hash</b>: a power-parked healthy miner also sits at zero hash. What separates dead from parked is that reboot_zero hammers it and it still won't run. Keep <i>zero-hash</i> weighted below the <i>score floor</i> so it can never surface a parked miner on its own.
    </div>

    <!-- Live preview â€” the whole point of the signals/score split -->
    <div id="dmPreview" style="margin-top:14px; padding:12px; background:rgba(0,0,0,0.3); border-radius:8px; border:1px solid #333;">
        <div style="font-size:12px; color:#888; margin-bottom:8px;">Live preview &mdash; how many miners these settings would flag, scored against the current signal store:</div>
        <div style="display:flex; gap:26px; flex-wrap:wrap; align-items:flex-end;">
            <div><div style="font-size:26px; font-weight:700; color:#ff4444;" id="dmPvDead">&mdash;</div><div style="font-size:11px; color:#888;">DEAD (pull)</div></div>
            <div><div style="font-size:26px; font-weight:700; color:#ffaa00;" id="dmPvSuspect">&mdash;</div><div style="font-size:11px; color:#888;">SUSPECT</div></div>
            <div><div style="font-size:26px; font-weight:700; color:#666;" id="dmPvDropped">&mdash;</div><div style="font-size:11px; color:#888;">below floor / parked</div></div>
            <div style="border-left:1px solid #444; padding-left:22px;"><div style="font-size:13px; color:#aaa;" id="dmPvDelta">&nbsp;</div><div style="font-size:11px; color:#666;" id="dmPvMeta">&nbsp;</div></div>
        </div>
    </div>

    <div id="dmGrid" style="display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:10px 22px; margin-top:14px; font-size:13px; color:#ccc; align-items:stretch;">
        <label style="display:flex; flex-direction:column; gap:3px;">Weight: reboots <span style="color:#777; font-size:11px;">"tried, won't run" &mdash; strongest signal</span>
            <input type="number" id="dmWReboots" min="0" max="100" step="1" oninput="dmPreview()" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Weight: hardware error <span style="color:#777; font-size:11px;">hashboard/fan/temp/power fault present</span>
            <input type="number" id="dmWHw" min="0" max="100" step="1" oninput="dmPreview()" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Weight: zero-hash <span style="color:#777; font-size:11px;">keep BELOW the score floor</span>
            <input type="number" id="dmWZero" min="0" max="100" step="1" oninput="dmPreview()" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Weight: crash-loop <span style="color:#777; font-size:11px;">repeated sub-30-min uptimes</span>
            <input type="number" id="dmWUnstable" min="0" max="100" step="1" oninput="dmPreview()" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Reboots for full points <span style="color:#777; font-size:11px;">reboots/30d that earns the whole reboot weight</span>
            <input type="number" id="dmRebootsFull" min="1" max="500" step="1" oninput="dmPreview()" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Pull threshold <span style="color:#777; font-size:11px;">score &ge; this = "dead", pull for repair</span>
            <input type="number" id="dmPull" min="1" max="100" step="1" oninput="dmPreview()" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Score floor <span style="color:#777; font-size:11px;">below this a miner isn't reported at all</span>
            <input type="number" id="dmFloor" min="0" max="100" step="1" oninput="dmPreview()" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Never-hashed reboot trigger <span style="color:#777; font-size:11px;">never hashed + this many reboots = auto-dead</span>
            <input type="number" id="dmNeverReboots" min="0" max="500" step="1" oninput="dmPreview()" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Auto-dead min days <span style="color:#777; font-size:11px;">history needed before auto-dead can fire</span>
            <input type="number" id="dmAutoMinDays" min="1" max="30" step="1" oninput="dmPreview()" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px;">Parked-gate (days) <span style="color:#777; font-size:11px;">genuinely hashed within this many days &rarr; parked, not dead</span>
            <input type="number" id="dmNoHashDays" min="1" max="365" step="1" oninput="dmPreview()" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        <label style="display:flex; flex-direction:column; gap:3px; grid-column:1/-1;">Hardware-fault categories <span style="color:#777; font-size:11px;">error categories that count as hardware failure (comma-separated)</span>
            <input type="text" id="dmHwCats" placeholder="hashboard, fan, temp_sensor, overtemp, power" oninput="dmPreview()" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
    </div>

    <div style="margin-top:16px; padding:12px; background:rgba(0,120,60,0.07); border:1px solid rgba(0,200,100,0.25); border-radius:8px;">
        <div style="display:flex; align-items:center; gap:12px; flex-wrap:wrap;">
            <label style="display:flex; align-items:center; gap:8px; color:#ddd; font-size:13px; cursor:pointer;">
                <input type="checkbox" id="dmPodGate" onchange="dmPreview()"> <b>Peer-relative pod gate</b>
            </label>
            <label style="display:flex; align-items:center; gap:8px; color:#ccc; font-size:13px;">pod-active fraction
                <input type="number" id="dmPodFrac" min="0" max="1" step="0.05" oninput="dmPreview()" style="width:90px; background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        </div>
        <div style="color:#8fbf9f; font-size:12px; margin-top:8px; line-height:1.5;">
            Judges a miner only on days its <b>own pod</b> was demonstrably able to run &mdash; if this share of the pod's miners hashed that day, a silent miner is genuinely broken; if the pod was dark, the day is discarded rather than counted as evidence of death. Uses the pod's other miners as the control group, so it covers gas, gen-down, peplink-down and breaker outages from one data source.
            <br>A pod dark for <i>weeks</i> (Osprey) is already immune &mdash; it produces no snapshots at all, so its miners are never scored. What this protects is a pod <b>coming back</b>: M60s boot sleeping, and without the gate any with &ge;10 reboots would auto-confirm dead within <i>auto-dead min days</i>.
        </div>
    </div>

    <div style="margin-top:16px; padding-top:12px; border-top:1px solid #442222;">
        <div style="color:#cc8888; font-size:12px; margin-bottom:10px;">
            <i class="fas fa-clock"></i> <b>Extraction settings</b> &mdash; these change what gets pulled out of history, so they only take effect on the <b>next extraction run</b> (cron, every 2 h). The live preview above will not reflect them until then.
        </div>
        <div style="display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:10px 22px; font-size:13px; color:#ccc;">
            <label style="display:flex; flex-direction:column; gap:3px;">Scoring window (days)
                <input type="number" id="dmWindowDays" min="1" max="90" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
            <label style="display:flex; flex-direction:column; gap:3px;">Min samples/day <span style="color:#777; font-size:11px;">day counts as an eval day at &ge; this (~43/day when online)</span>
                <input type="number" id="dmMinSamples" min="1" max="200" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
            <label style="display:flex; flex-direction:column; gap:3px;">Short-uptime (sec) <span style="color:#777; font-size:11px;">uptime below this counts as a recent reboot</span>
                <input type="number" id="dmShortUptime" min="60" max="86400" step="60" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
            <label style="display:flex; flex-direction:column; gap:3px;">Real-hash threshold (TH/s) <span style="color:#777; font-size:11px;">hashrate that counts as "actually hashing"</span>
                <input type="number" id="dmRealHash" min="1" max="500" step="1" style="background:#2a2a2a; border:1px solid #555; color:#fff; padding:5px; border-radius:3px;"></label>
        </div>
    </div>

    <div style="margin-top:14px;">
        <button onclick="saveDeadMiner()" style="background-color:#993333; color:white; border:none; padding:8px 16px; font-size:14px; border-radius:4px; cursor:pointer;">
            <i class="fas fa-save"></i> Save Dead-Miner Settings
        </button>
    </div>
</div>

<!-- Generator Warning Settings - full width below other sections -->
<div style="background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 10px; padding: 20px; margin-top: 20px;">
    <div style="display:flex; align-items:center; gap:14px; flex-wrap:wrap;">
        <h3 style="color: #00ff00; margin: 0;">Generator Warning Settings</h3>
        <label style="color:#ccc; font-size:13px; display:flex; align-items:center; gap:6px;">
            <input type="checkbox" id="gwEnabled"> Warnings enabled
        </label>
        <span style="color:#888; font-size:12px;">Thresholds in operator units (&deg;F / psi / V / rpm). These are tunable starter values.</span>
    </div>

    <div style="background:rgba(74,158,255,0.08); border:1px solid rgba(74,158,255,0.25); border-radius:8px; padding:10px 14px; margin-top:14px; color:#bcd; font-size:12.5px; line-height:1.5;">
        These warnings evaluate off <b>both</b> live telemetry and Mesa data &mdash; we prefer live when a gen has a wifi bridge, and fall back to Mesa otherwise (slower, but valid). Every threshold below is available from either source. If we later add data points that only exist on the live feed, we'll designate those individually.
    </div>

    <h4 style="color:#4a9eff; margin:18px 0 6px;">Thresholds <span style="color:#888; font-weight:normal; font-size:12px;">(blank = not checked; a field can flag on low and/or high)</span></h4>
    <div style="overflow-x:auto;">
    <table style="width:100%; border-collapse:collapse; font-size:13px; min-width:560px;">
        <thead><tr style="color:#888; text-align:left;">
            <th style="padding:6px 8px;">On</th><th style="padding:6px 8px;">Field</th>
            <th style="padding:6px 8px;">Crit Low</th><th style="padding:6px 8px;">Warn Low</th>
            <th style="padding:6px 8px;">Warn High</th><th style="padding:6px 8px;">Crit High</th>
            <th style="padding:6px 8px;">Unit</th>
            <th style="padding:6px 8px;" title="When this rule hits CRITICAL, arm a pre-emptive capacity hold on the gen (reserve one gen's worth of headroom for the group).">Pre-empt</th>
        </tr></thead>
        <tbody id="gwThresholdRows"></tbody>
    </table>
    </div>

    <h4 style="color:#4a9eff; margin:18px 0 6px;">Controller Status Bits <span style="color:#888; font-weight:normal; font-size:12px;">(specific faults the controller flags; DSE Warning is the suppressed fleet-wide rollup)</span></h4>
    <div style="overflow-x:auto;">
    <table style="width:100%; border-collapse:collapse; font-size:13px; min-width:420px;">
        <thead><tr style="color:#888; text-align:left;">
            <th style="padding:6px 8px;">On</th><th style="padding:6px 8px;">Condition</th>
            <th style="padding:6px 8px;">Bit</th><th style="padding:6px 8px;">Severity</th>
        </tr></thead>
        <tbody id="gwStatusBitRows"></tbody>
    </table>
    </div>

    <div style="margin-top:15px; display:flex; align-items:center; gap:12px;">
        <button onclick="saveGenWarnings()" style="background-color:#28a745; color:white; border:none; padding:8px 16px; font-size:14px; border-radius:4px; cursor:pointer;">
            <i class="fas fa-save"></i> Save Warning Settings
        </button>
        <span id="gwSaveStatus" style="font-size:13px;"></span>
    </div>
</div>

    <script>
    // Pass dynamic data to window object for external JavaScript
    window.siteGroups = {
)r   r   z	        "z": rþ   z&
    };

    window.siteGroupPods = {
z": {z            "z
        },z%
    };
    
    window.configData = z{}z/var/www/html/ngon)Ú	PAGE_KEYSz
    ;
    window.pageKeys = rø   )r   z!
    window.genWarningDefaults = z;
    window.genFieldMeta = zv
    window.genWarningDefaults = {"enabled": true, "thresholds": {}, "status_bits": {}};
    window.genFieldMeta = {};)Ú
gen_fieldsz"
    window.AR_CATEGORY_CATALOG = z%
    window.AR_CATEGORY_CATALOG = {};z2
    </script>
    <script src="site_manager.js?v=zsite_manager.jsuã±  "></script>

    <script>
    // ===== Generator Warning Settings (self-contained; not part of site_manager.js) =====
    function gwEffectiveRules() {
        var d = window.genWarningDefaults || {enabled:true, thresholds:{}, status_bits:{}};
        var saved = (window.configData && window.configData.misc && window.configData.misc.gen_warnings) || null;
        function mergeMap(base, over) {
            var out = {};
            Object.keys(base||{}).forEach(function(k){ out[k] = Object.assign({}, base[k]); });
            if (over) Object.keys(over).forEach(function(k){ out[k] = Object.assign({}, (base&&base[k])||{}, over[k]); });
            return out;
        }
        return {
            enabled: saved && typeof saved.enabled === 'boolean' ? saved.enabled : (d.enabled !== false),
            thresholds: mergeMap(d.thresholds, saved && saved.thresholds),
            status_bits: mergeMap(d.status_bits, saved && saved.status_bits)
        };
    }

    function gwNumCell(cls, v) {
        var val = (v === null || v === undefined) ? '' : v;
        return '<td style="padding:6px 8px;"><input type="number" step="any" class="gw-th '+cls+'" value="'+val+'" '+
               'style="width:72px; background:#333; border:1px solid #555; color:#fff; padding:4px; border-radius:3px;"></td>';
    }

    function renderGenWarnings() {
        if (!document.getElementById('gwThresholdRows')) return;
        var eff = gwEffectiveRules();
        var meta = window.genFieldMeta || {};
        document.getElementById('gwEnabled').checked = eff.enabled !== false;

        var tb = document.getElementById('gwThresholdRows'); tb.innerHTML = '';
        Object.keys(eff.thresholds).forEach(function(field){
            var r = eff.thresholds[field];
            var unit = r.unit || (meta[field] && meta[field].unit) || '';
            var label = r.label || (meta[field] && meta[field].label) || field;
            var tr = document.createElement('tr');
            tr.style.borderTop = '1px solid rgba(255,255,255,0.07)';
            tr.setAttribute('data-field', field);
            tr.setAttribute('data-label', label);
            tr.setAttribute('data-unit', unit);
            tr.innerHTML =
                '<td style="padding:6px 8px;"><input type="checkbox" class="gw-th-en" '+(r.enabled!==false?'checked':'')+'></td>'+
                '<td style="padding:6px 8px; color:#ddd;">'+label+'</td>'+
                gwNumCell('gw-crit-low', r.crit_low) + gwNumCell('gw-warn-low', r.warn_low) +
                gwNumCell('gw-warn-high', r.warn_high) + gwNumCell('gw-crit-high', r.crit_high) +
                '<td style="padding:6px 8px; color:#888;">'+unit+'</td>'+
                '<td style="padding:6px 8px;"><input type="checkbox" class="gw-th-preempt" '+(r.preemptive?'checked':'')+' title="Arm a capacity hold when this rule hits critical"></td>';
            tb.appendChild(tr);
        });

        var sb = document.getElementById('gwStatusBitRows'); sb.innerHTML = '';
        Object.keys(eff.status_bits).sort(function(a,b){return a-b;}).forEach(function(bit){
            var r = eff.status_bits[bit];
            var sev = r.severity || 'warn';
            var label = r.label || ('Bit '+bit);
            var tr = document.createElement('tr');
            tr.style.borderTop = '1px solid rgba(255,255,255,0.07)';
            tr.setAttribute('data-bit', bit);
            tr.setAttribute('data-label', label);
            tr.innerHTML =
                '<td style="padding:6px 8px;"><input type="checkbox" class="gw-bit-en" '+(r.enabled!==false?'checked':'')+'></td>'+
                '<td style="padding:6px 8px; color:#ddd;">'+label+'</td>'+
                '<td style="padding:6px 8px; color:#888;">'+bit+'</td>'+
                '<td style="padding:6px 8px;"><select class="gw-bit-sev" style="background:#333; border:1px solid #555; color:#fff; padding:4px; border-radius:3px;">'+
                    '<option value="warn"'+(sev==='warn'?' selected':'')+'>warn</option>'+
                    '<option value="critical"'+(sev==='critical'?' selected':'')+'>critical</option>'+
                '</select></td>';
            sb.appendChild(tr);
        });
    }

    function saveGenWarnings() {
        var out = {enabled: document.getElementById('gwEnabled').checked, thresholds:{}, status_bits:{}};
        document.querySelectorAll('#gwThresholdRows tr').forEach(function(tr){
            function val(sel){ var el=tr.querySelector(sel); if(!el||el.value==='') return null; var n=parseFloat(el.value); return isNaN(n)?null:n; }
            out.thresholds[tr.getAttribute('data-field')] = {
                label: tr.getAttribute('data-label'), unit: tr.getAttribute('data-unit'),
                enabled: tr.querySelector('.gw-th-en').checked,
                crit_low: val('.gw-crit-low'), warn_low: val('.gw-warn-low'),
                warn_high: val('.gw-warn-high'), crit_high: val('.gw-crit-high'),
                preemptive: tr.querySelector('.gw-th-preempt').checked
            };
        });
        document.querySelectorAll('#gwStatusBitRows tr').forEach(function(tr){
            out.status_bits[tr.getAttribute('data-bit')] = {
                label: tr.getAttribute('data-label'),
                enabled: tr.querySelector('.gw-bit-en').checked,
                severity: tr.querySelector('.gw-bit-sev').value
            };
        });
        var status = document.getElementById('gwSaveStatus');
        status.textContent = 'Saving...'; status.style.color = '#888';
        fetch('site_manager.py', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'},
            body:'ajax_gen_warnings='+encodeURIComponent(JSON.stringify(out))})
            .then(function(r){return r.json();})
            .then(function(j){
                if (j.success) {
                    status.textContent = 'Saved âœ“ (takes effect next 5-min sweep)'; status.style.color = '#00ff00';
                    if (!window.configData.misc) window.configData.misc = {};
                    window.configData.misc.gen_warnings = out;
                    setTimeout(function(){ status.textContent = ''; }, 4000);
                } else {
                    status.textContent = 'Error: ' + (j.error || 'unknown'); status.style.color = '#ff4444';
                }
            })
            .catch(function(e){ status.textContent = 'Error: ' + e; status.style.color = '#ff4444'; });
    }

    // ===== Gen Control engine-action toggle (self-contained) =====
    function renderGenControl() {
        var el = document.getElementById('gcEngineActions');
        if (!el) return;
        var gc = (window.configData && window.configData.misc && window.configData.misc.gen_control) || {};
        el.checked = !!gc.enable_engine_actions;
    }

    function saveGenControl() {
        var on = document.getElementById('gcEngineActions').checked;
        var status = document.getElementById('gcSaveStatus');
        status.textContent = 'Saving...'; status.style.color = '#888';
        fetch('site_manager.py', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'},
            body:'ajax_gen_control='+encodeURIComponent(JSON.stringify({enable_engine_actions:on}))})
            .then(function(r){return r.json();})
            .then(function(j){
                if (j.success) {
                    status.textContent = on ? 'ENABLED âœ“' : 'disabled âœ“';
                    status.style.color = on ? '#ff6600' : '#00ff00';
                    if (!window.configData.misc) window.configData.misc = {};
                    window.configData.misc.gen_control = {enable_engine_actions:on};
                    setTimeout(function(){ status.textContent = ''; }, 4000);
                } else {
                    status.textContent = 'Error: ' + (j.error || 'unknown'); status.style.color = '#ff4444';
                }
            })
            .catch(function(e){ status.textContent = 'Error: ' + e; status.style.color = '#ff4444'; });
    }

    // ===== Live Power Management settings (self-contained) =====
    function pmDefaults() {
        return {live_control_enabled:false, live_control_groups:[], live_fresh_sec:60,
                live_boot_wake_sec:120, live_wake_lockout_sec:300, live_wake_lockout_highload_sec:450,
                live_shed_lockout_sec:15, deadband_miners:2};
    }
    function renderPowerMgmt() {
        if (!document.getElementById('pmLiveEnabled')) return;
        var d = pmDefaults();
        var pm = (window.configData && window.configData.misc && window.configData.misc.power_mgmt) || {};
        function g(k){ return (pm[k] !== undefined && pm[k] !== null) ? pm[k] : d[k]; }
        document.getElementById('pmLiveEnabled').checked = !!g('live_control_enabled');
        var groups = g('live_control_groups') || [];
        document.getElementById('pmGroups').value = Array.isArray(groups) ? groups.join(', ') : '';
        document.getElementById('pmFresh').value = g('live_fresh_sec');
        document.getElementById('pmBoot').value = g('live_boot_wake_sec');
        document.getElementById('pmWakeLock').value = g('live_wake_lockout_sec');
        document.getElementById('pmWakeLockHi').value = g('live_wake_lockout_highload_sec');
        document.getElementById('pmShedLock').value = g('live_shed_lockout_sec');
        document.getElementById('pmDeadband').value = g('deadband_miners');
    }
    function savePowerMgmt() {
        var d = pmDefaults();
        function num(id, def){ var el=document.getElementById(id); var n=parseFloat(el.value); return isNaN(n)?def:n; }
        var raw = (document.getElementById('pmGroups').value || '').trim();
        var groups = raw ? raw.split(',').map(function(s){return s.trim();}).filter(Boolean) : [];
        var out = {
            live_control_enabled: document.getElementById('pmLiveEnabled').checked,
            live_control_groups: groups,
            live_fresh_sec: num('pmFresh', d.live_fresh_sec),
            live_boot_wake_sec: num('pmBoot', d.live_boot_wake_sec),
            live_wake_lockout_sec: num('pmWakeLock', d.live_wake_lockout_sec),
            live_wake_lockout_highload_sec: num('pmWakeLockHi', d.live_wake_lockout_highload_sec),
            live_shed_lockout_sec: num('pmShedLock', d.live_shed_lockout_sec),
            deadband_miners: parseInt(num('pmDeadband', d.deadband_miners), 10)
        };
        var status = document.getElementById('pmSaveStatus');
        status.textContent = 'Saving...'; status.style.color = '#888';
        fetch('site_manager.py', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'},
            body:'ajax_power_mgmt='+encodeURIComponent(JSON.stringify(out))})
            .then(function(r){return r.json();})
            .then(function(j){
                if (j.success) {
                    status.textContent = out.live_control_enabled ? 'LIVE âœ“ (effective within ~5s)' : 'saved âœ“ (Mesa mode)';
                    status.style.color = out.live_control_enabled ? '#00cc66' : '#00ff00';
                    if (!window.configData.misc) window.configData.misc = {};
                    window.configData.misc.power_mgmt = out;
                    setTimeout(function(){ status.textContent = ''; }, 4000);
                } else {
                    status.textContent = 'Error: ' + (j.error || 'unknown'); status.style.color = '#ff4444';
                }
            })
            .catch(function(e){ status.textContent = 'Error: ' + e; status.style.color = '#ff4444'; });
    }

    // ===== Auto-Restart gate settings (self-contained) =====
    function arDefaults() {
        return {system_enabled:false, window:{start:'21:00', end:'05:00'}, just_ran_minutes:5, confirm_seconds:45,
                warmup:{mode:'minutes', minutes:5, target_f:160, max_minutes:15},
                exclude_categories:['knock','oil','speed','overtemp','coolant','ecu','estop','electrical','catalyst','breaker','start','frequency','voltage','ignition','emissions','scrubber'],
                gas:{block_on_category_gas:true, flow_min_mcfd:null},
                retrip:{window_minutes:15},
                circuit_breaker:{max_attempts:3, window_minutes:120, cooldown_minutes:120}};
    }
    var AR_HIDDEN_CATS = {gas:1, manual:1, estop:1};   // handled by their own gates
    var AR_LOCKED_CATS = {overload:1};                 // cascade victim, never excludable
    function renderArCatList(selected) {
        var host = document.getElementById('arCatList');
        if (!host) return;
        function esc(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;'); }
        var cat = window.AR_CATEGORY_CATALOG || {};
        var sel = {}; (selected||[]).forEach(function(c){ sel[String(c).toLowerCase()]=1; });
        var names = Object.keys(cat).filter(function(c){ return !AR_HIDDEN_CATS[c]; }).sort();
        var html = '';
        names.forEach(function(c){
            var codes = (cat[c].codes || []);
            var locked = !!AR_LOCKED_CATS[c];
            var checked = (sel[c] && !locked) ? 'checked' : '';
            var dis = locked ? 'disabled' : '';
            var col = locked ? '#777' : '#ddd';
            var title = locked ? 'title="Overload is a cascade victim (the survivors of a cascade, not the culprit) â€” never excluded."' : '';
            var caret = codes.length
                ? '<span class="ar-cat-caret" style="cursor:pointer; color:#888; font-size:11px; margin-left:6px;" title="show the Mesa codes in this cause">&#9656; ' + codes.length + '</span>'
                : '<span style="color:#555; font-size:11px; margin-left:6px;">&mdash;</span>';
            var codeBox = codes.length
                ? '<div class="ar-cat-codes" style="display:none; margin:2px 0 6px 22px; color:#8fb3d9; font-size:11px; line-height:1.5;">' + codes.map(esc).join('<br>') + '</div>'
                : '';
            html += '<div style="padding:1px 0;"><label style="display:flex; align-items:center; gap:7px; color:' + col + ';" ' + title + '>' +
                    '<input type="checkbox" class="ar-cat-cb" data-cat="' + c + '" ' + checked + ' ' + dis + '> ' + c + caret + '</label>' + codeBox + '</div>';
        });
        host.innerHTML = html;
        host.onclick = function(e){
            var t = e.target;
            if (t && t.classList && t.classList.contains('ar-cat-caret')) {
                var box = t.parentNode.parentNode.querySelector('.ar-cat-codes');
                if (box) box.style.display = (box.style.display === 'none' ? 'block' : 'none');
            }
        };
    }
    function renderAutoRestart() {
        if (!document.getElementById('arWinStart')) return;
        var d = arDefaults();
        var ar = (window.configData && window.configData.misc && window.configData.misc.auto_restart) || {};
        var w = ar.window || d.window, wu = ar.warmup || d.warmup, gas = ar.gas || d.gas, cb = ar.circuit_breaker || d.circuit_breaker;
        document.getElementById('arSystemEnabled').checked = (ar.system_enabled === true);
        document.getElementById('arWinStart').value = w.start || d.window.start;
        document.getElementById('arWinEnd').value = w.end || d.window.end;
        document.getElementById('arJustRan').value = (ar.just_ran_minutes != null) ? ar.just_ran_minutes : d.just_ran_minutes;
        document.getElementById('arConfirm').value = (ar.confirm_seconds != null) ? ar.confirm_seconds : d.confirm_seconds;
        document.getElementById('arWarmMode').value = wu.mode || d.warmup.mode;
        document.getElementById('arWarmMin').value = (wu.minutes != null) ? wu.minutes : d.warmup.minutes;
        document.getElementById('arWarmTarget').value = (wu.target_f != null) ? wu.target_f : d.warmup.target_f;
        document.getElementById('arWarmMax').value = (wu.max_minutes != null) ? wu.max_minutes : d.warmup.max_minutes;
        var ex = ar.exclude_categories || d.exclude_categories;
        renderArCatList(Array.isArray(ex) ? ex : []);
        var rt = ar.retrip || d.retrip;
        document.getElementById('arRetripWin').value = (rt && rt.window_minutes != null) ? rt.window_minutes : d.retrip.window_minutes;
        document.getElementById('arGasBlock').checked = (gas.block_on_category_gas != null) ? !!gas.block_on_category_gas : true;
        document.getElementById('arFlowMin').value = (gas.flow_min_mcfd != null) ? gas.flow_min_mcfd : '';
        document.getElementById('arCbMax').value = (cb.max_attempts != null) ? cb.max_attempts : d.circuit_breaker.max_attempts;
        document.getElementById('arCbWin').value = (cb.window_minutes != null) ? cb.window_minutes : d.circuit_breaker.window_minutes;
        document.getElementById('arCbCool').value = (cb.cooldown_minutes != null) ? cb.cooldown_minutes : d.circuit_breaker.cooldown_minutes;
    }
    // Alert switches. Catalogue (keys + labels) is injected server-side from
    // notifications_api; current state comes from the config already in the page.
    function renderAlerts() {
        var box = document.getElementById('alertCatList');
        if (!box) return;
        var cats = window.ALERT_CATALOGUE || [];
        if (!cats.length) {
            box.innerHTML = '<span style="color:#ff6666;font-size:13px;">'
                + 'Category list unavailable &mdash; notifications API not reachable.</span>';
            return;
        }
        var state = (window.configData && window.configData.misc && window.configData.misc.alerts) || {};
        box.innerHTML = cats.map(function (c) {
            // Absent means enabled â€” same default the server applies.
            var on = (state[c.key] !== false);
            return '<label style="color:#ddd; font-size:13px; display:flex; align-items:center; gap:8px; cursor:pointer;">'
                 + '<input type="checkbox" class="alertCat" data-key="' + c.key + '"' + (on ? ' checked' : '') + '> '
                 + c.label + '</label>';
        }).join('');
    }

    function saveAlertSettings() {
        const out = {};
        document.querySelectorAll('#alertCatList .alertCat').forEach(cb => {
            out[cb.getAttribute('data-key')] = cb.checked;
        });
        const status = document.getElementById('alSaveStatus');
        if (status) { status.textContent = 'Saving...'; status.style.color = '#888'; }
        const fd = new FormData();
        fd.append('ajax_alerts', JSON.stringify(out));
        fetch(window.location.pathname, { method: 'POST', body: fd })
            .then(r => r.json())
            .then(d => {
                if (!status) return;
                if (d.success) {
                    const n = (d.disabled || []).length;
                    status.textContent = n ? ('Saved â€” ' + n + ' category(ies) silenced') : 'Saved â€” all alerts on';
                    status.style.color = '#00cc66';
                } else {
                    status.textContent = d.error || 'Save failed';
                    status.style.color = '#ff4444';
                }
                setTimeout(() => { if (status) status.textContent = ''; }, 4000);
            })
            .catch(e => {
                if (status) { status.textContent = 'Save failed: ' + e; status.style.color = '#ff4444'; }
            });
    }

    function saveAutoRestart() {
        var d = arDefaults();
        function num(id, def){ var el=document.getElementById(id); var n=parseFloat(el.value); return isNaN(n)?def:n; }
        var flowRaw = (document.getElementById('arFlowMin').value || '').trim();
        var exSel = Array.prototype.slice.call(document.querySelectorAll('#arCatList .ar-cat-cb'))
                        .filter(function(cb){ return cb.checked && !cb.disabled; })
                        .map(function(cb){ return cb.dataset.cat; });
        var out = {
            system_enabled: document.getElementById('arSystemEnabled').checked,
            window: {start: document.getElementById('arWinStart').value || d.window.start,
                     end: document.getElementById('arWinEnd').value || d.window.end},
            just_ran_minutes: num('arJustRan', d.just_ran_minutes),
            confirm_seconds: num('arConfirm', d.confirm_seconds),
            warmup: {mode: document.getElementById('arWarmMode').value || 'minutes',
                     minutes: num('arWarmMin', d.warmup.minutes),
                     target_f: num('arWarmTarget', d.warmup.target_f),
                     max_minutes: num('arWarmMax', d.warmup.max_minutes)},
            exclude_categories: exSel,
            gas: {block_on_category_gas: document.getElementById('arGasBlock').checked,
                  flow_min_mcfd: flowRaw ? parseFloat(flowRaw) : null},
            retrip: {window_minutes: num('arRetripWin', d.retrip.window_minutes)},
            circuit_breaker: {max_attempts: parseInt(num('arCbMax', d.circuit_breaker.max_attempts), 10),
                              window_minutes: num('arCbWin', d.circuit_breaker.window_minutes),
                              cooldown_minutes: num('arCbCool', d.circuit_breaker.cooldown_minutes)}
        };
        var status = document.getElementById('arSaveStatus');
        status.textContent = 'Saving...'; status.style.color = '#888';
        fetch('site_manager.py', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'},
            body:'ajax_auto_restart='+encodeURIComponent(JSON.stringify(out))})
            .then(function(r){return r.json();})
            .then(function(j){
                if (j.success) {
                    status.textContent = 'saved âœ“ (effective within ~15s)'; status.style.color = '#00cc66';
                    if (!window.configData.misc) window.configData.misc = {};
                    window.configData.misc.auto_restart = out;
                    setTimeout(function(){ status.textContent = ''; }, 4000);
                } else {
                    status.textContent = 'Error: ' + (j.error || 'unknown'); status.style.color = '#ff4444';
                }
            })
            .catch(function(e){ status.textContent = 'Error: ' + e; status.style.color = '#ff4444'; });
    }

    // ===== Gas-pressure hold defaults (self-contained) =====
    // Mirrors gas_hold.DEFAULTS in /opt/ngon/apps/generators/gas_hold.py â€” the
    // BACKEND is authoritative; these are only what the inputs show before the
    // block has ever been saved. Keep the two in step.
    function ghDefaults() {
        return {watch_psi:18, hold1_psi:16, hold2_psi:14, release_margin_psi:1,
                release_minutes:30, confirm_samples:2, min_live_gens:2, stale_minutes:45,
                hold1_reserve:1, hold2_reserve:2};
    }
    function renderGasHold() {
        if (!document.getElementById('ghWatch')) return;
        var d = ghDefaults();
        var gh = (window.configData && window.configData.misc && window.configData.misc.gas_hold) || {};
        var v = gh.defaults || {};
        document.getElementById('ghSystemEnabled').checked = (gh.system_enabled === true);
        var map = {ghWatch:'watch_psi', ghHold1:'hold1_psi', ghHold2:'hold2_psi',
                   ghRelMargin:'release_margin_psi', ghRelMin:'release_minutes',
                   ghConfirm:'confirm_samples', ghMinGens:'min_live_gens', ghStale:'stale_minutes',
                   ghRes1:'hold1_reserve', ghRes2:'hold2_reserve'};
        Object.keys(map).forEach(function(id) {
            var k = map[id];
            document.getElementById(id).value = (v[k] != null) ? v[k] : d[k];
        });
    }
    function saveGasHold() {
        var d = ghDefaults();
        function num(id, def){ var el=document.getElementById(id); var n=parseFloat(el.value); return isNaN(n)?def:n; }
        var out = {
            system_enabled: document.getElementById('ghSystemEnabled').checked,
            defaults: {
                watch_psi: num('ghWatch', d.watch_psi),
                hold1_psi: num('ghHold1', d.hold1_psi),
                hold2_psi: num('ghHold2', d.hold2_psi),
                release_margin_psi: num('ghRelMargin', d.release_margin_psi),
                release_minutes: num('ghRelMin', d.release_minutes),
                hold1_reserve: parseInt(num('ghRes1', d.hold1_reserve), 10),
                hold2_reserve: parseInt(num('ghRes2', d.hold2_reserve), 10),
                confirm_samples: parseInt(num('ghConfirm', d.confirm_samples), 10),
                min_live_gens: parseInt(num('ghMinGens', d.min_live_gens), 10),
                stale_minutes: num('ghStale', d.stale_minutes)
            }
        };
        var status = document.getElementById('ghSaveStatus');
        status.textContent = 'Saving...'; status.style.color = '#888';
        fetch('site_manager.py', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'},
            body:'ajax_gas_hold='+encodeURIComponent(JSON.stringify(out))})
            .then(function(r){return r.json();})
            .then(function(j){
                if (j.success) {
                    status.textContent = 'saved âœ“ (effective within ~15s)'; status.style.color = '#00cc66';
                    if (!window.configData.misc) window.configData.misc = {};
                    window.configData.misc.gas_hold = out;
                    renderGasHold();
                    setTimeout(function(){ status.textContent = ''; }, 4000);
                } else {
                    status.textContent = 'Error: ' + (j.error || 'unknown'); status.style.color = '#ff4444';
                }
            })
            .catch(function(e){ status.textContent = 'Error: ' + e; status.style.color = '#ff4444'; });
    }

    // ===== Dead-miner scoring weights (self-contained) =====
    function dmDefaults() {
        return {w_reboots:40, w_hw_error:25, w_zero_hash:20, w_unstable:15,
                reboots_full:30, pull_threshold:75, score_floor:25,
                never_hashed_reboots:10, auto_min_days:3,
                hw_error_cats:['hashboard','fan','temp_sensor','overtemp','power'],
                no_hash_days:30, use_pod_gate:true, pod_active_frac:0.5,
                window_days:7, min_samples_per_day:24,
                short_uptime:1800, real_hash_th:100};
    }
    var DM_FIELDS = {
        w_reboots:'dmWReboots', w_hw_error:'dmWHw', w_zero_hash:'dmWZero',
        w_unstable:'dmWUnstable', reboots_full:'dmRebootsFull',
        pull_threshold:'dmPull', score_floor:'dmFloor',
        never_hashed_reboots:'dmNeverReboots', auto_min_days:'dmAutoMinDays',
        no_hash_days:'dmNoHashDays', pod_active_frac:'dmPodFrac',
        window_days:'dmWindowDays',
        min_samples_per_day:'dmMinSamples', short_uptime:'dmShortUptime',
        real_hash_th:'dmRealHash'
    };
    function renderDeadMiner() {
        if (!document.getElementById('dmWReboots')) return;
        var d = dmDefaults();
        var dm = (window.configData && window.configData.misc && window.configData.misc.dead_miner) || {};
        Object.keys(DM_FIELDS).forEach(function(k){
            var el = document.getElementById(DM_FIELDS[k]);
            if (el) el.value = (dm[k] != null) ? dm[k] : d[k];
        });
        var cats = dm.hw_error_cats || d.hw_error_cats;
        document.getElementById('dmHwCats').value = Array.isArray(cats) ? cats.join(', ') : '';
        document.getElementById('dmPodGate').checked = (dm.use_pod_gate != null) ? !!dm.use_pod_gate : d.use_pod_gate;
        dmPreview();
    }
    function dmCollect() {
        var d = dmDefaults(), out = {};
        Object.keys(DM_FIELDS).forEach(function(k){
            var el = document.getElementById(DM_FIELDS[k]);
            var n = el ? parseFloat(el.value) : NaN;
            out[k] = isNaN(n) ? d[k] : n;
        });
        var raw = (document.getElementById('dmHwCats').value || '').trim();
        out.hw_error_cats = raw ? raw.split(',').map(function(s){return s.trim().toLowerCase();}).filter(Boolean) : [];
        out.use_pod_gate = document.getElementById('dmPodGate').checked;
        return out;
    }
    // Debounced so dragging a number input doesn't fire a request per keystroke.
    var dmPvTimer = null;
    function dmPreview() {
        if (dmPvTimer) clearTimeout(dmPvTimer);
        dmPvTimer = setTimeout(dmPreviewNow, 250);
    }
    function dmPreviewNow() {
        var body = dmCollect();
        fetch('site_manager.py', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'},
            body:'ajax_dead_preview='+encodeURIComponent(JSON.stringify(body))})
            .then(function(r){return r.json();})
            .then(function(j){
                if (!j.success) { document.getElementById('dmPvMeta').textContent = 'preview error: '+(j.error||''); return; }
                var p = j.preview, s = j.saved;
                document.getElementById('dmPvDead').textContent = p.dead;
                document.getElementById('dmPvSuspect').textContent = p.suspect;
                document.getElementById('dmPvDropped').textContent = p.dropped;
                var dd = p.dead - s.dead;
                var el = document.getElementById('dmPvDelta');
                if (dd === 0) { el.textContent = 'same as saved settings'; el.style.color = '#888'; }
                else { el.textContent = (dd > 0 ? '+' : '') + dd + ' vs saved (' + s.dead + ' dead)';
                       el.style.color = dd > 0 ? '#ff8888' : '#88cc88'; }
                var when = p.extracted_at ? p.extracted_at.replace('T',' ').slice(0,16) : 'never';
                document.getElementById('dmPvMeta').textContent = p.total + ' miners in store Â· extracted ' + when;
            })
            .catch(function(e){ document.getElementById('dmPvMeta').textContent = 'preview error: '+e; });
    }
    function saveDeadMiner() {
        var out = dmCollect();
        var status = document.getElementById('dmSaveStatus');
        if (out.score_floor > out.pull_threshold) {
            status.textContent = 'Score floor cannot exceed the pull threshold'; status.style.color = '#ff4444';
            return;
        }
        status.textContent = 'Saving...'; status.style.color = '#888';
        fetch('site_manager.py', {method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded'},
            body:'ajax_dead_miner='+encodeURIComponent(JSON.stringify(out))})
            .then(function(r){return r.json();})
            .then(function(j){
                if (j.success) {
                    status.textContent = 'saved âœ“ (fleet re-scored on next page load)'; status.style.color = '#00cc66';
                    if (!window.configData.misc) window.configData.misc = {};
                    window.configData.misc.dead_miner = j.saved || out;
                    dmPreview();
                    setTimeout(function(){ status.textContent = ''; }, 4000);
                } else {
                    status.textContent = 'Error: ' + (j.error || 'unknown'); status.style.color = '#ff4444';
                }
            })
            .catch(function(e){ status.textContent = 'Error: ' + e; status.style.color = '#ff4444'; });
    }

    renderGenControl();
    renderGenWarnings();
    renderPowerMgmt();
    renderAutoRestart();
    renderAlerts();
    renderGasHold();
    renderDeadMiner();
    </script>

<!-- Miner Type Selection Modal -->
<div id="minerTypeModal" class="modal">
    <div class="modal-content">
        <h3 id="modalTitle">Select Miner Type</h3>
        <p id="modalPodName"></p>
        <select id="minerTypeSelect">
            <option value="M60">M60</option>
        </select>
        <div class="modal-buttons">
            <button class="modal-btn modal-btn-save" onclick="saveMinerType()">Save</button>
            <button class="modal-btn modal-btn-cancel" onclick="closeMinerTypeModal()">Cancel</button>
        </div>
    </div>
</div>

<!-- Text Input Modal -->
<div id="textInputModal" class="modal">
    <div class="modal-content">
        <h3 id="textModalTitle">Edit</h3>
        <p id="textModalSubtitle"></p>
        <input type="text" id="textModalInput" placeholder="Enter value...">
        <textarea id="textModalTextarea" placeholder="Enter text..." style="display: none;"></textarea>
        <div class="modal-buttons">
            <button class="modal-btn modal-btn-save" onclick="saveTextInput()">Save</button>
            <button class="modal-btn" onclick="deleteCurrentEntity()" style="background: #dc2626; color: white;" id="deleteEntityBtn">Delete</button>
            <button class="modal-btn modal-btn-cancel" onclick="closeTextInputModal()">Cancel</button>
        </div>
    </div>
</div>

<!-- Create Pod Modal -->
<div id="createPodModal" class="modal">
    <div class="modal-content">
        <h3>Create New Pod</h3>
        <div style="margin-bottom: 15px;">
            <label>Site:</label>
            <select id="podSiteSelect" style="width: 100%; background: #333; border: 1px solid #555; color: white; padding: 8px; margin-top: 5px;" onchange="updateGroupsForPod()">
                <option value="">Select a site...</option>
            </select>
        </div>
        <div style="margin-bottom: 15px;">
            <label>Group:</label>
            <select id="podGroupSelect" style="width: 100%; background: #333; border: 1px solid #555; color: white; padding: 8px; margin-top: 5px;">
                <option value="">Select a group...</option>
            </select>
        </div>
        <div style="margin-bottom: 15px;">
            <label>Pod Name:</label>
            <input type="text" id="podNameInput" placeholder="Enter pod name..." style="width: 100%; background: #333; border: 1px solid #555; color: white; padding: 8px; margin-top: 5px;">
        </div>
        <div class="modal-buttons">
            <button class="modal-btn modal-btn-save" onclick="createPodFromModal()">Create</button>
            <button class="modal-btn modal-btn-cancel" onclick="closeCreatePodModal()">Cancel</button>
        </div>
    </div>
</div>

<!-- Create Generator Modal -->
<div id="createGeneratorModal" class="modal">
    <div class="modal-content">
        <h3>Create New Generator</h3>
        <div style="margin-bottom: 15px;">
            <label>Generator ID:</label>
            <input type="text" id="genIdInput" placeholder="Enter generator ID..." style="width: 100%; background: #333; border: 1px solid #555; color: white; padding: 8px; margin-top: 5px;">
        </div>
        <div style="margin-bottom: 15px;">
            <label>Site:</label>
            <select id="genSiteSelect" style="width: 100%; background: #333; border: 1px solid #555; color: white; padding: 8px; margin-top: 5px;" onchange="updateGroupsForGenerator()">
                <option value="">Select a site...</option>
                <option value="Out of Service">Out of Service</option>
                <option value="Spares">Spares</option>
            </select>
        </div>
        <div style="margin-bottom: 15px;">
            <label>Group:</label>
            <select id="genGroupSelect" style="width: 100%; background: #333; border: 1px solid #555; color: white; padding: 8px; margin-top: 5px;">
                <option value="">Select a group...</option>
            </select>
        </div>
        <div class="modal-buttons">
            <button class="modal-btn modal-btn-save" onclick="createGeneratorFromModal()">Create</button>
            <button class="modal-btn modal-btn-cancel" onclick="closeCreateGeneratorModal()">Cancel</button>
        </div>
    </div>
</div>

<!-- Create Group Modal -->
<div id="createGroupModal" class="modal">
    <div class="modal-content">
        <h3>Create New Group</h3>
        <div style="margin-bottom: 15px;">
            <label>Site:</label>
            <select id="groupSiteSelect" style="width: 100%; background: #333; border: 1px solid #555; color: white; padding: 8px; margin-top: 5px;">
                <option value="">Select a site...</option>
            </select>
        </div>
        <div style="margin-bottom: 15px;">
            <label>Group Name:</label>
            <input type="text" id="groupNameInput" placeholder="Enter group name..." style="width: 100%; background: #333; border: 1px solid #555; color: white; padding: 8px; margin-top: 5px;">
        </div>
        <div class="modal-buttons">
            <button class="modal-btn modal-btn-save" onclick="createGroupFromModal()">Create</button>
            <button class="modal-btn modal-btn-cancel" onclick="closeCreateGroupModal()">Cancel</button>
        </div>
    </div>
</div>

<!-- Create Site Modal -->
<div id="createSiteModal" class="modal">
    <div class="modal-content">
        <h3>Create New Site</h3>
        <div style="margin-bottom: 15px;">
            <label>Site Name:</label>
            <input type="text" id="siteNameInput" placeholder="Enter site name..." style="width: 100%; background: #333; border: 1px solid #555; color: white; padding: 8px; margin-top: 5px;">
        </div>
        <div style="margin-bottom: 15px;">
            <label>Site ID:</label>
            <input type="text" id="siteIdInput" placeholder="e.g., CSITE-999..." style="width: 100%; background: #333; border: 1px solid #555; color: white; padding: 8px; margin-top: 5px;">
        </div>
        <div class="modal-buttons">
            <button class="modal-btn modal-btn-save" onclick="createSiteFromModal()">Create</button>
            <button class="modal-btn modal-btn-cancel" onclick="closeCreateSiteModal()">Cancel</button>
        </div>
    </div>
</div>

<!-- Peplink Configuration Modal -->
<div id="peplinkModal" class="modal">
    <div class="modal-content">
        <h3 id="peplinkModalTitle">Configure Peplink</h3>
        <p id="peplinkModalSubtitle"></p>
        <div style="margin: 20px 0;">
            <label for="peplinkGroupId" style="display: block; margin-bottom: 5px; color: #00ff00;">Group ID:</label>
            <input type="number" id="peplinkGroupId" placeholder="Enter group ID..." style="width: 100%; padding: 10px; margin-bottom: 15px; background: #333; border: 1px solid #555; color: white; border-radius: 5px;">
            
            <label for="peplinkDeviceId" style="display: block; margin-bottom: 5px; color: #00ff00;">Device ID:</label>
            <input type="number" id="peplinkDeviceId" placeholder="Enter device ID..." style="width: 100%; padding: 10px; margin-bottom: 15px; background: #333; border: 1px solid #555; color: white; border-radius: 5px;">
            
            <label for="peplinkRouterIp" style="display: block; margin-bottom: 5px; color: #00ff00;">Router IP:</label>
            <input type="text" id="peplinkRouterIp" placeholder="Enter router IP (e.g., 10.4.1.1)..." style="width: 100%; padding: 10px; margin-bottom: 15px; background: #333; border: 1px solid #555; color: white; border-radius: 5px;">
            
            <label style="display: flex; align-items: center; margin-bottom: 10px; color: #00ff00;">
                <input type="checkbox" id="peplinkDisplayChildren" style="margin-right: 10px; transform: scale(1.2);">
                Display Children (show associated pods)
            </label>
        </div>
        <div class="modal-buttons">
            <button class="modal-btn modal-btn-save" onclick="savePeplink()">Save</button>
            <button class="modal-btn" onclick="deletePeplink()" style="background: #dc2626; color: white;">Delete</button>
            <button class="modal-btn modal-btn-cancel" onclick="closePeplinkModal()">Cancel</button>
        </div>
    </div>
</div>

<!-- Master Switch Configuration Modal -->
<div id="switchModal" class="modal">
    <div class="modal-content">
        <h3 id="switchModalTitle">Configure Master Switch</h3>
        <p id="switchModalSubtitle"></p>
        <div style="margin: 20px 0;">
            <label for="switchIp" style="display: block; margin-bottom: 5px; color: #8B5CF6;">Switch IP:</label>
            <input type="text" id="switchIp" placeholder="Enter switch IP (e.g., 10.4.1.100)..." style="width: 100%; padding: 10px; margin-bottom: 15px; background: #333; border: 1px solid #555; color: white; border-radius: 5px;">
        </div>
        <div class="modal-buttons">
            <button class="modal-btn modal-btn-save" onclick="saveSwitch()">Save</button>
            <button class="modal-btn" onclick="deleteSwitchData()" style="background: #dc2626; color: white;">Delete</button>
            <button class="modal-btn modal-btn-cancel" onclick="closeSwitchModal()">Cancel</button>
        </div>
    </div>
</div>

<!-- Power Target Modal -->
<div id="powerTargetModal" class="modal">
    <div class="modal-content">
        <h3 id="powerTargetModalTitle">Power Target</h3>
        <p id="powerTargetModalSubtitle"></p>
        <div style="margin: 20px 0;">
            <label for="powerTargetWatts" style="display: block; margin-bottom: 5px; color: #ffaa00;">Watts per miner:</label>
            <input type="number" id="powerTargetWatts" placeholder="e.g. 3000" min="0" style="width: 100%; padding: 10px; margin-bottom: 10px; background: #333; border: 1px solid #555; color: white; border-radius: 5px;">
            <p style="color: #888; font-size: 12px; margin: 0;">Set to 0 or leave empty for unmanaged (no power limit).</p>
        </div>
        <div class="modal-buttons">
            <button class="modal-btn modal-btn-save" onclick="savePowerTarget()">Save</button>
            <button class="modal-btn" onclick="clearPowerTarget()" style="background: #dc2626; color: white;">Clear</button>
            <button class="modal-btn modal-btn-cancel" onclick="closePowerTargetModal()">Cancel</button>
        </div>
    </div>
</div>

<!-- Max Gen kW Modal -->
<div id="maxGenKwModal" class="modal">
    <div class="modal-content">
        <h3 id="maxGenKwModalTitle">Max Generator kW</h3>
        <p id="maxGenKwModalSubtitle"></p>
        <div style="margin: 20px 0;">
            <label for="maxGenKwValue" style="display: block; margin-bottom: 5px; color: #ffaa00;">kW per generator:</label>
            <input type="number" id="maxGenKwValue" placeholder="e.g. 330" min="0" step="1" style="width: 100%; padding: 10px; margin-bottom: 10px; background: #333; border: 1px solid #555; color: white; border-radius: 5px;">
            <p style="color: #888; font-size: 12px; margin: 0;">Max safe kW capacity per generator in this group.</p>
        </div>
        <div class="modal-buttons">
            <button class="modal-btn modal-btn-save" onclick="saveMaxGenKw()">Save</button>
            <button class="modal-btn modal-btn-cancel" onclick="closeMaxGenKwModal()">Cancel</button>
        </div>
    </div>
</div>

<!-- Generator Edit Modal -->
<div id="generatorModal" class="modal">
    <div class="modal-content" style="width: 400px;">
        <h3>Edit Generator</h3>
        <p id="genModalSubtitle"></p>

        <label for="genModalMac" style="display: block; margin-bottom: 5px; color: #00ff00;">MAC Address:</label>
        <input type="text" id="genModalMac" placeholder="E8A4C1143E33"
               style="width: 100%; padding: 10px; margin-bottom: 15px; background: #333; border: 1px solid #555;
                      color: white; border-radius: 5px; font-family: monospace; text-transform: uppercase;"
               maxlength="12" pattern="[A-Fa-f0-9]{12}">

        <label for="genModalBridgeMac" style="display: block; margin-bottom: 5px; color: #00ff00;">Bridge MAC (MikroTik):</label>
        <input type="text" id="genModalBridgeMac" placeholder="04F41C8AC14A"
               style="width: 100%; padding: 10px; margin-bottom: 8px; background: #333; border: 1px solid #555;
                      color: white; border-radius: 5px; font-family: monospace; text-transform: uppercase;"
               maxlength="17">
        <p style="color: #888; font-size: 11px; margin: 0 0 15px;">
            Wifi MAC of the gen's MikroTik bridge (from Peplink / InControl). Colons optional &mdash; stored without.
        </p>

        <p style="color: #888; font-size: 12px; margin-bottom: 15px;">
            Use <a href="/status/gen_manager.py" style="color: #4a9eff;">Generator Manager</a> for notes, status, and moves.
        </p>

        <div class="modal-buttons">
            <button class="modal-btn modal-btn-save" onclick="saveGenerator()">Save</button>
            <button class="modal-btn modal-btn-cancel" onclick="closeGeneratorModal()">Cancel</button>
        </div>
    </div>
</div>

<!-- Network Configuration Modal -->
<div id="networkModal" class="modal">
    <div class="modal-content">
        <h3 id="networkModalTitle">Configure Network</h3>
        <p id="networkModalSubtitle"></p>
        <div style="margin: 20px 0;">
            <label for="networkCidr" style="display: block; margin-bottom: 5px; color: #0080ff;">Network CIDR:</label>
            <input type="text" id="networkCidr" placeholder="Enter network CIDR (e.g., 10.1.1.0/24)..." style="width: 100%; padding: 10px; margin-bottom: 15px; background: #333; border: 1px solid #555; color: white; border-radius: 5px;">
            <small style="color: #888; font-size: 12px;">Examples: 10.1.1.0/24, 192.168.1.0/24, 172.16.0.0/22</small>
        </div>
        <div class="modal-buttons">
            <button class="modal-btn modal-btn-save" onclick="saveNetwork()">Save</button>
            <button class="modal-btn" onclick="deleteNetwork()" style="background: #dc2626; color: white;">Delete</button>
            <button class="modal-btn modal-btn-cancel" onclick="closeNetworkModal()">Cancel</button>
        </div>
    </div>
</div>


</div> <!-- Close mainContent -->

</body>
</html>)T)ÔÚcgiÚcgitbrÝ   rÙ   r  r   ÚinsertÚmanagers.auth_managerr   r   r   rÛ   r¹   ÚFieldStorageÚformÚauthÚrequire_authÚauth_requiredÚshould_exitÚheadersr  ÚexitÚgetvalueÚhttp.cookiesÚhttpÚcookiesÚSimpleCookieÚcookieÚoutputÚminer_types_datarÚ   rÜ   rã   rÞ   rä   Úloadsr   r   r  r  r  rM   r*  r3   Úemployees_datar   Úgen_warnings_dataÚgen_warnings_cfgÚgc_datar‘   Úboolr’   Ú
setdefaultÚpm_datar/   Úgroupsr  r,  rL   ÚpmÚar_datarP   ÚwinÚwurS   ÚcbÚexÚflow_minr"   r#   r$   ÚarÚal_datar	  rt   rß   rv   r(   r4   r”   ÚsortedÚoffÚdumpsÚgh_dataÚdr|   ÚwatchÚhold1Úhold2r&   Úurllib.requestÚurllibÚrequestÚurlopenÚRequestÚreadÚdm_dataÚminers.dead_hunterr�   r�   r–   ÚcatsÚdmÚdp_datar®   r¯   Údm_load_configr°   Ú	candidater«   r±   Úconfig_dataÚ
new_configÚexistingÚexisting_usersÚunameÚudataÚexisting_sitesr¿   rå   Úexisting_groupsrÀ   ræ   r¶   r»   Úupdater¾   Úmanagers.config_managerrÁ   ÚcmÚbaseÚchangedÚfieldÚvalÚupdate_valuer   ÚreplaceÚmsgÚ_broadcast_updateÚenableré   rõ   r+  r-  rá   râ   rô   Úis_authenticatedÚusernameÚsite_webhooksÚ	alerts_whÚchat_whÚalerts_shortÚ
chat_shortrÈ   rÉ   rÊ   rË   rÌ   rÍ   rÎ   Úsite_peplinkr.  r/  r0  rX   r  Ú	pep_labelÚsite_switch_ipÚsite_networkÚ
group_podsÚgroup_generatorsÚgroup_peplinkÚgroup_switch_ipÚgroup_networkrÕ   Úlenr&  r"  Úescaped_noterç   rè   rñ   rò   Úpod_peplinkÚpod_switch_iprÐ   r  Úpower_labelÚ_alert_catsÚ_urlreqÚ_rÚdecoder
  r  Ú	pods_listÚfull_configÚlinksr5  r¸   r   Ú_gwÚDEFAULT_RULESÚGEN_FIELD_METAr6  Ú_gf_catÚcategory_catalogÚgetmtimeÚjoinÚdirnameÚabspathÚ__file__r-   r-   r-   r.   Ú<module>   sî  





ÿ
ÿ €ÿ


ÿ

ÿ €ÿ


ÿ

ÿ €ÿ


ÿ
ÿÿ €ÿ


ÿ
ø
ÿ €ÿ


ÿ


ÿÿüÿ&ýðÿ €ÿ
	

ÿ

€ÿ €ÿ


ÿ


öþÿÿþ
ÿ €ÿ


ÿ

	ÿþýüûúùø	÷
öõôóñðïîÿ&€ÿ


€

$€ÿ


ÿ€ÿ$€


€þ
ÿ €ÿ



 
€$€ÿ

	1 Tü7
	
$$
$$
$$
<""


Z"6"0*




J"."."$ "





>*$ $ÿ€ÿ
   
"€
&
€
ÿ€þ
ÿ
þÿÿÿ.þ
þ