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
|
asgiref==3.9.1
|
||||||
blinker
|
beautifulsoup4==4.13.3
|
||||||
bs4
|
bidict==0.23.1
|
||||||
certifi
|
blinker==1.9.0
|
||||||
cffi
|
Brlapi==0.8.3
|
||||||
charset-normalizer
|
bs4==0.0.2
|
||||||
click
|
certifi==2025.1.31
|
||||||
cryptography
|
cffi==1.17.1
|
||||||
Flask
|
channels==4.3.1
|
||||||
Flask-Cors
|
chardet==4.0.0
|
||||||
idna
|
charset-normalizer==3.4.1
|
||||||
itsdangerous
|
click==8.1.8
|
||||||
Jinja2
|
colorama==0.4.4
|
||||||
MarkupSafe
|
configobj==5.0.6
|
||||||
psutil
|
cryptography==3.4.8
|
||||||
pyarmor
|
cupshelpers==1.0
|
||||||
pycparser
|
dbus-python==1.2.18
|
||||||
pycups
|
defer==1.0.6
|
||||||
pyudev
|
distro==1.7.0
|
||||||
requests
|
distro-info==1.1+ubuntu0.2
|
||||||
soupsieve
|
Django==5.2.6
|
||||||
spidev
|
dnspython==2.8.0
|
||||||
swig
|
evdev==1.9.2
|
||||||
urllib3
|
eventlet==0.40.3
|
||||||
Werkzeug
|
Flask==3.1.0
|
||||||
gunicorn
|
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__
|
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__
|
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__
|
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__
|
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__
|
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>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Printer Media Sizes</title>
|
<title>Power Options</title>
|
||||||
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico">
|
<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/css/styles.css" />
|
||||||
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
|
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css" />
|
||||||
<script src="/static/js/jquery-3.7.1.js"></script>
|
<style>
|
||||||
<style>
|
:root{
|
||||||
|
--brand: #1e40af; /* blue-800 */
|
||||||
|
--brand-hover: #15327f;
|
||||||
|
--warn: #f59e0b; /* amber-500 */
|
||||||
|
--warn-hover:#d97706;
|
||||||
|
--danger: #ef4444; /* red-500 */
|
||||||
|
--danger-hover:#dc2626;
|
||||||
|
--border: #d1d5db; /* gray-300 */
|
||||||
|
--text: #111827; /* gray-900 */
|
||||||
|
--muted: #4b5563; /* gray-600 */
|
||||||
|
--bg: #ffffff;
|
||||||
|
--bg-alt: #f9fafb; /* gray-50 */
|
||||||
|
--focus: #2563eb; /* blue-600 */
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark){
|
||||||
|
:root{
|
||||||
|
--brand: #60a5fa;
|
||||||
|
--brand-hover:#3b82f6;
|
||||||
|
--warn:#fbbf24;
|
||||||
|
--warn-hover:#f59e0b;
|
||||||
|
--danger:#f87171;
|
||||||
|
--danger-hover:#ef4444;
|
||||||
|
--border:#374151;
|
||||||
|
--text:#e5e7eb;
|
||||||
|
--muted:#9ca3af;
|
||||||
|
--bg:#0b0f14;
|
||||||
|
--bg-alt:#111827;
|
||||||
|
--focus:#60a5fa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.header-img {
|
*{ box-sizing: border-box; }
|
||||||
margin-top: 30px; /* Adjust the margin as needed */
|
html, body{ height: 100%; }
|
||||||
margin-bottom: 30px;
|
body{
|
||||||
width: auto;
|
margin: 0;
|
||||||
height: 70px; /* Adjust the height as needed */
|
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
||||||
}
|
color: var(--text);
|
||||||
.center-content {
|
background: var(--bg);
|
||||||
display: flex;
|
line-height: 1.55;
|
||||||
flex-direction: column;
|
}
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label, select, #media-sizes, button {
|
#navbar{ position: sticky; top: 0; z-index: 20; }
|
||||||
margin: 5px 0;
|
|
||||||
text-align: center;
|
|
||||||
font-size: larger;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
.content-wrapper{
|
||||||
padding: 3px;
|
max-width: 900px;
|
||||||
font-size: 1.1em;
|
margin: 0 auto;
|
||||||
width: 250px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
@media (min-width: 980px){
|
||||||
|
.content-wrapper{ padding: 24px; }
|
||||||
|
}
|
||||||
|
|
||||||
#media-sizes ul {
|
.card{
|
||||||
list-style-type: none;
|
padding: 20px;
|
||||||
padding: 0;
|
border: 1px solid var(--border);
|
||||||
}
|
border-radius: 12px;
|
||||||
|
background: var(--bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
#media-sizes li {
|
.center-content{
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
margin-bottom: 5px;
|
align-items: center;
|
||||||
}
|
text-align: center;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
#media-sizes input[type="checkbox"] {
|
h1{ margin: 0; font-size: clamp(1.5rem, 2vw, 2rem); }
|
||||||
transform: scale(1.5);
|
.muted{ color: var(--muted); }
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#media-sizes span {
|
.header-img{
|
||||||
font-size: 1.2em;
|
margin: 10px 0 6px;
|
||||||
}
|
width: auto;
|
||||||
|
height: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
.actions{
|
||||||
background-color: red;
|
display: flex;
|
||||||
color: white;
|
gap: 12px;
|
||||||
padding: 10px 20px;
|
flex-wrap: wrap;
|
||||||
border: none;
|
justify-content: center;
|
||||||
border-radius: 5px;
|
margin-top: 8px;
|
||||||
font-size: 1.2em;
|
}
|
||||||
cursor: pointer;
|
|
||||||
width: 150px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
button{
|
||||||
background-color: darkblue;
|
appearance: none;
|
||||||
}
|
border: 1px solid transparent;
|
||||||
</style>
|
border-radius: 10px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
button:focus-visible{
|
||||||
|
outline: 3px solid var(--focus);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
.btn-warn{
|
||||||
|
background: var(--warn);
|
||||||
|
border-color: var(--warn-hover);
|
||||||
|
}
|
||||||
|
.btn-warn:hover{ background: var(--warn-hover); }
|
||||||
|
.btn-danger{
|
||||||
|
background: var(--danger);
|
||||||
|
border-color: var(--danger-hover);
|
||||||
|
}
|
||||||
|
.btn-danger:hover{ background: var(--danger-hover); }
|
||||||
|
|
||||||
|
.status{
|
||||||
|
min-height: 1.2em;
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 0.98rem;
|
||||||
|
}
|
||||||
|
.status[aria-live]{ /* ensure space even when empty */ display: block; }
|
||||||
|
|
||||||
|
.help{
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner{
|
||||||
|
display: inline-block;
|
||||||
|
width: 1em; height: 1em;
|
||||||
|
border: 2px solid currentColor;
|
||||||
|
border-right-color: transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
vertical-align: -2px;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
@keyframes spin{ to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
.sr-only{
|
||||||
|
position: absolute;
|
||||||
|
width: 1px; height: 1px;
|
||||||
|
padding: 0; margin: -1px;
|
||||||
|
overflow: hidden; clip: rect(0,0,1,1);
|
||||||
|
white-space: nowrap; border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="navbar"></div>
|
<div id="navbar" role="navigation" aria-label="Primary"></div>
|
||||||
<script>
|
<script>
|
||||||
fetch('/static/html/nav.html')
|
fetch('/static/html/nav.html')
|
||||||
.then(response => response.text())
|
.then(r => r.ok ? r.text() : Promise.reject(r.status))
|
||||||
.then(data => {
|
.then(html => { document.getElementById('navbar').innerHTML = html; })
|
||||||
document.getElementById('navbar').innerHTML = data;
|
.catch(() => {
|
||||||
});
|
document.getElementById('navbar').innerHTML =
|
||||||
</script>
|
'<div class="card" role="alert">Navigation failed to load.</div>';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="content-wrapper">
|
<main class="content-wrapper">
|
||||||
<div class="content center-content">
|
<section class="card center-content" aria-labelledby="power-heading">
|
||||||
<h1>Power Options</h1>
|
<h1 id="power-heading">Power Options</h1>
|
||||||
<img src="/static/images/switch-icon.png" alt="cards" class="header-img">
|
<img src="/static/images/switch-icon.png" alt="Power switch icon" class="header-img" />
|
||||||
<button id="reboot-button">Reboot</button>
|
<p class="muted help">
|
||||||
<button id="shutdown-button">Shutdown</button>
|
These actions affect the entire device. Save your work before proceeding.
|
||||||
</div>
|
</p>
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
<script>
|
<div class="actions" role="group" aria-label="Power controls">
|
||||||
$(document).ready(function() {
|
<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>
|
||||||
|
|
||||||
$('#reboot-button').click(function() {
|
<div id="status" class="status" role="status" aria-live="polite"></div>
|
||||||
$.post('/reboot', function(response) {
|
</section>
|
||||||
alert("Reboot initiated");
|
</main>
|
||||||
}).fail(function() {
|
|
||||||
alert("Reboot failed");
|
<script>
|
||||||
});
|
const rebootBtn = document.getElementById('reboot-button');
|
||||||
|
const shutdownBtn = document.getElementById('shutdown-button');
|
||||||
|
const statusEl = document.getElementById('status');
|
||||||
|
|
||||||
|
function setBusy(btn, busy){
|
||||||
|
btn.disabled = busy;
|
||||||
|
if (busy) btn.setAttribute('aria-busy', 'true'); else btn.removeAttribute('aria-busy');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(message, isError = false){
|
||||||
|
statusEl.textContent = '';
|
||||||
|
if (!message) return;
|
||||||
|
statusEl.innerHTML = (isError ? '' : '<span class="spinner"></span>') + message;
|
||||||
|
statusEl.style.color = isError ? 'var(--danger)' : 'inherit';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function postAction(url, startMsg, successMsg){
|
||||||
|
setStatus(startMsg, false);
|
||||||
|
try{
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
|
if (!res.ok) throw new Error('Network error');
|
||||||
|
let data = null;
|
||||||
|
try { data = await res.json(); } catch {}
|
||||||
|
const ok = (data && (data.reply === true || data.ok === true)) || res.ok;
|
||||||
|
if (!ok) throw new Error((data && data.message) || 'Server error');
|
||||||
|
// Success: message without spinner
|
||||||
|
statusEl.innerHTML = successMsg;
|
||||||
|
statusEl.style.color = 'inherit';
|
||||||
|
} catch (e){
|
||||||
|
console.error(e);
|
||||||
|
setStatus('Operation failed. Please try again.', true);
|
||||||
|
alert('The request did not complete successfully.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$('#shutdown-button').click(function() {
|
rebootBtn?.addEventListener('click', async () => {
|
||||||
$.post('/shutdown', function(response) {
|
const confirmed = confirm('Reboot now? All services will restart.');
|
||||||
alert("Shutdown initiated");
|
if (!confirmed) return;
|
||||||
}).fail(function() {
|
setBusy(rebootBtn, true);
|
||||||
alert("Shutdown failed");
|
setBusy(shutdownBtn, true);
|
||||||
});
|
await postAction('/reboot', 'Rebooting device… This may take up to a minute.', 'Reboot initiated.');
|
||||||
});
|
// Keep buttons disabled briefly to prevent repeats
|
||||||
|
setTimeout(() => { setBusy(rebootBtn, false); setBusy(shutdownBtn, false); }, 4000);
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
|
|
||||||
|
shutdownBtn?.addEventListener('click', async () => {
|
||||||
|
const confirmed = confirm('Shut down now? The device will power off.');
|
||||||
|
if (!confirmed) return;
|
||||||
|
setBusy(rebootBtn, true);
|
||||||
|
setBusy(shutdownBtn, true);
|
||||||
|
await postAction('/shutdown', 'Shutting down… The device will power off shortly.', 'Shutdown initiated.');
|
||||||
|
// Keep disabled; device may go offline
|
||||||
|
setTimeout(() => { setBusy(rebootBtn, false); setBusy(shutdownBtn, false); }, 4000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
</html>
|
</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>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>About Printio</title>
|
<title>About Printio</title>
|
||||||
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico">
|
<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/css/styles.css" />
|
||||||
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
|
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css" />
|
||||||
<style>
|
<style>
|
||||||
h1 {
|
:root{
|
||||||
margin-bottom: 20px;
|
--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 {
|
* { box-sizing: border-box; }
|
||||||
margin: 5px 0;
|
html, body { height: 100%; }
|
||||||
text-align: center;
|
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 {
|
#navbar { position: sticky; top: 0; z-index: 20; }
|
||||||
padding: 3px;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#media-sizes ul {
|
h1 { margin: 0 0 16px; font-size: clamp(1.5rem, 2vw, 2rem); }
|
||||||
list-style-type: none;
|
h3 { margin: 0 0 12px; font-size: 1.125rem; }
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#media-sizes li {
|
p { margin: 0 0 12px; }
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#media-sizes input[type="checkbox"] {
|
a { color: var(--brand); text-decoration: underline; text-underline-offset: 2px; }
|
||||||
transform: scale(1.5);
|
a:hover { color: var(--brand-hover); }
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#media-sizes span {
|
.content-wrapper{
|
||||||
font-size: 1.2em;
|
display: grid;
|
||||||
}
|
grid-template-columns: 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
@media (min-width: 840px){
|
||||||
background-color: blue;
|
.content-wrapper{
|
||||||
color: white;
|
grid-template-columns: 300px 1fr;
|
||||||
padding: 10px 20px;
|
gap: 24px;
|
||||||
border: none;
|
padding: 24px;
|
||||||
border-radius: 5px;
|
}
|
||||||
font-size: 1.2em;
|
.left-column{
|
||||||
cursor: pointer;
|
position: sticky;
|
||||||
}
|
top: 72px; /* keep below navbar */
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
button:hover {
|
.left-column, .right-column { width: 100%; }
|
||||||
background-color: darkblue;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
.card{
|
||||||
display: block;
|
padding: 16px;
|
||||||
margin-top: 1em;
|
border: 1px solid var(--border);
|
||||||
margin-bottom: 1em;
|
border-radius: 10px;
|
||||||
margin-left: 0;
|
background: var(--bg-alt);
|
||||||
margin-right: 0;
|
}
|
||||||
}
|
.info-box{ margin-bottom: 16px; }
|
||||||
|
|
||||||
.license-section {
|
.kv p{
|
||||||
margin-top: 30px;
|
display: flex;
|
||||||
text-align: center;
|
justify-content: space-between;
|
||||||
}
|
gap: 12px;
|
||||||
|
border-bottom: 1px dashed var(--border);
|
||||||
|
padding: 6px 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.kv p span:first-child{ color: var(--muted); }
|
||||||
|
.kv p span:last-child{ font-variant-numeric: tabular-nums; }
|
||||||
|
|
||||||
.license-status {
|
.license-section{
|
||||||
font-size: 1.1em;
|
margin-top: 20px;
|
||||||
margin-bottom: 15px;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
.license-status{ font-size: 1rem; margin: 0 0 10px; }
|
||||||
|
|
||||||
.license-input {
|
.field{
|
||||||
margin-top: 10px;
|
display: grid;
|
||||||
text-align: left;
|
grid-template-columns: 140px 1fr auto;
|
||||||
}
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
@media (max-width: 520px){
|
||||||
|
.field{ grid-template-columns: 1fr; }
|
||||||
|
.field label{ margin-bottom: -4px; }
|
||||||
|
}
|
||||||
|
|
||||||
.license-input input {
|
input[type="text"], input[type="password"]{
|
||||||
padding: 10px;
|
padding: 10px 12px;
|
||||||
width: 300px;
|
font-size: 1rem;
|
||||||
font-size: 1.1em;
|
border: 1px solid var(--border);
|
||||||
margin-right: 10px;
|
border-radius: 8px;
|
||||||
}
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
input[readonly]{
|
||||||
|
background: rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
.license-input button {
|
button{
|
||||||
background-color: #4CAF50;
|
appearance: none;
|
||||||
padding: 10px 20px;
|
border: none;
|
||||||
font-size: 1.1em;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
padding: 10px 16px;
|
||||||
border-radius: 5px;
|
font-size: 1rem;
|
||||||
border: none;
|
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 {
|
.actions{
|
||||||
background-color: #45a049;
|
display: flex;
|
||||||
}
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
/* New Styles for Layout */
|
.qr{
|
||||||
.content-wrapper {
|
width: 140px;
|
||||||
display: flex;
|
max-width: 40vw;
|
||||||
}
|
height: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.left-column {
|
.muted{ color: var(--muted); font-size: 0.95rem; }
|
||||||
width: 280px;
|
</style>
|
||||||
padding-right: 20px;
|
<script src="/static/js/crypto-js.min.js" defer></script>
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-column {
|
|
||||||
width: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-box {
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.license-box {
|
|
||||||
padding: 15px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
#p {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src="/static/js/crypto-js.min.js"></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="content-wrapper">
|
<script>
|
||||||
<div class="left-column">
|
// Load navbar with basic error handling
|
||||||
<div class="info-box">
|
fetch('/static/html/nav.html')
|
||||||
<h3>System Info</h3>
|
.then(r => r.ok ? r.text() : Promise.reject(r.status))
|
||||||
<p>Version: {{info.software_version}}
|
.then(html => { document.getElementById('navbar').innerHTML = html; })
|
||||||
<p>CPU %: {{info.cpu}}</p>
|
.catch(() => { document.getElementById('navbar').innerHTML = '<div class="card" role="alert">Navigation failed to load.</div>'; });
|
||||||
<p>CPU T: {{info.cpu_t}}</p>
|
</script>
|
||||||
<p>Disk Size: {{info.disk_size}}</p>
|
|
||||||
<p>Disk Used: {{info.disk_used}}</p>
|
|
||||||
<p>RAM Size: {{info.ram_size}}</p>
|
|
||||||
<p>RAM Used: {{info.ram_used}}</p>
|
|
||||||
<p>Up Time: {{info.uptime}}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="license-box">
|
<main class="content-wrapper">
|
||||||
<h3>License Info </h3>
|
<!-- Sidebar -->
|
||||||
<p>License: {{info.license}}</p>
|
<aside class="left-column" aria-label="System and License Information">
|
||||||
<!--p>Image Magic: {{info.image_magic}}</p-->
|
<section class="card info-box" aria-labelledby="sysinfo-heading">
|
||||||
<!--p>Hashtag: {{info.hash}}</p-->
|
<h3 id="sysinfo-heading">System Info</h3>
|
||||||
<!--p>Drop Folder: {{info.drop_folder}}</p-->
|
<div class="kv">
|
||||||
</div>
|
<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>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="right-column">
|
<section class="card" aria-labelledby="licenseinfo-heading">
|
||||||
<h1>About Printio</h1>
|
<h3 id="licenseinfo-heading">License Info</h3>
|
||||||
<p>
|
<p class="muted">Status: <strong>{{info.license}}</strong></p>
|
||||||
Printio is a professional printing solution designed for seamless
|
<!-- Optional fields kept commented
|
||||||
printing from iPads. It is particularly popular in the photobooth
|
<p>ImageMagick: {{info.image_magic}}</p>
|
||||||
industry, supporting a wide range of sublimation printers. Printio
|
<p>Drop Folder: {{info.drop_folder}}</p>
|
||||||
is compatible with well-known brands such as DNP, Sinfonia, Hiti,
|
-->
|
||||||
and Mitsubishi, as well as many other printers, including Zebra label
|
</section>
|
||||||
printers, and most HP and Epson models. This versatility makes Printio
|
</aside>
|
||||||
an ideal choice for diverse printing needs.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Warning: Unauthorized reproduction or distribution of this product
|
|
||||||
is strictly prohibited and may result in severe civil and criminal
|
|
||||||
penalties.
|
|
||||||
</p>
|
|
||||||
<a href="https://www.ataphotobooths.com/knowledge-base/category/printio-setup/" target="_blank">Printio Help: Click me!</a>
|
|
||||||
<div></div>
|
|
||||||
<img src="/static/images/printio_help_qr.jpg" alt="Printio" style="width: 120px; margin-top: 20px;">
|
|
||||||
|
|
||||||
|
<!-- Main -->
|
||||||
|
<section class="right-column">
|
||||||
|
<h1>About Printio</h1>
|
||||||
|
<p>
|
||||||
|
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>
|
||||||
|
|
||||||
<div class="license-section" {{info.hidden}}>
|
<p>
|
||||||
<div class="license-input">
|
<a href="https://www.ataphotobooths.com/knowledge-base/category/printio-setup/" target="_blank" rel="noopener noreferrer">
|
||||||
<label>__________________________________________________________</label>
|
Printio Help: Click me!
|
||||||
</div>
|
</a>
|
||||||
<div class="license-input">
|
</p>
|
||||||
<label for="activation-serial">ID Code:</label>
|
|
||||||
<input type="text" id="idcode" value={{info.idcode}} readonly>
|
<figure aria-label="Printio Help QR Code" style="margin: 16px 0;">
|
||||||
<button onclick="copyToClipboard()">Copy</button>
|
<img class="qr" src="/static/images/printio_help_qr.jpg" alt="Scan for Printio setup help" />
|
||||||
</div>
|
</figure>
|
||||||
<div class="license-input">
|
|
||||||
<label for="activation-serial">SerialNo</label>
|
<!-- License Section (conditionally hidden) -->
|
||||||
<input type="password" id="license-password" placeholder="Enter Activation Key">
|
<section class="license-section card" {{info.hidden}} aria-labelledby="license-activation-heading">
|
||||||
<button onclick="activateLicense()">Activate</button>
|
<h3 id="license-activation-heading">License Activation</h3>
|
||||||
</div>
|
<p class="license-status muted" id="license-status" aria-live="polite"></p>
|
||||||
</div>
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="idcode">ID Code</label>
|
||||||
|
<input type="text" id="idcode" value="{{info.idcode}}" readonly aria-readonly="true" />
|
||||||
|
<div class="actions">
|
||||||
|
<button type="button" id="copy-btn" class="btn-ok" aria-describedby="copy-hint">
|
||||||
|
<i class="fa fa-copy" aria-hidden="true"></i> Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<p class="muted" id="copy-hint">Copies your ID Code to the clipboard.</p>
|
||||||
|
|
||||||
<script>
|
<div class="field">
|
||||||
function encryptPassword(password) {
|
<label for="license-password">Activation Key</label>
|
||||||
// Simple encryption using CryptoJS (you can replace with your method)
|
<input type="password" id="license-password" placeholder="Enter Activation Key" autocomplete="one-time-code" />
|
||||||
return password;
|
<div class="actions">
|
||||||
|
<button type="button" id="activate-btn">
|
||||||
|
<i class="fa fa-bolt" aria-hidden="true"></i> Activate
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Optional: simple (placeholder) hashing before sending; replace with real logic if needed.
|
||||||
|
function hashPass(plain) {
|
||||||
|
try {
|
||||||
|
// Example using CryptoJS if you choose to enable hashing later:
|
||||||
|
// return CryptoJS.SHA256(plain).toString();
|
||||||
|
return plain; // currently pass-through; replace when backend expects a hash
|
||||||
|
} catch {
|
||||||
|
return plain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(msg, ok = true){
|
||||||
|
const el = document.getElementById('license-status');
|
||||||
|
if (!el) return;
|
||||||
|
el.textContent = msg || '';
|
||||||
|
el.style.color = ok ? 'inherit' : '#dc2626'; // red-600 in light; acceptable in dark
|
||||||
|
}
|
||||||
|
|
||||||
|
async function activateLicense() {
|
||||||
|
const input = document.getElementById('license-password');
|
||||||
|
const pass = (input?.value || '').trim();
|
||||||
|
if (!pass){
|
||||||
|
setStatus('Please enter your activation key.', false);
|
||||||
|
input?.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStatus('Activating…');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/activate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ pass: hashPass(pass) })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error('Network error');
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.reply === true) {
|
||||||
|
alert('Activation successful!');
|
||||||
|
window.location.href = '/about';
|
||||||
|
} else {
|
||||||
|
setStatus('Activation failed. Please verify your key and try again.', false);
|
||||||
|
alert('Activation failed. Try again.');
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Activation error:', err);
|
||||||
|
setStatus('Error during activation attempt.', false);
|
||||||
|
alert('Error during activation attempt.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function activateLicense() {
|
async function copyToClipboard() {
|
||||||
const password = document.getElementById('license-password').value;
|
const el = document.getElementById('idcode');
|
||||||
const encryptedPassword = encryptPassword(password);
|
const btn = document.getElementById('copy-btn');
|
||||||
fetch('/activate', {
|
if (!el) return alert('No ID Code field found.');
|
||||||
method: 'POST',
|
const text = el.value || el.textContent || '';
|
||||||
headers: {
|
if (!text) return alert('Nothing to copy.');
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
try {
|
||||||
body: JSON.stringify({ pass: encryptedPassword })
|
if (navigator.clipboard?.writeText) {
|
||||||
})
|
await navigator.clipboard.writeText(text);
|
||||||
.then(response => response.json())
|
} else {
|
||||||
.then(data => {
|
// Fallback
|
||||||
console.log("Activation result:", data);
|
const ta = document.createElement('textarea');
|
||||||
if (data.reply == true) {
|
ta.value = text;
|
||||||
alert("Activation Successful!!!");
|
ta.style.position = 'fixed';
|
||||||
window.location.href = "/about"; // Redirect to /about page
|
ta.style.opacity = '0';
|
||||||
} else {
|
document.body.appendChild(ta);
|
||||||
alert("Activation Failed.... Try Again");
|
ta.select();
|
||||||
}
|
document.execCommand('copy');
|
||||||
})
|
document.body.removeChild(ta);
|
||||||
.catch(error => {
|
|
||||||
console.error("Error during activation attempt:", error);
|
|
||||||
alert("Error during activation attempt.");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
btn?.classList.add('copied');
|
||||||
|
btn?.setAttribute('aria-label', 'Copied!');
|
||||||
|
setStatus('ID Code copied to clipboard.');
|
||||||
|
setTimeout(() => {
|
||||||
|
btn?.removeAttribute('aria-label');
|
||||||
|
setStatus('');
|
||||||
|
}, 1500);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Clipboard error:', e);
|
||||||
|
alert('Failed to copy to clipboard.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wire up buttons
|
||||||
|
document.getElementById('copy-btn')?.addEventListener('click', copyToClipboard);
|
||||||
|
document.getElementById('activate-btn')?.addEventListener('click', activateLicense);
|
||||||
|
|
||||||
function copyToClipboard() {
|
// Allow Enter key to activate from password field
|
||||||
const idCode = document.getElementById('idcode');
|
document.getElementById('license-password')?.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') activateLicense();
|
||||||
// Check if the element exists
|
});
|
||||||
if (!idCode) {
|
</script>
|
||||||
console.error('Element with ID "idcode" not found');
|
|
||||||
alert('Error: Could not find the text to copy');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the value or text content (depending on element type)
|
|
||||||
const textToCopy = idCode.value || idCode.textContent || '';
|
|
||||||
console.log('Text to copy:', textToCopy);
|
|
||||||
|
|
||||||
// Ensure there's something to copy
|
|
||||||
if (!textToCopy) {
|
|
||||||
console.warn('No text to copy');
|
|
||||||
alert('Nothing to copy');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, try the Clipboard API if available
|
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
||||||
console.log('Attempting to use Clipboard API');
|
|
||||||
navigator.clipboard.writeText(textToCopy)
|
|
||||||
.then(() => {
|
|
||||||
console.log('Clipboard API success');
|
|
||||||
alert('ID code copied to clipboard');
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error('Clipboard API failed:', err);
|
|
||||||
attemptFallbackCopy(textToCopy);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('Clipboard API unavailable, using fallback');
|
|
||||||
attemptFallbackCopy(textToCopy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function attemptFallbackCopy(textToCopy) {
|
|
||||||
try {
|
|
||||||
// Create a temporary textarea for copying
|
|
||||||
const tempTextarea = document.createElement('textarea');
|
|
||||||
tempTextarea.value = textToCopy;
|
|
||||||
tempTextarea.style.position = 'fixed'; // Prevent scrolling issues
|
|
||||||
tempTextarea.style.opacity = '0'; // Make it invisible
|
|
||||||
document.body.appendChild(tempTextarea);
|
|
||||||
tempTextarea.focus();
|
|
||||||
tempTextarea.select();
|
|
||||||
|
|
||||||
// Attempt to copy
|
|
||||||
const successful = document.execCommand('copy');
|
|
||||||
document.body.removeChild(tempTextarea);
|
|
||||||
|
|
||||||
if (successful) {
|
|
||||||
console.log('Fallback copy successful');
|
|
||||||
//alert('ID code copied to clipboard (using fallback)');
|
|
||||||
} else {
|
|
||||||
console.warn('Fallback copy failed: execCommand returned false');
|
|
||||||
alert('Failed to copy to clipboard: Copy command unsuccessful');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Fallback copy error:', err);
|
|
||||||
alert('Failed to copy to clipboard: ' + err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,157 +1,369 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Printio</title>
|
<title>Printio</title>
|
||||||
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico">
|
<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/css/styles.css" />
|
||||||
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
|
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css" />
|
||||||
<style>
|
<style>
|
||||||
.header-img {
|
:root{
|
||||||
margin-top: 30px;
|
--brand: #1e40af; /* blue-800 */
|
||||||
margin-bottom: 30px;
|
--brand-hover: #15327f;
|
||||||
width: auto;
|
--danger: #ef4444; /* red-500 */
|
||||||
height: 100px;
|
--ok: #16a34a; /* green-600 */
|
||||||
}
|
--ok-hover: #118039;
|
||||||
h2 {
|
--border: #d1d5db; /* gray-300 */
|
||||||
margin-top: 0;
|
--text: #111827; /* gray-900 */
|
||||||
font-size: 24px;
|
--muted: #4b5563; /* gray-600 */
|
||||||
color: #565656;
|
--bg: #ffffff;
|
||||||
}
|
--bg-alt: #f9fafb; /* gray-50 */
|
||||||
table {
|
--focus: #2563eb; /* blue-600 */
|
||||||
border-collapse: collapse;
|
}
|
||||||
width: 100%;
|
@media (prefers-color-scheme: dark){
|
||||||
margin-top: 20px;
|
:root{
|
||||||
box-sizing: border-box;
|
--brand: #60a5fa;
|
||||||
}
|
--brand-hover: #3b82f6;
|
||||||
th, td {
|
--danger: #f87171;
|
||||||
border: 1px solid #ccc;
|
--ok: #34d399;
|
||||||
padding: 10px;
|
--ok-hover: #10b981;
|
||||||
text-align: left;
|
--border: #374151;
|
||||||
box-sizing: border-box;
|
--text: #e5e7eb;
|
||||||
}
|
--muted: #9ca3af;
|
||||||
th {
|
--bg: #0b0f14;
|
||||||
background-color: #f8f8f8;
|
--bg-alt: #111827;
|
||||||
}
|
--focus: #60a5fa;
|
||||||
.button-red {
|
}
|
||||||
padding: 5px 10px;
|
}
|
||||||
background-color: #f44336;
|
|
||||||
color: white;
|
*{ box-sizing: border-box; }
|
||||||
border: none;
|
html, body{ height: 100%; }
|
||||||
border-radius: 5px;
|
body{
|
||||||
cursor: pointer;
|
margin: 0;
|
||||||
}
|
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
||||||
.button-test {
|
color: var(--text);
|
||||||
padding: 5px 10px;
|
background: var(--bg);
|
||||||
background-color: #4CAF50;
|
line-height: 1.55;
|
||||||
color: white;
|
}
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
#navbar{ position: sticky; top: 0; z-index: 20; }
|
||||||
cursor: pointer;
|
|
||||||
}
|
h1, h2{ margin: 0 0 12px; }
|
||||||
.placeholder-img {
|
h1{ font-size: clamp(1.5rem, 2vw, 2rem); }
|
||||||
position: absolute;
|
h2{ font-size: clamp(1.1rem, 1.6vw, 1.4rem); color: var(--muted); }
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
.content-wrapper{
|
||||||
}
|
max-width: 1100px;
|
||||||
.placeholder-img img {
|
margin: 0 auto;
|
||||||
top: 20px;
|
padding: 16px;
|
||||||
width: 60px;
|
}
|
||||||
height: 60px;
|
@media (min-width: 980px){
|
||||||
}
|
.content-wrapper{ padding: 24px; }
|
||||||
/* Styles for the message log */
|
}
|
||||||
#messageLog {
|
|
||||||
display: none;
|
.card{
|
||||||
margin-top: 20px;
|
padding: 16px;
|
||||||
width: 100%;
|
border: 1px solid var(--border);
|
||||||
height: 350px;
|
border-radius: 10px;
|
||||||
box-sizing: border-box;
|
background: var(--bg-alt);
|
||||||
resize: none;
|
}
|
||||||
overflow: auto;
|
|
||||||
white-space: nowrap;
|
.header-img{
|
||||||
}
|
margin: 24px 0;
|
||||||
.toggle-button {
|
width: auto;
|
||||||
margin-top: 20px;
|
height: 100px;
|
||||||
background-color: #007BFF;
|
}
|
||||||
color: white;
|
|
||||||
border: none;
|
/* Switch row */
|
||||||
padding: 3px 20px;
|
.switch-row{
|
||||||
cursor: pointer;
|
display: flex;
|
||||||
border-radius: 5px;
|
align-items: center;
|
||||||
}
|
justify-content: center;
|
||||||
</style>
|
gap: 12px;
|
||||||
|
margin: 12px 0 6px;
|
||||||
|
}
|
||||||
|
.switch-label{ font-weight: 600; color: var(--text); }
|
||||||
|
.switch-input{ transform: scale(1.1); }
|
||||||
|
|
||||||
|
.error{
|
||||||
|
color: var(--danger);
|
||||||
|
text-align: center;
|
||||||
|
margin: 8px 0 0;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
button{
|
||||||
|
appearance: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
font-size: 0.98rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
background: var(--brand);
|
||||||
|
}
|
||||||
|
button:hover{ background: var(--brand-hover); }
|
||||||
|
button:focus-visible{
|
||||||
|
outline: 3px solid var(--focus);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
.btn-danger{ background: var(--danger); }
|
||||||
|
.btn-ok{ background: var(--ok); }
|
||||||
|
.btn-ok:hover{ background: var(--ok-hover); }
|
||||||
|
|
||||||
|
.btn-ghost{
|
||||||
|
background: transparent;
|
||||||
|
color: var(--brand);
|
||||||
|
border: 1px solid var(--brand);
|
||||||
|
}
|
||||||
|
.btn-ghost:hover{
|
||||||
|
color: #fff;
|
||||||
|
background: var(--brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table */
|
||||||
|
.table-wrap{
|
||||||
|
margin-top: 16px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
.table-scroll{
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
table{
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 720px;
|
||||||
|
}
|
||||||
|
thead th{
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: var(--bg-alt);
|
||||||
|
color: var(--text);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
tbody td{
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
padding: 10px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
tbody tr:hover{
|
||||||
|
background: rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions{
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Job history */
|
||||||
|
details{
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
summary{
|
||||||
|
list-style: none;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--brand);
|
||||||
|
}
|
||||||
|
summary::-webkit-details-marker{ display: none; }
|
||||||
|
|
||||||
|
.log{
|
||||||
|
margin-top: 12px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--bg);
|
||||||
|
padding: 12px;
|
||||||
|
max-height: 360px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.log pre{
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sr-only{
|
||||||
|
position: absolute;
|
||||||
|
width: 1px; height: 1px;
|
||||||
|
padding: 0; margin: -1px;
|
||||||
|
overflow: hidden; clip: rect(0, 0, 1, 1);
|
||||||
|
white-space: nowrap; border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="navbar"></div>
|
<div id="navbar" role="navigation" aria-label="Primary"></div>
|
||||||
<script>
|
<script>
|
||||||
fetch('/static/html/nav.html')
|
fetch('/static/html/nav.html')
|
||||||
.then(response => response.text())
|
.then(r => r.ok ? r.text() : Promise.reject(r.status))
|
||||||
.then(data => {
|
.then(html => { document.getElementById('navbar').innerHTML = html; })
|
||||||
document.getElementById('navbar').innerHTML = data;
|
.catch(() => { document.getElementById('navbar').innerHTML = '<div class="card" role="alert">Navigation failed to load.</div>'; });
|
||||||
});
|
</script>
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="content-wrapper">
|
<main class="content-wrapper">
|
||||||
<div class="content">
|
<section class="content card" aria-labelledby="active-printers-heading">
|
||||||
<img src="/static/images/printio_logo.png" alt="Printio" class="header-img">
|
<img src="/static/images/printio_white_logo.png" alt="Printio logo" class="header-img" />
|
||||||
<h2>Active Printers</h2>
|
<h1 id="active-printers-heading">Active Printers</h1>
|
||||||
<table border="1">
|
|
||||||
<tr>
|
|
||||||
<th>Printer Name</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>Prints Left</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
{% for printer in printers %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ printer.name }}</td>
|
|
||||||
<td>{{ printer.state }}</td>
|
|
||||||
<td>{{ printer.prints_left_message }}</td>
|
|
||||||
<td>
|
|
||||||
<form action="{{ url_for('cancel_all_jobs', printer_name=printer.name) }}" method="post" style="display:inline;">
|
|
||||||
<button type="submit" class="button-red">Kill Jobs</button>
|
|
||||||
</form>
|
|
||||||
<form action="{{ url_for('test_print', printer_name=printer.name) }}" method="post" style="display:inline;">
|
|
||||||
<button type="submit" class="button-test">Test Print</button>
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- Button to toggle the message log -->
|
<div class="switch-row">
|
||||||
<button class="toggle-button" onclick="toggleMessageLog()">Job History</button>
|
<label class="switch-label" for="printOnConnect">Print on Connect</label>
|
||||||
<!-- Hidden textarea for the message log -->
|
<input
|
||||||
<textarea id="messageLog" placeholder="Job Status Log..." readonly></textarea>
|
type="checkbox"
|
||||||
|
id="printOnConnect"
|
||||||
|
name="printOnConnect"
|
||||||
|
class="switch-input"
|
||||||
|
aria-describedby="printOnConnectHelp printOnConnectError"
|
||||||
|
{{ settings.printOnConnect }}>
|
||||||
|
</div>
|
||||||
|
<p id="printOnConnectHelp" class="sr-only">If enabled, a test or queued job can print automatically upon printer connection.</p>
|
||||||
|
<div id="printOnConnectError" class="error" role="alert" aria-live="polite"></div>
|
||||||
|
|
||||||
|
<div class="table-wrap" role="region" aria-label="Printer list">
|
||||||
|
<div class="table-scroll">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Printer Name</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
<th scope="col">Prints Left</th>
|
||||||
|
<th scope="col">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for printer in printers %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ printer.name }}</td>
|
||||||
|
<td>{{ printer.state }}</td>
|
||||||
|
<td>{{ printer.prints_left_message }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="actions">
|
||||||
|
<form action="{{ url_for('cancel_all_jobs', printer_name=printer.name) }}" method="post">
|
||||||
|
<button type="submit" class="btn-danger">
|
||||||
|
<i class="fa fa-ban" aria-hidden="true"></i> Kill Jobs
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<form action="{{ url_for('test_print', printer_name=printer.name) }}" method="post">
|
||||||
|
<button type="submit" class="btn-ok">
|
||||||
|
<i class="fa fa-print" aria-hidden="true"></i> Test Print
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<details id="jobHistory">
|
||||||
// Toggles the visibility of the message log textarea
|
<summary>
|
||||||
function toggleMessageLog() {
|
<i class="fa fa-history" aria-hidden="true"></i>
|
||||||
var messageLog = document.getElementById('messageLog');
|
Job History
|
||||||
if (messageLog.style.display === '' || messageLog.style.display === 'none') {
|
</summary>
|
||||||
messageLog.style.display = 'block';
|
<div class="log" id="messageLog" aria-live="off">
|
||||||
loadPrinterJobs(); // Load job messages when showing the textarea
|
<pre id="logText">Loading…</pre>
|
||||||
} else {
|
</div>
|
||||||
messageLog.style.display = 'none';
|
</details>
|
||||||
}
|
</section>
|
||||||
}
|
</main>
|
||||||
|
|
||||||
// Load the printer job information and show in the textarea
|
<script>
|
||||||
function loadPrinterJobs() {
|
/**
|
||||||
var messageLog = document.getElementById('messageLog');
|
* Initialize the "Print on Connect" checkbox behavior.
|
||||||
var jobs = {{ job_history | tojson | safe }};
|
*/
|
||||||
var logText = "";
|
function initPrintOnConnect() {
|
||||||
|
const checkbox = document.getElementById('printOnConnect');
|
||||||
|
const errorDiv = document.getElementById('printOnConnectError');
|
||||||
|
|
||||||
jobs.forEach(function(job, index) {
|
if (!checkbox) return;
|
||||||
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";
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set the value of the textarea to display the job information
|
checkbox.addEventListener('change', () => {
|
||||||
messageLog.value = logText;
|
const value = checkbox.checked;
|
||||||
}
|
|
||||||
</script>
|
fetch('/printer-settings', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ 'print-on-connect': value })
|
||||||
|
})
|
||||||
|
.then((resp) => {
|
||||||
|
if (!resp.ok) throw new Error('Server rejected the setting');
|
||||||
|
return resp.json();
|
||||||
|
})
|
||||||
|
.then((json) => {
|
||||||
|
if (!json.reply) throw new Error(json.message || 'Server error');
|
||||||
|
// Clear any prior error
|
||||||
|
errorDiv.style.display = 'none';
|
||||||
|
errorDiv.textContent = '';
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Failed to save print-on-connect:', err);
|
||||||
|
// Revert checkbox
|
||||||
|
checkbox.checked = !value;
|
||||||
|
// Show error
|
||||||
|
errorDiv.textContent = 'Failed to save setting to server';
|
||||||
|
errorDiv.style.display = 'block';
|
||||||
|
setTimeout(() => {
|
||||||
|
errorDiv.style.display = 'none';
|
||||||
|
}, 4000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load job history on demand.
|
||||||
|
*/
|
||||||
|
function loadPrinterJobs() {
|
||||||
|
const jobs = {{ job_history | tojson | safe }};
|
||||||
|
const pre = document.getElementById('logText');
|
||||||
|
if (!pre) return;
|
||||||
|
|
||||||
|
if (!Array.isArray(jobs) || jobs.length === 0) {
|
||||||
|
pre.textContent = 'No jobs found.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a readable, tabular-ish log
|
||||||
|
const lines = jobs.map(j => {
|
||||||
|
const id = j.job_id ?? '';
|
||||||
|
const t = j.time ?? '';
|
||||||
|
const size = j.job_size ?? '';
|
||||||
|
const pn = j.printer_name ?? '';
|
||||||
|
const state = j.job_state ?? '';
|
||||||
|
const reason = j.job_reason ?? '';
|
||||||
|
return `Job ${id} | Time: ${t} | Size: ${size} | Printer: ${pn} | State: ${state} | Reason: ${reason}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
pre.textContent = lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initPrintOnConnect();
|
||||||
|
|
||||||
|
// Lazy-load job history when the <details> opens
|
||||||
|
const details = document.getElementById('jobHistory');
|
||||||
|
if (details) {
|
||||||
|
details.addEventListener('toggle', () => {
|
||||||
|
if (details.open) loadPrinterJobs();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,350 +1,288 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>WiFi Configuration</title>
|
<title>WiFi Configuration</title>
|
||||||
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico">
|
<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/css/styles.css" />
|
||||||
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
|
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css" />
|
||||||
<style>
|
<style>
|
||||||
h1 {
|
:root{
|
||||||
margin-top: 30px;
|
--bg: #0b0d10;
|
||||||
font-size: 36px;
|
--panel: #12161b;
|
||||||
color: #333;
|
--card: #141a20;
|
||||||
}
|
--muted: #c7d1db;
|
||||||
.button-container {
|
--primary: #3b82f6;
|
||||||
display: flex;
|
--primary-700: #1d4ed8;
|
||||||
justify-content: space-between;
|
--ring: rgba(59,130,246,.35);
|
||||||
align-items: center;
|
--radius: 14px;
|
||||||
margin-top: 20px;
|
--gap: 16px;
|
||||||
}
|
--ok: #22c55e;
|
||||||
.btn {
|
--warn: #f59e0b;
|
||||||
padding: 10px 20px;
|
--err: #ef4444;
|
||||||
font-size: 1.2em;
|
}
|
||||||
cursor: pointer;
|
html, body { height:100%; }
|
||||||
background-color: blue;
|
body{
|
||||||
color: white;
|
margin:0; color:var(--muted);
|
||||||
width: 150px;
|
background: linear-gradient(180deg, #0b0d10 0%, #0e1217 100%);
|
||||||
border-radius: 5px;
|
font: 500 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, sans-serif;
|
||||||
border: none;
|
-webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
|
||||||
text-decoration: none;
|
}
|
||||||
}
|
a{ color: inherit; }
|
||||||
.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 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-container {
|
/* Navbar inject target */
|
||||||
width: 80%; /* Set container width to keep content centered */
|
#navbar { position: sticky; top:0; z-index: 100; backdrop-filter: blur(6px); }
|
||||||
margin: 0 auto;
|
|
||||||
}
|
.wrapper{ max-width: 1100px; margin: 0 auto; padding: 24px 16px 56px; }
|
||||||
summary {
|
|
||||||
font-size: 1.3em;
|
/* Header */
|
||||||
font-weight: bold;
|
.page-header{ display:flex; align-items:center; gap:16px; justify-content:flex-start; margin: 4px 0 12px; }
|
||||||
cursor: pointer;
|
.page-header .header-img{ width:auto; height:72px; margin:0; position:static !important; filter: drop-shadow(0 4px 14px rgba(0,0,0,.4)); }
|
||||||
display: inline-block;
|
h1{ font-size: clamp(22px, 3vw, 32px); font-weight: 700; letter-spacing:.2px; margin:0; }
|
||||||
width: 250px; /* Set a fixed width for alignment */
|
.subtitle{ opacity:.9; font-size:.95rem; margin-top:6px; }
|
||||||
text-align: left; /* Ensures left alignment of the text */
|
|
||||||
white-space: nowrap; /* Prevents wrapping */
|
/* Cards */
|
||||||
}
|
details.card{ background: var(--card); border: 1px solid rgba(255,255,255,.06); border-radius: var(--radius);
|
||||||
summary::before {
|
overflow:hidden; box-shadow: 0 10px 30px rgba(0,0,0,.35); margin-bottom: 18px; }
|
||||||
content: "▼ "; /* Bullet symbol */
|
details.card[open]{ box-shadow: 0 16px 38px rgba(0,0,0,.45); }
|
||||||
color: black; /* Bullet color */
|
details.card > summary{ list-style:none; display:flex; align-items:center; gap:10px; cursor:pointer;
|
||||||
margin-right: 8px; /* Space between bullet and text */
|
padding: 14px 16px; background: linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,.00)); font-weight:700; }
|
||||||
font-size: 1.3em; /* Matches summary font size */
|
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);
|
||||||
.details-content {
|
transform: rotate(-45deg); transition: transform .18s ease; margin-right: 4px; opacity:.9; }
|
||||||
display: flex;
|
details.card[open] > summary .chev{ transform: rotate(45deg); }
|
||||||
flex-direction: column;
|
.card-body{ padding: 14px 16px 16px; display:grid; gap:18px; background: var(--panel); border-top: 1px solid rgba(255,255,255,.06); }
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
/* Actions / Buttons */
|
||||||
}
|
.actions{ display:flex; gap:10px; flex-wrap:wrap; align-items:center; }
|
||||||
</style>
|
.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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="navbar"></div>
|
<div id="navbar"></div>
|
||||||
<script>
|
<script>
|
||||||
fetch('/static/html/nav.html')
|
fetch('/static/html/nav.html').then(r=>r.text()).then(html=>{ document.getElementById('navbar').innerHTML = html; });
|
||||||
.then(response => response.text())
|
</script>
|
||||||
.then(data => {
|
|
||||||
document.getElementById('navbar').innerHTML = data;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="content-wrapper">
|
<main class="wrapper">
|
||||||
<div class="content">
|
<header class="page-header">
|
||||||
<h1>WiFi Internet Access</h1>
|
<img src="/static/images/internet_icon.png" alt="internet" class="header-img" />
|
||||||
<img src="/static/images/internet_icon.png" alt="internet" class="header-img">
|
<div>
|
||||||
<label id="label-status">Status:</label>
|
<h1>WiFi Internet Access</h1>
|
||||||
<div class="button-container">
|
<div class="subtitle"><span id="label-status">Status:</span></div>
|
||||||
<button class="btn" id="scanButton" onclick="scanWifi()">Scan</button>
|
</div>
|
||||||
<div class="small-btn-container">
|
</header>
|
||||||
<button class="btn small-btn" id="forgetButton" onclick="checkForgetNetworks()">Forget all</button>
|
|
||||||
<button class="btn small-btn" id="testButton" onclick="checkInternetAccess()">Test Internet</button>
|
<!-- Networks card -->
|
||||||
</div>
|
<details class="card" open>
|
||||||
</div>
|
<summary><span class="chev"></span>Networks</summary>
|
||||||
<div class="scan-container">
|
<div class="card-body">
|
||||||
<div id="scanStatus" class="status"></div>
|
<div class="actions">
|
||||||
</div>
|
<button class="btn" id="scanButton" onclick="scanWifi()"><i class="fa-solid fa-wifi"></i> Scan</button>
|
||||||
<div class="network-list">
|
<div class="small-btn-group">
|
||||||
<table id="networksTable">
|
<button class="btn alt" id="forgetButton" onclick="checkForgetNetworks()"><i class="fa-regular fa-trash-can"></i> Forget all</button>
|
||||||
<thead>
|
<button class="btn success" id="testButton" onclick="checkInternetAccess()"><i class="fa-solid fa-globe"></i> Test Internet</button>
|
||||||
<tr>
|
</div>
|
||||||
<th>SSID</th>
|
|
||||||
<th>Freq</th>
|
|
||||||
<th>Signal</th>
|
|
||||||
<th>Bssid</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<!-- List of networks will be appended here -->
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="connect-container">
|
|
||||||
<button class="btn" id="connectToNetworkButton"onclick="connectToNetwork()">Connect</button>
|
|
||||||
</div>
|
|
||||||
<details>
|
|
||||||
<summary>Connection Status</summary>
|
|
||||||
<div class="status" id="connectionStatus"></div>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary>Notice</summary>
|
|
||||||
<label id="label-wifi">*WPA3 is not currently supported. If your hotsport/Access point is setup for WPA3, please change it to WPA2 before trying to connect to it.</label>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<div id="scanStatus" class="status"></div>
|
||||||
window.onload = function() { OnPageLoad(); };
|
|
||||||
|
|
||||||
function scanWifi() {
|
<div class="grid-2">
|
||||||
console.log("Starting WiFi scan...");
|
<div class="network-list">
|
||||||
document.getElementById('connectionStatus').innerText = ""
|
<table id="networksTable">
|
||||||
document.getElementById('scanStatus').innerText = "Scanning for networks...";
|
<thead>
|
||||||
fetch('/wifi_scan')
|
<tr>
|
||||||
.then(response => response.json())
|
<th>SSID</th>
|
||||||
.then(data => {
|
<th>Freq</th>
|
||||||
console.log("Scan complete. Data received:", data);
|
<th>Signal</th>
|
||||||
document.getElementById('scanStatus').innerText = "Scan complete.";
|
<th>Bssid</th>
|
||||||
let tableBody = document.getElementById('networksTable').getElementsByTagName('tbody')[0];
|
</tr>
|
||||||
tableBody.innerHTML = '';
|
</thead>
|
||||||
data.networks.forEach(network => {
|
<tbody></tbody>
|
||||||
let row = tableBody.insertRow();
|
</table>
|
||||||
let cell1 = row.insertCell(0);
|
</div>
|
||||||
let cell2 = row.insertCell(1);
|
|
||||||
let cell3 = row.insertCell(2);
|
|
||||||
let cell4 = row.insertCell(3);
|
|
||||||
cell1.textContent = network.ssid || '';
|
|
||||||
cell2.textContent = network.Freq || '';
|
|
||||||
cell3.textContent = network.signal || '';
|
|
||||||
cell4.textContent = network.bssid || '';
|
|
||||||
row.addEventListener('click', function() {
|
|
||||||
const rows = document.querySelectorAll('#networksTable tbody tr');
|
|
||||||
rows.forEach(r => r.classList.remove('selected'));
|
|
||||||
row.classList.add('selected');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("Error during scan:", error);
|
|
||||||
document.getElementById('scanStatus').innerText = "Error during scan.";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function connectToNetwork() {
|
<div class="actions" style="align-items:flex-start;">
|
||||||
console.log("Attempting to connect to network...");
|
<button class="btn warn" id="connectToNetworkButton" onclick="connectToNetwork()">
|
||||||
const table = document.getElementById('networksTable');
|
<i class="fa-solid fa-plug"></i> Connect
|
||||||
const selectedRow = table.querySelector('tbody tr.selected');
|
</button>
|
||||||
if (!selectedRow) {
|
</div>
|
||||||
document.getElementById('connectionStatus').innerText = "Please select a network to connect to.";
|
</div>
|
||||||
return;
|
</div>
|
||||||
}
|
</details>
|
||||||
const ssid = selectedRow.cells[0].textContent;
|
|
||||||
const bssid = selectedRow.cells[3].textContent;
|
|
||||||
const password = prompt("Enter the WiFi password for " + ssid + ":");
|
|
||||||
if (!password) {
|
|
||||||
document.getElementById('connectionStatus').innerText = "Connection cancelled.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
document.getElementById('connectionStatus').innerText = "Please wait...";
|
|
||||||
document.getElementById('connectToNetworkButton').disabled = true;
|
|
||||||
document.getElementById('scanButton').disabled = true;
|
|
||||||
document.getElementById('testButton').disabled = true;
|
|
||||||
document.getElementById('forgetButton').disabled = true;
|
|
||||||
fetch('/wifi_connect', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ ssid: ssid, bssid: bssid, password: password })
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
console.log("Connection attempt result:", data);
|
|
||||||
document.getElementById('connectionStatus').innerText = data.message;
|
|
||||||
checkWifiStatus();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("Error during connection attempt:", error);
|
|
||||||
document.getElementById('connectionStatus').innerText = "Error during connection attempt.";
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
// Re-enable buttons after the connection attempt
|
|
||||||
document.getElementById('connectToNetworkButton').disabled = false;
|
|
||||||
document.getElementById('scanButton').disabled = false;
|
|
||||||
document.getElementById('testButton').disabled = false;
|
|
||||||
document.getElementById('forgetButton').disabled = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkInternetAccess() {
|
<!-- Connection status -->
|
||||||
fetch('/wifi_test')
|
<details class="card">
|
||||||
.then(response => response.json())
|
<summary><span class="chev"></span>Connection Status</summary>
|
||||||
.then(data => {
|
<div class="card-body">
|
||||||
const statusMessage = data.success ? "Internet Access Ok" : "No Internet Access";
|
<div class="status" id="connectionStatus"></div>
|
||||||
alert(statusMessage); // Display message in a popup
|
</div>
|
||||||
})
|
</details>
|
||||||
.catch(error => {
|
|
||||||
console.error("Error checking internet access:", error);
|
|
||||||
alert("Error checking internet access."); // Display error in a popup
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkForgetNetworks() {
|
<!-- Notice -->
|
||||||
fetch('/wifi_forget')
|
<details class="card">
|
||||||
.then(response => response.json())
|
<summary><span class="chev"></span>Notice</summary>
|
||||||
.then(data => {
|
<div class="card-body">
|
||||||
const statusMessage = data.success ? "All networks forgotten" : "Nothing happened";
|
<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>
|
||||||
alert(statusMessage); // Display message in a popup
|
</div>
|
||||||
})
|
</details>
|
||||||
.catch(error => {
|
</main>
|
||||||
console.error("Error forgetting networks:", error);
|
|
||||||
alert("Error forgetting networks."); // Display error in a popup
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkWifiStatus() {
|
<script>
|
||||||
fetch('/wifi_status')
|
window.onload = function() { OnPageLoad(); };
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
console.log("Data received:", data);
|
|
||||||
document.getElementById('label-status').innerText = "Status: " + data.msg;
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error("Error getting wifi status:", error);
|
|
||||||
document.getElementById('label-status').innerText = "Status: ...";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function OnPageLoad(){
|
function scanWifi() {
|
||||||
checkWifiStatus();
|
console.log("Starting WiFi scan...");
|
||||||
}
|
document.getElementById('connectionStatus').innerText = "";
|
||||||
|
const ss = document.getElementById('scanStatus');
|
||||||
|
ss.innerText = "Scanning for networks...";
|
||||||
|
fetch('/wifi_scan')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log("Scan complete. Data received:", data);
|
||||||
|
ss.innerText = "Scan complete.";
|
||||||
|
let tableBody = document.getElementById('networksTable').getElementsByTagName('tbody')[0];
|
||||||
|
tableBody.innerHTML = '';
|
||||||
|
(data.networks || []).forEach(network => {
|
||||||
|
let row = tableBody.insertRow();
|
||||||
|
row.insertCell(0).textContent = network.ssid || '';
|
||||||
|
row.insertCell(1).textContent = network.Freq || '';
|
||||||
|
row.insertCell(2).textContent = network.signal || '';
|
||||||
|
row.insertCell(3).textContent = network.bssid || '';
|
||||||
|
row.addEventListener('click', function() {
|
||||||
|
const rows = document.querySelectorAll('#networksTable tbody tr');
|
||||||
|
rows.forEach(r => r.classList.remove('selected'));
|
||||||
|
row.classList.add('selected');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Error during scan:", error);
|
||||||
|
ss.innerText = "Error during scan.";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
function connectToNetwork() {
|
||||||
|
console.log("Attempting to connect to network...");
|
||||||
|
const table = document.getElementById('networksTable');
|
||||||
|
const selectedRow = table.querySelector('tbody tr.selected');
|
||||||
|
if (!selectedRow) {
|
||||||
|
document.getElementById('connectionStatus').innerText = "Please select a network to connect to.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ssid = selectedRow.cells[0].textContent;
|
||||||
|
const bssid = selectedRow.cells[3].textContent;
|
||||||
|
const password = prompt("Enter the WiFi password for " + ssid + ":");
|
||||||
|
if (!password) {
|
||||||
|
document.getElementById('connectionStatus').innerText = "Connection cancelled.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById('connectionStatus').innerText = "Please wait...";
|
||||||
|
document.getElementById('connectToNetworkButton').disabled = true;
|
||||||
|
document.getElementById('scanButton').disabled = true;
|
||||||
|
document.getElementById('testButton').disabled = true;
|
||||||
|
document.getElementById('forgetButton').disabled = true;
|
||||||
|
fetch('/wifi_connect', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ ssid: ssid, bssid: bssid, password: password })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log("Connection attempt result:", data);
|
||||||
|
document.getElementById('connectionStatus').innerText = data.message;
|
||||||
|
checkWifiStatus();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Error during connection attempt:", error);
|
||||||
|
document.getElementById('connectionStatus').innerText = "Error during connection attempt.";
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
document.getElementById('connectToNetworkButton').disabled = false;
|
||||||
|
document.getElementById('scanButton').disabled = false;
|
||||||
|
document.getElementById('testButton').disabled = false;
|
||||||
|
document.getElementById('forgetButton').disabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkInternetAccess() {
|
||||||
|
fetch('/wifi_test')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const statusMessage = data.success ? "Internet Access Ok" : "No Internet Access";
|
||||||
|
alert(statusMessage);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Error checking internet access:", error);
|
||||||
|
alert("Error checking internet access.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkForgetNetworks() {
|
||||||
|
fetch('/wifi_forget')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const statusMessage = data.success ? "All networks forgotten" : "Nothing happened";
|
||||||
|
alert(statusMessage);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Error forgetting networks:", error);
|
||||||
|
alert("Error forgetting networks.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWifiStatus() {
|
||||||
|
fetch('/wifi_status')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log("Data received:", data);
|
||||||
|
document.getElementById('label-status').innerText = "Status: " + (data.msg || '...');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error("Error getting wifi status:", error);
|
||||||
|
document.getElementById('label-status').innerText = "Status: ...";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function OnPageLoad(){ checkWifiStatus(); }
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico">
|
<title>Activation Required</title>
|
||||||
<link rel="stylesheet" href="/static/css/styles.css">
|
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico" />
|
||||||
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css">
|
<link rel="stylesheet" href="/static/css/styles.css" />
|
||||||
|
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css" />
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--brand: #1e40af; /* blue-800 */
|
||||||
|
--brand-hover:#15327f;
|
||||||
|
--danger: #ef4444; /* red-500 */
|
||||||
|
--danger-ink: #7f1d1d; /* dark red text */
|
||||||
|
--border: #d1d5db; /* gray-300 */
|
||||||
|
--text: #111827; /* gray-900 */
|
||||||
|
--muted: #4b5563; /* gray-600 */
|
||||||
|
--bg: #ffffff;
|
||||||
|
--bg-alt: #f9fafb; /* gray-50 */
|
||||||
|
--focus: #2563eb; /* blue-600 */
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark){
|
||||||
|
:root{
|
||||||
|
--brand: #60a5fa;
|
||||||
|
--brand-hover:#3b82f6;
|
||||||
|
--danger:#f87171;
|
||||||
|
--danger-ink:#fecaca;
|
||||||
|
--border:#374151;
|
||||||
|
--text:#e5e7eb;
|
||||||
|
--muted:#9ca3af;
|
||||||
|
--bg:#0b0f14;
|
||||||
|
--bg-alt:#111827;
|
||||||
|
--focus:#60a5fa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
<style>
|
*{ box-sizing: border-box; }
|
||||||
.activation-message {
|
html, body{ height: 100%; }
|
||||||
text-align: center;
|
body{
|
||||||
font-size: 1.5em;
|
margin: 0;
|
||||||
color: #ff1493; /* Neon pink/red color */
|
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
||||||
}
|
color: var(--text);
|
||||||
|
background: var(--bg);
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
.activation-message a {
|
#navbar{ position: sticky; top: 0; z-index: 20; }
|
||||||
color: blue; /* Blue color for the link */
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.activation-message a:hover {
|
.content-wrapper{
|
||||||
text-decoration: underline;
|
max-width: 900px;
|
||||||
}
|
margin: 0 auto;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
@media (min-width: 980px){
|
||||||
|
.content-wrapper{ padding: 24px; }
|
||||||
|
}
|
||||||
|
|
||||||
.activation-message .primary-message {
|
.card{
|
||||||
margin-bottom: 20px;
|
padding: 20px;
|
||||||
font-weight: bold;
|
border: 1px solid var(--border);
|
||||||
}
|
border-radius: 12px;
|
||||||
</style>
|
background: var(--bg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.center{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1{ margin: 0; font-size: clamp(1.5rem, 2vw, 2rem); }
|
||||||
|
|
||||||
|
.alert{
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: start;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border: 1px solid var(--danger);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: color-mix(in srgb, var(--danger) 12%, transparent);
|
||||||
|
}
|
||||||
|
.alert i{ color: var(--danger); }
|
||||||
|
.alert strong{ color: var(--danger-ink); }
|
||||||
|
|
||||||
|
.actions{
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn{
|
||||||
|
appearance: none;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
background: var(--brand);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.btn:hover{ background: var(--brand-hover); }
|
||||||
|
.btn:focus-visible{
|
||||||
|
outline: 3px solid var(--focus);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.muted{ color: var(--muted); font-size: 0.96rem; }
|
||||||
|
|
||||||
|
.sr-only{
|
||||||
|
position: absolute;
|
||||||
|
width: 1px; height: 1px;
|
||||||
|
padding: 0; margin: -1px;
|
||||||
|
overflow: hidden; clip: rect(0,0,1,1);
|
||||||
|
white-space: nowrap; border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="navbar"></div>
|
<div id="navbar" role="navigation" aria-label="Primary"></div>
|
||||||
<script>
|
<script>
|
||||||
fetch('/static/html/nav.html')
|
fetch('/static/html/nav.html')
|
||||||
.then(response => response.text())
|
.then(r => r.ok ? r.text() : Promise.reject(r.status))
|
||||||
.then(data => {
|
.then(html => { document.getElementById('navbar').innerHTML = html; })
|
||||||
document.getElementById('navbar').innerHTML = data;
|
.catch(() => {
|
||||||
});
|
document.getElementById('navbar').innerHTML =
|
||||||
</script>
|
'<div class="card" role="alert">Navigation failed to load.</div>';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="activation-message">
|
<main class="content-wrapper">
|
||||||
<p class="primary-message">This software is not activated!</p>
|
<section class="card center" aria-labelledby="activation-heading">
|
||||||
<p>Please go to the <a href="/about">about</a> page to activate this software.</p>
|
<h1 id="activation-heading">Activation Required</h1>
|
||||||
</div>
|
|
||||||
|
<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>
|
</body>
|
||||||
</html>
|
</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