This commit is contained in:
admin 2025-10-27 22:24:25 -07:00
parent 6ac35f19ad
commit 1505e3861b
34 changed files with 2575 additions and 1371 deletions

5
data/sys_settings.json Normal file
View File

@ -0,0 +1,5 @@
{
"print-on-connect": false,
"enable-led-pulse": true,
"led-pulse-duration": 1000
}

View File

@ -1,26 +1,100 @@
beautifulsoup4
blinker
bs4
certifi
cffi
charset-normalizer
click
cryptography
Flask
Flask-Cors
idna
itsdangerous
Jinja2
MarkupSafe
psutil
pyarmor
pycparser
pycups
pyudev
requests
soupsieve
spidev
swig
urllib3
Werkzeug
gunicorn
asgiref==3.9.1
beautifulsoup4==4.13.3
bidict==0.23.1
blinker==1.9.0
Brlapi==0.8.3
bs4==0.0.2
certifi==2025.1.31
cffi==1.17.1
channels==4.3.1
chardet==4.0.0
charset-normalizer==3.4.1
click==8.1.8
colorama==0.4.4
configobj==5.0.6
cryptography==3.4.8
cupshelpers==1.0
dbus-python==1.2.18
defer==1.0.6
distro==1.7.0
distro-info==1.1+ubuntu0.2
Django==5.2.6
dnspython==2.8.0
evdev==1.9.2
eventlet==0.40.3
Flask==3.1.0
Flask-Cors==5.0.0
flask-sock==0.7.0
Flask-SocketIO==5.5.1
greenlet==3.2.4
gunicorn==23.0.0
h11==0.16.0
httplib2==0.20.2
idna==3.10
importlib-metadata==4.6.4
iotop==0.6
itsdangerous==2.2.0
jeepney==0.7.1
Jinja2==3.1.5
keyring==23.5.0
launchpadlib==1.10.16
lazr.restfulclient==0.14.4
lazr.uri==1.0.6
louis==3.20.0
MarkupSafe==3.0.2
more-itertools==8.10.0
MouseInfo==0.1.3
netifaces==0.11.0
oauthlib==3.2.0
packaging==24.2
pillow==11.3.0
psutil==5.9.0
pyarmor==9.0.7
pyarmor.cli.core==7.6.4
PyAutoGUI==0.9.54
pycairo==1.20.1
pycparser==2.22
pycups==2.0.1
PyGetWindow==0.0.9
PyGObject==3.42.1
PyJWT==2.3.0
PyMsgBox==2.0.1
pynput==1.8.1
pyparsing==2.4.7
pyperclip==1.10.0
PyRect==0.2.0
PyScreeze==1.0.1
python-apt==2.4.0+ubuntu4
python-dateutil==2.8.1
python-debian==0.1.43+ubuntu1.1
python-engineio==4.12.2
python-socketio==5.13.0
python-xapp==2.2.1
python-xlib==0.33
python3-xlib==0.15
pytweening==1.2.0
pyudev==0.24.3
pyxdg==0.27
PyYAML==5.4.1
requests==2.32.3
SecretStorage==3.3.1
setproctitle==1.2.2
simple-websocket==1.1.0
six==1.16.0
soupsieve==2.6
spidev==3.6
sqlparse==0.5.3
swig==4.3.0
terminator==2.1.1
typing_extensions==4.12.2
ubuntu-drivers-common==0.0.0
ubuntu-pro-client==8001
unattended-upgrades==0.1
urllib3==1.26.5
uvicorn==0.35.0
wadllib==1.3.6
Werkzeug==3.1.3
wsproto==1.2.0
xdg==5
xkit==0.0.0
zipp==1.0.0

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
# Pyarmor 9.0.7 (trial), 000000, non-profits, 2025-03-14T02:02:35.798448
# Pyarmor 9.0.7 (trial), 000000, non-profits, 2025-10-27T22:23:03.382081
from pyarmor_runtime_000000 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\n\x00o\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00x\x02\x00\x00\x12\t\x04\x00S\xe3\\\x0f5d8 \xcf\x05\x074%\xa1\x04\x8e\x00\x00\x00\x00\x00\x00\x00\x00E\x91\xe1\x86\x0ea\xa1\x16\x0e\xb4\x1b\x1b\xea\xc4\xe8N\xcc\xb5\x08\x1e\x92\x90\x01S\xac\xab\xf0\xee\xbc\xcdr\xc3\xad\xa6\xee\x19"\\B\x1f\xd8\x83\xaaU`\xb2\xa5\'\xf3_=\xeb@\x10D&\xbc1P|\x81\xf5^\x01Z\xd6\xbc\xcc6Y\x1d\xe4q\x16\xda\xee\n\xa9\x88\xf5`M"4\x97\x9f\xef\xd9\xb6%\xf8\x1d\x8f\xd5\xee\xa9\x922\xe79\xf4\xea,\xff\tm\xc1S\x91\xc6M\xe1,I\xef\xb9\xeb\x9d\x93\xc5}\x03,i.\xf4$\x9c\x8b\xeaF\xa9\xc2\x94\xc8\xf3\xf5\x9d\xa0C\x97\xe3[p\xa3\xdem\x0f\x9bwW\xca\xca.\x043\xf064g\x1f\xd5l\x9e\xd5U$.\xda$QY\xa2\xd3\xfa\xb9P\x1f\x84\xfa\x9c\x86/\xc8X\xab+Zn\x14\xe0\xf3\xa5H\xabF\x9f\x89\xec\xdb3\xf74\xf6e1\x06\xc5$\xb7\x8cH\x7f\xd50\x0e`\x8f\x1d&\x0e&\xef\x92\xca\xc3\x85\xef\xea/"\x1b)\xf7\xfd\x9c\x1d9l\xbe\xee\xae\xf0\x96}\xc0`\x06]\xdb\x9a=\\\xe8\xdbU\x8ac5S\x84J\x8f\xdd\xda\x82\xfd\x02\xe0\xb3D\xd4R=\x90|r\xa8\xdd/\xd0\xd3.6\x7f\x1fD\xf7!\x1e\xeb\x92\xc0)\x95qN\xd8\x0b\x126\xf7B\x88\x12\x98j\x1f\xb8\x08\xdf_\xbb\x1c\x0f7K!\xb2\xc3\xe4\xfe\x80\xacc\x18\xb7\x0c\x94\xbe\xbc\xec\x93<\xf60\x97\x87\x811\xe6\xa1\xad 0\x08\xe1Q#\'S\xb6\xa0!e\xa7\xe8\xb1\xe8\x14\x198\x14I\xf2\x1a$\x8f\xe6\xb6&\x90B\xae\xddm\xd06@\x1f\x98\xf29\x10\x7f\xab=\xf2y\xdex\x08\x1b\x93\xe9\x00\xff\xb2\x9f\x07P\xbe$\xa3\xe8\x10\xd3\xf4R\x98\xac\xe22\xf1A\xa3B\xb7\x81l\x18\xb6:mHf\xc53Zo&Q0\x1e4\xc5\xaa\xca\x07\xc5\x8f\x15\xad\xb3\x8f}\xa1<\xb1Z\xc8\x16\xf53r\xd8\xdf~\xa6\xdbn\xc3nN{Pp\xbc\x87\n\xde\xaf \xeb=\xd9\x87\x9dGhX\xa67.\x80\xe7\xd2\xb9P\xc3\xbfP;\xd9\x9e\x82\xa8,\xca\x18/n\x9es\x15\xd7\xc0\xdc\x05i\xbb)\xbc\xad\xeb\xe4>\x88H)\x10\xb5<\xd9\x8a\x82\xdc\xfdq\xa7\xb2\x91\rL\x1d\xd2\x1f\xc4\xd6\xaf\x1d\x1d\xed\xe9\xb1\xf0\xde,\xdbaZH\x1an\xddO@[0\nC\xfc\xfe\x15N8\xcd&o!\x1a\xb5f\x13{4\x85ED\xdd0\x1bm\xbbXb\xbc\x8dL\x81\xfez2\x19$\x1f\xe61\x95\xd8\x1c\xfd\x8e8i\xd4\xa7\xae\x01\xdb{b\x17X\xc3)\xd0\x9f\xafX\x0c<\xd0v\xd2\x08\xef\x0f\x91Y\n\xa2')
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\n\x00o\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00x\x02\x00\x00\x12\t\x04\x00\xd9\xc2\x8c\x97\x8b\xfc8\xac@\x8e&\x0f-\xe4\xdd?\x00\x00\x00\x00\x00\x00\x00\x00\x08\x85\xca\xd8\x99u\xc0Jp\xfd\x1e\xffCe~B\xb3\xd0\x05-\xad\xd9\xa7\xb2%\x8e}\xebB?A?\xb4\x11\xf0\x81v\xf5-\x18\xe3}\n\x02\x1a|\xbf\x1du\x9c\xff\xaa\xcc\xcf\xf5\x08f\x0ea\x02\xc1=)\xb3\xbe0\x08Ig\xf8!P\xef\x97Hl\xc5d\xd13^[\xee\x15*J\xed6\xad6R&\xba\xd0\xb3\x95+I\x9a\xe6$\\=\xf0\xc5\xf5vx\x93d\x9b\xb1\xda\xa3B\x8a\xd6da$\xed>\xc3\xcaP\xc5\xa0\xf1\x1b"\xfc\x99m:\x18\xee\xac\xfd\xe7%=\xe6\x00\xbb\xfcA\x10\xe0\x15\xa2h]\xf4g\xf6\x08K\xdf\xb3\x9b\xc9\x13\x90\x12\xa0\x93h\xd3\xb0\x19aP}J4hX7L\xf3L\xf0\x14\x0f\xa5&\x04\'\x8a\xed\xba|\xe7]T8\x18l\xbfw\xb1\xe5\xed&jw\xc4O\x07\x86\x1dw\xaa\xd6G\xb1P\xa2D\xd4\xd7\xbao\xc8TVbOl\xbf!\\\xbc\x05.)>\xcf\xbcq\xd3vG-\xbc\xbd\xfd]\xa5{\xe7n\x04H\t\xc3\xd4\xf0!\xad\xc6\x15}\xeb\x80\xf2P\x06\xbb9\xdd\x89\xf4\x7f\xd2\xeeK\xd5\x84\x0f]\x92\x9c\xd8\xb6\x03Y\x96\xf8\xf3t2y\x00\x9e%w\xfa\x1d<\x9ao*\xb3$\xf9c<`,\xd8\xb4\xb1\xd68u]\xc5\xdf\xfe.E\xb3\xe8\xc4\x8f(\xb9\xa6\xafD\xd1\xe9\xcc\xcf\xffx\x80\x9f~\xc6M\xcfz\xe4\x1f\xc9\x15\xe4\x802Ck(\xcbAFuU\x0bv\xac\x01\xe5\x9a4A\xe4\xd6\xd3(\x11\x01\x98\xffb\x16C\xc8*\xe2\n\xe4q\xb5?\x954X6\xadm\x9a(\x92\xa5Ux\xbe\xd0\xc4\x8d\xfa\x02\xd6gy"\xb2pK\xd1\xb7,zL\xc3\x14Y\x03\xee\xc1\xc6\x82\xc8O\x07\xc8]\x08\x86.Bz\x1d1B\x8d\x86\xaf\xfa\x13\x87g}\x0e\x05:{\xb8\xbb\'\x07\x84B\xc8\xc99i\x174 [\xd3\xc68\xab\x03X\x9c\x18&\x18\xad\x0f\xd29\xe5\x18\x13\x0cz\x17_X?j\xe6\x96\xcb/\xa9\x18\xc4Q}\xbe\xb1\xef#\x7f(\x8f\xef\xe49\xba\\\xc5\xd4\xec4\xabrO\xf3\x01\x85\xf5\x8a:\x11%d\xad2_\x194\xa6%\xb1\x88_\x99\x11-\xfa.\xc5\x19\xf6\xf8H\xaa\xd5\xac\xf1\xa5\xd5\xaf\x0bO8\xf5{\xf4t\x98u,\xd8\x1b\xeb\x8e\'\x06\xf4\xd64W\xeaT\x04Z\xd4B\xfel\x7f\xc1\x8b}Rt\x00\xc6(\x91\xa1I\xc0p\xd0\x1c?.\xeeq\x9b\xf4\xa9\xbc8\xf9{\xd0q\xd9\xbcQ\xcf6\x87\xd5\x1f&\xf7\x187\x94\x8d\xb4\xd9;\x06\xbex\xd1E\x0e\r')

View File

@ -1,3 +1,3 @@
# Pyarmor 9.0.7 (trial), 000000, non-profits, 2025-03-14T02:02:35.823082
# Pyarmor 9.0.7 (trial), 000000, non-profits, 2025-10-27T22:23:03.404795
from pyarmor_runtime_000000 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\n\x00o\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00-\x04\x00\x00\x12\t\x04\x00\xf8\xe9\xce]\xe7y;\xf5\x82\x91+~x=\xc2\x8d\x00\x00\x00\x00\x00\x00\x00\x00\xa5\xdb\x0b\x90\xdeN\x00J\xe3\x18 \xae\xd5]\xabH9\x052\x8f\xfc\xcf\x9a\xc1\xe0?)_)l\xe4\xb4H\x8f~\xb6*L\xb7y&\x9f\\E\xd4\x04:\x04\xe3\xb9"\x8f\xe1\x86\x88\xb1\xf3\xa3\xb4\x98\x97\xf2\xb6\x1a|\x16w(^\x96\x13\xa6\xf0\x91\xfd\r\x11<\x10\xe7\xbd\xf6\x10\xfe\xa5\xb98C\x84[\x18\xb0j\xce\xd6\xa7[\xf1\xcb;R\x9ec\xaa\x05\xa34\xa6lo\xbe\xa9\x03U\x9fU\xc6\x16\x03\x9c^\x18|(\xd7\xa0}T\xce^\xb87\x88nH~\xe7\xd0\xe3\x10\x14+\x8b\x04\xb49!\x0b\x08\xbbx\xd5\x98\x7f\xee\xfb\x04\x8f\x0f;\x96\xbe\xf8\xa7\xeb\xf1\xed_\xa0\xfdl]\x9f\x1a6"\x185<\xca\xf7\xaa\xa6\xe1wC\xbe\xcfzE\x8fM.9\xcc\xc3\xc1\xf9*\x14\x86I\x9fz\xda\xf9\xd2\x149\xe9\x8a\xd0i\\\x81\xa0_X\x07\x8a5\xb4\xdf\x7fc\xd2\x1ag\x11\x1fj\xc8\x1d\x02\x88X\x05|\x81\xd0\xc8\x81x\xab@r\x13\xdbY\xcem\xf4v\x0e\x9a\'\xbb\x1b\xd0\x95\xb9\xc9J\x80\x94\xbe{\xab\x7f\x12\xeca)-\xc05\x02u\xb3\xb2\x9e\xf0\xaf \xf8a\xcc\xee\x1ayj\xdb|\td\x05*2\xd9d8\xb0\xdb\xf2\xa9\x98\x0cH@\xd5\x0f\x9b\xbb.\x95xdr\x85\xb4\xcb\xc6\xba\xf8\xc8\xbf\\\x16\xd7\xdfZd\xa5\xb8\xc4\xf5\xff\x12\xcc@\x9cp\xe6\xe2G"\x11\xac\xdd>\xdd\xfe\xbc9\xf6@\xaa\xd4\xe7\x0f\xf6\xa5\xfe\x85\x98\xe3\xcc\xd2\xde\x9a\x98\xd0\xe9\x9eR\x95\xe3\x1f\x0e\xc1\xff\xaf\x00-Z_\x08CA\xe8\x97\x96q\x93\x0b\x8d\xdaO\x93\x9c\x90\xfa>k\xe1\x90?\xc4E\x98\xe3L\xaf\x02_\x06\xe9\xc0\x18\xc90X\xfe\x17i\xbc\x86;\xae\x91\xc0\x1dV\xb9*VG\xa76}\xc7\xba`x\xe9/g\xc4\x02#\xa5\xe8\xc9|3\x1c?@\xf2\xd6R\xa3w"\'$EY\t~)\xc0\xec\xb1\x18\xb9l\x1e\x18P/\xa7\xaf\xdf\xf4\xd3\xd0\x85\xe7]n\xae\x1e\x88Hi\xc7O<R\x06\xfc\x04Y\x01\xfeS\x94\x0f\xa7pFRe\xb1D5\xaa\xee\xec\x00\x90\x8e\xc5\xf1\x99\x8fo=\x88k\x96\xb0\xdfD\x94|\x92\xe6\xba1#\x8a\xb9L\xec8e:\x8e\x9e\xb8\x01AQ\x04\xeaz\xa0\x92\x16\xf2\xab2D\xfb\xd8\x86\xe1=\xfb\xbf\x7f\xa0\x97Z\x81\xb6\xa4\x7f*\x8c\xa2k\xccV\xdb.\x05\x8f]\xce\xf3"\xefL\xa4V\xfd\xc7\x81\xf3>\x8b\x00\x81\xca@\xdc[\\7\x89\x00\xa5b\xfb\xd0\t\xa4}\x96\xfe.=A\xc2\xfe\xa7\xcf+}\xea\xae}J\xe1\xf8\xc5\xaf\x94"\xa0\xd7\x1f\x96\x17\xach\xf6\x94#\x02\x9et\xd0S\xf9@\x8cA\xc9o\x1c\xbc\xa5$dD\xceY#\x8e\xe7\xdf\xdb\xb2\t\xf1V\xa0\x90\xcc\xc0\x90\xbc\x1b\xe3\xdd\x96=\x80/(\xca\x18L\x087\xfe\x98B\x94v\xe5j\x17\xb13\xcb,\xd5\x8aT\xf3!\x98\xeb\x11\xef\xcd\xf8\x1e\x15\xcf\xee3\x8d\x86\xa1\xeaE3\x9c\xc0@mp\xc4\xd3\x16;\x9eS\x15\x83\x8e\xb1\xc1\xba\xbaZ:\xa3D\xd8<\x1c\xb7\xe9\x1d\x0e06\x8c\xa4\x98\x9bISa\x7f\xe3\xdd\x90R6WZ\x11Z9\x87\x1fS}\xdf*\xf2\x0f\xb2\xd8\x11\x16\x18\xfa\xcb\xd6\x14\xfby"\xa5&\xb53\xeb\x03\xc1\xbc\xfa\x11!H\x9d\xa7\x1a\x03wx\xed\xa5\x9e\x90\xa1\x99\xba\x05\x1e\xf8\xd4IO\rs$`\x1d\x06\xa6\x98\xa3\x14\xeb\xed@\xb1f\xf3\xc7\xae[r\r\\5\xf3\xee\t\xb5I\xd3\x16\xc4\x02\x84&5\xae\xc1\x03]+\xcf#c\xaf\xd5l6\xf5?\x1e\nPB\xd9\xd6\x89\x7f \x94\xfaS\xd6\xac\xb5\xfeB\x85\xcf\xeb\x86w\x1b\x1bY\xa0\x83\xa9\xf5\xd1\x1b&\xdc\x05\xe32!>\xb0\xc3\x9f\xf8\xc8\xe1%:W\x03i\xda\xb4\xc4\xb0\xbd\xf6\x07! \x01\x14\x1b\xcc\x9b\x8d\xa6\xbc\x84\tY>\x08\xd0\x00\xcbR<\x0e\x13\xb5\xfdSO5\x83\x1c\xd5T/\xa3\xc5q\x9di\xe5i\xbf\x10\xceE\xd4&\xf6h\xe9\xcd\x83\xf3%\xcb\xbe\xc0\xcda\xd8P\xb2m\xb7\xde\xf8\x97m\x08H\xc0t\xfd\x9e\x85d\xad\x90\x1a%K\xd2\x82\xf1\x8a\x86\x80\xf0\xa1\x89\x87\x9b\x15%\xd2t\x17\x1b\x8c\xc4\x91\x04\xbd\x97\x8aZ\xd2\x8f\xf7\x90\xb9\xb4\x99\xec\x98P\x13z\x0b\xa8\xfc%\xd2\xfd\x80(\x10\x998i\xe0d!')
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\n\x00o\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00-\x04\x00\x00\x12\t\x04\x00\xf9 \x82\x98\x98\xe4A\xc6\x05V\xae\x86\xed\x9c\xc1\x99\x00\x00\x00\x00\x00\x00\x00\x00\x98\xbd\xc8\x83y\x93<\x1b\xd7P=\xaa\x8a\xbf\xd3G\xde\x13\xa4\x02\xbf~\xaf\xbec \xd3\xdf\xd9;\xa7oOE\xeb\x0e\xadW\xd7\x0f8[\xf7r\x9b\x8e\x8e\xd1\xfa\xb7<\xccO\xdc\x9c\x05gr\xc9\xc4\x1c\x06]\x8a\x891z0pX\x8c\x164\x194\xc0\xcbD\x10g\x18\'~\xed\xdch3\xf7\x1a(x"\x93.\x83h\xcc\x08~\xd3qT\x8b16^\x8a\xb7Q\xdf\x8c|\x99)9:\xeb\xc0^WA\x14\xbez\xb3\x08\x9c\\h\xa1\xe1Rm\xa3\x8f\xba\x16:l\x08\x86\xfc\x8b\xa4h\xd5M\xbe\x0b>\xf4\x97\x95\x90\x9a\xeag\xd9\x1e\xc68@\xfd\xad\xe8\xb7Ke\xe7\xf5a/C&y\xb1\x8dR>\\\x89\xabN\xa4\x1a\x02\xbb\x99\x80p6\xdct\xc8\xe8p\xf4\xc5AI\x80\xcf\xec\xd4\xa1\xaaW\xa4\x88b\xf9\rbMG\x92\xc0\xfaH%,\xbf\x0c\xdb\xbev\x887,-N\xa8\x1c\xca\xfb;\xa3p\xd4\x1a\x9b%\x0bR\x14\x80\xb5\x01\t\xed\x0e\x0f\xdft*_B\'\x9dI\x0bP\xb8 \xbaJtd\xa0U6\x10k\xa5\x9c\xc1\xb5\xa7\xd93y\xed\x899\xc4^\x9c\x1b`\x93\xfe\x89Ww\xd8w\\E]\xde\x18\xd6\xb5\x9f6\xd1\xd6\xa4\xc9\xee\'\xb3\xba\x02D[\xbaT\xc6\x7f\xd9\x19\x01\xd6\xdc3\xfev\r\xa17\xe94\x03\xb2Y\xd2@7mL\xf4\xc94\xc9\xc4\x88\x1b\xa9\x82\xd8\xd5\x96o\x92\x10l\\\xca\xde\xa0{KR\xda\x8d\xca\x7f\xf7\xbft\x1aQ\x1d\xfa,Om\xbch\xe0\xa5\xae\x9a\x85.nIi\x177\xce\xb4\r\x15B3>!\x19\x1a\xd8\x19\xbdt\xb5\xf59[\xac?\x05\xb8\xee:\xe3\x1e\xad\xe0\x8cB\xe7\xce\xc3\x9eWh\xf1\x82\xa6\xf5"3\xf7&~E\x00`\x12\x8eA\x0f\x03\xef\x07\xeap\xcb\x87I\xce\x03\x83\x191\xbe\xb7\x01\x13\x17.\xe2\x16\x94\x81\xb9\\\x16\x82\x97$O?\x92\xcd=\xa3\xe8^hs\xa5u\x8c\xab\x06y\xbd\xdd\xc5\xaal\x9d\xeaZ\xc7\xe6\xe1\xacL\xc1\xd3\x16F\n8.\x81Y\x95w\x0f\x95:\x88\xa6\x01@WU\x8f\r\xf1\x88\xa6\x95\x10B\x9d\xf0\xbc\x1e\xd8\xdd\xf4+o\x88`i@$\xb4\x979D\xfc\xd0+V\xac\x84<t\xad\xa5\xcbU<R\xd7\xc4\xa9\xa9\xc5\xc3\x88y\x82\xd7xt\x0b\x80\xc1s\xb7\xdc\xed\xb6\xb7\xd10cJ\xc88\xec8\xf6\xd9\xb2\x9e\x03>g\x97w|\x99\x9d-\xb2o\x0b-\xea\xf9\xe3\x11\xcd\xe4\xc9\x9b\xd1\xe9K?7\xf9f\xf05\xa7\x8c\x08\xff\xa7\xd7:\xa9\x16\x0b\x99\x998xw:=\x0e\x0b3\xcd,\xe4\xd1i\xc0\x08\x92\xed\x9a\x90\x89\xc0\x9b\x07\x10>\x9e\x95Q\xd7` c\x88\x8b\xfb9\x9a\xe4SG\xc3o\xab(\xcd\x0e\xf3s\x93\xd9*\xc17\xcdM4\x07\x87\xbe \xc4\xe5\xbaAowE\x9a\x07\x91Vd\x01\xcd\xb1B\x14N\xde\x9b\xf1\xd2\x14UJr\xc7\xc5\x93\xd2\xf5\x85/\xe4\xa6\x11\xc4\xc0 \xf84Gn\xcc~\xb9DP 3\xfe\x94\xac\xf6{)\x07\xb0\xfa9-\x88^\xc0\xb7\\\x043@)\xdc\x87%k\x80\x05&\x11&\x02\x94\xcdlV{\xa6Mr,p\xce8\x7f\x12\tE\xf7)\xa1/\x88\xdb\x08\x01\x84\xa2\xbb\xd1\x91\xd2\xdc\x96%\xa2\xfa\x99nV\xfe\xe3\x17\x1a\xf0\xa5\xceg\xb44\x89\x1aN\xeeS^\xd5\x85\x06j\xe1\xb8\x0f\xbb\xbc\xb5\xd7\xb1z\xf1\x82\xa9\x83u\xe1\xe6\x9f=\xb2\xb9\xfd\x88(\xfd9\xa3\xfc\x91\x9e\xb3\x14\xccP\xdc\xb9\xceY+q\x1c\xbcZ\xde\xccm7,O\x1a\x85W\xea\x1dR\x0b\x969\xf3\xc3\xfca\xabk!\xf7\x8dy\x8egE\x9a=Q\xbe\x11\xc4\xbb\xd7\x82\x8d]\xd9\xa1\x94}yr\x98^\xac\r\xf0\xef[\xe8\x80\xc2blN\xe79^\x07\x82\xcdK\xfc\xe3-5[\n\xd7\xe0P\x146\xdf\x02\xeao\x9c\x05.\xa2\xb8\x1c\x13[\x9a\xe6-\xdf}\x8fv\x15\xa6\xf8LF#q;\xc8\x82[\x14\xee%\x8cf[\x9b\x11\xe1>(\xb8\xe6\x10\xc73M\x1f\x18\x9e\xe7\x1e\x1c\xbb>\x04\x10\xff=\xaeP}\xadW\x06\x15\xbc\xc7\xfaI\xb4\xe9\xa8\xf8~\xcfQ2\x9b\x0b\rf\x8bu\x85\x9cc\x96n|\xb4V\xdc&aH[)\xc8w\xc4\x89\\\xbf\xd3\xfdm\xa0\xa5r\x98\x96\xb2\xa3\xc3\xbaG')

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
# Pyarmor 9.0.7 (trial), 000000, non-profits, 2025-03-14T02:02:35.897509
# Pyarmor 9.0.7 (trial), 000000, non-profits, 2025-10-27T22:23:03.483996
from pyarmor_runtime_000000 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\n\x00o\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00\x84\x00\x00\x00\x12\t\x04\x00\xb0*\x98l\xa4\xf4\x12\x14\xb8\n_\xa9\x03\xce\xedi\x00\x00\x00\x00\x00\x00\x00\x00\x1d\xe6\x1aC\xa8\xe6#\xbf{\x99\xc4\xce\x88\xad[4\xc8\xfbs\xb4\x9d8\xde\xe3!\xaab\xf9\xd3\xcf\xc9\xd7\x84+\xa8;\xcb\x11\x0c\xc5\x1b\xd1\xa9\x90\x08\xab\xad\x8fE\xd0b\xad\x15\xb0\'7TK\x82L\xc9\xbd\xda=x(\xdf\x83\xd0\x84f\xe2\xd3\xdd\xb1\xa8lU\xde\x0f`\xa9\x07`\xeeg4\xa4w\xeb\n%\xdck\x12\\\xde\xd1Z\xeaZ\x90\x86o\xa6\xa7\xb9\x7f\xba\xda\x06\xe3\x0c\xadv\x9f$\xc6\x87\xfc"\xb1E\x8e@T`\xd40\xa2\xfbc')
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\n\x00o\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00\x84\x00\x00\x00\x12\t\x04\x00\x97^\xd8\x95\x16B\xef\xdb\x8d%3\x04\xd13\x9f<\x00\x00\x00\x00\x00\x00\x00\x00b\xf3\x9d\xb0\x8f\x0f\rE^\x08\xa1\xd4\xf2\x08<\xe9\x8e\x08\x94s\xe3\t\xee\r\xa1\x9b\xf6\xca\x0cU\x86\n\xfce?\x1b\xee\x86\xfb\xbb?\xe49\x1d\x9c\x9b\x02\xe1\x9a\xe3\x92\x84B\xf74\xeaK\x11!@\x1e\xb9\xa0&\xd7\xcab\x95\xc1\xf0!\\\x98\xfbq\x85\x1c\xa8\x15\xfd\x05.ruG\xe1I\xd9V\tp\x94\xeaB7g\x8e\xae:8\xd5Sc\xe2~\xfaZ\xd7C\x85\x134j\xf9y[B\xf0\x95\xc5\x8ddWkn\x07\x12\xc7J\xf0\x0b\x98')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,2 @@
# Pyarmor 9.0.7 (trial), 000000, 2025-03-14T02:02:35.420965
# Pyarmor 9.0.7 (trial), 000000, 2025-10-27T22:23:02.994132
from .pyarmor_runtime import __pyarmor__

View File

@ -1,3 +1,3 @@
# Pyarmor 9.0.7 (trial), 000000, non-profits, 2025-03-14T02:02:36.425013
# Pyarmor 9.0.7 (trial), 000000, non-profits, 2025-10-27T22:23:04.037554
from pyarmor_runtime_000000 import __pyarmor__
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\n\x00o\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00\x0b\x03\x00\x00\x12\t\x04\x00\xfcN\x18\xea\xa1JKN\xb1\xb4\xf5O?\x92\x1a\xce\x00\x00\x00\x00\x00\x00\x00\x00\xb3d\x87\xc41\xfc\x8fS\x9d\x9aBs\xc0y]b\xaeK\xc2\xc40m\xdc\xbf\xbe\xdf\x8b\xab\xae\x87Fc\x07\x1a\x11n\xbd\xbd\x8fS\x9eZ\x8d\xb5"\xaa\xe8\xc3\x8bdt\xdf\xe7\xdf\x84\xe2r\xda\xf5Z\x00p\xf2\xe5@\x8e\x0e4j\xa5;\xbd\xff\x14?\xf6\xa7\xc4[\x19\x9f\x9b\x1f7\xd0Iy\xcb\xa4\x96\xff\xbeF\x12q0q\xf7>~2\xb5n\x03\x14\x10\xa8\x1f\x1fl\xd4\x8f;\x02\x1d\'\xcf_\xf3\x85\xc4\xe2\x84\xdb\x9c}\xfb\xabF\xe3\xa4A\x97\xd7\x80\xdbz^\x1e\xf3\x95\t\xba\xb2HM\x97\xf9\xa5\x18\xa9\x12\xffY\xa7\x0f\x1b\xca7}C9Q5\xf64^\xe2\xfd{TM\xfe\xb8\xb0\x16\x9bj\x8eJ \xd7\xd3\xc9\xe2\x0fW\xfe\x11C\xb5\xb7\x0e\xe4\xa4\x9b\xad\x9f\xce\xeeb\xf2\xc4HW6\xc2I\xac\xf87\x96\xa8\xc1\xee\x1f\xab@\x9d\xb3|\x85\xbd\xf2\x98\x95\xbb\x1b\xb99\xb7\xca\xf3\x90\x81Tk9R\t+\xb4H\x14\xae\xba8\x02\xe6\xca\x9dm\r]-\xa9b,\x8f\t\xe7m\x1b\x88M\x8f\xb4\x16"\xffT\x04\xda,zV\x82\xa0\x98\x00\x1d\r0i\xa6\xfd\xb3\xef\x18\x13<\xbc\xd9\xf8\xa0 \xedn\xcf\x86\xe4\x91\x10\xd5\xf6\x9a\xe8%|\xc7Rn<Y\xa2\x95\x0f)\xb1\x89P\xd4p9\xb1\x14\xc4Em\xc9%F\x9c\xb5\x985\xcb\x07\xfb\xf7V\xe4\xc8{\xdd\xad?\xc7r\xc1\x8aDA\xbdc\xd6\x0c\xb1\x08f\xe1XE\x1d\xc6\x15@\xe0\xb3\xc5\xbaf\xfa\x90\xb3\xddps\xf3\xd1\xcfqM\xce\xcb\r\xb2\xfc\x95d\x1c\xc4\x8e\x85`\x0f\x92\xad\\v\x07v\x16w2\xe6\xf7h\xab\xbf\xaf\x81e\xea\xaf\xcd\xd6P\xb5\x02x\xf2\xf6\xb0Q\x84)\x1d\x94~\xdc\xfbL/F\x15\xcb,\xc1\xd0G0\xb3\x1dNW\x8fa6F!\xd8\xbf9\xa5k?\x8c\n\'t3\x1a\x1d\xafk\xca\xd4\xda\x8d)\xc6\xf0\xc6\n\x9f\xaa\x1c\xd7b\x8c\xee\x96\x1c\xc9\x9d\xe9\xb0\xf6L\x87m\xeb\xf3\x0eI\xf7\xf3\x93^FL\xa2\xe2\xf1R~\xb9q\xc6K6\xc7\xf9\xee\xce\xc5\x1c\xb7\x8fbQ=\x02\x89\xb1\xbd \xb3\x04\x98,\x05\xddv\x10~\x05\xf2\x158D\xcb\xf2\xef\xeb\xd2\xcc\xd8\r\xd6\x8fe\'\xa66\x8f\xa8I\xe5`\x0bH\xda\xa3W\n\xdc\x05\xfb\xb8\x81\xaax\xed\xe5*\xba\x97\xfe\xbev\xbe\x9aL\xc0\xf0\x88\xf0\xd2V\xeaT\xee\x19\xb1\tT\x1f\xc4\x92\xc0\xc3Z\xcd`\x8e\xc4\xaa\x8e1*\xb3\xc3\xaf\xd9\x15P\x8f-\x8c\x91GD\x92\xf33J#\x1cM\x01\x9b$\xf2\xc5L0\x1e4\xbe_\xebt\x16\x0f\x19x[\xe6\x95m\xb3\xbf\x90Yt\xb6\x16\xa2L\x895\xa2\xcd\x0c\x00Vz\xc7zs\x81*\x81%CU\x98l\x15\xb0\x80\xb8KN\xfa\x9c\xacYg\'\x99\xe5\xc3\xdcb\x15\xc7t\xb8Bk\x96\x04\xf1!G\xef\xfbs\xcf\xe7\xa5\xb3h?\x9c\xd7\xa8\xd8[\r\xb1|\xc6\x0f\xe4\xee\xe1\x10\xd9\xfe\xa5\x9b\xf2\xdf])\x89]x\x01\xee\xb7\xdaR\xc2\xa5\xa2A\xee\x87 w\x19\x1fF\xb3\x01\xae@\xb2\x94\xfd\xbd\xbc&\x8c\xc28\xff\xd7&\xd6\xc1\r5\xb5')
__pyarmor__(__name__, __file__, b'PY000000\x00\x03\n\x00o\r\r\n\x80\x00\x01\x00\x08\x00\x00\x00\x04\x00\x00\x00@\x00\x00\x00\x0b\x03\x00\x00\x12\t\x04\x00\xf1\x1cjC\xb5\xb6\x08yreT3\x1b\x1b\xc10\x00\x00\x00\x00\x00\x00\x00\x00Jb-}n.\x18&\x85\x8aqR\xce\x1f\x1a\xf30L\xb4\xcc*9#\xde~M\xb4\x86\x80\x89\x99\xefu2\x12\xa3\xd1GQC\x04\x92\x06>\x01\xdb\x11\xfc\xf04\x15\xb2\xe2B\xa9\x16\xa1\xf7<\xd11\xdc6\xd3\xdb\xd8\x1dn\x83\xcf$\xc4\xa5\x8e\x08\xa7\\\xca\x9e@\xbf\x8c\x13\xf1[\xa9\xdf\xe6$E%\xfa\xa9\xa8\xe7\xbeq\xf1\xe2,\xed0_\x89z\n\x87\x13\xbe\x9e\x0f0\xccY\xe57L\xf5\xe7\xe1:\xc3_\xadF\x8es\x8fK\\\xc4\x1e\xa0\xf6\xe4\xa1q\x04\x05VCW\x1a\x8b\xfa\xbe\x0ePu\x81\x18\x19\xd7h\xc5P\xe7\xcf\xf7\x9a\xaan\xe9:\xa8&\x17t\x82-&\xff\xd3\xc0r\x05\x1a\x7f\xfb\xdbBM:\xbf\xca\x14y\x90\xb5\x98<`\xc0X\x10\x0c\xa4>\xc5\xce\t$\xe3(A\xfe\x7fN\xb4R#\xda\xf8O\xde\xda"t\xaeD.\x00R\xa3\xb9\xf5jYy\xd0\x8e\xe4\x06\xf9A\x18\x14\xcc\x01\xb8e\x7f\x9b\x1ae\x97\xa3\x02kwY\x81\xfe16C\x85\x90:B\xec\xa6M\xdc\x80\xc73Y\xe3/\x96\xe70f\xb3\xce\x84\x0c\xa9k\xf1z\x8f\xfe\xc8q\xdf\x83Q\x10f\x01\x9a\xed\xfd\x9bh0t\x8d\xdb\xa8\xc1px\xb5\xd6k\x1e\xdf\xfd7a)I\xb4\xd7\x81Y/\x83\xcd\xf3\x99BTZ\x16\x1d\xc3\xe0\xf5\xef$\xd1\xae4\xd6\x8d\x0e\xe5g\xeaw\\\xfcl\x8c\xd5\x1b\xa5^\x96\xf4\xe7\xb1-\xd6G\xf1\xa0(U\x1b\xdf\xf5\xc1\x02\x89\x16\xa6_\xcdfU\x80\x10N\x83\xb6zt\xc1~\xdd\xf8I\x19\xf2\t\x83\xac1\x13} (\x12o\x8d\x06(\x1cN-\x92T\xed\xcf\xa0\x0c\x1al\x08G.\xf6mX\xf4\xa0\xef\x117\xf6\xe2E\xae\xa9\xccP\x96\x97\x1f\x86\x850\x15\x15\x03\t$*\x85&Z\x15\x07O\xa9L-\x8bz\x0fh\x96\x06\xf6\xe89?\x13;\x9d\xa0\xa6\x87\xaa\xd1\xdaeo\xc1\x10"\xb1\xdcsO\x10F,]\xeeg+J\x9d\x97\x06\xc9`\x89\x03h\xf8\xdat\x9a\xa6\xcd~W\xfa\x7f".\x04\x9c\xb2\xa2\x81FS\xe6\x0e\x15i\xa8\xdaZ\x17\xae\x1d\x92^\xbc4\xf1k\x1f\x94\x91\xf3V\x95f\xf8E\xbb\xaf\xe8J\xec\xceX\xbd3*d\xa7\xd6ZF\x94\xe4\x81\xc3p\xa0\xe9{\x87\xbev\xd7Y\xc2f\xf4\xee\x9c\xa0Y\xe44Y\x90\x12z\x07\xc1sJ\xf6\x8d\x89\x86\x1a\x00\x1c\x04r\x07\xc7\x9a\x98\xb6\xcc{h\x87\xa0\xb8\xde}?\x17=\x17\xaa\no\xb1\xaatz\xcd\xdd\x04z\x11_y\xa0\xd0\x14\xbc\x87AL\xd4\xa3\xb7\x01\x9d\xb1Y\xb8\xbb\xac\xa4U\xd8\x04\xd0\xf3\xd2NB\x0f\x87&\xd4\xfc\x83t\xb6\x12r\x82\xb8V\xe0\xd3\x98\x11^\xea\x8a\x0f\xea\x8a\x8fS\xc0\xce3\n\xfe\'\xf9\xa5V}\xf7\xa8\xce\xb1\xcd\x97\x95\xa5\x8d\xa3\x02\xe9\xe5\x1e\x02\xe0k*i\xae&\xc6\xfe\xcb6\xa2\x1b\xabV!\xd0Eh\xad?\xd2\xe1\xec\xf2K\x10h2g\x7f\x0e\xb3\x7f\xc5\xe46\xcf\x17\x9f\xa2\xdf\x92(\x80\x17S\xcaHqz\x96@\xeaS1n\x96{\x9eM\x1dK\xd0\x92\x8eT\xb0["\x92\x99\x87\x14\xd9\xde\x8f\x13\x11')

File diff suppressed because one or more lines are too long

15
static/css/switches.css Normal file
View File

@ -0,0 +1,15 @@
/* switches.css - tiny, local CSS-only slide switch component */
:root{--switch-bg:#d1d5db;--switch-on:var(--brand, #2563eb);--switch-knob:#ffffff}
.switch{display:inline-flex;align-items:center;gap:10px;user-select:none}
.visually-hidden{position:absolute!important;height:1px;width:1px;overflow:hidden;clip:rect(1px,1px,1px,1px);white-space:nowrap;border:0;padding:0;margin:-1px}
.switch-toggle{position:relative;width:48px;height:28px;display:inline-block}
.switch-toggle input{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;z-index:2;cursor:pointer}
.switch-toggle .slider{position:absolute;left:0;top:0;right:0;bottom:0;background:var(--switch-bg);border-radius:999px;transition:background .18s ease,box-shadow .18s ease;box-shadow:inset 0 0 0 1px rgba(0,0,0,0.03)}
.switch-toggle .slider::before{content:"";position:absolute;left:4px;top:4px;width:20px;height:20px;background:var(--switch-knob);border-radius:50%;transition:transform .18s ease,box-shadow .18s ease;box-shadow:0 1px 2px rgba(0,0,0,0.12)}
.switch-toggle input:checked + .slider{background:var(--switch-on)}
.switch-toggle input:checked + .slider::before{transform:translateX(20px)}
.switch-toggle input:focus-visible + .slider{outline:3px solid rgba(37,99,235,0.18);outline-offset:2px}
.switch-label{font-weight:600;color:var(--text,#111827)}
.switch-toggle.small{width:36px;height:20px}
.switch-toggle.small .slider::before{width:12px;height:12px;left:3px;top:3px}
.switch-toggle.small input:checked + .slider::before{transform:translateX(16px)}

View File

@ -1,119 +1,250 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Printer Media Sizes</title>
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico">
<link rel="stylesheet" href="/static/css/styles.css">
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
<script src="/static/js/jquery-3.7.1.js"></script>
<style>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Power Options</title>
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico" />
<link rel="stylesheet" href="/static/css/styles.css" />
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css" />
<style>
:root{
--brand: #1e40af; /* blue-800 */
--brand-hover: #15327f;
--warn: #f59e0b; /* amber-500 */
--warn-hover:#d97706;
--danger: #ef4444; /* red-500 */
--danger-hover:#dc2626;
--border: #d1d5db; /* gray-300 */
--text: #111827; /* gray-900 */
--muted: #4b5563; /* gray-600 */
--bg: #ffffff;
--bg-alt: #f9fafb; /* gray-50 */
--focus: #2563eb; /* blue-600 */
}
@media (prefers-color-scheme: dark){
:root{
--brand: #60a5fa;
--brand-hover:#3b82f6;
--warn:#fbbf24;
--warn-hover:#f59e0b;
--danger:#f87171;
--danger-hover:#ef4444;
--border:#374151;
--text:#e5e7eb;
--muted:#9ca3af;
--bg:#0b0f14;
--bg-alt:#111827;
--focus:#60a5fa;
}
}
.header-img {
margin-top: 30px; /* Adjust the margin as needed */
margin-bottom: 30px;
width: auto;
height: 70px; /* Adjust the height as needed */
}
.center-content {
display: flex;
flex-direction: column;
align-items: center;
}
h1 {
margin-bottom: 5px;
}
*{ box-sizing: border-box; }
html, body{ height: 100%; }
body{
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
color: var(--text);
background: var(--bg);
line-height: 1.55;
}
label, select, #media-sizes, button {
margin: 5px 0;
text-align: center;
font-size: larger;
font-weight: 200;
}
#navbar{ position: sticky; top: 0; z-index: 20; }
select {
padding: 3px;
font-size: 1.1em;
width: 250px;
}
.content-wrapper{
max-width: 900px;
margin: 0 auto;
padding: 16px;
}
@media (min-width: 980px){
.content-wrapper{ padding: 24px; }
}
#media-sizes ul {
list-style-type: none;
padding: 0;
}
.card{
padding: 20px;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--bg-alt);
}
#media-sizes li {
display: flex;
align-items: center;
margin-bottom: 5px;
}
.center-content{
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 14px;
}
#media-sizes input[type="checkbox"] {
transform: scale(1.5);
margin-right: 12px;
}
h1{ margin: 0; font-size: clamp(1.5rem, 2vw, 2rem); }
.muted{ color: var(--muted); }
#media-sizes span {
font-size: 1.2em;
}
.header-img{
margin: 10px 0 6px;
width: auto;
height: 72px;
}
button {
background-color: red;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
font-size: 1.2em;
cursor: pointer;
width: 150px;
margin-bottom: 30px;
}
.actions{
display: flex;
gap: 12px;
flex-wrap: wrap;
justify-content: center;
margin-top: 8px;
}
button:hover {
background-color: darkblue;
}
</style>
button{
appearance: none;
border: 1px solid transparent;
border-radius: 10px;
padding: 10px 16px;
font-size: 1rem;
cursor: pointer;
color: #fff;
display: inline-flex;
align-items: center;
gap: 8px;
}
button:focus-visible{
outline: 3px solid var(--focus);
outline-offset: 2px;
}
.btn-warn{
background: var(--warn);
border-color: var(--warn-hover);
}
.btn-warn:hover{ background: var(--warn-hover); }
.btn-danger{
background: var(--danger);
border-color: var(--danger-hover);
}
.btn-danger:hover{ background: var(--danger-hover); }
.status{
min-height: 1.2em;
margin-top: 6px;
font-size: 0.98rem;
}
.status[aria-live]{ /* ensure space even when empty */ display: block; }
.help{
margin-top: 4px;
font-size: 0.95rem;
}
.spinner{
display: inline-block;
width: 1em; height: 1em;
border: 2px solid currentColor;
border-right-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
vertical-align: -2px;
margin-right: 6px;
}
@keyframes spin{ to { transform: rotate(360deg); } }
.sr-only{
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0,0,1,1);
white-space: nowrap; border: 0;
}
</style>
</head>
<body>
<div id="navbar"></div>
<script>
fetch('/static/html/nav.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
</script>
<div id="navbar" role="navigation" aria-label="Primary"></div>
<script>
fetch('/static/html/nav.html')
.then(r => r.ok ? r.text() : Promise.reject(r.status))
.then(html => { document.getElementById('navbar').innerHTML = html; })
.catch(() => {
document.getElementById('navbar').innerHTML =
'<div class="card" role="alert">Navigation failed to load.</div>';
});
</script>
<div class="content-wrapper">
<div class="content center-content">
<h1>Power Options</h1>
<img src="/static/images/switch-icon.png" alt="cards" class="header-img">
<button id="reboot-button">Reboot</button>
<button id="shutdown-button">Shutdown</button>
</div>
</div>
</body>
<main class="content-wrapper">
<section class="card center-content" aria-labelledby="power-heading">
<h1 id="power-heading">Power Options</h1>
<img src="/static/images/switch-icon.png" alt="Power switch icon" class="header-img" />
<p class="muted help">
These actions affect the entire device. Save your work before proceeding.
</p>
<script>
$(document).ready(function() {
$('#reboot-button').click(function() {
$.post('/reboot', function(response) {
alert("Reboot initiated");
}).fail(function() {
alert("Reboot failed");
});
<div class="actions" role="group" aria-label="Power controls">
<button type="button" id="reboot-button" class="btn-warn">
<i class="fa fa-rotate-right" aria-hidden="true"></i>
Reboot
</button>
<button type="button" id="shutdown-button" class="btn-danger">
<i class="fa fa-power-off" aria-hidden="true"></i>
Shut Down
</button>
</div>
<div id="status" class="status" role="status" aria-live="polite"></div>
</section>
</main>
<script>
const rebootBtn = document.getElementById('reboot-button');
const shutdownBtn = document.getElementById('shutdown-button');
const statusEl = document.getElementById('status');
function setBusy(btn, busy){
btn.disabled = busy;
if (busy) btn.setAttribute('aria-busy', 'true'); else btn.removeAttribute('aria-busy');
}
function setStatus(message, isError = false){
statusEl.textContent = '';
if (!message) return;
statusEl.innerHTML = (isError ? '' : '<span class="spinner"></span>') + message;
statusEl.style.color = isError ? 'var(--danger)' : 'inherit';
}
async function postAction(url, startMsg, successMsg){
setStatus(startMsg, false);
try{
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
if (!res.ok) throw new Error('Network error');
let data = null;
try { data = await res.json(); } catch {}
const ok = (data && (data.reply === true || data.ok === true)) || res.ok;
if (!ok) throw new Error((data && data.message) || 'Server error');
// Success: message without spinner
statusEl.innerHTML = successMsg;
statusEl.style.color = 'inherit';
} catch (e){
console.error(e);
setStatus('Operation failed. Please try again.', true);
alert('The request did not complete successfully.');
}
}
$('#shutdown-button').click(function() {
$.post('/shutdown', function(response) {
alert("Shutdown initiated");
}).fail(function() {
alert("Shutdown failed");
});
});
rebootBtn?.addEventListener('click', async () => {
const confirmed = confirm('Reboot now? All services will restart.');
if (!confirmed) return;
setBusy(rebootBtn, true);
setBusy(shutdownBtn, true);
await postAction('/reboot', 'Rebooting device… This may take up to a minute.', 'Reboot initiated.');
// Keep buttons disabled briefly to prevent repeats
setTimeout(() => { setBusy(rebootBtn, false); setBusy(shutdownBtn, false); }, 4000);
});
</script>
</html>
shutdownBtn?.addEventListener('click', async () => {
const confirmed = confirm('Shut down now? The device will power off.');
if (!confirmed) return;
setBusy(rebootBtn, true);
setBusy(shutdownBtn, true);
await postAction('/shutdown', 'Shutting down… The device will power off shortly.', 'Shutdown initiated.');
// Keep disabled; device may go offline
setTimeout(() => { setBusy(rebootBtn, false); setBusy(shutdownBtn, false); }, 4000);
});
</script>
</body>
</html>

119
static/html_orig/power.html Normal file
View File

@ -0,0 +1,119 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Printer Media Sizes</title>
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico">
<link rel="stylesheet" href="/static/css/styles.css">
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
<script src="/static/js/jquery-3.7.1.js"></script>
<style>
.header-img {
margin-top: 30px; /* Adjust the margin as needed */
margin-bottom: 30px;
width: auto;
height: 70px; /* Adjust the height as needed */
}
.center-content {
display: flex;
flex-direction: column;
align-items: center;
}
h1 {
margin-bottom: 5px;
}
label, select, #media-sizes, button {
margin: 5px 0;
text-align: center;
font-size: larger;
font-weight: 200;
}
select {
padding: 3px;
font-size: 1.1em;
width: 250px;
}
#media-sizes ul {
list-style-type: none;
padding: 0;
}
#media-sizes li {
display: flex;
align-items: center;
margin-bottom: 5px;
}
#media-sizes input[type="checkbox"] {
transform: scale(1.5);
margin-right: 12px;
}
#media-sizes span {
font-size: 1.2em;
}
button {
background-color: red;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
font-size: 1.2em;
cursor: pointer;
width: 150px;
margin-bottom: 30px;
}
button:hover {
background-color: darkblue;
}
</style>
</head>
<body>
<div id="navbar"></div>
<script>
fetch('/static/html/nav.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
</script>
<div class="content-wrapper">
<div class="content center-content">
<h1>Power Options</h1>
<img src="/static/images/switch-icon.png" alt="cards" class="header-img">
<button id="reboot-button">Reboot</button>
<button id="shutdown-button">Shutdown</button>
</div>
</div>
</body>
<script>
$(document).ready(function() {
$('#reboot-button').click(function() {
$.post('/reboot', function(response) {
alert("Reboot initiated");
}).fail(function() {
alert("Reboot failed");
});
});
$('#shutdown-button').click(function() {
$.post('/shutdown', function(response) {
alert("Shutdown initiated");
}).fail(function() {
alert("Shutdown failed");
});
});
});
</script>
</html>

View File

@ -1,309 +1,357 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About Printio</title>
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico">
<link rel="stylesheet" href="/static/css/styles.css">
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
<style>
h1 {
margin-bottom: 20px;
}
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>About Printio</title>
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico" />
<link rel="stylesheet" href="/static/css/styles.css" />
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css" />
<style>
:root{
--brand: #1e40af; /* blue-800 */
--brand-hover: #15327f;
--ok: #16a34a; /* green-600 */
--ok-hover: #118039;
--border: #d1d5db; /* gray-300 */
--text: #111827; /* gray-900 */
--muted: #4b5563; /* gray-600 */
--bg: #ffffff;
--bg-alt: #f9fafb; /* gray-50 */
--focus: #2563eb; /* blue-600 */
}
@media (prefers-color-scheme: dark){
:root{
--brand: #60a5fa;
--brand-hover: #3b82f6;
--ok: #34d399;
--ok-hover: #10b981;
--border: #374151;
--text: #e5e7eb;
--muted: #9ca3af;
--bg: #0b0f14;
--bg-alt: #111827;
--focus: #60a5fa;
}
}
label, select, #media-sizes, button {
margin: 5px 0;
text-align: center;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body{
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
color: var(--text);
background: var(--bg);
line-height: 1.55;
}
select {
padding: 3px;
font-size: 1.1em;
}
#navbar { position: sticky; top: 0; z-index: 20; }
#media-sizes ul {
list-style-type: none;
padding: 0;
}
h1 { margin: 0 0 16px; font-size: clamp(1.5rem, 2vw, 2rem); }
h3 { margin: 0 0 12px; font-size: 1.125rem; }
#media-sizes li {
display: flex;
align-items: center;
margin-bottom: 5px;
}
p { margin: 0 0 12px; }
#media-sizes input[type="checkbox"] {
transform: scale(1.5);
margin-right: 12px;
}
a { color: var(--brand); text-decoration: underline; text-underline-offset: 2px; }
a:hover { color: var(--brand-hover); }
#media-sizes span {
font-size: 1.2em;
}
.content-wrapper{
display: grid;
grid-template-columns: 1fr;
gap: 20px;
max-width: 1100px;
margin: 0 auto;
padding: 16px;
}
button {
background-color: blue;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
font-size: 1.2em;
cursor: pointer;
}
@media (min-width: 840px){
.content-wrapper{
grid-template-columns: 300px 1fr;
gap: 24px;
padding: 24px;
}
.left-column{
position: sticky;
top: 72px; /* keep below navbar */
align-self: start;
}
}
button:hover {
background-color: darkblue;
}
.left-column, .right-column { width: 100%; }
p {
display: block;
margin-top: 1em;
margin-bottom: 1em;
margin-left: 0;
margin-right: 0;
}
.card{
padding: 16px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--bg-alt);
}
.info-box{ margin-bottom: 16px; }
.license-section {
margin-top: 30px;
text-align: center;
}
.kv p{
display: flex;
justify-content: space-between;
gap: 12px;
border-bottom: 1px dashed var(--border);
padding: 6px 0;
margin: 0;
font-size: 0.95rem;
}
.kv p span:first-child{ color: var(--muted); }
.kv p span:last-child{ font-variant-numeric: tabular-nums; }
.license-status {
font-size: 1.1em;
margin-bottom: 15px;
}
.license-section{
margin-top: 20px;
text-align: left;
}
.license-status{ font-size: 1rem; margin: 0 0 10px; }
.license-input {
margin-top: 10px;
text-align: left;
}
.field{
display: grid;
grid-template-columns: 140px 1fr auto;
gap: 10px;
align-items: center;
margin: 10px 0;
}
@media (max-width: 520px){
.field{ grid-template-columns: 1fr; }
.field label{ margin-bottom: -4px; }
}
.license-input input {
padding: 10px;
width: 300px;
font-size: 1.1em;
margin-right: 10px;
}
input[type="text"], input[type="password"]{
padding: 10px 12px;
font-size: 1rem;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
color: var(--text);
}
input[readonly]{
background: rgba(0,0,0,0.03);
}
.license-input button {
background-color: #4CAF50;
padding: 10px 20px;
font-size: 1.1em;
cursor: pointer;
border-radius: 5px;
border: none;
}
button{
appearance: none;
border: none;
border-radius: 8px;
padding: 10px 16px;
font-size: 1rem;
cursor: pointer;
color: #fff;
background: var(--brand);
}
button:hover{ background: var(--brand-hover); }
button:focus-visible{
outline: 3px solid var(--focus);
outline-offset: 2px;
}
.btn-ok{
background: var(--ok);
}
.btn-ok:hover{
background: var(--ok-hover);
}
.license-input button:hover {
background-color: #45a049;
}
.actions{
display: flex;
gap: 10px;
flex-wrap: wrap;
}
/* New Styles for Layout */
.content-wrapper {
display: flex;
}
.qr{
width: 140px;
max-width: 40vw;
height: auto;
border-radius: 8px;
border: 1px solid var(--border);
background: #fff;
}
.left-column {
width: 280px;
padding-right: 20px;
box-sizing: border-box;
}
.right-column {
width: 70%;
}
.info-box {
padding: 15px;
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 5px;
}
.license-box {
padding: 15px;
border: 1px solid #ccc;
border-radius: 5px;
}
#p {
margin-left: 10px;
}
</style>
<script src="/static/js/crypto-js.min.js"></script>
.muted{ color: var(--muted); font-size: 0.95rem; }
</style>
<script src="/static/js/crypto-js.min.js" defer></script>
</head>
<body>
<div id="navbar"></div>
<script>
fetch('/static/html/nav.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
</script>
<div id="navbar" role="navigation" aria-label="Primary"></div>
<div class="content-wrapper">
<div class="left-column">
<div class="info-box">
<h3>System Info</h3>
<p>Version: {{info.software_version}}
<p>CPU %: {{info.cpu}}</p>
<p>CPU T: {{info.cpu_t}}</p>
<p>Disk Size: {{info.disk_size}}</p>
<p>Disk Used: {{info.disk_used}}</p>
<p>RAM Size: {{info.ram_size}}</p>
<p>RAM Used: {{info.ram_used}}</p>
<p>Up Time: {{info.uptime}}</p>
</div>
<script>
// Load navbar with basic error handling
fetch('/static/html/nav.html')
.then(r => r.ok ? r.text() : Promise.reject(r.status))
.then(html => { document.getElementById('navbar').innerHTML = html; })
.catch(() => { document.getElementById('navbar').innerHTML = '<div class="card" role="alert">Navigation failed to load.</div>'; });
</script>
<div class="license-box">
<h3>License Info </h3>
<p>License: {{info.license}}</p>
<!--p>Image Magic: {{info.image_magic}}</p-->
<!--p>Hashtag: {{info.hash}}</p-->
<!--p>Drop Folder: {{info.drop_folder}}</p-->
</div>
<main class="content-wrapper">
<!-- Sidebar -->
<aside class="left-column" aria-label="System and License Information">
<section class="card info-box" aria-labelledby="sysinfo-heading">
<h3 id="sysinfo-heading">System Info</h3>
<div class="kv">
<p><span>Version</span><span>{{info.software_version}}</span></p>
<p><span>CPU %</span><span>{{info.cpu}}</span></p>
<p><span>CPU Temp</span><span>{{info.cpu_t}}</span></p>
<p><span>Disk Size</span><span>{{info.disk_size}}</span></p>
<p><span>Disk Used</span><span>{{info.disk_used}}</span></p>
<p><span>RAM Size</span><span>{{info.ram_size}}</span></p>
<p><span>RAM Used</span><span>{{info.ram_used}}</span></p>
<p><span>Up Time</span><span>{{info.uptime}}</span></p>
</div>
</section>
<div class="right-column">
<h1>About Printio</h1>
<p>
Printio is a professional printing solution designed for seamless
printing from iPads. It is particularly popular in the photobooth
industry, supporting a wide range of sublimation printers. Printio
is compatible with well-known brands such as DNP, Sinfonia, Hiti,
and Mitsubishi, as well as many other printers, including Zebra label
printers, and most HP and Epson models. This versatility makes Printio
an ideal choice for diverse printing needs.
</p>
<p>
Warning: Unauthorized reproduction or distribution of this product
is strictly prohibited and may result in severe civil and criminal
penalties.
</p>
<a href="https://www.ataphotobooths.com/knowledge-base/category/printio-setup/" target="_blank">Printio Help: Click me!</a>
<div></div>
<img src="/static/images/printio_help_qr.jpg" alt="Printio" style="width: 120px; margin-top: 20px;">
<section class="card" aria-labelledby="licenseinfo-heading">
<h3 id="licenseinfo-heading">License Info</h3>
<p class="muted">Status: <strong>{{info.license}}</strong></p>
<!-- Optional fields kept commented
<p>ImageMagick: {{info.image_magic}}</p>
<p>Drop Folder: {{info.drop_folder}}</p>
-->
</section>
</aside>
<!-- Main -->
<section class="right-column">
<h1>About Printio</h1>
<p>
Printio is a professional printing solution designed for seamless printing from iPads. Its widely used in the photobooth industry and supports a range of dye-sublimation printers. Printio works with well-known brands such as DNP, Sinfonia, HiTi, and Mitsubishi, as well as many other printers, including Zebra label printers and most HP and Epson models—making it a versatile choice for diverse printing needs.
</p>
<p class="muted">
Warning: Unauthorized reproduction or distribution of this product is strictly prohibited and may result in civil and criminal penalties.
</p>
<div class="license-section" {{info.hidden}}>
<div class="license-input">
<label>__________________________________________________________</label>
</div>
<div class="license-input">
<label for="activation-serial">ID Code:</label>
<input type="text" id="idcode" value={{info.idcode}} readonly>
<button onclick="copyToClipboard()">Copy</button>
</div>
<div class="license-input">
<label for="activation-serial">SerialNo</label>
<input type="password" id="license-password" placeholder="Enter Activation Key">
<button onclick="activateLicense()">Activate</button>
</div>
</div>
<p>
<a href="https://www.ataphotobooths.com/knowledge-base/category/printio-setup/" target="_blank" rel="noopener noreferrer">
Printio Help: Click me!
</a>
</p>
<figure aria-label="Printio Help QR Code" style="margin: 16px 0;">
<img class="qr" src="/static/images/printio_help_qr.jpg" alt="Scan for Printio setup help" />
</figure>
<!-- License Section (conditionally hidden) -->
<section class="license-section card" {{info.hidden}} aria-labelledby="license-activation-heading">
<h3 id="license-activation-heading">License Activation</h3>
<p class="license-status muted" id="license-status" aria-live="polite"></p>
<div class="field">
<label for="idcode">ID Code</label>
<input type="text" id="idcode" value="{{info.idcode}}" readonly aria-readonly="true" />
<div class="actions">
<button type="button" id="copy-btn" class="btn-ok" aria-describedby="copy-hint">
<i class="fa fa-copy" aria-hidden="true"></i> Copy
</button>
</div>
</div>
</div>
<p class="muted" id="copy-hint">Copies your ID Code to the clipboard.</p>
<script>
function encryptPassword(password) {
// Simple encryption using CryptoJS (you can replace with your method)
return password;
<div class="field">
<label for="license-password">Activation Key</label>
<input type="password" id="license-password" placeholder="Enter Activation Key" autocomplete="one-time-code" />
<div class="actions">
<button type="button" id="activate-btn">
<i class="fa fa-bolt" aria-hidden="true"></i> Activate
</button>
</div>
</div>
</section>
</section>
</main>
<script>
// Optional: simple (placeholder) hashing before sending; replace with real logic if needed.
function hashPass(plain) {
try {
// Example using CryptoJS if you choose to enable hashing later:
// return CryptoJS.SHA256(plain).toString();
return plain; // currently pass-through; replace when backend expects a hash
} catch {
return plain;
}
}
function setStatus(msg, ok = true){
const el = document.getElementById('license-status');
if (!el) return;
el.textContent = msg || '';
el.style.color = ok ? 'inherit' : '#dc2626'; // red-600 in light; acceptable in dark
}
async function activateLicense() {
const input = document.getElementById('license-password');
const pass = (input?.value || '').trim();
if (!pass){
setStatus('Please enter your activation key.', false);
input?.focus();
return;
}
setStatus('Activating…');
try {
const res = await fetch('/activate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pass: hashPass(pass) })
});
if (!res.ok) throw new Error('Network error');
const data = await res.json();
if (data.reply === true) {
alert('Activation successful!');
window.location.href = '/about';
} else {
setStatus('Activation failed. Please verify your key and try again.', false);
alert('Activation failed. Try again.');
}
} catch (err) {
console.error('Activation error:', err);
setStatus('Error during activation attempt.', false);
alert('Error during activation attempt.');
}
}
function activateLicense() {
const password = document.getElementById('license-password').value;
const encryptedPassword = encryptPassword(password);
fetch('/activate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ pass: encryptedPassword })
})
.then(response => response.json())
.then(data => {
console.log("Activation result:", data);
if (data.reply == true) {
alert("Activation Successful!!!");
window.location.href = "/about"; // Redirect to /about page
} else {
alert("Activation Failed.... Try Again");
}
})
.catch(error => {
console.error("Error during activation attempt:", error);
alert("Error during activation attempt.");
});
async function copyToClipboard() {
const el = document.getElementById('idcode');
const btn = document.getElementById('copy-btn');
if (!el) return alert('No ID Code field found.');
const text = el.value || el.textContent || '';
if (!text) return alert('Nothing to copy.');
try {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
} else {
// Fallback
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
}
btn?.classList.add('copied');
btn?.setAttribute('aria-label', 'Copied!');
setStatus('ID Code copied to clipboard.');
setTimeout(() => {
btn?.removeAttribute('aria-label');
setStatus('');
}, 1500);
} catch (e) {
console.error('Clipboard error:', e);
alert('Failed to copy to clipboard.');
}
}
// Wire up buttons
document.getElementById('copy-btn')?.addEventListener('click', copyToClipboard);
document.getElementById('activate-btn')?.addEventListener('click', activateLicense);
function copyToClipboard() {
const idCode = document.getElementById('idcode');
// Check if the element exists
if (!idCode) {
console.error('Element with ID "idcode" not found');
alert('Error: Could not find the text to copy');
return;
}
// Get the value or text content (depending on element type)
const textToCopy = idCode.value || idCode.textContent || '';
console.log('Text to copy:', textToCopy);
// Ensure there's something to copy
if (!textToCopy) {
console.warn('No text to copy');
alert('Nothing to copy');
return;
}
// First, try the Clipboard API if available
if (navigator.clipboard && navigator.clipboard.writeText) {
console.log('Attempting to use Clipboard API');
navigator.clipboard.writeText(textToCopy)
.then(() => {
console.log('Clipboard API success');
alert('ID code copied to clipboard');
})
.catch(err => {
console.error('Clipboard API failed:', err);
attemptFallbackCopy(textToCopy);
});
} else {
console.log('Clipboard API unavailable, using fallback');
attemptFallbackCopy(textToCopy);
}
}
function attemptFallbackCopy(textToCopy) {
try {
// Create a temporary textarea for copying
const tempTextarea = document.createElement('textarea');
tempTextarea.value = textToCopy;
tempTextarea.style.position = 'fixed'; // Prevent scrolling issues
tempTextarea.style.opacity = '0'; // Make it invisible
document.body.appendChild(tempTextarea);
tempTextarea.focus();
tempTextarea.select();
// Attempt to copy
const successful = document.execCommand('copy');
document.body.removeChild(tempTextarea);
if (successful) {
console.log('Fallback copy successful');
//alert('ID code copied to clipboard (using fallback)');
} else {
console.warn('Fallback copy failed: execCommand returned false');
alert('Failed to copy to clipboard: Copy command unsuccessful');
}
} catch (err) {
console.error('Fallback copy error:', err);
alert('Failed to copy to clipboard: ' + err.message);
}
}
</script>
// Allow Enter key to activate from password field
document.getElementById('license-password')?.addEventListener('keydown', (e) => {
if (e.key === 'Enter') activateLicense();
});
</script>
</body>
</html>

View File

@ -1,157 +1,369 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Printio</title>
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico">
<link rel="stylesheet" href="/static/css/styles.css">
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
<style>
.header-img {
margin-top: 30px;
margin-bottom: 30px;
width: auto;
height: 100px;
}
h2 {
margin-top: 0;
font-size: 24px;
color: #565656;
}
table {
border-collapse: collapse;
width: 100%;
margin-top: 20px;
box-sizing: border-box;
}
th, td {
border: 1px solid #ccc;
padding: 10px;
text-align: left;
box-sizing: border-box;
}
th {
background-color: #f8f8f8;
}
.button-red {
padding: 5px 10px;
background-color: #f44336;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.button-test {
padding: 5px 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.placeholder-img {
position: absolute;
top: 20px;
right: 20px;
}
.placeholder-img img {
top: 20px;
width: 60px;
height: 60px;
}
/* Styles for the message log */
#messageLog {
display: none;
margin-top: 20px;
width: 100%;
height: 350px;
box-sizing: border-box;
resize: none;
overflow: auto;
white-space: nowrap;
}
.toggle-button {
margin-top: 20px;
background-color: #007BFF;
color: white;
border: none;
padding: 3px 20px;
cursor: pointer;
border-radius: 5px;
}
</style>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Printio</title>
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico" />
<link rel="stylesheet" href="/static/css/styles.css" />
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css" />
<style>
:root{
--brand: #1e40af; /* blue-800 */
--brand-hover: #15327f;
--danger: #ef4444; /* red-500 */
--ok: #16a34a; /* green-600 */
--ok-hover: #118039;
--border: #d1d5db; /* gray-300 */
--text: #111827; /* gray-900 */
--muted: #4b5563; /* gray-600 */
--bg: #ffffff;
--bg-alt: #f9fafb; /* gray-50 */
--focus: #2563eb; /* blue-600 */
}
@media (prefers-color-scheme: dark){
:root{
--brand: #60a5fa;
--brand-hover: #3b82f6;
--danger: #f87171;
--ok: #34d399;
--ok-hover: #10b981;
--border: #374151;
--text: #e5e7eb;
--muted: #9ca3af;
--bg: #0b0f14;
--bg-alt: #111827;
--focus: #60a5fa;
}
}
*{ box-sizing: border-box; }
html, body{ height: 100%; }
body{
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
color: var(--text);
background: var(--bg);
line-height: 1.55;
}
#navbar{ position: sticky; top: 0; z-index: 20; }
h1, h2{ margin: 0 0 12px; }
h1{ font-size: clamp(1.5rem, 2vw, 2rem); }
h2{ font-size: clamp(1.1rem, 1.6vw, 1.4rem); color: var(--muted); }
.content-wrapper{
max-width: 1100px;
margin: 0 auto;
padding: 16px;
}
@media (min-width: 980px){
.content-wrapper{ padding: 24px; }
}
.card{
padding: 16px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--bg-alt);
}
.header-img{
margin: 24px 0;
width: auto;
height: 100px;
}
/* Switch row */
.switch-row{
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin: 12px 0 6px;
}
.switch-label{ font-weight: 600; color: var(--text); }
.switch-input{ transform: scale(1.1); }
.error{
color: var(--danger);
text-align: center;
margin: 8px 0 0;
display: none;
}
/* Actions */
button{
appearance: none;
border: none;
border-radius: 8px;
padding: 8px 14px;
font-size: 0.98rem;
cursor: pointer;
color: #fff;
background: var(--brand);
}
button:hover{ background: var(--brand-hover); }
button:focus-visible{
outline: 3px solid var(--focus);
outline-offset: 2px;
}
.btn-danger{ background: var(--danger); }
.btn-ok{ background: var(--ok); }
.btn-ok:hover{ background: var(--ok-hover); }
.btn-ghost{
background: transparent;
color: var(--brand);
border: 1px solid var(--brand);
}
.btn-ghost:hover{
color: #fff;
background: var(--brand);
}
/* Table */
.table-wrap{
margin-top: 16px;
border: 1px solid var(--border);
border-radius: 10px;
overflow: hidden;
background: var(--bg);
}
.table-scroll{
width: 100%;
overflow-x: auto;
}
table{
border-collapse: collapse;
width: 100%;
min-width: 720px;
}
thead th{
position: sticky;
top: 0;
background: var(--bg-alt);
color: var(--text);
border-bottom: 1px solid var(--border);
text-align: left;
padding: 10px;
font-weight: 600;
}
tbody td{
border-top: 1px solid var(--border);
padding: 10px;
vertical-align: middle;
}
tbody tr:hover{
background: rgba(0,0,0,0.03);
}
.actions{
display: inline-flex;
gap: 8px;
flex-wrap: wrap;
}
/* Job history */
details{
margin-top: 16px;
}
summary{
list-style: none;
cursor: pointer;
user-select: none;
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 600;
color: var(--brand);
}
summary::-webkit-details-marker{ display: none; }
.log{
margin-top: 12px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--bg);
padding: 12px;
max-height: 360px;
overflow: auto;
}
.log pre{
margin: 0;
white-space: pre;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
font-size: 0.9rem;
line-height: 1.4;
}
.sr-only{
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0, 0, 1, 1);
white-space: nowrap; border: 0;
}
</style>
</head>
<body>
<div id="navbar"></div>
<script>
fetch('/static/html/nav.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
</script>
<div id="navbar" role="navigation" aria-label="Primary"></div>
<script>
fetch('/static/html/nav.html')
.then(r => r.ok ? r.text() : Promise.reject(r.status))
.then(html => { document.getElementById('navbar').innerHTML = html; })
.catch(() => { document.getElementById('navbar').innerHTML = '<div class="card" role="alert">Navigation failed to load.</div>'; });
</script>
<div class="content-wrapper">
<div class="content">
<img src="/static/images/printio_logo.png" alt="Printio" class="header-img">
<h2>Active Printers</h2>
<table border="1">
<tr>
<th>Printer Name</th>
<th>Status</th>
<th>Prints Left</th>
<th>Actions</th>
</tr>
{% for printer in printers %}
<tr>
<td>{{ printer.name }}</td>
<td>{{ printer.state }}</td>
<td>{{ printer.prints_left_message }}</td>
<td>
<form action="{{ url_for('cancel_all_jobs', printer_name=printer.name) }}" method="post" style="display:inline;">
<button type="submit" class="button-red">Kill Jobs</button>
</form>
<form action="{{ url_for('test_print', printer_name=printer.name) }}" method="post" style="display:inline;">
<button type="submit" class="button-test">Test Print</button>
</form>
</td>
</tr>
{% endfor %}
</table>
<main class="content-wrapper">
<section class="content card" aria-labelledby="active-printers-heading">
<img src="/static/images/printio_white_logo.png" alt="Printio logo" class="header-img" />
<h1 id="active-printers-heading">Active Printers</h1>
<!-- Button to toggle the message log -->
<button class="toggle-button" onclick="toggleMessageLog()">Job History</button>
<!-- Hidden textarea for the message log -->
<textarea id="messageLog" placeholder="Job Status Log..." readonly></textarea>
<div class="switch-row">
<label class="switch-label" for="printOnConnect">Print on Connect</label>
<input
type="checkbox"
id="printOnConnect"
name="printOnConnect"
class="switch-input"
aria-describedby="printOnConnectHelp printOnConnectError"
{{ settings.printOnConnect }}>
</div>
<p id="printOnConnectHelp" class="sr-only">If enabled, a test or queued job can print automatically upon printer connection.</p>
<div id="printOnConnectError" class="error" role="alert" aria-live="polite"></div>
<div class="table-wrap" role="region" aria-label="Printer list">
<div class="table-scroll">
<table>
<thead>
<tr>
<th scope="col">Printer Name</th>
<th scope="col">Status</th>
<th scope="col">Prints Left</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
{% for printer in printers %}
<tr>
<td>{{ printer.name }}</td>
<td>{{ printer.state }}</td>
<td>{{ printer.prints_left_message }}</td>
<td>
<div class="actions">
<form action="{{ url_for('cancel_all_jobs', printer_name=printer.name) }}" method="post">
<button type="submit" class="btn-danger">
<i class="fa fa-ban" aria-hidden="true"></i> Kill Jobs
</button>
</form>
<form action="{{ url_for('test_print', printer_name=printer.name) }}" method="post">
<button type="submit" class="btn-ok">
<i class="fa fa-print" aria-hidden="true"></i> Test Print
</button>
</form>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<script>
// Toggles the visibility of the message log textarea
function toggleMessageLog() {
var messageLog = document.getElementById('messageLog');
if (messageLog.style.display === '' || messageLog.style.display === 'none') {
messageLog.style.display = 'block';
loadPrinterJobs(); // Load job messages when showing the textarea
} else {
messageLog.style.display = 'none';
}
}
<details id="jobHistory">
<summary>
<i class="fa fa-history" aria-hidden="true"></i>
Job History
</summary>
<div class="log" id="messageLog" aria-live="off">
<pre id="logText">Loading…</pre>
</div>
</details>
</section>
</main>
// Load the printer job information and show in the textarea
function loadPrinterJobs() {
var messageLog = document.getElementById('messageLog');
var jobs = {{ job_history | tojson | safe }};
var logText = "";
jobs.forEach(function(job, index) {
logText += "Job: " + job.job_id + ", Time: " + job.time + ", Size: " + job.job_size + ", Printer: " + job.printer_name + ", State: " + job.job_state + ', Reason: ' + job.job_reason + "\r\n";
});
<script>
/**
* Initialize the "Print on Connect" checkbox behavior.
*/
function initPrintOnConnect() {
const checkbox = document.getElementById('printOnConnect');
const errorDiv = document.getElementById('printOnConnectError');
// Set the value of the textarea to display the job information
messageLog.value = logText;
}
</script>
if (!checkbox) return;
checkbox.addEventListener('change', () => {
const value = checkbox.checked;
fetch('/printer-settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ 'print-on-connect': value })
})
.then((resp) => {
if (!resp.ok) throw new Error('Server rejected the setting');
return resp.json();
})
.then((json) => {
if (!json.reply) throw new Error(json.message || 'Server error');
// Clear any prior error
errorDiv.style.display = 'none';
errorDiv.textContent = '';
})
.catch((err) => {
console.error('Failed to save print-on-connect:', err);
// Revert checkbox
checkbox.checked = !value;
// Show error
errorDiv.textContent = 'Failed to save setting to server';
errorDiv.style.display = 'block';
setTimeout(() => {
errorDiv.style.display = 'none';
}, 4000);
});
});
}
/**
* Load job history on demand.
*/
function loadPrinterJobs() {
const jobs = {{ job_history | tojson | safe }};
const pre = document.getElementById('logText');
if (!pre) return;
if (!Array.isArray(jobs) || jobs.length === 0) {
pre.textContent = 'No jobs found.';
return;
}
// Build a readable, tabular-ish log
const lines = jobs.map(j => {
const id = j.job_id ?? '';
const t = j.time ?? '';
const size = j.job_size ?? '';
const pn = j.printer_name ?? '';
const state = j.job_state ?? '';
const reason = j.job_reason ?? '';
return `Job ${id} | Time: ${t} | Size: ${size} | Printer: ${pn} | State: ${state} | Reason: ${reason}`;
});
pre.textContent = lines.join('\n');
}
document.addEventListener('DOMContentLoaded', () => {
initPrintOnConnect();
// Lazy-load job history when the <details> opens
const details = document.getElementById('jobHistory');
if (details) {
details.addEventListener('toggle', () => {
if (details.open) loadPrinterJobs();
});
}
});
</script>
</body>
</html>

View File

@ -1,350 +1,288 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WiFi Configuration</title>
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico">
<link rel="stylesheet" href="/static/css/styles.css">
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
<style>
h1 {
margin-top: 30px;
font-size: 36px;
color: #333;
}
.button-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
}
.btn {
padding: 10px 20px;
font-size: 1.2em;
cursor: pointer;
background-color: blue;
color: white;
width: 150px;
border-radius: 5px;
border: none;
text-decoration: none;
}
.btn:hover {
background-color: #0056b3;
}
.small-btn {
width: 100px;
padding: 5px 10px;
font-size: 0.8em;
background-color: darkslateblue;
}
.small-btn-container {
margin-left: auto;
}
.scan-container {
margin-top: 20px;
}
.scan-container .btn {
background-color: #28a745;
}
.scan-container .btn:hover {
background-color: #218838;
}
.connect-container {
margin-top: 20px;
}
.connect-container .btn {
padding: 5px 15px;
border-radius: 5px;
background-color: #ffc107;
margin-bottom: 30px;
}
.connect-container .btn:hover {
background-color: #e0a800;
}
.network-list {
margin-top: 20px;
width: 100%;
max-height: 200px;
overflow-y: auto;
border: 1px solid #ccc;
background-color: white;
box-sizing: border-box;
}
.network-list table {
width: 100%;
border-collapse: collapse;
table-layout: auto;
box-sizing: border-box;
}
.network-list th, .network-list td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
white-space: nowrap;
box-sizing: border-box;
}
.network-list th {
background-color: #f2f2f2;
}
.network-list tr.selected {
background-color: #d1e7dd;
}
.status {
margin-top: 20px;
font-size: 18px;
color: #555;
}
.home-btn {
position: absolute;
top: 20px;
left: 20px;
padding: 10px 20px;
background-color: #6c757d;
color: white;
text-decoration: none;
border-radius: 5px;
}
.home-btn:hover {
background-color: #5a6268;
}
.placeholder-img {
position: absolute;
top: 20px;
right: 20px;
}
.placeholder-img img {
top: 20px;
width: 70px;
height: 70px;
}
.header-img {
margin-top: 20px; /* Adjust the margin as needed */
margin-bottom: 30px;
width: auto;
height: 80px; /* Adjust the height as needed */
position: absolute;
left: 30px;
top: 60px;
}
button:disabled {
opacity: 0.5; /* Makes the button look visually disabled */
cursor: not-allowed; /* Changes the cursor to indicate it's disabled */
}
details {
width: 100%;
margin-bottom: 20px; /* Adds space between details sections */
}
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WiFi Configuration</title>
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico" />
<link rel="stylesheet" href="/static/css/styles.css" />
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css" />
<style>
:root{
--bg: #0b0d10;
--panel: #12161b;
--card: #141a20;
--muted: #c7d1db;
--primary: #3b82f6;
--primary-700: #1d4ed8;
--ring: rgba(59,130,246,.35);
--radius: 14px;
--gap: 16px;
--ok: #22c55e;
--warn: #f59e0b;
--err: #ef4444;
}
html, body { height:100%; }
body{
margin:0; color:var(--muted);
background: linear-gradient(180deg, #0b0d10 0%, #0e1217 100%);
font: 500 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, sans-serif;
-webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
a{ color: inherit; }
.details-container {
width: 80%; /* Set container width to keep content centered */
margin: 0 auto;
}
summary {
font-size: 1.3em;
font-weight: bold;
cursor: pointer;
display: inline-block;
width: 250px; /* Set a fixed width for alignment */
text-align: left; /* Ensures left alignment of the text */
white-space: nowrap; /* Prevents wrapping */
}
summary::before {
content: "▼ "; /* Bullet symbol */
color: black; /* Bullet color */
margin-right: 8px; /* Space between bullet and text */
font-size: 1.3em; /* Matches summary font size */
}
.details-content {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
</style>
/* Navbar inject target */
#navbar { position: sticky; top:0; z-index: 100; backdrop-filter: blur(6px); }
.wrapper{ max-width: 1100px; margin: 0 auto; padding: 24px 16px 56px; }
/* Header */
.page-header{ display:flex; align-items:center; gap:16px; justify-content:flex-start; margin: 4px 0 12px; }
.page-header .header-img{ width:auto; height:72px; margin:0; position:static !important; filter: drop-shadow(0 4px 14px rgba(0,0,0,.4)); }
h1{ font-size: clamp(22px, 3vw, 32px); font-weight: 700; letter-spacing:.2px; margin:0; }
.subtitle{ opacity:.9; font-size:.95rem; margin-top:6px; }
/* Cards */
details.card{ background: var(--card); border: 1px solid rgba(255,255,255,.06); border-radius: var(--radius);
overflow:hidden; box-shadow: 0 10px 30px rgba(0,0,0,.35); margin-bottom: 18px; }
details.card[open]{ box-shadow: 0 16px 38px rgba(0,0,0,.45); }
details.card > summary{ list-style:none; display:flex; align-items:center; gap:10px; cursor:pointer;
padding: 14px 16px; background: linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,.00)); font-weight:700; }
details.card > summary::-webkit-details-marker{ display:none; }
.chev{ width: 10px; height:10px; border-right:2px solid var(--muted); border-bottom:2px solid var(--muted);
transform: rotate(-45deg); transition: transform .18s ease; margin-right: 4px; opacity:.9; }
details.card[open] > summary .chev{ transform: rotate(45deg); }
.card-body{ padding: 14px 16px 16px; display:grid; gap:18px; background: var(--panel); border-top: 1px solid rgba(255,255,255,.06); }
/* Actions / Buttons */
.actions{ display:flex; gap:10px; flex-wrap:wrap; align-items:center; }
.actions .small-btn-group{ margin-left:auto; display:flex; gap:10px; flex-wrap:wrap; }
button.btn{ background: var(--primary); color:white; border:1px solid rgba(255,255,255,.08); border-radius: 10px;
padding: 10px 14px; font-weight:700; letter-spacing:.2px; cursor:pointer;
transition: transform .06s ease, background .15s ease, box-shadow .15s ease; min-width:140px; }
button.btn:hover{ background: var(--primary-700); }
button.btn:active{ transform: translateY(1px); }
button.btn[disabled]{ opacity:.5; cursor:not-allowed; }
button.btn.success{ background: var(--ok); }
button.btn.warn{ background: var(--warn); color:#0b0d10; }
button.btn.alt{ background: #64748b; }
/* Status badges/text */
.status{ font-size: 1rem; opacity:.95; }
.status.ok{ color: var(--ok); }
.status.err{ color: var(--err); }
.status.warn{ color: var(--warn); }
/* Table */
.network-list{ width:100%; max-height: 320px; overflow:auto; border:1px solid rgba(255,255,255,.08);
background:#0f141a; border-radius: 12px; }
table#networksTable{ width:100%; border-collapse: separate; border-spacing: 0; }
#networksTable thead th{
position: sticky; top:0; background: #0f1620; color:#dbe7f3;
text-align:left; padding:10px 12px; font-weight:700; border-bottom:1px solid rgba(255,255,255,.08);
}
#networksTable tbody td{ padding:10px 12px; border-bottom:1px solid rgba(255,255,255,.06); white-space: nowrap; }
#networksTable tbody tr{ cursor:pointer; }
#networksTable tbody tr:hover{ background: rgba(59,130,246,.08); }
#networksTable tbody tr.selected{ background: rgba(34,197,94,.18); }
/* Grid helpers */
.grid-2{ display:grid; grid-template-columns: 1fr; gap: var(--gap); }
@media (min-width: 900px){ .grid-2{ grid-template-columns: 1.2fr .8fr; } }
/* Keep original IDs styling overrides (safely) */
#label-status{ display:block; margin-top: 6px; }
#scanStatus{ min-height: 20px; }
</style>
</head>
<body>
<div id="navbar"></div>
<script>
fetch('/static/html/nav.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
</script>
<div id="navbar"></div>
<script>
fetch('/static/html/nav.html').then(r=>r.text()).then(html=>{ document.getElementById('navbar').innerHTML = html; });
</script>
<div class="content-wrapper">
<div class="content">
<h1>WiFi Internet Access</h1>
<img src="/static/images/internet_icon.png" alt="internet" class="header-img">
<label id="label-status">Status:</label>
<div class="button-container">
<button class="btn" id="scanButton" onclick="scanWifi()">Scan</button>
<div class="small-btn-container">
<button class="btn small-btn" id="forgetButton" onclick="checkForgetNetworks()">Forget all</button>
<button class="btn small-btn" id="testButton" onclick="checkInternetAccess()">Test Internet</button>
</div>
</div>
<div class="scan-container">
<div id="scanStatus" class="status"></div>
</div>
<div class="network-list">
<table id="networksTable">
<thead>
<tr>
<th>SSID</th>
<th>Freq</th>
<th>Signal</th>
<th>Bssid</th>
</tr>
</thead>
<tbody>
<!-- List of networks will be appended here -->
</tbody>
</table>
</div>
<div class="connect-container">
<button class="btn" id="connectToNetworkButton"onclick="connectToNetwork()">Connect</button>
</div>
<details>
<summary>Connection Status</summary>
<div class="status" id="connectionStatus"></div>
</details>
<details>
<summary>Notice</summary>
<label id="label-wifi">*WPA3 is not currently supported. If your hotsport/Access point is setup for WPA3, please change it to WPA2 before trying to connect to it.</label>
</details>
</div>
<main class="wrapper">
<header class="page-header">
<img src="/static/images/internet_icon.png" alt="internet" class="header-img" />
<div>
<h1>WiFi Internet Access</h1>
<div class="subtitle"><span id="label-status">Status:</span></div>
</div>
</header>
<!-- Networks card -->
<details class="card" open>
<summary><span class="chev"></span>Networks</summary>
<div class="card-body">
<div class="actions">
<button class="btn" id="scanButton" onclick="scanWifi()"><i class="fa-solid fa-wifi"></i> Scan</button>
<div class="small-btn-group">
<button class="btn alt" id="forgetButton" onclick="checkForgetNetworks()"><i class="fa-regular fa-trash-can"></i> Forget all</button>
<button class="btn success" id="testButton" onclick="checkInternetAccess()"><i class="fa-solid fa-globe"></i> Test Internet</button>
</div>
</div>
</div>
<script>
window.onload = function() { OnPageLoad(); };
function scanWifi() {
console.log("Starting WiFi scan...");
document.getElementById('connectionStatus').innerText = ""
document.getElementById('scanStatus').innerText = "Scanning for networks...";
fetch('/wifi_scan')
.then(response => response.json())
.then(data => {
console.log("Scan complete. Data received:", data);
document.getElementById('scanStatus').innerText = "Scan complete.";
let tableBody = document.getElementById('networksTable').getElementsByTagName('tbody')[0];
tableBody.innerHTML = '';
data.networks.forEach(network => {
let row = tableBody.insertRow();
let cell1 = row.insertCell(0);
let cell2 = row.insertCell(1);
let cell3 = row.insertCell(2);
let cell4 = row.insertCell(3);
cell1.textContent = network.ssid || '';
cell2.textContent = network.Freq || '';
cell3.textContent = network.signal || '';
cell4.textContent = network.bssid || '';
row.addEventListener('click', function() {
const rows = document.querySelectorAll('#networksTable tbody tr');
rows.forEach(r => r.classList.remove('selected'));
row.classList.add('selected');
});
});
})
.catch(error => {
console.error("Error during scan:", error);
document.getElementById('scanStatus').innerText = "Error during scan.";
});
}
<div id="scanStatus" class="status"></div>
function connectToNetwork() {
console.log("Attempting to connect to network...");
const table = document.getElementById('networksTable');
const selectedRow = table.querySelector('tbody tr.selected');
if (!selectedRow) {
document.getElementById('connectionStatus').innerText = "Please select a network to connect to.";
return;
}
const ssid = selectedRow.cells[0].textContent;
const bssid = selectedRow.cells[3].textContent;
const password = prompt("Enter the WiFi password for " + ssid + ":");
if (!password) {
document.getElementById('connectionStatus').innerText = "Connection cancelled.";
return;
}
document.getElementById('connectionStatus').innerText = "Please wait...";
document.getElementById('connectToNetworkButton').disabled = true;
document.getElementById('scanButton').disabled = true;
document.getElementById('testButton').disabled = true;
document.getElementById('forgetButton').disabled = true;
fetch('/wifi_connect', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ ssid: ssid, bssid: bssid, password: password })
})
.then(response => response.json())
.then(data => {
console.log("Connection attempt result:", data);
document.getElementById('connectionStatus').innerText = data.message;
checkWifiStatus();
})
.catch(error => {
console.error("Error during connection attempt:", error);
document.getElementById('connectionStatus').innerText = "Error during connection attempt.";
})
.finally(() => {
// Re-enable buttons after the connection attempt
document.getElementById('connectToNetworkButton').disabled = false;
document.getElementById('scanButton').disabled = false;
document.getElementById('testButton').disabled = false;
document.getElementById('forgetButton').disabled = false;
});
}
<div class="grid-2">
<div class="network-list">
<table id="networksTable">
<thead>
<tr>
<th>SSID</th>
<th>Freq</th>
<th>Signal</th>
<th>Bssid</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
function checkInternetAccess() {
fetch('/wifi_test')
.then(response => response.json())
.then(data => {
const statusMessage = data.success ? "Internet Access Ok" : "No Internet Access";
alert(statusMessage); // Display message in a popup
})
.catch(error => {
console.error("Error checking internet access:", error);
alert("Error checking internet access."); // Display error in a popup
});
}
function checkForgetNetworks() {
fetch('/wifi_forget')
.then(response => response.json())
.then(data => {
const statusMessage = data.success ? "All networks forgotten" : "Nothing happened";
alert(statusMessage); // Display message in a popup
})
.catch(error => {
console.error("Error forgetting networks:", error);
alert("Error forgetting networks."); // Display error in a popup
});
}
<div class="actions" style="align-items:flex-start;">
<button class="btn warn" id="connectToNetworkButton" onclick="connectToNetwork()">
<i class="fa-solid fa-plug"></i> Connect
</button>
</div>
</div>
</div>
</details>
function checkWifiStatus() {
fetch('/wifi_status')
.then(response => response.json())
.then(data => {
console.log("Data received:", data);
document.getElementById('label-status').innerText = "Status: " + data.msg;
})
.catch(error => {
console.error("Error getting wifi status:", error);
document.getElementById('label-status').innerText = "Status: ...";
});
}
<!-- Connection status -->
<details class="card">
<summary><span class="chev"></span>Connection Status</summary>
<div class="card-body">
<div class="status" id="connectionStatus"></div>
</div>
</details>
function OnPageLoad(){
checkWifiStatus();
}
</script>
<!-- Notice -->
<details class="card">
<summary><span class="chev"></span>Notice</summary>
<div class="card-body">
<label id="label-wifi">*WPA3 is not currently supported. If your hotspot/Access Point is set for WPA3, please change it to WPA2 before trying to connect.</label>
</div>
</details>
</main>
<script>
window.onload = function() { OnPageLoad(); };
function scanWifi() {
console.log("Starting WiFi scan...");
document.getElementById('connectionStatus').innerText = "";
const ss = document.getElementById('scanStatus');
ss.innerText = "Scanning for networks...";
fetch('/wifi_scan')
.then(response => response.json())
.then(data => {
console.log("Scan complete. Data received:", data);
ss.innerText = "Scan complete.";
let tableBody = document.getElementById('networksTable').getElementsByTagName('tbody')[0];
tableBody.innerHTML = '';
(data.networks || []).forEach(network => {
let row = tableBody.insertRow();
row.insertCell(0).textContent = network.ssid || '';
row.insertCell(1).textContent = network.Freq || '';
row.insertCell(2).textContent = network.signal || '';
row.insertCell(3).textContent = network.bssid || '';
row.addEventListener('click', function() {
const rows = document.querySelectorAll('#networksTable tbody tr');
rows.forEach(r => r.classList.remove('selected'));
row.classList.add('selected');
});
});
})
.catch(error => {
console.error("Error during scan:", error);
ss.innerText = "Error during scan.";
});
}
function connectToNetwork() {
console.log("Attempting to connect to network...");
const table = document.getElementById('networksTable');
const selectedRow = table.querySelector('tbody tr.selected');
if (!selectedRow) {
document.getElementById('connectionStatus').innerText = "Please select a network to connect to.";
return;
}
const ssid = selectedRow.cells[0].textContent;
const bssid = selectedRow.cells[3].textContent;
const password = prompt("Enter the WiFi password for " + ssid + ":");
if (!password) {
document.getElementById('connectionStatus').innerText = "Connection cancelled.";
return;
}
document.getElementById('connectionStatus').innerText = "Please wait...";
document.getElementById('connectToNetworkButton').disabled = true;
document.getElementById('scanButton').disabled = true;
document.getElementById('testButton').disabled = true;
document.getElementById('forgetButton').disabled = true;
fetch('/wifi_connect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ssid: ssid, bssid: bssid, password: password })
})
.then(response => response.json())
.then(data => {
console.log("Connection attempt result:", data);
document.getElementById('connectionStatus').innerText = data.message;
checkWifiStatus();
})
.catch(error => {
console.error("Error during connection attempt:", error);
document.getElementById('connectionStatus').innerText = "Error during connection attempt.";
})
.finally(() => {
document.getElementById('connectToNetworkButton').disabled = false;
document.getElementById('scanButton').disabled = false;
document.getElementById('testButton').disabled = false;
document.getElementById('forgetButton').disabled = false;
});
}
function checkInternetAccess() {
fetch('/wifi_test')
.then(response => response.json())
.then(data => {
const statusMessage = data.success ? "Internet Access Ok" : "No Internet Access";
alert(statusMessage);
})
.catch(error => {
console.error("Error checking internet access:", error);
alert("Error checking internet access.");
});
}
function checkForgetNetworks() {
fetch('/wifi_forget')
.then(response => response.json())
.then(data => {
const statusMessage = data.success ? "All networks forgotten" : "Nothing happened";
alert(statusMessage);
})
.catch(error => {
console.error("Error forgetting networks:", error);
alert("Error forgetting networks.");
});
}
function checkWifiStatus() {
fetch('/wifi_status')
.then(response => response.json())
.then(data => {
console.log("Data received:", data);
document.getElementById('label-status').innerText = "Status: " + (data.msg || '...');
})
.catch(error => {
console.error("Error getting wifi status:", error);
document.getElementById('label-status').innerText = "Status: ...";
});
}
function OnPageLoad(){ checkWifiStatus(); }
</script>
</body>
</html>

View File

@ -0,0 +1,11 @@
{# Jinja macro for a slide switch component #}
{% macro switch(id='switch', label='Toggle', checked=false, small=false) -%}
<label class="switch" for="{{ id }}">
<span class="switch-label">{{ label }}</span>
<span class="switch-toggle{{ ' small' if small else '' }}" aria-hidden="false">
<input id="{{ id }}" type="checkbox" {{ 'checked' if checked else '' }} />
<span class="slider" aria-hidden="true"></span>
</span>
<span class="visually-hidden">Toggle {{ label }}</span>
</label>
{%- endmacro %}

File diff suppressed because it is too large Load Diff

View File

@ -1,47 +1,162 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico">
<link rel="stylesheet" href="/static/css/styles.css">
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Activation Required</title>
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico" />
<link rel="stylesheet" href="/static/css/styles.css" />
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css" />
<style>
:root{
--brand: #1e40af; /* blue-800 */
--brand-hover:#15327f;
--danger: #ef4444; /* red-500 */
--danger-ink: #7f1d1d; /* dark red text */
--border: #d1d5db; /* gray-300 */
--text: #111827; /* gray-900 */
--muted: #4b5563; /* gray-600 */
--bg: #ffffff;
--bg-alt: #f9fafb; /* gray-50 */
--focus: #2563eb; /* blue-600 */
}
@media (prefers-color-scheme: dark){
:root{
--brand: #60a5fa;
--brand-hover:#3b82f6;
--danger:#f87171;
--danger-ink:#fecaca;
--border:#374151;
--text:#e5e7eb;
--muted:#9ca3af;
--bg:#0b0f14;
--bg-alt:#111827;
--focus:#60a5fa;
}
}
<style>
.activation-message {
text-align: center;
font-size: 1.5em;
color: #ff1493; /* Neon pink/red color */
}
*{ box-sizing: border-box; }
html, body{ height: 100%; }
body{
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
color: var(--text);
background: var(--bg);
line-height: 1.55;
}
.activation-message a {
color: blue; /* Blue color for the link */
text-decoration: none;
}
#navbar{ position: sticky; top: 0; z-index: 20; }
.activation-message a:hover {
text-decoration: underline;
}
.content-wrapper{
max-width: 900px;
margin: 0 auto;
padding: 16px;
}
@media (min-width: 980px){
.content-wrapper{ padding: 24px; }
}
.activation-message .primary-message {
margin-bottom: 20px;
font-weight: bold;
}
</style>
.card{
padding: 20px;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--bg-alt);
}
.center{
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 14px;
}
h1{ margin: 0; font-size: clamp(1.5rem, 2vw, 2rem); }
.alert{
width: 100%;
display: grid;
grid-template-columns: auto 1fr;
gap: 12px;
align-items: start;
padding: 12px 14px;
border: 1px solid var(--danger);
border-radius: 10px;
background: color-mix(in srgb, var(--danger) 12%, transparent);
}
.alert i{ color: var(--danger); }
.alert strong{ color: var(--danger-ink); }
.actions{
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: center;
margin-top: 6px;
}
.btn{
appearance: none;
border: 1px solid transparent;
border-radius: 10px;
padding: 10px 16px;
font-size: 1rem;
cursor: pointer;
color: #fff;
background: var(--brand);
display: inline-flex;
align-items: center;
gap: 8px;
text-decoration: none;
}
.btn:hover{ background: var(--brand-hover); }
.btn:focus-visible{
outline: 3px solid var(--focus);
outline-offset: 2px;
}
.muted{ color: var(--muted); font-size: 0.96rem; }
.sr-only{
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden; clip: rect(0,0,1,1);
white-space: nowrap; border: 0;
}
</style>
</head>
<body>
<div id="navbar"></div>
<script>
fetch('/static/html/nav.html')
.then(response => response.text())
.then(data => {
document.getElementById('navbar').innerHTML = data;
});
</script>
<div id="navbar" role="navigation" aria-label="Primary"></div>
<script>
fetch('/static/html/nav.html')
.then(r => r.ok ? r.text() : Promise.reject(r.status))
.then(html => { document.getElementById('navbar').innerHTML = html; })
.catch(() => {
document.getElementById('navbar').innerHTML =
'<div class="card" role="alert">Navigation failed to load.</div>';
});
</script>
<div class="activation-message">
<p class="primary-message">This software is not activated!</p>
<p>Please go to the <a href="/about">about</a> page to activate this software.</p>
</div>
<main class="content-wrapper">
<section class="card center" aria-labelledby="activation-heading">
<h1 id="activation-heading">Activation Required</h1>
<div class="alert" role="alert" aria-live="polite">
<i class="fa fa-triangle-exclamation" aria-hidden="true"></i>
<div>
<p class="primary"><strong>This software is not activated.</strong></p>
<p class="muted">To continue, activate your license from the About page.</p>
</div>
</div>
<div class="actions" role="group" aria-label="Activation actions">
<a class="btn" href="/about">
<i class="fa fa-key" aria-hidden="true"></i>
Go to About to Activate
</a>
</div>
</section>
</main>
</body>
</html>

View File

@ -1,25 +0,0 @@
#!/bin/bash
source /home/orangepi/printio/.venv/bin/activate
# Stop the services
echo "printio stopping..."
sudo systemctl stop printio
#echo "printio_monitor stopping..."
#sudo systemctl stop printio_monitor
echo ""
# Use pyarmor to protect Python scripts in the printio directory
pyarmor gen -O /home/orangepi/printio/src/ /home/orangepi/printio_master/src_master/*.py
# Use rsync to copy files if they are different
rsync -av --progress /home/orangepi/printio_master/static/ /home/orangepi/printio/static/
rsync -av --progress /home/orangepi/printio_master/templates/ /home/orangepi/printio/templates/
rsync -av --progress /home/orangepi/printio_master/data/ /home/orangepi/printio/data/
# Start the services back up
echo ""
sudo systemctl start printio
echo "printio restarted..."
#sudo systemctl start printio_monitor
#echo "printio_monitor restarted..."