commit
This commit is contained in:
parent
6ac35f19ad
commit
1505e3861b
5
data/sys_settings.json
Normal file
5
data/sys_settings.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"print-on-connect": false,
|
||||
"enable-led-pulse": true,
|
||||
"led-pulse-duration": 1000
|
||||
}
|
||||
126
requirements.txt
126
requirements.txt
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -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')
|
||||
|
||||
@ -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
@ -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
@ -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__
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -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
15
static/css/switches.css
Normal 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)}
|
||||
@ -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>
|
||||
<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>
|
||||
|
||||
.header-img {
|
||||
margin-top: 30px; /* Adjust the margin as needed */
|
||||
margin-bottom: 30px;
|
||||
width: auto;
|
||||
height: 70px; /* Adjust the height as needed */
|
||||
: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 */
|
||||
}
|
||||
.center-content {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
*{ 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; }
|
||||
|
||||
.content-wrapper{
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
}
|
||||
@media (min-width: 980px){
|
||||
.content-wrapper{ padding: 24px; }
|
||||
}
|
||||
|
||||
.card{
|
||||
padding: 20px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
background: var(--bg-alt);
|
||||
}
|
||||
|
||||
.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;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 3px;
|
||||
font-size: 1.1em;
|
||||
width: 250px;
|
||||
h1{ margin: 0; font-size: clamp(1.5rem, 2vw, 2rem); }
|
||||
.muted{ color: var(--muted); }
|
||||
|
||||
.header-img{
|
||||
margin: 10px 0 6px;
|
||||
width: auto;
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
#media-sizes ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#media-sizes li {
|
||||
.actions{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
#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;
|
||||
button{
|
||||
appearance: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 10px;
|
||||
padding: 10px 16px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
width: 150px;
|
||||
margin-bottom: 30px;
|
||||
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;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: darkblue;
|
||||
.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>
|
||||
<div id="navbar" role="navigation" aria-label="Primary"></div>
|
||||
<script>
|
||||
fetch('/static/html/nav.html')
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
document.getElementById('navbar').innerHTML = data;
|
||||
.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>
|
||||
<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>
|
||||
|
||||
<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.');
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
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>
|
||||
|
||||
<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>
|
||||
119
static/html_orig/power.html
Normal file
119
static/html_orig/power.html
Normal 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>
|
||||
@ -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">
|
||||
<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">
|
||||
<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;
|
||||
: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; }
|
||||
|
||||
h1 { margin: 0 0 16px; font-size: clamp(1.5rem, 2vw, 2rem); }
|
||||
h3 { margin: 0 0 12px; font-size: 1.125rem; }
|
||||
|
||||
p { margin: 0 0 12px; }
|
||||
|
||||
a { color: var(--brand); text-decoration: underline; text-underline-offset: 2px; }
|
||||
a:hover { color: var(--brand-hover); }
|
||||
|
||||
.content-wrapper{
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
#media-sizes ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
#media-sizes li {
|
||||
.left-column, .right-column { width: 100%; }
|
||||
|
||||
.card{
|
||||
padding: 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
background: var(--bg-alt);
|
||||
}
|
||||
.info-box{ margin-bottom: 16px; }
|
||||
|
||||
.kv p{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
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; }
|
||||
|
||||
#media-sizes input[type="checkbox"] {
|
||||
transform: scale(1.5);
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
#media-sizes span {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: blue;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 1.2em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: darkblue;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.license-section {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.license-status {
|
||||
font-size: 1.1em;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.license-input {
|
||||
margin-top: 10px;
|
||||
.license-section{
|
||||
margin-top: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
.license-status{ font-size: 1rem; margin: 0 0 10px; }
|
||||
|
||||
.license-input input {
|
||||
padding: 10px;
|
||||
width: 300px;
|
||||
font-size: 1.1em;
|
||||
margin-right: 10px;
|
||||
.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 button {
|
||||
background-color: #4CAF50;
|
||||
padding: 10px 20px;
|
||||
font-size: 1.1em;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* New Styles for Layout */
|
||||
.content-wrapper {
|
||||
.actions{
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.left-column {
|
||||
width: 280px;
|
||||
padding-right: 20px;
|
||||
box-sizing: border-box;
|
||||
.qr{
|
||||
width: 140px;
|
||||
max-width: 40vw;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.muted{ color: var(--muted); font-size: 0.95rem; }
|
||||
</style>
|
||||
<script src="/static/js/crypto-js.min.js"></script>
|
||||
<script src="/static/js/crypto-js.min.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar"></div>
|
||||
<div id="navbar" role="navigation" aria-label="Primary"></div>
|
||||
|
||||
<script>
|
||||
// Load navbar with basic error handling
|
||||
fetch('/static/html/nav.html')
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
document.getElementById('navbar').innerHTML = data;
|
||||
});
|
||||
.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="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>
|
||||
<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="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>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<div class="right-column">
|
||||
<!-- Main -->
|
||||
<section 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.
|
||||
Printio is a professional printing solution designed for seamless printing from iPads. It’s 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>
|
||||
|
||||
<p>
|
||||
Warning: Unauthorized reproduction or distribution of this product
|
||||
is strictly prohibited and may result in severe civil and criminal
|
||||
penalties.
|
||||
<a href="https://www.ataphotobooths.com/knowledge-base/category/printio-setup/" target="_blank" rel="noopener noreferrer">
|
||||
Printio Help: Click me!
|
||||
</a>
|
||||
</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;">
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<!-- 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>
|
||||
<p class="muted" id="copy-hint">Copies your ID Code to the clipboard.</p>
|
||||
|
||||
<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>
|
||||
function encryptPassword(password) {
|
||||
// Simple encryption using CryptoJS (you can replace with your method)
|
||||
return password;
|
||||
}
|
||||
|
||||
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.");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
// Optional: simple (placeholder) hashing before sending; replace with real logic if needed.
|
||||
function hashPass(plain) {
|
||||
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();
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to copy
|
||||
const successful = document.execCommand('copy');
|
||||
document.body.removeChild(tempTextarea);
|
||||
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
|
||||
}
|
||||
|
||||
if (successful) {
|
||||
console.log('Fallback copy successful');
|
||||
//alert('ID code copied to clipboard (using fallback)');
|
||||
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 {
|
||||
console.warn('Fallback copy failed: execCommand returned false');
|
||||
alert('Failed to copy to clipboard: Copy command unsuccessful');
|
||||
setStatus('Activation failed. Please verify your key and try again.', false);
|
||||
alert('Activation failed. Try again.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Fallback copy error:', err);
|
||||
alert('Failed to copy to clipboard: ' + err.message);
|
||||
console.error('Activation error:', err);
|
||||
setStatus('Error during activation attempt.', false);
|
||||
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);
|
||||
|
||||
// Allow Enter key to activate from password field
|
||||
document.getElementById('license-password')?.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') activateLicense();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -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">
|
||||
<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">
|
||||
<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;
|
||||
: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;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
font-size: 24px;
|
||||
color: #565656;
|
||||
|
||||
/* Switch row */
|
||||
.switch-row{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin: 12px 0 6px;
|
||||
}
|
||||
table {
|
||||
.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%;
|
||||
margin-top: 20px;
|
||||
box-sizing: border-box;
|
||||
min-width: 720px;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
thead th{
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--bg-alt);
|
||||
color: var(--text);
|
||||
border-bottom: 1px solid var(--border);
|
||||
text-align: left;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
th {
|
||||
background-color: #f8f8f8;
|
||||
tbody td{
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 10px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.button-red {
|
||||
padding: 5px 10px;
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
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);
|
||||
}
|
||||
.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;
|
||||
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;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.toggle-button {
|
||||
margin-top: 20px;
|
||||
background-color: #007BFF;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 3px 20px;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
.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>
|
||||
<div id="navbar" role="navigation" aria-label="Primary"></div>
|
||||
<script>
|
||||
fetch('/static/html/nav.html')
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
document.getElementById('navbar').innerHTML = data;
|
||||
});
|
||||
.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">
|
||||
<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>
|
||||
|
||||
<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>Printer Name</th>
|
||||
<th>Status</th>
|
||||
<th>Prints Left</th>
|
||||
<th>Actions</th>
|
||||
<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>
|
||||
<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>
|
||||
<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" style="display:inline;">
|
||||
<button type="submit" class="button-test">Test Print</button>
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
<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>
|
||||
|
||||
<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';
|
||||
}
|
||||
/**
|
||||
* Initialize the "Print on Connect" checkbox behavior.
|
||||
*/
|
||||
function initPrintOnConnect() {
|
||||
const checkbox = document.getElementById('printOnConnect');
|
||||
const errorDiv = document.getElementById('printOnConnectError');
|
||||
|
||||
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 the printer job information and show in the textarea
|
||||
/**
|
||||
* Load job history on demand.
|
||||
*/
|
||||
function loadPrinterJobs() {
|
||||
var messageLog = document.getElementById('messageLog');
|
||||
var jobs = {{ job_history | tojson | safe }};
|
||||
var logText = "";
|
||||
const jobs = {{ job_history | tojson | safe }};
|
||||
const pre = document.getElementById('logText');
|
||||
if (!pre) return;
|
||||
|
||||
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";
|
||||
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}`;
|
||||
});
|
||||
|
||||
// Set the value of the textarea to display the job information
|
||||
messageLog.value = logText;
|
||||
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>
|
||||
|
||||
@ -1,194 +1,131 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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">
|
||||
<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;
|
||||
: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;
|
||||
}
|
||||
.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 */
|
||||
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;
|
||||
/* 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;
|
||||
});
|
||||
fetch('/static/html/nav.html').then(r=>r.text()).then(html=>{ document.getElementById('navbar').innerHTML = html; });
|
||||
</script>
|
||||
|
||||
<div class="content-wrapper">
|
||||
<div class="content">
|
||||
<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>
|
||||
<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 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 class="scan-container">
|
||||
|
||||
<div id="scanStatus" class="status"></div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="network-list">
|
||||
<table id="networksTable">
|
||||
<thead>
|
||||
@ -199,50 +136,57 @@
|
||||
<th>Bssid</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- List of networks will be appended here -->
|
||||
</tbody>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="connect-container">
|
||||
<button class="btn" id="connectToNetworkButton"onclick="connectToNetwork()">Connect</button>
|
||||
|
||||
<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>
|
||||
<details>
|
||||
<summary>Connection Status</summary>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- 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>
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
window.onload = function() { OnPageLoad(); };
|
||||
|
||||
function scanWifi() {
|
||||
console.log("Starting WiFi scan...");
|
||||
document.getElementById('connectionStatus').innerText = ""
|
||||
document.getElementById('scanStatus').innerText = "Scanning for networks...";
|
||||
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);
|
||||
document.getElementById('scanStatus').innerText = "Scan complete.";
|
||||
ss.innerText = "Scan complete.";
|
||||
let tableBody = document.getElementById('networksTable').getElementsByTagName('tbody')[0];
|
||||
tableBody.innerHTML = '';
|
||||
data.networks.forEach(network => {
|
||||
(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.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'));
|
||||
@ -252,7 +196,7 @@
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error during scan:", error);
|
||||
document.getElementById('scanStatus').innerText = "Error during scan.";
|
||||
ss.innerText = "Error during scan.";
|
||||
});
|
||||
}
|
||||
|
||||
@ -278,9 +222,7 @@
|
||||
document.getElementById('forgetButton').disabled = true;
|
||||
fetch('/wifi_connect', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ssid: ssid, bssid: bssid, password: password })
|
||||
})
|
||||
.then(response => response.json())
|
||||
@ -294,7 +236,6 @@
|
||||
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;
|
||||
@ -307,11 +248,11 @@
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const statusMessage = data.success ? "Internet Access Ok" : "No Internet Access";
|
||||
alert(statusMessage); // Display message in a popup
|
||||
alert(statusMessage);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error checking internet access:", error);
|
||||
alert("Error checking internet access."); // Display error in a popup
|
||||
alert("Error checking internet access.");
|
||||
});
|
||||
}
|
||||
|
||||
@ -320,11 +261,11 @@
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const statusMessage = data.success ? "All networks forgotten" : "Nothing happened";
|
||||
alert(statusMessage); // Display message in a popup
|
||||
alert(statusMessage);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error forgetting networks:", error);
|
||||
alert("Error forgetting networks."); // Display error in a popup
|
||||
alert("Error forgetting networks.");
|
||||
});
|
||||
}
|
||||
|
||||
@ -333,7 +274,7 @@
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log("Data received:", data);
|
||||
document.getElementById('label-status').innerText = "Status: " + data.msg;
|
||||
document.getElementById('label-status').innerText = "Status: " + (data.msg || '...');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error getting wifi status:", error);
|
||||
@ -341,10 +282,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
function OnPageLoad(){
|
||||
checkWifiStatus();
|
||||
}
|
||||
|
||||
function OnPageLoad(){ checkWifiStatus(); }
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
11
templates/macros/switches.html
Normal file
11
templates/macros/switches.html
Normal 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
@ -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>
|
||||
.activation-message {
|
||||
text-align: center;
|
||||
font-size: 1.5em;
|
||||
color: #ff1493; /* Neon pink/red color */
|
||||
: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;
|
||||
}
|
||||
}
|
||||
|
||||
.activation-message a {
|
||||
color: blue; /* Blue color for the link */
|
||||
*{ 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; }
|
||||
|
||||
.content-wrapper{
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
}
|
||||
@media (min-width: 980px){
|
||||
.content-wrapper{ padding: 24px; }
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.activation-message a:hover {
|
||||
text-decoration: underline;
|
||||
.btn:hover{ background: var(--brand-hover); }
|
||||
.btn:focus-visible{
|
||||
outline: 3px solid var(--focus);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.activation-message .primary-message {
|
||||
margin-bottom: 20px;
|
||||
font-weight: bold;
|
||||
.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>
|
||||
<div id="navbar" role="navigation" aria-label="Primary"></div>
|
||||
<script>
|
||||
fetch('/static/html/nav.html')
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
document.getElementById('navbar').innerHTML = data;
|
||||
.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>
|
||||
<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>
|
||||
|
||||
@ -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..."
|
||||
Loading…
x
Reference in New Issue
Block a user