From c37cf023c15884efff9def3ff4568491e3b761ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=9C=EA=B3=A8=EC=95=BD=EC=82=AC?= Date: Thu, 11 Sep 2025 13:45:29 +0900 Subject: [PATCH] =?UTF-8?q?=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94=20?= =?UTF-8?q?=EB=B8=8C=EB=9E=9C=EB=94=A9=EC=9D=84=20PharmQ=20Super=20Admin?= =?UTF-8?q?=20(PSA)=EB=A1=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - base.html 상단 네비게이션 브랜딩 변경 - 팜큐 약국 관리 시스템 → PharmQ Super Admin (PSA) - UI 일관성 향상을 위한 브랜딩 통합 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- farmq-admin/check_existing_data.py | 104 + farmq-admin/debug_machine.py | 79 + farmq-admin/flask-venv/bin/Activate.ps1 | 247 + farmq-admin/flask-venv/bin/activate | 70 + farmq-admin/flask-venv/bin/activate.csh | 27 + farmq-admin/flask-venv/bin/activate.fish | 69 + farmq-admin/flask-venv/bin/flask | 8 + farmq-admin/flask-venv/bin/normalizer | 8 + farmq-admin/flask-venv/bin/pip | 8 + farmq-admin/flask-venv/bin/pip3 | 8 + farmq-admin/flask-venv/bin/pip3.12 | 8 + farmq-admin/flask-venv/bin/python | 1 + farmq-admin/flask-venv/bin/python3 | 1 + farmq-admin/flask-venv/bin/python3.12 | 1 + .../site/python3.12/greenlet/greenlet.h | 164 + farmq-admin/flask-venv/lib64 | 1 + farmq-admin/flask-venv/pyvenv.cfg | 5 + farmq-admin/init_sample_data.py | 209 + farmq-admin/static/novnc/AUTHORS | 13 + farmq-admin/static/novnc/LICENSE.txt | 62 + farmq-admin/static/novnc/README.md | 230 + farmq-admin/static/novnc/app/error-handler.js | 79 + farmq-admin/static/novnc/app/images/alt.svg | 92 + .../static/novnc/app/images/clipboard.svg | 106 + .../static/novnc/app/images/connect.svg | 96 + farmq-admin/static/novnc/app/images/ctrl.svg | 96 + .../static/novnc/app/images/ctrlaltdel.svg | 100 + .../static/novnc/app/images/disconnect.svg | 94 + farmq-admin/static/novnc/app/images/drag.svg | 76 + farmq-admin/static/novnc/app/images/error.svg | 81 + farmq-admin/static/novnc/app/images/esc.svg | 92 + .../static/novnc/app/images/expander.svg | 69 + .../static/novnc/app/images/fullscreen.svg | 93 + .../static/novnc/app/images/handle.svg | 82 + .../static/novnc/app/images/handle_bg.svg | 172 + .../static/novnc/app/images/icons/Makefile | 42 + .../novnc/app/images/icons/novnc-icon-sm.svg | 163 + .../novnc/app/images/icons/novnc-icon.svg | 163 + .../novnc/app/images/icons/novnc-ios-120.png | Bin 0 -> 3215 bytes .../novnc/app/images/icons/novnc-ios-152.png | Bin 0 -> 4190 bytes .../novnc/app/images/icons/novnc-ios-167.png | Bin 0 -> 4574 bytes .../novnc/app/images/icons/novnc-ios-180.png | Bin 0 -> 4730 bytes .../novnc/app/images/icons/novnc-ios-40.png | Bin 0 -> 1245 bytes .../novnc/app/images/icons/novnc-ios-58.png | Bin 0 -> 1602 bytes .../novnc/app/images/icons/novnc-ios-60.png | Bin 0 -> 1595 bytes .../novnc/app/images/icons/novnc-ios-80.png | Bin 0 -> 1825 bytes .../novnc/app/images/icons/novnc-ios-87.png | Bin 0 -> 2708 bytes .../novnc/app/images/icons/novnc-ios-icon.svg | 183 + .../static/novnc/app/images/icons/novnc.ico | Bin 0 -> 310566 bytes farmq-admin/static/novnc/app/images/info.svg | 81 + .../static/novnc/app/images/keyboard.svg | 88 + farmq-admin/static/novnc/app/images/power.svg | 87 + .../static/novnc/app/images/settings.svg | 76 + farmq-admin/static/novnc/app/images/tab.svg | 86 + .../novnc/app/images/toggleextrakeys.svg | 90 + .../static/novnc/app/images/warning.svg | 81 + .../static/novnc/app/images/windows.svg | 65 + farmq-admin/static/novnc/app/locale/README | 1 + farmq-admin/static/novnc/app/locale/cs.json | 71 + farmq-admin/static/novnc/app/locale/de.json | 69 + farmq-admin/static/novnc/app/locale/el.json | 69 + farmq-admin/static/novnc/app/locale/es.json | 68 + farmq-admin/static/novnc/app/locale/fr.json | 78 + farmq-admin/static/novnc/app/locale/it.json | 72 + farmq-admin/static/novnc/app/locale/ja.json | 72 + farmq-admin/static/novnc/app/locale/ko.json | 70 + farmq-admin/static/novnc/app/locale/nl.json | 73 + farmq-admin/static/novnc/app/locale/pl.json | 69 + .../static/novnc/app/locale/pt_BR.json | 72 + farmq-admin/static/novnc/app/locale/ru.json | 72 + farmq-admin/static/novnc/app/locale/sv.json | 80 + farmq-admin/static/novnc/app/locale/tr.json | 69 + .../static/novnc/app/locale/zh_CN.json | 69 + .../static/novnc/app/locale/zh_TW.json | 69 + farmq-admin/static/novnc/app/localization.js | 179 + farmq-admin/static/novnc/app/sounds/CREDITS | 4 + farmq-admin/static/novnc/app/sounds/bell.mp3 | Bin 0 -> 4531 bytes farmq-admin/static/novnc/app/sounds/bell.oga | Bin 0 -> 8495 bytes .../static/novnc/app/styles/Orbitron700.ttf | Bin 0 -> 38580 bytes .../static/novnc/app/styles/Orbitron700.woff | Bin 0 -> 17472 bytes farmq-admin/static/novnc/app/styles/base.css | 922 ++++ farmq-admin/static/novnc/app/styles/input.css | 281 ++ farmq-admin/static/novnc/app/ui.js | 1782 +++++++ farmq-admin/static/novnc/app/webutil.js | 186 + farmq-admin/static/novnc/core/base64.js | 104 + .../static/novnc/core/decoders/copyrect.js | 27 + .../static/novnc/core/decoders/hextile.js | 191 + .../static/novnc/core/decoders/jpeg.js | 141 + farmq-admin/static/novnc/core/decoders/raw.js | 66 + farmq-admin/static/novnc/core/decoders/rre.js | 44 + .../static/novnc/core/decoders/tight.js | 331 ++ .../static/novnc/core/decoders/tightpng.js | 27 + .../static/novnc/core/decoders/zrle.js | 185 + farmq-admin/static/novnc/core/deflator.js | 85 + farmq-admin/static/novnc/core/des.js | 266 + farmq-admin/static/novnc/core/display.js | 525 ++ farmq-admin/static/novnc/core/encodings.js | 48 + farmq-admin/static/novnc/core/inflator.js | 66 + .../static/novnc/core/input/domkeytable.js | 311 ++ .../static/novnc/core/input/fixedkeys.js | 129 + .../static/novnc/core/input/gesturehandler.js | 567 +++ .../static/novnc/core/input/keyboard.js | 283 ++ farmq-admin/static/novnc/core/input/keysym.js | 616 +++ .../static/novnc/core/input/keysymdef.js | 688 +++ farmq-admin/static/novnc/core/input/util.js | 191 + farmq-admin/static/novnc/core/input/vkeys.js | 116 + .../static/novnc/core/input/xtscancodes.js | 173 + farmq-admin/static/novnc/core/ra2.js | 567 +++ farmq-admin/static/novnc/core/rfb.js | 3373 +++++++++++++ farmq-admin/static/novnc/core/util/browser.js | 152 + farmq-admin/static/novnc/core/util/cursor.js | 247 + farmq-admin/static/novnc/core/util/element.js | 32 + farmq-admin/static/novnc/core/util/events.js | 138 + .../static/novnc/core/util/eventtarget.js | 35 + farmq-admin/static/novnc/core/util/int.js | 15 + farmq-admin/static/novnc/core/util/logging.js | 56 + farmq-admin/static/novnc/core/util/md5.js | 79 + farmq-admin/static/novnc/core/util/strings.js | 28 + farmq-admin/static/novnc/core/websock.js | 353 ++ farmq-admin/static/novnc/docs/API-internal.md | 89 + farmq-admin/static/novnc/docs/API.md | 546 +++ farmq-admin/static/novnc/docs/EMBEDDING.md | 105 + farmq-admin/static/novnc/docs/LIBRARY.md | 31 + .../static/novnc/docs/LICENSE.BSD-2-Clause | 22 + .../static/novnc/docs/LICENSE.BSD-3-Clause | 24 + farmq-admin/static/novnc/docs/LICENSE.MPL-2.0 | 373 ++ farmq-admin/static/novnc/docs/LICENSE.OFL-1.1 | 91 + .../static/novnc/docs/flash_policy.txt | 4 + farmq-admin/static/novnc/docs/links | 76 + farmq-admin/static/novnc/docs/notes | 5 + farmq-admin/static/novnc/docs/novnc_proxy.1 | 37 + farmq-admin/static/novnc/docs/rfb_notes | 147 + .../static/novnc/docs/rfbproto-3.3.pdf | Bin 0 -> 110778 bytes .../static/novnc/docs/rfbproto-3.7.pdf | Bin 0 -> 165552 bytes .../static/novnc/docs/rfbproto-3.8.pdf | Bin 0 -> 143840 bytes farmq-admin/static/novnc/karma.conf.js | 85 + farmq-admin/static/novnc/package.json | 79 + farmq-admin/static/novnc/po/.eslintrc | 5 + farmq-admin/static/novnc/po/Makefile | 36 + farmq-admin/static/novnc/po/cs.po | 294 ++ farmq-admin/static/novnc/po/de.po | 303 ++ farmq-admin/static/novnc/po/el.po | 323 ++ farmq-admin/static/novnc/po/es.po | 284 ++ farmq-admin/static/novnc/po/fr.po | 300 ++ farmq-admin/static/novnc/po/it.po | 300 ++ farmq-admin/static/novnc/po/ja.po | 324 ++ farmq-admin/static/novnc/po/ko.po | 290 ++ farmq-admin/static/novnc/po/nl.po | 322 ++ farmq-admin/static/novnc/po/noVNC.pot | 332 ++ farmq-admin/static/novnc/po/pl.po | 325 ++ farmq-admin/static/novnc/po/po2js | 43 + farmq-admin/static/novnc/po/pt_BR.po | 299 ++ farmq-admin/static/novnc/po/ru.po | 302 ++ farmq-admin/static/novnc/po/sv.po | 339 ++ farmq-admin/static/novnc/po/tr.po | 288 ++ farmq-admin/static/novnc/po/xgettext-html | 119 + farmq-admin/static/novnc/po/zh_CN.po | 284 ++ farmq-admin/static/novnc/po/zh_TW.po | 285 ++ farmq-admin/static/novnc/snap/hooks/configure | 3 + .../static/novnc/snap/local/svc_wrapper.sh | 29 + farmq-admin/static/novnc/snap/snapcraft.yaml | 61 + farmq-admin/static/novnc/tests/.eslintrc | 15 + farmq-admin/static/novnc/tests/assertions.js | 96 + .../static/novnc/tests/fake.websocket.js | 88 + farmq-admin/static/novnc/tests/playback-ui.js | 215 + farmq-admin/static/novnc/tests/playback.js | 178 + farmq-admin/static/novnc/tests/test.base64.js | 33 + .../static/novnc/tests/test.browser.js | 244 + .../static/novnc/tests/test.copyrect.js | 83 + .../static/novnc/tests/test.deflator.js | 82 + .../static/novnc/tests/test.display.js | 399 ++ .../static/novnc/tests/test.gesturehandler.js | 1026 ++++ farmq-admin/static/novnc/tests/test.helper.js | 207 + .../static/novnc/tests/test.hextile.js | 232 + .../static/novnc/tests/test.inflator.js | 113 + farmq-admin/static/novnc/tests/test.int.js | 16 + farmq-admin/static/novnc/tests/test.jpeg.js | 288 ++ .../static/novnc/tests/test.keyboard.js | 543 +++ .../static/novnc/tests/test.localization.js | 61 + farmq-admin/static/novnc/tests/test.ra2.js | 357 ++ farmq-admin/static/novnc/tests/test.raw.js | 129 + farmq-admin/static/novnc/tests/test.rfb.js | 4321 +++++++++++++++++ farmq-admin/static/novnc/tests/test.rre.js | 107 + farmq-admin/static/novnc/tests/test.tight.js | 394 ++ .../static/novnc/tests/test.tightpng.js | 144 + farmq-admin/static/novnc/tests/test.util.js | 89 + .../static/novnc/tests/test.websock.js | 552 +++ .../static/novnc/tests/test.webutil.js | 216 + farmq-admin/static/novnc/tests/test.zrle.js | 124 + .../static/novnc/tests/vnc_playback.html | 26 + farmq-admin/static/novnc/utils/.eslintrc | 8 + farmq-admin/static/novnc/utils/README.md | 14 + .../static/novnc/utils/b64-to-binary.pl | 17 + farmq-admin/static/novnc/utils/convert.js | 140 + .../static/novnc/utils/genkeysymdef.js | 127 + farmq-admin/static/novnc/utils/novnc_proxy | 214 + farmq-admin/static/novnc/utils/u2x11 | 28 + farmq-admin/static/novnc/utils/validate | 45 + farmq-admin/static/novnc/vendor/pako/LICENSE | 21 + .../static/novnc/vendor/pako/README.md | 6 + farmq-admin/static/novnc/vnc.html | 341 ++ farmq-admin/static/novnc/vnc_lite.html | 187 + farmq-admin/templates/base.html | 4 +- farmq-admin/templates/vm_list.html | 420 ++ farmq-admin/templates/vnc_console.html | 244 + farmq-admin/templates/vnc_redirect.html | 248 + farmq-admin/utils/proxmox_client.py | 165 + 207 files changed, 38417 insertions(+), 2 deletions(-) create mode 100644 farmq-admin/check_existing_data.py create mode 100644 farmq-admin/debug_machine.py create mode 100644 farmq-admin/flask-venv/bin/Activate.ps1 create mode 100644 farmq-admin/flask-venv/bin/activate create mode 100644 farmq-admin/flask-venv/bin/activate.csh create mode 100644 farmq-admin/flask-venv/bin/activate.fish create mode 100755 farmq-admin/flask-venv/bin/flask create mode 100755 farmq-admin/flask-venv/bin/normalizer create mode 100755 farmq-admin/flask-venv/bin/pip create mode 100755 farmq-admin/flask-venv/bin/pip3 create mode 100755 farmq-admin/flask-venv/bin/pip3.12 create mode 120000 farmq-admin/flask-venv/bin/python create mode 120000 farmq-admin/flask-venv/bin/python3 create mode 120000 farmq-admin/flask-venv/bin/python3.12 create mode 100644 farmq-admin/flask-venv/include/site/python3.12/greenlet/greenlet.h create mode 120000 farmq-admin/flask-venv/lib64 create mode 100644 farmq-admin/flask-venv/pyvenv.cfg create mode 100644 farmq-admin/init_sample_data.py create mode 100644 farmq-admin/static/novnc/AUTHORS create mode 100644 farmq-admin/static/novnc/LICENSE.txt create mode 100644 farmq-admin/static/novnc/README.md create mode 100644 farmq-admin/static/novnc/app/error-handler.js create mode 100644 farmq-admin/static/novnc/app/images/alt.svg create mode 100644 farmq-admin/static/novnc/app/images/clipboard.svg create mode 100644 farmq-admin/static/novnc/app/images/connect.svg create mode 100644 farmq-admin/static/novnc/app/images/ctrl.svg create mode 100644 farmq-admin/static/novnc/app/images/ctrlaltdel.svg create mode 100644 farmq-admin/static/novnc/app/images/disconnect.svg create mode 100644 farmq-admin/static/novnc/app/images/drag.svg create mode 100644 farmq-admin/static/novnc/app/images/error.svg create mode 100644 farmq-admin/static/novnc/app/images/esc.svg create mode 100644 farmq-admin/static/novnc/app/images/expander.svg create mode 100644 farmq-admin/static/novnc/app/images/fullscreen.svg create mode 100644 farmq-admin/static/novnc/app/images/handle.svg create mode 100644 farmq-admin/static/novnc/app/images/handle_bg.svg create mode 100644 farmq-admin/static/novnc/app/images/icons/Makefile create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc-icon-sm.svg create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc-icon.svg create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc-ios-120.png create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc-ios-152.png create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc-ios-167.png create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc-ios-180.png create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc-ios-40.png create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc-ios-58.png create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc-ios-60.png create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc-ios-80.png create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc-ios-87.png create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc-ios-icon.svg create mode 100644 farmq-admin/static/novnc/app/images/icons/novnc.ico create mode 100644 farmq-admin/static/novnc/app/images/info.svg create mode 100644 farmq-admin/static/novnc/app/images/keyboard.svg create mode 100644 farmq-admin/static/novnc/app/images/power.svg create mode 100644 farmq-admin/static/novnc/app/images/settings.svg create mode 100644 farmq-admin/static/novnc/app/images/tab.svg create mode 100644 farmq-admin/static/novnc/app/images/toggleextrakeys.svg create mode 100644 farmq-admin/static/novnc/app/images/warning.svg create mode 100644 farmq-admin/static/novnc/app/images/windows.svg create mode 100644 farmq-admin/static/novnc/app/locale/README create mode 100644 farmq-admin/static/novnc/app/locale/cs.json create mode 100644 farmq-admin/static/novnc/app/locale/de.json create mode 100644 farmq-admin/static/novnc/app/locale/el.json create mode 100644 farmq-admin/static/novnc/app/locale/es.json create mode 100644 farmq-admin/static/novnc/app/locale/fr.json create mode 100644 farmq-admin/static/novnc/app/locale/it.json create mode 100644 farmq-admin/static/novnc/app/locale/ja.json create mode 100644 farmq-admin/static/novnc/app/locale/ko.json create mode 100644 farmq-admin/static/novnc/app/locale/nl.json create mode 100644 farmq-admin/static/novnc/app/locale/pl.json create mode 100644 farmq-admin/static/novnc/app/locale/pt_BR.json create mode 100644 farmq-admin/static/novnc/app/locale/ru.json create mode 100644 farmq-admin/static/novnc/app/locale/sv.json create mode 100644 farmq-admin/static/novnc/app/locale/tr.json create mode 100644 farmq-admin/static/novnc/app/locale/zh_CN.json create mode 100644 farmq-admin/static/novnc/app/locale/zh_TW.json create mode 100644 farmq-admin/static/novnc/app/localization.js create mode 100644 farmq-admin/static/novnc/app/sounds/CREDITS create mode 100644 farmq-admin/static/novnc/app/sounds/bell.mp3 create mode 100644 farmq-admin/static/novnc/app/sounds/bell.oga create mode 100644 farmq-admin/static/novnc/app/styles/Orbitron700.ttf create mode 100644 farmq-admin/static/novnc/app/styles/Orbitron700.woff create mode 100644 farmq-admin/static/novnc/app/styles/base.css create mode 100644 farmq-admin/static/novnc/app/styles/input.css create mode 100644 farmq-admin/static/novnc/app/ui.js create mode 100644 farmq-admin/static/novnc/app/webutil.js create mode 100644 farmq-admin/static/novnc/core/base64.js create mode 100644 farmq-admin/static/novnc/core/decoders/copyrect.js create mode 100644 farmq-admin/static/novnc/core/decoders/hextile.js create mode 100644 farmq-admin/static/novnc/core/decoders/jpeg.js create mode 100644 farmq-admin/static/novnc/core/decoders/raw.js create mode 100644 farmq-admin/static/novnc/core/decoders/rre.js create mode 100644 farmq-admin/static/novnc/core/decoders/tight.js create mode 100644 farmq-admin/static/novnc/core/decoders/tightpng.js create mode 100644 farmq-admin/static/novnc/core/decoders/zrle.js create mode 100644 farmq-admin/static/novnc/core/deflator.js create mode 100644 farmq-admin/static/novnc/core/des.js create mode 100644 farmq-admin/static/novnc/core/display.js create mode 100644 farmq-admin/static/novnc/core/encodings.js create mode 100644 farmq-admin/static/novnc/core/inflator.js create mode 100644 farmq-admin/static/novnc/core/input/domkeytable.js create mode 100644 farmq-admin/static/novnc/core/input/fixedkeys.js create mode 100644 farmq-admin/static/novnc/core/input/gesturehandler.js create mode 100644 farmq-admin/static/novnc/core/input/keyboard.js create mode 100644 farmq-admin/static/novnc/core/input/keysym.js create mode 100644 farmq-admin/static/novnc/core/input/keysymdef.js create mode 100644 farmq-admin/static/novnc/core/input/util.js create mode 100644 farmq-admin/static/novnc/core/input/vkeys.js create mode 100644 farmq-admin/static/novnc/core/input/xtscancodes.js create mode 100644 farmq-admin/static/novnc/core/ra2.js create mode 100644 farmq-admin/static/novnc/core/rfb.js create mode 100644 farmq-admin/static/novnc/core/util/browser.js create mode 100644 farmq-admin/static/novnc/core/util/cursor.js create mode 100644 farmq-admin/static/novnc/core/util/element.js create mode 100644 farmq-admin/static/novnc/core/util/events.js create mode 100644 farmq-admin/static/novnc/core/util/eventtarget.js create mode 100644 farmq-admin/static/novnc/core/util/int.js create mode 100644 farmq-admin/static/novnc/core/util/logging.js create mode 100644 farmq-admin/static/novnc/core/util/md5.js create mode 100644 farmq-admin/static/novnc/core/util/strings.js create mode 100644 farmq-admin/static/novnc/core/websock.js create mode 100644 farmq-admin/static/novnc/docs/API-internal.md create mode 100644 farmq-admin/static/novnc/docs/API.md create mode 100644 farmq-admin/static/novnc/docs/EMBEDDING.md create mode 100644 farmq-admin/static/novnc/docs/LIBRARY.md create mode 100644 farmq-admin/static/novnc/docs/LICENSE.BSD-2-Clause create mode 100644 farmq-admin/static/novnc/docs/LICENSE.BSD-3-Clause create mode 100644 farmq-admin/static/novnc/docs/LICENSE.MPL-2.0 create mode 100644 farmq-admin/static/novnc/docs/LICENSE.OFL-1.1 create mode 100644 farmq-admin/static/novnc/docs/flash_policy.txt create mode 100644 farmq-admin/static/novnc/docs/links create mode 100644 farmq-admin/static/novnc/docs/notes create mode 100644 farmq-admin/static/novnc/docs/novnc_proxy.1 create mode 100644 farmq-admin/static/novnc/docs/rfb_notes create mode 100644 farmq-admin/static/novnc/docs/rfbproto-3.3.pdf create mode 100644 farmq-admin/static/novnc/docs/rfbproto-3.7.pdf create mode 100644 farmq-admin/static/novnc/docs/rfbproto-3.8.pdf create mode 100644 farmq-admin/static/novnc/karma.conf.js create mode 100644 farmq-admin/static/novnc/package.json create mode 100644 farmq-admin/static/novnc/po/.eslintrc create mode 100644 farmq-admin/static/novnc/po/Makefile create mode 100644 farmq-admin/static/novnc/po/cs.po create mode 100644 farmq-admin/static/novnc/po/de.po create mode 100644 farmq-admin/static/novnc/po/el.po create mode 100644 farmq-admin/static/novnc/po/es.po create mode 100644 farmq-admin/static/novnc/po/fr.po create mode 100644 farmq-admin/static/novnc/po/it.po create mode 100644 farmq-admin/static/novnc/po/ja.po create mode 100644 farmq-admin/static/novnc/po/ko.po create mode 100644 farmq-admin/static/novnc/po/nl.po create mode 100644 farmq-admin/static/novnc/po/noVNC.pot create mode 100644 farmq-admin/static/novnc/po/pl.po create mode 100755 farmq-admin/static/novnc/po/po2js create mode 100644 farmq-admin/static/novnc/po/pt_BR.po create mode 100644 farmq-admin/static/novnc/po/ru.po create mode 100644 farmq-admin/static/novnc/po/sv.po create mode 100644 farmq-admin/static/novnc/po/tr.po create mode 100755 farmq-admin/static/novnc/po/xgettext-html create mode 100644 farmq-admin/static/novnc/po/zh_CN.po create mode 100644 farmq-admin/static/novnc/po/zh_TW.po create mode 100644 farmq-admin/static/novnc/snap/hooks/configure create mode 100755 farmq-admin/static/novnc/snap/local/svc_wrapper.sh create mode 100644 farmq-admin/static/novnc/snap/snapcraft.yaml create mode 100644 farmq-admin/static/novnc/tests/.eslintrc create mode 100644 farmq-admin/static/novnc/tests/assertions.js create mode 100644 farmq-admin/static/novnc/tests/fake.websocket.js create mode 100644 farmq-admin/static/novnc/tests/playback-ui.js create mode 100644 farmq-admin/static/novnc/tests/playback.js create mode 100644 farmq-admin/static/novnc/tests/test.base64.js create mode 100644 farmq-admin/static/novnc/tests/test.browser.js create mode 100644 farmq-admin/static/novnc/tests/test.copyrect.js create mode 100644 farmq-admin/static/novnc/tests/test.deflator.js create mode 100644 farmq-admin/static/novnc/tests/test.display.js create mode 100644 farmq-admin/static/novnc/tests/test.gesturehandler.js create mode 100644 farmq-admin/static/novnc/tests/test.helper.js create mode 100644 farmq-admin/static/novnc/tests/test.hextile.js create mode 100644 farmq-admin/static/novnc/tests/test.inflator.js create mode 100644 farmq-admin/static/novnc/tests/test.int.js create mode 100644 farmq-admin/static/novnc/tests/test.jpeg.js create mode 100644 farmq-admin/static/novnc/tests/test.keyboard.js create mode 100644 farmq-admin/static/novnc/tests/test.localization.js create mode 100644 farmq-admin/static/novnc/tests/test.ra2.js create mode 100644 farmq-admin/static/novnc/tests/test.raw.js create mode 100644 farmq-admin/static/novnc/tests/test.rfb.js create mode 100644 farmq-admin/static/novnc/tests/test.rre.js create mode 100644 farmq-admin/static/novnc/tests/test.tight.js create mode 100644 farmq-admin/static/novnc/tests/test.tightpng.js create mode 100644 farmq-admin/static/novnc/tests/test.util.js create mode 100644 farmq-admin/static/novnc/tests/test.websock.js create mode 100644 farmq-admin/static/novnc/tests/test.webutil.js create mode 100644 farmq-admin/static/novnc/tests/test.zrle.js create mode 100644 farmq-admin/static/novnc/tests/vnc_playback.html create mode 100644 farmq-admin/static/novnc/utils/.eslintrc create mode 100644 farmq-admin/static/novnc/utils/README.md create mode 100755 farmq-admin/static/novnc/utils/b64-to-binary.pl create mode 100755 farmq-admin/static/novnc/utils/convert.js create mode 100755 farmq-admin/static/novnc/utils/genkeysymdef.js create mode 100755 farmq-admin/static/novnc/utils/novnc_proxy create mode 100755 farmq-admin/static/novnc/utils/u2x11 create mode 100755 farmq-admin/static/novnc/utils/validate create mode 100644 farmq-admin/static/novnc/vendor/pako/LICENSE create mode 100644 farmq-admin/static/novnc/vendor/pako/README.md create mode 100644 farmq-admin/static/novnc/vnc.html create mode 100644 farmq-admin/static/novnc/vnc_lite.html create mode 100644 farmq-admin/templates/vm_list.html create mode 100644 farmq-admin/templates/vnc_console.html create mode 100644 farmq-admin/templates/vnc_redirect.html create mode 100644 farmq-admin/utils/proxmox_client.py diff --git a/farmq-admin/check_existing_data.py b/farmq-admin/check_existing_data.py new file mode 100644 index 0000000..8d34faf --- /dev/null +++ b/farmq-admin/check_existing_data.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +""" +기존 Headscale 데이터 확인 스크립트 +""" + +import sqlite3 +import os +from datetime import datetime + +def check_headscale_data(): + """Headscale 데이터베이스의 기존 데이터 확인""" + db_path = '/srv/headscale-setup/data/db.sqlite' + + if not os.path.exists(db_path): + print(f"❌ Database file not found: {db_path}") + return + + print(f"📊 Checking Headscale database: {db_path}") + print("=" * 60) + + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + try: + # 테이블 목록 확인 + cursor.execute("SELECT name FROM sqlite_master WHERE type='table'") + tables = cursor.fetchall() + print(f"📋 Available tables: {[table[0] for table in tables]}") + print() + + # Users 테이블 확인 + try: + cursor.execute("SELECT * FROM users") + users = cursor.fetchall() + print(f"👥 Users ({len(users)} records):") + if users: + cursor.execute("PRAGMA table_info(users)") + columns = [col[1] for col in cursor.fetchall()] + print(f" Columns: {columns}") + for user in users: + print(f" - {dict(zip(columns, user))}") + print() + except Exception as e: + print(f" ❌ Error reading users: {e}") + + # Nodes 테이블 확인 + try: + cursor.execute("SELECT * FROM nodes") + nodes = cursor.fetchall() + print(f"💻 Nodes ({len(nodes)} records):") + if nodes: + cursor.execute("PRAGMA table_info(nodes)") + columns = [col[1] for col in cursor.fetchall()] + print(f" Columns: {columns}") + for node in nodes: + node_dict = dict(zip(columns, node)) + print(f" - ID: {node_dict.get('id')}, Given Name: {node_dict.get('given_name')}, " + f"Hostname: {node_dict.get('hostname')}, IPv4: {node_dict.get('ipv4')}, " + f"User ID: {node_dict.get('user_id')}") + print() + except Exception as e: + print(f" ❌ Error reading nodes: {e}") + + # PreAuthKeys 테이블 확인 + try: + cursor.execute("SELECT * FROM pre_auth_keys") + keys = cursor.fetchall() + print(f"🔑 PreAuth Keys ({len(keys)} records):") + if keys: + cursor.execute("PRAGMA table_info(pre_auth_keys)") + columns = [col[1] for col in cursor.fetchall()] + print(f" Columns: {columns}") + for key in keys: + key_dict = dict(zip(columns, key)) + print(f" - ID: {key_dict.get('id')}, Key: {key_dict.get('key')[:20]}..., " + f"Used: {key_dict.get('used')}, User ID: {key_dict.get('user_id')}") + print() + except Exception as e: + print(f" ❌ Error reading pre_auth_keys: {e}") + + # API Keys 테이블 확인 + try: + cursor.execute("SELECT * FROM api_keys") + api_keys = cursor.fetchall() + print(f"🗝️ API Keys ({len(api_keys)} records):") + if api_keys: + cursor.execute("PRAGMA table_info(api_keys)") + columns = [col[1] for col in cursor.fetchall()] + print(f" Columns: {columns}") + for api_key in api_keys: + key_dict = dict(zip(columns, api_key)) + print(f" - ID: {key_dict.get('id')}, Prefix: {key_dict.get('prefix')}, " + f"Created: {key_dict.get('created_at')}") + print() + except Exception as e: + print(f" ❌ Error reading api_keys: {e}") + + except Exception as e: + print(f"❌ Database connection error: {e}") + finally: + conn.close() + +if __name__ == '__main__': + check_headscale_data() \ No newline at end of file diff --git a/farmq-admin/debug_machine.py b/farmq-admin/debug_machine.py new file mode 100644 index 0000000..12bf93b --- /dev/null +++ b/farmq-admin/debug_machine.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +""" +머신 상세 정보 디버깅 +""" + +from utils.database import init_database, get_session, get_machine_with_details +from models import Node, MachineSpecs, MonitoringData, PharmacyInfo + +def debug_machine_detail(machine_id=1): + """머신 상세 정보 디버깅""" + print(f"🔍 Debugging machine detail for ID: {machine_id}") + + session = get_session() + + try: + # 1. 머신 정보 확인 + machine = session.query(Node).filter_by(id=machine_id).first() + print(f"📱 Machine: {machine}") + if machine: + print(f" - ID: {machine.id}") + print(f" - Hostname: {machine.hostname}") + print(f" - Given Name: {machine.given_name}") + print(f" - User ID: {machine.user_id}") + print(f" - IPv4: {machine.ipv4}") + print(f" - Last Seen: {machine.last_seen}") + + # is_online 메서드 테스트 + try: + online_status = machine.is_online() + print(f" - Online Status: {online_status}") + except Exception as e: + print(f" - Online Status Error: {e}") + + # 2. 머신 스펙 확인 + specs = session.query(MachineSpecs).filter_by(machine_id=machine_id).first() + print(f"💾 Specs: {specs}") + if specs: + print(f" - CPU: {specs.cpu_model}") + print(f" - RAM: {specs.ram_gb}GB") + print(f" - Storage: {specs.storage_gb}GB") + + # 3. 모니터링 데이터 확인 + monitoring = session.query(MonitoringData).filter_by( + machine_id=machine_id + ).order_by(MonitoringData.collected_at.desc()).first() + print(f"📊 Latest Monitoring: {monitoring}") + if monitoring: + print(f" - CPU Usage: {monitoring.cpu_usage}%") + print(f" - Temperature: {monitoring.cpu_temperature}°C") + print(f" - Collected at: {monitoring.collected_at}") + + # 4. get_machine_with_details 함수 테스트 + print(f"\n🧪 Testing get_machine_with_details function...") + try: + details = get_machine_with_details(machine_id) + print(f" ✅ Success: {details is not None}") + if details: + print(f" - Machine: {details['machine'].hostname if details['machine'] else None}") + print(f" - Specs: {details['specs'] is not None}") + print(f" - Monitoring: {details['latest_monitoring'] is not None}") + print(f" - Online: {details['is_online']}") + print(f" - Pharmacy: {details['pharmacy']}") + except Exception as e: + print(f" ❌ Error: {e}") + import traceback + traceback.print_exc() + + except Exception as e: + print(f"❌ Database error: {e}") + import traceback + traceback.print_exc() + + finally: + session.close() + +if __name__ == '__main__': + # 데이터베이스 초기화 + init_database('sqlite:///srv/headscale-setup/data/db.sqlite') + debug_machine_detail(1) \ No newline at end of file diff --git a/farmq-admin/flask-venv/bin/Activate.ps1 b/farmq-admin/flask-venv/bin/Activate.ps1 new file mode 100644 index 0000000..b49d77b --- /dev/null +++ b/farmq-admin/flask-venv/bin/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/farmq-admin/flask-venv/bin/activate b/farmq-admin/flask-venv/bin/activate new file mode 100644 index 0000000..d38c261 --- /dev/null +++ b/farmq-admin/flask-venv/bin/activate @@ -0,0 +1,70 @@ +# This file must be used with "source bin/activate" *from bash* +# You cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # Call hash to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + hash -r 2> /dev/null + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +# on Windows, a path can contain colons and backslashes and has to be converted: +if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then + # transform D:\path\to\venv to /d/path/to/venv on MSYS + # and to /cygdrive/d/path/to/venv on Cygwin + export VIRTUAL_ENV=$(cygpath /srv/headscale-setup/farmq-admin/flask-venv) +else + # use the path as-is + export VIRTUAL_ENV=/srv/headscale-setup/farmq-admin/flask-venv +fi + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/"bin":$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1='(flask-venv) '"${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT='(flask-venv) ' + export VIRTUAL_ENV_PROMPT +fi + +# Call hash to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +hash -r 2> /dev/null diff --git a/farmq-admin/flask-venv/bin/activate.csh b/farmq-admin/flask-venv/bin/activate.csh new file mode 100644 index 0000000..286ae62 --- /dev/null +++ b/farmq-admin/flask-venv/bin/activate.csh @@ -0,0 +1,27 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. + +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV /srv/headscale-setup/farmq-admin/flask-venv + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/"bin":$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + set prompt = '(flask-venv) '"$prompt" + setenv VIRTUAL_ENV_PROMPT '(flask-venv) ' +endif + +alias pydoc python -m pydoc + +rehash diff --git a/farmq-admin/flask-venv/bin/activate.fish b/farmq-admin/flask-venv/bin/activate.fish new file mode 100644 index 0000000..0105ff7 --- /dev/null +++ b/farmq-admin/flask-venv/bin/activate.fish @@ -0,0 +1,69 @@ +# This file must be used with "source /bin/activate.fish" *from fish* +# (https://fishshell.com/). You cannot run it directly. + +function deactivate -d "Exit virtual environment and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + set -e _OLD_FISH_PROMPT_OVERRIDE + # prevents error when using nested fish instances (Issue #93858) + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + end + + set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT + if test "$argv[1]" != "nondestructive" + # Self-destruct! + functions -e deactivate + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV /srv/headscale-setup/farmq-admin/flask-venv + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/"bin $PATH + +# Unset PYTHONHOME if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # Save the current fish_prompt function as the function _old_fish_prompt. + functions -c fish_prompt _old_fish_prompt + + # With the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command. + set -l old_status $status + + # Output the venv prompt; color taken from the blue of the Python logo. + printf "%s%s%s" (set_color 4B8BBE) '(flask-venv) ' (set_color normal) + + # Restore the return status of the previous command. + echo "exit $old_status" | . + # Output the original/"old" prompt. + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT '(flask-venv) ' +end diff --git a/farmq-admin/flask-venv/bin/flask b/farmq-admin/flask-venv/bin/flask new file mode 100755 index 0000000..fe49310 --- /dev/null +++ b/farmq-admin/flask-venv/bin/flask @@ -0,0 +1,8 @@ +#!/srv/headscale-setup/farmq-admin/flask-venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from flask.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/farmq-admin/flask-venv/bin/normalizer b/farmq-admin/flask-venv/bin/normalizer new file mode 100755 index 0000000..d31749f --- /dev/null +++ b/farmq-admin/flask-venv/bin/normalizer @@ -0,0 +1,8 @@ +#!/srv/headscale-setup/farmq-admin/flask-venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from charset_normalizer.cli import cli_detect +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cli_detect()) diff --git a/farmq-admin/flask-venv/bin/pip b/farmq-admin/flask-venv/bin/pip new file mode 100755 index 0000000..185bba9 --- /dev/null +++ b/farmq-admin/flask-venv/bin/pip @@ -0,0 +1,8 @@ +#!/srv/headscale-setup/farmq-admin/flask-venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/farmq-admin/flask-venv/bin/pip3 b/farmq-admin/flask-venv/bin/pip3 new file mode 100755 index 0000000..185bba9 --- /dev/null +++ b/farmq-admin/flask-venv/bin/pip3 @@ -0,0 +1,8 @@ +#!/srv/headscale-setup/farmq-admin/flask-venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/farmq-admin/flask-venv/bin/pip3.12 b/farmq-admin/flask-venv/bin/pip3.12 new file mode 100755 index 0000000..185bba9 --- /dev/null +++ b/farmq-admin/flask-venv/bin/pip3.12 @@ -0,0 +1,8 @@ +#!/srv/headscale-setup/farmq-admin/flask-venv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/farmq-admin/flask-venv/bin/python b/farmq-admin/flask-venv/bin/python new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/farmq-admin/flask-venv/bin/python @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/farmq-admin/flask-venv/bin/python3 b/farmq-admin/flask-venv/bin/python3 new file mode 120000 index 0000000..ae65fda --- /dev/null +++ b/farmq-admin/flask-venv/bin/python3 @@ -0,0 +1 @@ +/usr/bin/python3 \ No newline at end of file diff --git a/farmq-admin/flask-venv/bin/python3.12 b/farmq-admin/flask-venv/bin/python3.12 new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/farmq-admin/flask-venv/bin/python3.12 @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/farmq-admin/flask-venv/include/site/python3.12/greenlet/greenlet.h b/farmq-admin/flask-venv/include/site/python3.12/greenlet/greenlet.h new file mode 100644 index 0000000..d02a16e --- /dev/null +++ b/farmq-admin/flask-venv/include/site/python3.12/greenlet/greenlet.h @@ -0,0 +1,164 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ + +/* Greenlet object interface */ + +#ifndef Py_GREENLETOBJECT_H +#define Py_GREENLETOBJECT_H + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is deprecated and undocumented. It does not change. */ +#define GREENLET_VERSION "1.0.0" + +#ifndef GREENLET_MODULE +#define implementation_ptr_t void* +#endif + +typedef struct _greenlet { + PyObject_HEAD + PyObject* weakreflist; + PyObject* dict; + implementation_ptr_t pimpl; +} PyGreenlet; + +#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type)) + + +/* C API functions */ + +/* Total number of symbols that are exported */ +#define PyGreenlet_API_pointers 12 + +#define PyGreenlet_Type_NUM 0 +#define PyExc_GreenletError_NUM 1 +#define PyExc_GreenletExit_NUM 2 + +#define PyGreenlet_New_NUM 3 +#define PyGreenlet_GetCurrent_NUM 4 +#define PyGreenlet_Throw_NUM 5 +#define PyGreenlet_Switch_NUM 6 +#define PyGreenlet_SetParent_NUM 7 + +#define PyGreenlet_MAIN_NUM 8 +#define PyGreenlet_STARTED_NUM 9 +#define PyGreenlet_ACTIVE_NUM 10 +#define PyGreenlet_GET_PARENT_NUM 11 + +#ifndef GREENLET_MODULE +/* This section is used by modules that uses the greenlet C API */ +static void** _PyGreenlet_API = NULL; + +# define PyGreenlet_Type \ + (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM]) + +# define PyExc_GreenletError \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM]) + +# define PyExc_GreenletExit \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM]) + +/* + * PyGreenlet_New(PyObject *args) + * + * greenlet.greenlet(run, parent=None) + */ +# define PyGreenlet_New \ + (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \ + _PyGreenlet_API[PyGreenlet_New_NUM]) + +/* + * PyGreenlet_GetCurrent(void) + * + * greenlet.getcurrent() + */ +# define PyGreenlet_GetCurrent \ + (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM]) + +/* + * PyGreenlet_Throw( + * PyGreenlet *greenlet, + * PyObject *typ, + * PyObject *val, + * PyObject *tb) + * + * g.throw(...) + */ +# define PyGreenlet_Throw \ + (*(PyObject * (*)(PyGreenlet * self, \ + PyObject * typ, \ + PyObject * val, \ + PyObject * tb)) \ + _PyGreenlet_API[PyGreenlet_Throw_NUM]) + +/* + * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args) + * + * g.switch(*args, **kwargs) + */ +# define PyGreenlet_Switch \ + (*(PyObject * \ + (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \ + _PyGreenlet_API[PyGreenlet_Switch_NUM]) + +/* + * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent) + * + * g.parent = new_parent + */ +# define PyGreenlet_SetParent \ + (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \ + _PyGreenlet_API[PyGreenlet_SetParent_NUM]) + +/* + * PyGreenlet_GetParent(PyObject* greenlet) + * + * return greenlet.parent; + * + * This could return NULL even if there is no exception active. + * If it does not return NULL, you are responsible for decrementing the + * reference count. + */ +# define PyGreenlet_GetParent \ + (*(PyGreenlet* (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM]) + +/* + * deprecated, undocumented alias. + */ +# define PyGreenlet_GET_PARENT PyGreenlet_GetParent + +# define PyGreenlet_MAIN \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_MAIN_NUM]) + +# define PyGreenlet_STARTED \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_STARTED_NUM]) + +# define PyGreenlet_ACTIVE \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_ACTIVE_NUM]) + + + + +/* Macro that imports greenlet and initializes C API */ +/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we + keep the older definition to be sure older code that might have a copy of + the header still works. */ +# define PyGreenlet_Import() \ + { \ + _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \ + } + +#endif /* GREENLET_MODULE */ + +#ifdef __cplusplus +} +#endif +#endif /* !Py_GREENLETOBJECT_H */ diff --git a/farmq-admin/flask-venv/lib64 b/farmq-admin/flask-venv/lib64 new file mode 120000 index 0000000..7951405 --- /dev/null +++ b/farmq-admin/flask-venv/lib64 @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/farmq-admin/flask-venv/pyvenv.cfg b/farmq-admin/flask-venv/pyvenv.cfg new file mode 100644 index 0000000..abb3386 --- /dev/null +++ b/farmq-admin/flask-venv/pyvenv.cfg @@ -0,0 +1,5 @@ +home = /usr/bin +include-system-site-packages = false +version = 3.12.3 +executable = /usr/bin/python3.12 +command = /usr/bin/python3 -m venv /srv/headscale-setup/farmq-admin/flask-venv diff --git a/farmq-admin/init_sample_data.py b/farmq-admin/init_sample_data.py new file mode 100644 index 0000000..137190e --- /dev/null +++ b/farmq-admin/init_sample_data.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +""" +FARMQ 관리 시스템 - 샘플 데이터 초기화 스크립트 +""" + +import os +import sys +from datetime import datetime, timedelta +import random +from sqlalchemy import text + +# 현재 디렉토리를 파이썬 경로에 추가 +current_dir = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, current_dir) + +from config import config +from utils.database import init_database, get_session, close_session +from models import PharmacyInfo, MachineSpecs, MonitoringData, Alert, User, Node + +def init_sample_data(): + """샘플 데이터 초기화""" + print("🔧 Initializing sample data for FARMQ Admin System...") + + # 데이터베이스 세션 획득 + session = get_session() + + try: + # 1. 기존 샘플 데이터 정리 + print("📝 Cleaning existing sample data...") + session.query(Alert).delete() + session.query(MonitoringData).delete() + session.query(MachineSpecs).delete() + session.query(PharmacyInfo).delete() + + # 2. 샘플 약국 데이터 생성 + print("🏥 Creating sample pharmacy data...") + pharmacies = [ + { + 'pharmacy_name': '서울약국', + 'business_number': '123-45-67890', + 'manager_name': '김철수', + 'phone': '02-1234-5678', + 'address': '서울특별시 강남구 테헤란로 123', + 'proxmox_host': '192.168.1.100', + 'user_id': 'seoul-pharmacy' + }, + { + 'pharmacy_name': '부산메디컬약국', + 'business_number': '987-65-43210', + 'manager_name': '이영희', + 'phone': '051-2345-6789', + 'address': '부산광역시 해운대구 센텀대로 456', + 'proxmox_host': '192.168.1.101', + 'user_id': 'busan-medical' + }, + { + 'pharmacy_name': '대구건강약국', + 'business_number': '456-78-91234', + 'manager_name': '박민수', + 'phone': '053-3456-7890', + 'address': '대구광역시 중구 동성로 789', + 'proxmox_host': '192.168.1.102', + 'user_id': 'daegu-health' + }, + { + 'pharmacy_name': '인천바다약국', + 'business_number': '321-54-87695', + 'manager_name': '최수진', + 'phone': '032-4567-8901', + 'address': '인천광역시 연수구 송도대로 321', + 'proxmox_host': '192.168.1.103', + 'user_id': 'incheon-sea' + }, + { + 'pharmacy_name': '광주햇살약국', + 'business_number': '654-32-10987', + 'manager_name': '정태영', + 'phone': '062-5678-9012', + 'address': '광주광역시 서구 상무대로 654', + 'proxmox_host': '192.168.1.104', + 'user_id': 'gwangju-sunshine' + } + ] + + pharmacy_objects = [] + for pharmacy_data in pharmacies: + pharmacy = PharmacyInfo(**pharmacy_data) + session.add(pharmacy) + pharmacy_objects.append(pharmacy) + + session.flush() # ID 생성을 위해 flush + + # 3. 기존 Headscale 사용자와 연결 + print("👥 Linking with existing Headscale users...") + existing_users = session.query(User).all() + existing_nodes = session.query(Node).all() + + print(f"📊 Found {len(existing_users)} users and {len(existing_nodes)} nodes in Headscale") + + # 4. 머신 스펙 데이터 생성 + print("💻 Creating machine specifications...") + cpu_models = [ + 'Intel Core i5-11400', + 'Intel Core i7-10700', + 'AMD Ryzen 5 5600X', + 'AMD Ryzen 7 5700G', + 'Intel Core i3-10100' + ] + + machine_specs = [] + for node in existing_nodes: + specs = MachineSpecs( + machine_id=node.id, + cpu_model=random.choice(cpu_models), + cpu_cores=random.choice([4, 6, 8]), + ram_gb=random.choice([8, 16, 32]), + storage_gb=random.choice([256, 512, 1024]), + network_speed=random.choice([100, 1000]), + os_info=f"Ubuntu 22.04 LTS", + created_at=datetime.now() + ) + session.add(specs) + machine_specs.append(specs) + + # 5. 모니터링 데이터 생성 (최근 24시간) + print("📈 Creating monitoring data...") + for spec in machine_specs: + # 각 머신별로 최근 24시간 데이터 생성 + base_time = datetime.now() - timedelta(hours=24) + + for i in range(144): # 10분 간격으로 24시간 = 144개 데이터 + monitoring_time = base_time + timedelta(minutes=i * 10) + + # 시간대별로 사실적인 패턴 생성 + hour = monitoring_time.hour + base_cpu = 20 if 6 <= hour <= 22 else 10 # 업무시간 vs 야간 + + monitoring = MonitoringData( + machine_id=spec.machine_id, + cpu_usage=max(0, min(100, base_cpu + random.gauss(0, 10))), + memory_usage=max(10, min(90, 30 + random.gauss(0, 15))), + disk_usage=max(20, min(80, 45 + random.gauss(0, 5))), + cpu_temperature=max(35, min(85, 55 + random.gauss(0, 8))), + network_in_bytes=random.randint(1000, 50000), + network_out_bytes=random.randint(500, 25000), + collected_at=monitoring_time + ) + session.add(monitoring) + + # 6. 알림 데이터 생성 + print("🚨 Creating alert data...") + alert_types = ['HIGH_CPU', 'HIGH_MEMORY', 'HIGH_TEMPERATURE', 'DISK_SPACE', 'NETWORK_ISSUE'] + alert_levels = ['INFO', 'WARNING', 'ERROR', 'CRITICAL'] + + for i in range(15): # 15개의 알림 생성 + alert_time = datetime.now() - timedelta(hours=random.randint(1, 72)) + machine_id = random.choice([spec.machine_id for spec in machine_specs]) + + alert_type = random.choice(alert_types) + level = random.choice(alert_levels) + + messages = { + 'HIGH_CPU': f'CPU 사용률 80% 초과', + 'HIGH_MEMORY': f'메모리 사용률 85% 초과', + 'HIGH_TEMPERATURE': f'CPU 온도 75°C 초과', + 'DISK_SPACE': f'디스크 사용률 80% 초과', + 'NETWORK_ISSUE': f'네트워크 연결 불안정' + } + + alert = Alert( + machine_id=machine_id, + alert_type=alert_type, + level=level, + message=messages[alert_type], + created_at=alert_time, + is_resolved=random.choice([True, False]) + ) + session.add(alert) + + # 커밋 + session.commit() + + print("✅ Sample data initialization completed!") + print(f" - {len(pharmacy_objects)} pharmacies created") + print(f" - {len(machine_specs)} machine specifications created") + print(f" - {144 * len(machine_specs)} monitoring records created") + print(f" - 15 alerts created") + + # 통계 출력 + print("\n📊 Database Statistics:") + print(f" - Total Users: {session.query(User).count()}") + print(f" - Total Nodes: {session.query(Node).count()}") + print(f" - Total Pharmacies: {session.query(PharmacyInfo).count()}") + print(f" - Total Monitoring Records: {session.query(MonitoringData).count()}") + print(f" - Active Alerts: {session.query(Alert).filter_by(is_resolved=False).count()}") + + except Exception as e: + print(f"❌ Error initializing sample data: {e}") + session.rollback() + raise + finally: + close_session() + +if __name__ == '__main__': + # 데이터베이스 초기화 + init_database('/srv/headscale-setup/headscale-data/db.sqlite') + + # 샘플 데이터 생성 + init_sample_data() \ No newline at end of file diff --git a/farmq-admin/static/novnc/AUTHORS b/farmq-admin/static/novnc/AUTHORS new file mode 100644 index 0000000..e8fb124 --- /dev/null +++ b/farmq-admin/static/novnc/AUTHORS @@ -0,0 +1,13 @@ +maintainers: +- Samuel Mannehed for Cendio AB (@samhed) +- Pierre Ossman for Cendio AB (@CendioOssman) +maintainersEmeritus: +- Joel Martin (@kanaka) +- Solly Ross (@directxman12) +- @astrand +contributors: +# There are a bunch of people that should be here. +# If you want to be on this list, feel free send a PR +# to add yourself. +- jalf +- NTT corp. diff --git a/farmq-admin/static/novnc/LICENSE.txt b/farmq-admin/static/novnc/LICENSE.txt new file mode 100644 index 0000000..37efdcd --- /dev/null +++ b/farmq-admin/static/novnc/LICENSE.txt @@ -0,0 +1,62 @@ +noVNC is Copyright (C) 2022 The noVNC Authors +(./AUTHORS) + +The noVNC core library files are licensed under the MPL 2.0 (Mozilla +Public License 2.0). The noVNC core library is composed of the +Javascript code necessary for full noVNC operation. This includes (but +is not limited to): + + core/**/*.js + app/*.js + test/playback.js + +The HTML, CSS, font and images files that included with the noVNC +source distibution (or repository) are not considered part of the +noVNC core library and are licensed under more permissive licenses. +The intent is to allow easy integration of noVNC into existing web +sites and web applications. + +The HTML, CSS, font and image files are licensed as follows: + + *.html : 2-Clause BSD license + + app/styles/*.css : 2-Clause BSD license + + app/styles/Orbitron* : SIL Open Font License 1.1 + (Copyright 2009 Matt McInerney) + + app/images/ : Creative Commons Attribution-ShareAlike + http://creativecommons.org/licenses/by-sa/3.0/ + +Some portions of noVNC are copyright to their individual authors. +Please refer to the individual source files and/or to the noVNC commit +history: https://github.com/novnc/noVNC/commits/master + +The are several files and projects that have been incorporated into +the noVNC core library. Here is a list of those files and the original +licenses (all MPL 2.0 compatible): + + core/base64.js : MPL 2.0 + + core/des.js : Various BSD style licenses + + vendor/pako/ : MIT + +Any other files not mentioned above are typically marked with +a copyright/license header at the top of the file. The default noVNC +license is MPL-2.0. + +The following license texts are included: + + docs/LICENSE.MPL-2.0 + docs/LICENSE.OFL-1.1 + docs/LICENSE.BSD-3-Clause (New BSD) + docs/LICENSE.BSD-2-Clause (Simplified BSD / FreeBSD) + vendor/pako/LICENSE (MIT) + +Or alternatively the license texts may be found here: + + http://www.mozilla.org/MPL/2.0/ + http://scripts.sil.org/OFL + http://en.wikipedia.org/wiki/BSD_licenses + https://opensource.org/licenses/MIT diff --git a/farmq-admin/static/novnc/README.md b/farmq-admin/static/novnc/README.md new file mode 100644 index 0000000..a771cb4 --- /dev/null +++ b/farmq-admin/static/novnc/README.md @@ -0,0 +1,230 @@ +## noVNC: HTML VNC Client Library and Application + +[![Test Status](https://github.com/novnc/noVNC/workflows/Test/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ATest) +[![Lint Status](https://github.com/novnc/noVNC/workflows/Lint/badge.svg)](https://github.com/novnc/noVNC/actions?query=workflow%3ALint) + +### Description + +noVNC is both a HTML VNC client JavaScript library and an application built on +top of that library. noVNC runs well in any modern browser including mobile +browsers (iOS and Android). + +Many companies, projects and products have integrated noVNC including +[OpenStack](http://www.openstack.org), +[OpenNebula](http://opennebula.org/), +[LibVNCServer](http://libvncserver.sourceforge.net), and +[ThinLinc](https://cendio.com/thinlinc). See +[the Projects and Companies wiki page](https://github.com/novnc/noVNC/wiki/Projects-and-companies-using-noVNC) +for a more complete list with additional info and links. + +### Table of Contents + +- [News/help/contact](#newshelpcontact) +- [Features](#features) +- [Screenshots](#screenshots) +- [Browser Requirements](#browser-requirements) +- [Server Requirements](#server-requirements) +- [Quick Start](#quick-start) +- [Installation from Snap Package](#installation-from-snap-package) +- [Integration and Deployment](#integration-and-deployment) +- [Authors/Contributors](#authorscontributors) + +### News/help/contact + +The project website is found at [novnc.com](http://novnc.com). +Notable commits, announcements and news are posted to +[@noVNC](http://www.twitter.com/noVNC). + +If you are a noVNC developer/integrator/user (or want to be) please join the +[noVNC discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc). + +Bugs and feature requests can be submitted via +[github issues](https://github.com/novnc/noVNC/issues). If you have questions +about using noVNC then please first use the +[discussion group](https://groups.google.com/forum/?fromgroups#!forum/novnc). +We also have a [wiki](https://github.com/novnc/noVNC/wiki/) with lots of +helpful information. + +If you are looking for a place to start contributing to noVNC, a good place to +start would be the issues that are marked as +["patchwelcome"](https://github.com/novnc/noVNC/issues?labels=patchwelcome). +Please check our +[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) though. + +If you want to show appreciation for noVNC you could donate to a great non- +profits such as: +[Compassion International](http://www.compassion.com/), +[SIL](http://www.sil.org), +[Habitat for Humanity](http://www.habitat.org), +[Electronic Frontier Foundation](https://www.eff.org/), +[Against Malaria Foundation](http://www.againstmalaria.com/), +[Nothing But Nets](http://www.nothingbutnets.net/), etc. +Please tweet [@noVNC](http://www.twitter.com/noVNC) if you do. + + +### Features + +* Supports all modern browsers including mobile (iOS, Android) +* Supported authentication methods: none, classical VNC, RealVNC's + RSA-AES, Tight, VeNCrypt Plain, XVP, Apple's Diffie-Hellman, + UltraVNC's MSLogonII +* Supported VNC encodings: raw, copyrect, rre, hextile, tight, tightPNG, + ZRLE, JPEG +* Supports scaling, clipping and resizing the desktop +* Local cursor rendering +* Clipboard copy/paste with full Unicode support +* Translations +* Touch gestures for emulating common mouse actions +* Licensed mainly under the [MPL 2.0](http://www.mozilla.org/MPL/2.0/), see + [the license document](LICENSE.txt) for details + +### Screenshots + +Running in Firefox before and after connecting: + +  + + +See more screenshots +[here](http://novnc.com/screenshots.html). + + +### Browser Requirements + +noVNC uses many modern web technologies so a formal requirement list is +not available. However these are the minimum versions we are currently +aware of: + +* Chrome 64, Firefox 79, Safari 13.4, Opera 51, Edge 79 + + +### Server Requirements + +noVNC follows the standard VNC protocol, but unlike other VNC clients it does +require WebSockets support. Many servers include support (e.g. +[x11vnc/libvncserver](http://libvncserver.sourceforge.net/), +[QEMU](http://www.qemu.org/), and +[MobileVNC](http://www.smartlab.at/mobilevnc/)), but for the others you need to +use a WebSockets to TCP socket proxy. noVNC has a sister project +[websockify](https://github.com/novnc/websockify) that provides a simple such +proxy. + + +### Quick Start + +* Use the `novnc_proxy` script to automatically download and start websockify, which + includes a mini-webserver and the WebSockets proxy. The `--vnc` option is + used to specify the location of a running VNC server: + + `./utils/novnc_proxy --vnc localhost:5901` + +* If you don't need to expose the web server to public internet, you can + bind to localhost: + + `./utils/novnc_proxy --vnc localhost:5901 --listen localhost:6081` + +* Point your browser to the cut-and-paste URL that is output by the `novnc_proxy` + script. Hit the Connect button, enter a password if the VNC server has one + configured, and enjoy! + +### Installation from Snap Package +Running the command below will install the latest release of noVNC from Snap: + +`sudo snap install novnc` + +#### Running noVNC from Snap Directly + +You can run the Snap-package installed novnc directly with, for example: + +`novnc --listen 6081 --vnc localhost:5901 # /snap/bin/novnc if /snap/bin is not in your PATH` + +If you want to use certificate files, due to standard Snap confinement restrictions you need to have them in the /home/\/snap/novnc/current/ directory. If your username is jsmith an example command would be: + + `novnc --listen 8443 --cert ~jsmith/snap/novnc/current/self.crt --key ~jsmith/snap/novnc/current/self.key --vnc ubuntu.example.com:5901` + +#### Running noVNC from Snap as a Service (Daemon) +The Snap package also has the capability to run a 'novnc' service which can be +configured to listen on multiple ports connecting to multiple VNC servers +(effectively a service runing multiple instances of novnc). +Instructions (with example values): + +List current services (out-of-box this will be blank): + +``` +sudo snap get novnc services +Key Value +services.n6080 {...} +services.n6081 {...} +``` + +Create a new service that listens on port 6082 and connects to the VNC server +running on port 5902 on localhost: + +`sudo snap set novnc services.n6082.listen=6082 services.n6082.vnc=localhost:5902` + +(Any services you define with 'snap set' will be automatically started) +Note that the name of the service, 'n6082' in this example, can be anything +as long as it doesn't start with a number or contain spaces/special characters. + +View the configuration of the service just created: + +``` +sudo snap get novnc services.n6082 +Key Value +services.n6082.listen 6082 +services.n6082.vnc localhost:5902 +``` + +Disable a service (note that because of a limitation in Snap it's currently not +possible to unset config variables, setting them to blank values is the way +to disable a service): + +`sudo snap set novnc services.n6082.listen='' services.n6082.vnc=''` + +(Any services you set to blank with 'snap set' like this will be automatically stopped) + +Verify that the service is disabled (blank values): + +``` +sudo snap get novnc services.n6082 +Key Value +services.n6082.listen +services.n6082.vnc +``` + +### Integration and Deployment + +Please see our other documents for how to integrate noVNC in your own software, +or deploying the noVNC application in production environments: + +* [Embedding](docs/EMBEDDING.md) - For the noVNC application +* [Library](docs/LIBRARY.md) - For the noVNC JavaScript library + + +### Authors/Contributors + +See [AUTHORS](AUTHORS) for a (full-ish) list of authors. If you're not on +that list and you think you should be, feel free to send a PR to fix that. + +* Core team: + * [Samuel Mannehed](https://github.com/samhed) (Cendio) + * [Pierre Ossman](https://github.com/CendioOssman) (Cendio) + +* Previous core contributors: + * [Joel Martin](https://github.com/kanaka) (Project founder) + * [Solly Ross](https://github.com/DirectXMan12) (Red Hat / OpenStack) + +* Notable contributions: + * UI and Icons : Pierre Ossman, Chris Gordon + * Original Logo : Michael Sersen + * tight encoding : Michael Tinglof (Mercuri.ca) + * RealVNC RSA AES authentication : USTC Vlab Team + +* Included libraries: + * base64 : Martijn Pieters (Digital Creations 2), Samuel Sieb (sieb.net) + * DES : Dave Zimmerman (Widget Workshop), Jef Poskanzer (ACME Labs) + * Pako : Vitaly Puzrin (https://github.com/nodeca/pako) + +Do you want to be on this list? Check out our +[contribution guide](https://github.com/novnc/noVNC/wiki/Contributing) and +start hacking! diff --git a/farmq-admin/static/novnc/app/error-handler.js b/farmq-admin/static/novnc/app/error-handler.js new file mode 100644 index 0000000..67b6372 --- /dev/null +++ b/farmq-admin/static/novnc/app/error-handler.js @@ -0,0 +1,79 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2019 The noVNC Authors + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +// Fallback for all uncought errors +function handleError(event, err) { + try { + const msg = document.getElementById('noVNC_fallback_errormsg'); + + // Work around Firefox bug: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1685038 + if (event.message === "ResizeObserver loop completed with undelivered notifications.") { + return false; + } + + // Only show the initial error + if (msg.hasChildNodes()) { + return false; + } + + let div = document.createElement("div"); + div.classList.add('noVNC_message'); + div.appendChild(document.createTextNode(event.message)); + msg.appendChild(div); + + if (event.filename) { + div = document.createElement("div"); + div.className = 'noVNC_location'; + let text = event.filename; + if (event.lineno !== undefined) { + text += ":" + event.lineno; + if (event.colno !== undefined) { + text += ":" + event.colno; + } + } + div.appendChild(document.createTextNode(text)); + msg.appendChild(div); + } + + if (err && err.stack) { + div = document.createElement("div"); + div.className = 'noVNC_stack'; + div.appendChild(document.createTextNode(err.stack)); + msg.appendChild(div); + } + + document.getElementById('noVNC_fallback_error') + .classList.add("noVNC_open"); + + } catch (exc) { + document.write("noVNC encountered an error."); + } + + // Try to disable keyboard interaction, best effort + try { + // Remove focus from the currently focused element in order to + // prevent keyboard interaction from continuing + if (document.activeElement) { document.activeElement.blur(); } + + // Don't let any element be focusable when showing the error + let keyboardFocusable = 'a[href], button, input, textarea, select, details, [tabindex]'; + document.querySelectorAll(keyboardFocusable).forEach((elem) => { + elem.setAttribute("tabindex", "-1"); + }); + } catch (exc) { + // Do nothing + } + + // Don't return true since this would prevent the error + // from being printed to the browser console. + return false; +} + +window.addEventListener('error', evt => handleError(evt, evt.error)); +window.addEventListener('unhandledrejection', evt => handleError(evt.reason, evt.reason)); diff --git a/farmq-admin/static/novnc/app/images/alt.svg b/farmq-admin/static/novnc/app/images/alt.svg new file mode 100644 index 0000000..e5bb461 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/alt.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/clipboard.svg b/farmq-admin/static/novnc/app/images/clipboard.svg new file mode 100644 index 0000000..79af275 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/clipboard.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/connect.svg b/farmq-admin/static/novnc/app/images/connect.svg new file mode 100644 index 0000000..56cde41 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/connect.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/ctrl.svg b/farmq-admin/static/novnc/app/images/ctrl.svg new file mode 100644 index 0000000..856e939 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/ctrl.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/ctrlaltdel.svg b/farmq-admin/static/novnc/app/images/ctrlaltdel.svg new file mode 100644 index 0000000..d7744ea --- /dev/null +++ b/farmq-admin/static/novnc/app/images/ctrlaltdel.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/disconnect.svg b/farmq-admin/static/novnc/app/images/disconnect.svg new file mode 100644 index 0000000..6be7d18 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/disconnect.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/drag.svg b/farmq-admin/static/novnc/app/images/drag.svg new file mode 100644 index 0000000..139caf9 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/drag.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/error.svg b/farmq-admin/static/novnc/app/images/error.svg new file mode 100644 index 0000000..8356d3f --- /dev/null +++ b/farmq-admin/static/novnc/app/images/error.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/esc.svg b/farmq-admin/static/novnc/app/images/esc.svg new file mode 100644 index 0000000..830152b --- /dev/null +++ b/farmq-admin/static/novnc/app/images/esc.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/expander.svg b/farmq-admin/static/novnc/app/images/expander.svg new file mode 100644 index 0000000..e163535 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/expander.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/fullscreen.svg b/farmq-admin/static/novnc/app/images/fullscreen.svg new file mode 100644 index 0000000..29bd05d --- /dev/null +++ b/farmq-admin/static/novnc/app/images/fullscreen.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/handle.svg b/farmq-admin/static/novnc/app/images/handle.svg new file mode 100644 index 0000000..4a7a126 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/handle.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/handle_bg.svg b/farmq-admin/static/novnc/app/images/handle_bg.svg new file mode 100644 index 0000000..7579c42 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/handle_bg.svg @@ -0,0 +1,172 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/icons/Makefile b/farmq-admin/static/novnc/app/images/icons/Makefile new file mode 100644 index 0000000..03eaed0 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/icons/Makefile @@ -0,0 +1,42 @@ +BROWSER_SIZES := 16 24 32 48 64 +#ANDROID_SIZES := 72 96 144 192 +# FIXME: The ICO is limited to 8 icons due to a Chrome bug: +# https://bugs.chromium.org/p/chromium/issues/detail?id=1381393 +ANDROID_SIZES := 96 144 192 +WEB_ICON_SIZES := $(BROWSER_SIZES) $(ANDROID_SIZES) + +#IOS_1X_SIZES := 20 29 40 76 # No such devices exist anymore +IOS_2X_SIZES := 40 58 80 120 152 167 +IOS_3X_SIZES := 60 87 120 180 +ALL_IOS_SIZES := $(IOS_1X_SIZES) $(IOS_2X_SIZES) $(IOS_3X_SIZES) + +ALL_ICONS := \ + $(ALL_IOS_SIZES:%=novnc-ios-%.png) \ + novnc.ico + +all: $(ALL_ICONS) + +# Our testing shows that the ICO file need to be sorted in largest to +# smallest to get the apporpriate behviour +WEB_ICON_SIZES_REVERSE := $(shell echo $(WEB_ICON_SIZES) | tr ' ' '\n' | sort -nr | tr '\n' ' ') +WEB_BASE_ICONS := $(WEB_ICON_SIZES_REVERSE:%=novnc-%.png) +.INTERMEDIATE: $(WEB_BASE_ICONS) + +novnc.ico: $(WEB_BASE_ICONS) + convert $(WEB_BASE_ICONS) "$@" + +# General conversion +novnc-%.png: novnc-icon.svg + convert -depth 8 -background transparent \ + -size $*x$* "$(lastword $^)" "$@" + +# iOS icons use their own SVG +novnc-ios-%.png: novnc-ios-icon.svg + convert -depth 8 -background transparent \ + -size $*x$* "$(lastword $^)" "$@" + +# The smallest sizes are generated using a different SVG +novnc-16.png novnc-24.png novnc-32.png: novnc-icon-sm.svg + +clean: + rm -f *.png diff --git a/farmq-admin/static/novnc/app/images/icons/novnc-icon-sm.svg b/farmq-admin/static/novnc/app/images/icons/novnc-icon-sm.svg new file mode 100644 index 0000000..aa1c6f1 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/icons/novnc-icon-sm.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/icons/novnc-icon.svg b/farmq-admin/static/novnc/app/images/icons/novnc-icon.svg new file mode 100644 index 0000000..1efff91 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/icons/novnc-icon.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/icons/novnc-ios-120.png b/farmq-admin/static/novnc/app/images/icons/novnc-ios-120.png new file mode 100644 index 0000000000000000000000000000000000000000..8da7bab3d0f0ff810af3073b14b52b557f91ada7 GIT binary patch literal 3215 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 literal 0 HcmV?d00001 diff --git a/farmq-admin/static/novnc/app/images/icons/novnc-ios-152.png b/farmq-admin/static/novnc/app/images/icons/novnc-ios-152.png new file mode 100644 index 0000000000000000000000000000000000000000..60b2bcef533e2030e2bb8d92bbbdf010d89f2d4f GIT binary patch literal 4190 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 JT=lypi-cL*%GNG~DMq5={U(y<`j@sI-2B}*(J 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 literal 0 HcmV?d00001 diff --git a/farmq-admin/static/novnc/app/images/icons/novnc-ios-40.png b/farmq-admin/static/novnc/app/images/icons/novnc-ios-40.png new file mode 100644 index 0000000000000000000000000000000000000000..cf14894daf12da14c536b26101fac933d47ddffe GIT binary patch literal 1245 zcmZ{iYfMuI6vxl)6G|}`ETyG9N?i+WX}N7Fr7b9ZT-x%oXj@QVwx#8v4nZpswndqA zgQ9_D(I9BFjx2*QhmYZ=(TwSIb2C#y6eQ6&l!-Vs(`7nQceii*un+(9JO7jXb3UBY zWiPcvGZGj85N)%XR}oDAM-gPAp5|NE5unr=N(=x;d!iOSRKoLXtgA`@+C%_5IspD6 zMmz2SY{dZXy8(240P#)xPnG2Ykm&Ye2d+>klu9M7i3U4>+)Nf|1Tu{*vM~~Q8(~>k zDwT@5i3+U%P5>8>>?EFp2h{+Z0k(liB+AOlg7qK}2;_1(%fUiLXp}oDDJe;#(FldY zn6wzChnbj|sMTt7b93VyaZ9vI(hO;gdIoHV{QP{rnGZF<=kswKk132H=}C-I1_>j{ zWHOeHMJ^yCI6}%Jp*$4BFss#Sv)Ps}Uw-IN?CaOy^Rb(o1$*~K1_CJ;E>P8K`HdU2 z>(|M0x%lka=*2~lNF-*ndBqC(!-vS!6kwP%D+`;Mp*?y8%a)-nEvX|TRD}YYo~BMt zg4fHtcoBw&VfSw6?q1r`64lw6xNRFUI}1HMjK)T8RTW*M$()@fEi8aqEz;}d3k&2s zcOW^L<@M6X$0==XXmxc&TN}5hhko!N+v7p<@}yH!NKQ_UN~O4ZmHhlU7!1ipMbc-_ zkckOMN=mR;MDz2=lP6%erEV=x%>dcDPBF&d4;lel|ar34Ufk;Obx?*F};Fp*8xHCqT4 z{i}th!qr4W57Wsr9^9NCJ7`a~N-?Qd z;t;sP%8)Ux)~Qyjw?=0$)Mc#o?eG3P^7+modwlWz`JJo;P6BtJufM;4U!|M&vGBv& zvJLp4aYLt(SE-Mx z_lCb4`||SG8S>%Y@bS-1)5D|J8Mai@y6vw!x|sGL^CQvB`zwzg&_y#psT4@}D~@m9 zORY*hw{hmNc1!AbJx|^}((AeTj<%^!UJ)@TU;AMGq)!}?QQ5frFdARs@%!;z-|lgp z7iSz7l!hNjeqjkFZzLMc-!9f~X+rqZdhr5L8eFnzVK@;`yMHu)NA WTmL^$2nT*80${^S%}0#x_x=V(2bBr{ literal 0 HcmV?d00001 diff --git a/farmq-admin/static/novnc/app/images/icons/novnc-ios-58.png b/farmq-admin/static/novnc/app/images/icons/novnc-ios-58.png new file mode 100644 index 0000000000000000000000000000000000000000..f6dfbebd2e233bfec83a4d557187eec9e660d0ed GIT binary patch literal 1602 zcmZ{iX;hO}8pofPm_(LLXtNxNq{$D$z~STV6>%ZT?hz_xL{C%D2t_a zX^T@p#!j(NK|w4>+)yhmgM=bic1554C+_jjLrp8q}f zKKFkxEyYOlVtD}o&1}-A6U6@)i%G;hl6{p$V9{nxvId~D+IxN@naG2)OzFu0C7}T2 z?*YsbTjfsxico;38wl;+00OpucGao|@Mug+vKeGD8HQnO8=JL;#a_)8Xa#}<0ecOb zujfZbMk0Fv7>;WhQ9*?I`C_n`a z1|uLKz&p_!ih#vpk&DQ%68!!BX*wE{$&8AMl1L@ z`Tch=7!1wLw7EIZY6Gp-@VPnU=ux2)>Tm>wg!snA#nsgKuU@^>Zs(Vm69t^QIyiNT{PD+LD_5>msiaSyApQLy z7K<=UHazS#F+sGHQd>)0EVQR*k<;l{S-C7X*Qc~JC^r{^gM)EAVra<2ZfDBn=$$(x zmkXp)epHlne4Kpe4*2GfeQ7m|{)e8-NUvNGZ*memS28+Ux1ct}WJA7yHa=sCyj z_W1sLqTV+rhci5k%*-I`*YnhBq_~(~SQw<$`XnWVXJ+D;FMD=$Kury8)vD06G+{)9 z$LZ6H8ykJBR<_wpR;wv*y|qFjp$LUSrBa%f=ELVx;^OEQ3nej;YBEvze11$!j8?1F zXf!I7O0U-w?)^Joqphch85WGY1nml;7INS|9g}HCG;3%rGAIjt%%??0NvTBcJgl@x5JN za9_mkklx!f**)EwX`{bp)+qCI{!;^U$ENEi_w1c@-d-!=H>-aYrVhwV=e34y+HlXq zeQzFrx8<}%VL8vLOYf$%Wh6dry7;oS*LdUU0nZaf&xY$uNTcZ54?f*{!K!aQ@~!-| zSzf>jd6iT9?bCti_=^Y5$9#0C`lR@sL*0xP8Gb<#e{pIRJ3e`!u^{*EuIE*b#-xj2w!!(OgH>JEe?4KRoZD-e`~Co)Ac_(2;NI2K|C6+^x*yFmv_t?+(_uhVnj(N#t4j8;mc=3PzOK z+-rEa^oq|~&uMF+BH-ta_EzzcPuQ28&y+9j@%LF?WItNi5BWLavrEQE&f1P$gIHA!W=Ff(b`n!%H^4ARs;>kK{XF_r>b=o-d za>2DQf9vko`*xOf{ZdB1+AW-)dDcY{>zZT7nj~BEv#t&G73VE|)WWx?$t895%(_}l z+F!~eA{7f8Kdff*vAFy->(6bGKbB^xhivoJoxyGYu=fiuT}jVqoAHbAWwqR-r~gP8 zmG3aT?XYijWaC+d*#rPXF-$DO#299i$#FCik5r0K6h~3jI)3IK1O?mdc{$EM7g(U- PF(Cl6Aw}Pz-B9^oj2c9P literal 0 HcmV?d00001 diff --git a/farmq-admin/static/novnc/app/images/icons/novnc-ios-60.png b/farmq-admin/static/novnc/app/images/icons/novnc-ios-60.png new file mode 100644 index 0000000000000000000000000000000000000000..8cda29530c8fc0a3ee7499be716f90e22b2c4fc8 GIT binary patch literal 1595 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 literal 0 HcmV?d00001 diff --git a/farmq-admin/static/novnc/app/images/icons/novnc-ios-87.png b/farmq-admin/static/novnc/app/images/icons/novnc-ios-87.png new file mode 100644 index 0000000000000000000000000000000000000000..4377d874b18d965cb430f80dc1f97d3e8f0daff0 GIT binary patch literal 2708 zcmbW3c{tQv8^?cx3`Ptg)mUQ;M#k93q=?57GM19D%!Dk-HWV_9EsQ5>2qB)3p0rq| zgh_Tv?^vG59!AK%3|X?g)BE51$NSHFU)Oy;=RW6r&$+I1|8d_bwl-!*1x^Y80C3d8 z+{BKd8&9+Y2)!*VuwuU#}V;gDlH7Z@=d@CTV+C2?5%A5Us5bK*?Ep%&Jr;2A*) zA#Q~yztp@q$Kowaj2yy;m-6rU!1|>-8oa+*+)~Q(q>o^SOI=6ah}82&emQmV;ljCG z6;THhsF8}f>6?4dsbZg})Hm|-tva%n#kTjqn)S;cc`$e`0blE4J}#aSkjpHF`&JDx z7w@ddEG#cxcc;@e8P*1c4Iy0x`lJ~Ky@CB}ze`_kX=w>3`Gd=G88q4{$(m1=d!6=w z!Uk$A7`z!*gg{xIjC?6=VgXvn!BH~wR^1GzIz%vB7MW0@d<5ef8JS@EWYqb&@q?Pv zQm6UolW_*+xn*%vF+x2c3~siXG3kF>zBNf~tEGEAXm0@I#u9h84+zzTd~T>FMjsd|OCB@G0dh@0-)! z$hSWAbxy1S^wQ#o{JVDbpDXx=Jut7+CtTI>J{Q}0-q}^F4t>kLSL^0>Z6q=I#c46q z6Ri2fwVB^PZ_^Wsl-F+U2W5GI5dTv1IU4FwJ`*vatGT9;)rTBBsEFN8u1i%1dFH=N{(%gTuv9s-;5{iB zLUB=~{3BS(Ga*PR2jww&)21w6IKlnPcUykCJ-2F(M)F3gPE(*A3s9xcnFmMc6XbD82cw0 zUfvvu3*YQd1^ViJ*=%;QiTq$K$@v&DcF^zRUX-7^U`vTrm?$j?VD zoK?aJBh|INL&}H#F^5vy^QzxnF5X%H1TBc155dKShB3v%$c8*}wW z%AJ*aUYDu^j$knPdUtNoSs+^nF~;p@a6h*gsR}2*^}BED>qX&8W~&S33yx=_NOb@4 zOZk9XglYZ#6It9i9PYv8E4C7z1cDmIKs|o%vGm}(?j$LL861v?3nky)LN3=_u*|$v zz!SZbNMO(0eLo<|1;f@*9vdhH9YlFF#VO=HIi(AvcVw{#Xz95pcLLL%q@a#q^#y+mw@_{xV5(4yXkb+@fW`WNNp(Fxh0HTk2Av4YK9 zAF&^E&zpPhV|yPl8yg$7osa>{R%HohYgnxw%;0h!0*`DYbz8%K#(Onihp^_Jl`D19 zcos*%gG)c(?+g*3gN)n9x(Xg94G;2dY^76X6*Es6LC@nJc{zwbA^*I3Y&2lU`|XV@ zJaGG;;)%!I^Us?mVj)o*A}H-Pm97vqHH{y0zHQqzQQg(m2q9Pv7gfdIzVNGqqodcp z)x#P#TaXt#&6nsE`|aCR9wI;7>h61ZyU?t-PPQAd5;@D#%G}kty$Ys@ z!7xQ)K&9t;Y&Lr;JuG)j9VX-v9^?_!a;6Sdea`kE2H^A7nXvIFkG9+6J5YhdiVEAo{ohGxh8PX{+oxEKwek?? zZT`!5R^Aph#E89m1yNRRFE-)NjIym$9AiR;Az2(u@r6r`yx=uux;dTW3vp3!Auz|Hvs=mlctO zP^v(rU@*P03R(8zMe2MDRF^ve>V(WH8W<7`*0i;^FCOlKlh`sSgj@nNx#;hq z<*+HHb+fI=uSfiEfhF*GmLmWx MOl?eF8x!LG4f?<8TL1t6 literal 0 HcmV?d00001 diff --git a/farmq-admin/static/novnc/app/images/icons/novnc-ios-icon.svg b/farmq-admin/static/novnc/app/images/icons/novnc-ios-icon.svg new file mode 100644 index 0000000..009452a --- /dev/null +++ b/farmq-admin/static/novnc/app/images/icons/novnc-ios-icon.svg @@ -0,0 +1,183 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/icons/novnc.ico b/farmq-admin/static/novnc/app/images/icons/novnc.ico new file mode 100644 index 0000000000000000000000000000000000000000..c3bc58e389396b7beb0fb0a97386e2af3604b386 GIT binary patch literal 310566 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 + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/keyboard.svg b/farmq-admin/static/novnc/app/images/keyboard.svg new file mode 100644 index 0000000..137b350 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/keyboard.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/power.svg b/farmq-admin/static/novnc/app/images/power.svg new file mode 100644 index 0000000..4925d3e --- /dev/null +++ b/farmq-admin/static/novnc/app/images/power.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/settings.svg b/farmq-admin/static/novnc/app/images/settings.svg new file mode 100644 index 0000000..dbb2e80 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/settings.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/tab.svg b/farmq-admin/static/novnc/app/images/tab.svg new file mode 100644 index 0000000..1ccb322 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/tab.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/toggleextrakeys.svg b/farmq-admin/static/novnc/app/images/toggleextrakeys.svg new file mode 100644 index 0000000..b578c0d --- /dev/null +++ b/farmq-admin/static/novnc/app/images/toggleextrakeys.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/warning.svg b/farmq-admin/static/novnc/app/images/warning.svg new file mode 100644 index 0000000..7114f9b --- /dev/null +++ b/farmq-admin/static/novnc/app/images/warning.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/farmq-admin/static/novnc/app/images/windows.svg b/farmq-admin/static/novnc/app/images/windows.svg new file mode 100644 index 0000000..ad5eec3 --- /dev/null +++ b/farmq-admin/static/novnc/app/images/windows.svg @@ -0,0 +1,65 @@ + + + +image/svg+xml + + + \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/README b/farmq-admin/static/novnc/app/locale/README new file mode 100644 index 0000000..ca4f548 --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/README @@ -0,0 +1 @@ +DO NOT MODIFY THE FILES IN THIS FOLDER, THEY ARE AUTOMATICALLY GENERATED FROM THE PO-FILES. diff --git a/farmq-admin/static/novnc/app/locale/cs.json b/farmq-admin/static/novnc/app/locale/cs.json new file mode 100644 index 0000000..589145e --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/cs.json @@ -0,0 +1,71 @@ +{ + "Connecting...": "Připojení...", + "Disconnecting...": "Odpojení...", + "Reconnecting...": "Obnova připojení...", + "Internal error": "Vnitřní chyba", + "Must set host": "Hostitel musí být nastavení", + "Connected (encrypted) to ": "Připojení (šifrované) k ", + "Connected (unencrypted) to ": "Připojení (nešifrované) k ", + "Something went wrong, connection is closed": "Něco se pokazilo, odpojeno", + "Failed to connect to server": "Chyba připojení k serveru", + "Disconnected": "Odpojeno", + "New connection has been rejected with reason: ": "Nové připojení bylo odmítnuto s odůvodněním: ", + "New connection has been rejected": "Nové připojení bylo odmítnuto", + "Password is required": "Je vyžadováno heslo", + "noVNC encountered an error:": "noVNC narazilo na chybu:", + "Hide/Show the control bar": "Skrýt/zobrazit ovládací panel", + "Move/Drag Viewport": "Přesunout/přetáhnout výřez", + "viewport drag": "přesun výřezu", + "Active Mouse Button": "Aktivní tlačítka myši", + "No mousebutton": "Žádné", + "Left mousebutton": "Levé tlačítko myši", + "Middle mousebutton": "Prostřední tlačítko myši", + "Right mousebutton": "Pravé tlačítko myši", + "Keyboard": "Klávesnice", + "Show Keyboard": "Zobrazit klávesnici", + "Extra keys": "Extra klávesy", + "Show Extra Keys": "Zobrazit extra klávesy", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Přepnout Ctrl", + "Alt": "Alt", + "Toggle Alt": "Přepnout Alt", + "Send Tab": "Odeslat tabulátor", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "Odeslat Esc", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "Poslat Ctrl-Alt-Del", + "Shutdown/Reboot": "Vypnutí/Restart", + "Shutdown/Reboot...": "Vypnutí/Restart...", + "Power": "Napájení", + "Shutdown": "Vypnout", + "Reboot": "Restart", + "Reset": "Reset", + "Clipboard": "Schránka", + "Clear": "Vymazat", + "Fullscreen": "Celá obrazovka", + "Settings": "Nastavení", + "Shared Mode": "Sdílený režim", + "View Only": "Pouze prohlížení", + "Clip to Window": "Přizpůsobit oknu", + "Scaling Mode:": "Přizpůsobení velikosti", + "None": "Žádné", + "Local Scaling": "Místní", + "Remote Resizing": "Vzdálené", + "Advanced": "Pokročilé", + "Repeater ID:": "ID opakovače", + "WebSocket": "WebSocket", + "Encrypt": "Šifrování:", + "Host:": "Hostitel:", + "Port:": "Port:", + "Path:": "Cesta", + "Automatic Reconnect": "Automatická obnova připojení", + "Reconnect Delay (ms):": "Zpoždění připojení (ms)", + "Show Dot when No Cursor": "Tečka místo chybějícího kurzoru myši", + "Logging:": "Logování:", + "Disconnect": "Odpojit", + "Connect": "Připojit", + "Password:": "Heslo", + "Send Password": "Odeslat heslo", + "Cancel": "Zrušit" +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/de.json b/farmq-admin/static/novnc/app/locale/de.json new file mode 100644 index 0000000..62e7336 --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/de.json @@ -0,0 +1,69 @@ +{ + "Connecting...": "Verbinden...", + "Disconnecting...": "Verbindung trennen...", + "Reconnecting...": "Verbindung wiederherstellen...", + "Internal error": "Interner Fehler", + "Must set host": "Richten Sie den Server ein", + "Connected (encrypted) to ": "Verbunden mit (verschlüsselt) ", + "Connected (unencrypted) to ": "Verbunden mit (unverschlüsselt) ", + "Something went wrong, connection is closed": "Etwas lief schief, Verbindung wurde getrennt", + "Disconnected": "Verbindung zum Server getrennt", + "New connection has been rejected with reason: ": "Verbindung wurde aus folgendem Grund abgelehnt: ", + "New connection has been rejected": "Verbindung wurde abgelehnt", + "Password is required": "Passwort ist erforderlich", + "noVNC encountered an error:": "Ein Fehler ist aufgetreten:", + "Hide/Show the control bar": "Kontrollleiste verstecken/anzeigen", + "Move/Drag Viewport": "Ansichtsfenster verschieben/ziehen", + "viewport drag": "Ansichtsfenster ziehen", + "Active Mouse Button": "Aktive Maustaste", + "No mousebutton": "Keine Maustaste", + "Left mousebutton": "Linke Maustaste", + "Middle mousebutton": "Mittlere Maustaste", + "Right mousebutton": "Rechte Maustaste", + "Keyboard": "Tastatur", + "Show Keyboard": "Tastatur anzeigen", + "Extra keys": "Zusatztasten", + "Show Extra Keys": "Zusatztasten anzeigen", + "Ctrl": "Strg", + "Toggle Ctrl": "Strg umschalten", + "Alt": "Alt", + "Toggle Alt": "Alt umschalten", + "Send Tab": "Tab senden", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "Escape senden", + "Ctrl+Alt+Del": "Strg+Alt+Entf", + "Send Ctrl-Alt-Del": "Strg+Alt+Entf senden", + "Shutdown/Reboot": "Herunterfahren/Neustarten", + "Shutdown/Reboot...": "Herunterfahren/Neustarten...", + "Power": "Energie", + "Shutdown": "Herunterfahren", + "Reboot": "Neustarten", + "Reset": "Zurücksetzen", + "Clipboard": "Zwischenablage", + "Clear": "Löschen", + "Fullscreen": "Vollbild", + "Settings": "Einstellungen", + "Shared Mode": "Geteilter Modus", + "View Only": "Nur betrachten", + "Clip to Window": "Auf Fenster begrenzen", + "Scaling Mode:": "Skalierungsmodus:", + "None": "Keiner", + "Local Scaling": "Lokales skalieren", + "Remote Resizing": "Serverseitiges skalieren", + "Advanced": "Erweitert", + "Repeater ID:": "Repeater ID:", + "WebSocket": "WebSocket", + "Encrypt": "Verschlüsselt", + "Host:": "Server:", + "Port:": "Port:", + "Path:": "Pfad:", + "Automatic Reconnect": "Automatisch wiederverbinden", + "Reconnect Delay (ms):": "Wiederverbindungsverzögerung (ms):", + "Logging:": "Protokollierung:", + "Disconnect": "Verbindung trennen", + "Connect": "Verbinden", + "Password:": "Passwort:", + "Cancel": "Abbrechen", + "Canvas not supported.": "Canvas nicht unterstützt." +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/el.json b/farmq-admin/static/novnc/app/locale/el.json new file mode 100644 index 0000000..f801251 --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/el.json @@ -0,0 +1,69 @@ +{ + "Connecting...": "Συνδέεται...", + "Disconnecting...": "Aποσυνδέεται...", + "Reconnecting...": "Επανασυνδέεται...", + "Internal error": "Εσωτερικό σφάλμα", + "Must set host": "Πρέπει να οριστεί ο διακομιστής", + "Connected (encrypted) to ": "Συνδέθηκε (κρυπτογραφημένα) με το ", + "Connected (unencrypted) to ": "Συνδέθηκε (μη κρυπτογραφημένα) με το ", + "Something went wrong, connection is closed": "Κάτι πήγε στραβά, η σύνδεση διακόπηκε", + "Disconnected": "Αποσυνδέθηκε", + "New connection has been rejected with reason: ": "Η νέα σύνδεση απορρίφθηκε διότι: ", + "New connection has been rejected": "Η νέα σύνδεση απορρίφθηκε ", + "Password is required": "Απαιτείται ο κωδικός πρόσβασης", + "noVNC encountered an error:": "το noVNC αντιμετώπισε ένα σφάλμα:", + "Hide/Show the control bar": "Απόκρυψη/Εμφάνιση γραμμής ελέγχου", + "Move/Drag Viewport": "Μετακίνηση/Σύρσιμο Θεατού πεδίου", + "viewport drag": "σύρσιμο θεατού πεδίου", + "Active Mouse Button": "Ενεργό Πλήκτρο Ποντικιού", + "No mousebutton": "Χωρίς Πλήκτρο Ποντικιού", + "Left mousebutton": "Αριστερό Πλήκτρο Ποντικιού", + "Middle mousebutton": "Μεσαίο Πλήκτρο Ποντικιού", + "Right mousebutton": "Δεξί Πλήκτρο Ποντικιού", + "Keyboard": "Πληκτρολόγιο", + "Show Keyboard": "Εμφάνιση Πληκτρολογίου", + "Extra keys": "Επιπλέον πλήκτρα", + "Show Extra Keys": "Εμφάνιση Επιπλέον Πλήκτρων", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Εναλλαγή Ctrl", + "Alt": "Alt", + "Toggle Alt": "Εναλλαγή Alt", + "Send Tab": "Αποστολή Tab", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "Αποστολή Escape", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "Αποστολή Ctrl-Alt-Del", + "Shutdown/Reboot": "Κλείσιμο/Επανεκκίνηση", + "Shutdown/Reboot...": "Κλείσιμο/Επανεκκίνηση...", + "Power": "Απενεργοποίηση", + "Shutdown": "Κλείσιμο", + "Reboot": "Επανεκκίνηση", + "Reset": "Επαναφορά", + "Clipboard": "Πρόχειρο", + "Clear": "Καθάρισμα", + "Fullscreen": "Πλήρης Οθόνη", + "Settings": "Ρυθμίσεις", + "Shared Mode": "Κοινόχρηστη Λειτουργία", + "View Only": "Μόνο Θέαση", + "Clip to Window": "Αποκοπή στο όριο του Παράθυρου", + "Scaling Mode:": "Λειτουργία Κλιμάκωσης:", + "None": "Καμία", + "Local Scaling": "Τοπική Κλιμάκωση", + "Remote Resizing": "Απομακρυσμένη Αλλαγή μεγέθους", + "Advanced": "Για προχωρημένους", + "Repeater ID:": "Repeater ID:", + "WebSocket": "WebSocket", + "Encrypt": "Κρυπτογράφηση", + "Host:": "Όνομα διακομιστή:", + "Port:": "Πόρτα διακομιστή:", + "Path:": "Διαδρομή:", + "Automatic Reconnect": "Αυτόματη επανασύνδεση", + "Reconnect Delay (ms):": "Καθυστέρηση επανασύνδεσης (ms):", + "Logging:": "Καταγραφή:", + "Disconnect": "Αποσύνδεση", + "Connect": "Σύνδεση", + "Password:": "Κωδικός Πρόσβασης:", + "Cancel": "Ακύρωση", + "Canvas not supported.": "Δεν υποστηρίζεται το στοιχείο Canvas" +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/es.json b/farmq-admin/static/novnc/app/locale/es.json new file mode 100644 index 0000000..b9e663a --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/es.json @@ -0,0 +1,68 @@ +{ + "Connecting...": "Conectando...", + "Connected (encrypted) to ": "Conectado (con encriptación) a", + "Connected (unencrypted) to ": "Conectado (sin encriptación) a", + "Disconnecting...": "Desconectando...", + "Disconnected": "Desconectado", + "Must set host": "Se debe configurar el host", + "Reconnecting...": "Reconectando...", + "Password is required": "La contraseña es obligatoria", + "Disconnect timeout": "Tiempo de desconexión agotado", + "noVNC encountered an error:": "noVNC ha encontrado un error:", + "Hide/Show the control bar": "Ocultar/Mostrar la barra de control", + "Move/Drag Viewport": "Mover/Arrastrar la ventana", + "viewport drag": "Arrastrar la ventana", + "Active Mouse Button": "Botón activo del ratón", + "No mousebutton": "Ningún botón del ratón", + "Left mousebutton": "Botón izquierdo del ratón", + "Middle mousebutton": "Botón central del ratón", + "Right mousebutton": "Botón derecho del ratón", + "Keyboard": "Teclado", + "Show Keyboard": "Mostrar teclado", + "Extra keys": "Teclas adicionales", + "Show Extra Keys": "Mostrar Teclas Adicionales", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Pulsar/Soltar Ctrl", + "Alt": "Alt", + "Toggle Alt": "Pulsar/Soltar Alt", + "Send Tab": "Enviar Tabulación", + "Tab": "Tabulación", + "Esc": "Esc", + "Send Escape": "Enviar Escape", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "Enviar Ctrl+Alt+Del", + "Shutdown/Reboot": "Apagar/Reiniciar", + "Shutdown/Reboot...": "Apagar/Reiniciar...", + "Power": "Encender", + "Shutdown": "Apagar", + "Reboot": "Reiniciar", + "Reset": "Restablecer", + "Clipboard": "Portapapeles", + "Clear": "Vaciar", + "Fullscreen": "Pantalla Completa", + "Settings": "Configuraciones", + "Encrypt": "Encriptar", + "Shared Mode": "Modo Compartido", + "View Only": "Solo visualización", + "Clip to Window": "Recortar al tamaño de la ventana", + "Scaling Mode:": "Modo de escalado:", + "None": "Ninguno", + "Local Scaling": "Escalado Local", + "Local Downscaling": "Reducción de escala local", + "Remote Resizing": "Cambio de tamaño remoto", + "Advanced": "Avanzado", + "Local Cursor": "Cursor Local", + "Repeater ID:": "ID del Repetidor:", + "WebSocket": "WebSocket", + "Host:": "Host:", + "Port:": "Puerto:", + "Path:": "Ruta:", + "Automatic Reconnect": "Reconexión automática", + "Reconnect Delay (ms):": "Retraso en la reconexión (ms):", + "Logging:": "Registrando:", + "Disconnect": "Desconectar", + "Connect": "Conectar", + "Password:": "Contraseña:", + "Cancel": "Cancelar", + "Canvas not supported.": "Canvas no soportado." +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/fr.json b/farmq-admin/static/novnc/app/locale/fr.json new file mode 100644 index 0000000..22531f7 --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/fr.json @@ -0,0 +1,78 @@ +{ + "HTTPS is required for full functionality": "", + "Connecting...": "En cours de connexion...", + "Disconnecting...": "Déconnexion en cours...", + "Reconnecting...": "Reconnexion en cours...", + "Internal error": "Erreur interne", + "Must set host": "Doit définir l'hôte", + "Connected (encrypted) to ": "Connecté (chiffré) à ", + "Connected (unencrypted) to ": "Connecté (non chiffré) à ", + "Something went wrong, connection is closed": "Quelque chose s'est mal passé, la connexion a été fermée", + "Failed to connect to server": "Échec de connexion au serveur", + "Disconnected": "Déconnecté", + "New connection has been rejected with reason: ": "Une nouvelle connexion a été rejetée avec motif : ", + "New connection has been rejected": "Une nouvelle connexion a été rejetée", + "Credentials are required": "Les identifiants sont requis", + "noVNC encountered an error:": "noVNC a rencontré une erreur :", + "Hide/Show the control bar": "Masquer/Afficher la barre de contrôle", + "Drag": "Faire glisser", + "Move/Drag Viewport": "Déplacer/faire glisser le Viewport", + "Keyboard": "Clavier", + "Show Keyboard": "Afficher le clavier", + "Extra keys": "Touches supplémentaires", + "Show Extra Keys": "Afficher les touches supplémentaires", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Basculer Ctrl", + "Alt": "Alt", + "Toggle Alt": "Basculer Alt", + "Toggle Windows": "Basculer Windows", + "Windows": "Windows", + "Send Tab": "Envoyer l'onglet", + "Tab": "l'onglet", + "Esc": "Esc", + "Send Escape": "Envoyer Escape", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "Envoyer Ctrl-Alt-Del", + "Shutdown/Reboot": "Arrêter/Redémarrer", + "Shutdown/Reboot...": "Arrêter/Redémarrer...", + "Power": "Alimentation", + "Shutdown": "Arrêter", + "Reboot": "Redémarrer", + "Reset": "Réinitialiser", + "Clipboard": "Presse-papiers", + "Edit clipboard content in the textarea below.": "", + "Settings": "Paramètres", + "Shared Mode": "Mode partagé", + "View Only": "Afficher uniquement", + "Clip to Window": "Clip à fenêtre", + "Scaling Mode:": "Mode mise à l'échelle :", + "None": "Aucun", + "Local Scaling": "Mise à l'échelle locale", + "Remote Resizing": "Redimensionnement à distance", + "Advanced": "Avancé", + "Quality:": "Qualité :", + "Compression level:": "Niveau de compression :", + "Repeater ID:": "ID Répéteur :", + "WebSocket": "WebSocket", + "Encrypt": "Chiffrer", + "Host:": "Hôte :", + "Port:": "Port :", + "Path:": "Chemin :", + "Automatic Reconnect": "Reconnecter automatiquemen", + "Reconnect Delay (ms):": "Délai de reconnexion (ms) :", + "Show Dot when No Cursor": "Afficher le point lorsqu'il n'y a pas de curseur", + "Logging:": "Se connecter :", + "Version:": "Version :", + "Disconnect": "Déconnecter", + "Connect": "Connecter", + "Server identity": "", + "The server has provided the following identifying information:": "", + "Fingerprint:": "", + "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "", + "Approve": "", + "Reject": "", + "Username:": "Nom d'utilisateur :", + "Password:": "Mot de passe :", + "Send Credentials": "Envoyer les identifiants", + "Cancel": "Annuler" +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/it.json b/farmq-admin/static/novnc/app/locale/it.json new file mode 100644 index 0000000..6fd2570 --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/it.json @@ -0,0 +1,72 @@ +{ + "Connecting...": "Connessione in corso...", + "Disconnecting...": "Disconnessione...", + "Reconnecting...": "Riconnessione...", + "Internal error": "Errore interno", + "Must set host": "Devi impostare l'host", + "Connected (encrypted) to ": "Connesso (crittografato) a ", + "Connected (unencrypted) to ": "Connesso (non crittografato) a", + "Something went wrong, connection is closed": "Qualcosa è andato storto, la connessione è stata chiusa", + "Failed to connect to server": "Impossibile connettersi al server", + "Disconnected": "Disconnesso", + "New connection has been rejected with reason: ": "La nuova connessione è stata rifiutata con motivo: ", + "New connection has been rejected": "La nuova connessione è stata rifiutata", + "Credentials are required": "Le credenziali sono obbligatorie", + "noVNC encountered an error:": "noVNC ha riscontrato un errore:", + "Hide/Show the control bar": "Nascondi/Mostra la barra di controllo", + "Drag": "", + "Move/Drag Viewport": "", + "Keyboard": "Tastiera", + "Show Keyboard": "Mostra tastiera", + "Extra keys": "Tasti Aggiuntivi", + "Show Extra Keys": "Mostra Tasti Aggiuntivi", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Tieni premuto Ctrl", + "Alt": "Alt", + "Toggle Alt": "Tieni premuto Alt", + "Toggle Windows": "Tieni premuto Windows", + "Windows": "Windows", + "Send Tab": "Invia Tab", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "Invia Esc", + "Ctrl+Alt+Del": "Ctrl+Alt+Canc", + "Send Ctrl-Alt-Del": "Invia Ctrl-Alt-Canc", + "Shutdown/Reboot": "Spegnimento/Riavvio", + "Shutdown/Reboot...": "Spegnimento/Riavvio...", + "Power": "Alimentazione", + "Shutdown": "Spegnimento", + "Reboot": "Riavvio", + "Reset": "Reset", + "Clipboard": "Clipboard", + "Clear": "Pulisci", + "Fullscreen": "Schermo intero", + "Settings": "Impostazioni", + "Shared Mode": "Modalità condivisa", + "View Only": "Sola Visualizzazione", + "Clip to Window": "", + "Scaling Mode:": "Modalità di ridimensionamento:", + "None": "Nessuna", + "Local Scaling": "Ridimensionamento Locale", + "Remote Resizing": "Ridimensionamento Remoto", + "Advanced": "Avanzate", + "Quality:": "Qualità:", + "Compression level:": "Livello Compressione:", + "Repeater ID:": "ID Ripetitore:", + "WebSocket": "WebSocket", + "Encrypt": "Crittografa", + "Host:": "Host:", + "Port:": "Porta:", + "Path:": "Percorso:", + "Automatic Reconnect": "Riconnessione Automatica", + "Reconnect Delay (ms):": "Ritardo Riconnessione (ms):", + "Show Dot when No Cursor": "Mostra Punto quando Nessun Cursore", + "Logging:": "", + "Version:": "Versione:", + "Disconnect": "Disconnetti", + "Connect": "Connetti", + "Username:": "Utente:", + "Password:": "Password:", + "Send Credentials": "Invia Credenziale", + "Cancel": "Annulla" +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/ja.json b/farmq-admin/static/novnc/app/locale/ja.json new file mode 100644 index 0000000..43fc5bf --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/ja.json @@ -0,0 +1,72 @@ +{ + "Connecting...": "接続しています...", + "Disconnecting...": "切断しています...", + "Reconnecting...": "再接続しています...", + "Internal error": "内部エラー", + "Must set host": "ホストを設定する必要があります", + "Connected (encrypted) to ": "接続しました (暗号化済み): ", + "Connected (unencrypted) to ": "接続しました (暗号化されていません): ", + "Something went wrong, connection is closed": "何らかの問題で、接続が閉じられました", + "Failed to connect to server": "サーバーへの接続に失敗しました", + "Disconnected": "切断しました", + "New connection has been rejected with reason: ": "新規接続は次の理由で拒否されました: ", + "New connection has been rejected": "新規接続は拒否されました", + "Credentials are required": "資格情報が必要です", + "noVNC encountered an error:": "noVNC でエラーが発生しました:", + "Hide/Show the control bar": "コントロールバーを隠す/表示する", + "Drag": "ドラッグ", + "Move/Drag Viewport": "ビューポートを移動/ドラッグ", + "Keyboard": "キーボード", + "Show Keyboard": "キーボードを表示", + "Extra keys": "追加キー", + "Show Extra Keys": "追加キーを表示", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Ctrl キーを切り替え", + "Alt": "Alt", + "Toggle Alt": "Alt キーを切り替え", + "Toggle Windows": "Windows キーを切り替え", + "Windows": "Windows", + "Send Tab": "Tab キーを送信", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "Escape キーを送信", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "Ctrl-Alt-Del を送信", + "Shutdown/Reboot": "シャットダウン/再起動", + "Shutdown/Reboot...": "シャットダウン/再起動...", + "Power": "電源", + "Shutdown": "シャットダウン", + "Reboot": "再起動", + "Reset": "リセット", + "Clipboard": "クリップボード", + "Clear": "クリア", + "Fullscreen": "全画面表示", + "Settings": "設定", + "Shared Mode": "共有モード", + "View Only": "表示のみ", + "Clip to Window": "ウィンドウにクリップ", + "Scaling Mode:": "スケーリングモード:", + "None": "なし", + "Local Scaling": "ローカルスケーリング", + "Remote Resizing": "リモートでリサイズ", + "Advanced": "高度", + "Quality:": "品質:", + "Compression level:": "圧縮レベル:", + "Repeater ID:": "リピーター ID:", + "WebSocket": "WebSocket", + "Encrypt": "暗号化", + "Host:": "ホスト:", + "Port:": "ポート:", + "Path:": "パス:", + "Automatic Reconnect": "自動再接続", + "Reconnect Delay (ms):": "再接続する遅延 (ミリ秒):", + "Show Dot when No Cursor": "カーソルがないときにドットを表示", + "Logging:": "ロギング:", + "Version:": "バージョン:", + "Disconnect": "切断", + "Connect": "接続", + "Username:": "ユーザー名:", + "Password:": "パスワード:", + "Send Credentials": "資格情報を送信", + "Cancel": "キャンセル" +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/ko.json b/farmq-admin/static/novnc/app/locale/ko.json new file mode 100644 index 0000000..e4ecddc --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/ko.json @@ -0,0 +1,70 @@ +{ + "Connecting...": "연결중...", + "Disconnecting...": "연결 해제중...", + "Reconnecting...": "재연결중...", + "Internal error": "내부 오류", + "Must set host": "호스트는 설정되어야 합니다.", + "Connected (encrypted) to ": "다음과 (암호화되어) 연결되었습니다:", + "Connected (unencrypted) to ": "다음과 (암호화 없이) 연결되었습니다:", + "Something went wrong, connection is closed": "무언가 잘못되었습니다, 연결이 닫혔습니다.", + "Failed to connect to server": "서버에 연결하지 못했습니다.", + "Disconnected": "연결이 해제되었습니다.", + "New connection has been rejected with reason: ": "새 연결이 다음 이유로 거부되었습니다:", + "New connection has been rejected": "새 연결이 거부되었습니다.", + "Password is required": "비밀번호가 필요합니다.", + "noVNC encountered an error:": "noVNC에 오류가 발생했습니다:", + "Hide/Show the control bar": "컨트롤 바 숨기기/보이기", + "Move/Drag Viewport": "움직이기/드래그 뷰포트", + "viewport drag": "뷰포트 드래그", + "Active Mouse Button": "마우스 버튼 활성화", + "No mousebutton": "마우스 버튼 없음", + "Left mousebutton": "왼쪽 마우스 버튼", + "Middle mousebutton": "중간 마우스 버튼", + "Right mousebutton": "오른쪽 마우스 버튼", + "Keyboard": "키보드", + "Show Keyboard": "키보드 보이기", + "Extra keys": "기타 키들", + "Show Extra Keys": "기타 키들 보이기", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Ctrl 켜기/끄기", + "Alt": "Alt", + "Toggle Alt": "Alt 켜기/끄기", + "Send Tab": "Tab 보내기", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "Esc 보내기", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "Ctrl+Alt+Del 보내기", + "Shutdown/Reboot": "셧다운/리붓", + "Shutdown/Reboot...": "셧다운/리붓...", + "Power": "전원", + "Shutdown": "셧다운", + "Reboot": "리붓", + "Reset": "리셋", + "Clipboard": "클립보드", + "Clear": "지우기", + "Fullscreen": "전체화면", + "Settings": "설정", + "Shared Mode": "공유 모드", + "View Only": "보기 전용", + "Clip to Window": "창에 클립", + "Scaling Mode:": "스케일링 모드:", + "None": "없음", + "Local Scaling": "로컬 스케일링", + "Remote Resizing": "원격 크기 조절", + "Advanced": "고급", + "Repeater ID:": "중계 ID", + "WebSocket": "웹소켓", + "Encrypt": "암호화", + "Host:": "호스트:", + "Port:": "포트:", + "Path:": "위치:", + "Automatic Reconnect": "자동 재연결", + "Reconnect Delay (ms):": "재연결 지연 시간 (ms)", + "Logging:": "로깅", + "Disconnect": "연결 해제", + "Connect": "연결", + "Password:": "비밀번호:", + "Send Password": "비밀번호 전송", + "Cancel": "취소" +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/nl.json b/farmq-admin/static/novnc/app/locale/nl.json new file mode 100644 index 0000000..0cdcc92 --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/nl.json @@ -0,0 +1,73 @@ +{ + "Connecting...": "Verbinden...", + "Disconnecting...": "Verbinding verbreken...", + "Reconnecting...": "Opnieuw verbinding maken...", + "Internal error": "Interne fout", + "Must set host": "Host moeten worden ingesteld", + "Connected (encrypted) to ": "Verbonden (versleuteld) met ", + "Connected (unencrypted) to ": "Verbonden (onversleuteld) met ", + "Something went wrong, connection is closed": "Er iets fout gelopen, verbinding werd verbroken", + "Failed to connect to server": "Verbinding maken met server is mislukt", + "Disconnected": "Verbinding verbroken", + "New connection has been rejected with reason: ": "Nieuwe verbinding is geweigerd omwille van de volgende reden: ", + "New connection has been rejected": "Nieuwe verbinding is geweigerd", + "Password is required": "Wachtwoord is vereist", + "noVNC encountered an error:": "noVNC heeft een fout bemerkt:", + "Hide/Show the control bar": "Verberg/Toon de bedieningsbalk", + "Move/Drag Viewport": "Verplaats/Versleep Kijkvenster", + "viewport drag": "kijkvenster slepen", + "Active Mouse Button": "Actieve Muisknop", + "No mousebutton": "Geen muisknop", + "Left mousebutton": "Linker muisknop", + "Middle mousebutton": "Middelste muisknop", + "Right mousebutton": "Rechter muisknop", + "Keyboard": "Toetsenbord", + "Show Keyboard": "Toon Toetsenbord", + "Extra keys": "Extra toetsen", + "Show Extra Keys": "Toon Extra Toetsen", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Ctrl omschakelen", + "Alt": "Alt", + "Toggle Alt": "Alt omschakelen", + "Toggle Windows": "Windows omschakelen", + "Windows": "Windows", + "Send Tab": "Tab Sturen", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "Escape Sturen", + "Ctrl+Alt+Del": "Ctrl-Alt-Del", + "Send Ctrl-Alt-Del": "Ctrl-Alt-Del Sturen", + "Shutdown/Reboot": "Uitschakelen/Herstarten", + "Shutdown/Reboot...": "Uitschakelen/Herstarten...", + "Power": "Systeem", + "Shutdown": "Uitschakelen", + "Reboot": "Herstarten", + "Reset": "Resetten", + "Clipboard": "Klembord", + "Clear": "Wissen", + "Fullscreen": "Volledig Scherm", + "Settings": "Instellingen", + "Shared Mode": "Gedeelde Modus", + "View Only": "Alleen Kijken", + "Clip to Window": "Randen buiten venster afsnijden", + "Scaling Mode:": "Schaalmodus:", + "None": "Geen", + "Local Scaling": "Lokaal Schalen", + "Remote Resizing": "Op Afstand Formaat Wijzigen", + "Advanced": "Geavanceerd", + "Repeater ID:": "Repeater ID:", + "WebSocket": "WebSocket", + "Encrypt": "Versleutelen", + "Host:": "Host:", + "Port:": "Poort:", + "Path:": "Pad:", + "Automatic Reconnect": "Automatisch Opnieuw Verbinden", + "Reconnect Delay (ms):": "Vertraging voor Opnieuw Verbinden (ms):", + "Show Dot when No Cursor": "Geef stip weer indien geen cursor", + "Logging:": "Logmeldingen:", + "Disconnect": "Verbinding verbreken", + "Connect": "Verbinden", + "Password:": "Wachtwoord:", + "Send Password": "Verzend Wachtwoord:", + "Cancel": "Annuleren" +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/pl.json b/farmq-admin/static/novnc/app/locale/pl.json new file mode 100644 index 0000000..006ac7a --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/pl.json @@ -0,0 +1,69 @@ +{ + "Connecting...": "Łączenie...", + "Disconnecting...": "Rozłączanie...", + "Reconnecting...": "Łączenie...", + "Internal error": "Błąd wewnętrzny", + "Must set host": "Host i port są wymagane", + "Connected (encrypted) to ": "Połączenie (szyfrowane) z ", + "Connected (unencrypted) to ": "Połączenie (nieszyfrowane) z ", + "Something went wrong, connection is closed": "Coś poszło źle, połączenie zostało zamknięte", + "Disconnected": "Rozłączony", + "New connection has been rejected with reason: ": "Nowe połączenie zostało odrzucone z powodu: ", + "New connection has been rejected": "Nowe połączenie zostało odrzucone", + "Password is required": "Hasło jest wymagane", + "noVNC encountered an error:": "noVNC napotkało błąd:", + "Hide/Show the control bar": "Pokaż/Ukryj pasek ustawień", + "Move/Drag Viewport": "Ruszaj/Przeciągaj Viewport", + "viewport drag": "przeciągnij viewport", + "Active Mouse Button": "Aktywny Przycisk Myszy", + "No mousebutton": "Brak przycisku myszy", + "Left mousebutton": "Lewy przycisk myszy", + "Middle mousebutton": "Środkowy przycisk myszy", + "Right mousebutton": "Prawy przycisk myszy", + "Keyboard": "Klawiatura", + "Show Keyboard": "Pokaż klawiaturę", + "Extra keys": "Przyciski dodatkowe", + "Show Extra Keys": "Pokaż przyciski dodatkowe", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Przełącz Ctrl", + "Alt": "Alt", + "Toggle Alt": "Przełącz Alt", + "Send Tab": "Wyślij Tab", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "Wyślij Escape", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "Wyślij Ctrl-Alt-Del", + "Shutdown/Reboot": "Wyłącz/Uruchom ponownie", + "Shutdown/Reboot...": "Wyłącz/Uruchom ponownie...", + "Power": "Włączony", + "Shutdown": "Wyłącz", + "Reboot": "Uruchom ponownie", + "Reset": "Resetuj", + "Clipboard": "Schowek", + "Clear": "Wyczyść", + "Fullscreen": "Pełny ekran", + "Settings": "Ustawienia", + "Shared Mode": "Tryb Współdzielenia", + "View Only": "Tylko Podgląd", + "Clip to Window": "Przytnij do Okna", + "Scaling Mode:": "Tryb Skalowania:", + "None": "Brak", + "Local Scaling": "Skalowanie lokalne", + "Remote Resizing": "Skalowanie zdalne", + "Advanced": "Zaawansowane", + "Repeater ID:": "ID Repeatera:", + "WebSocket": "WebSocket", + "Encrypt": "Szyfrowanie", + "Host:": "Host:", + "Port:": "Port:", + "Path:": "Ścieżka:", + "Automatic Reconnect": "Automatycznie wznawiaj połączenie", + "Reconnect Delay (ms):": "Opóźnienie wznawiania (ms):", + "Logging:": "Poziom logowania:", + "Disconnect": "Rozłącz", + "Connect": "Połącz", + "Password:": "Hasło:", + "Cancel": "Anuluj", + "Canvas not supported.": "Element Canvas nie jest wspierany." +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/pt_BR.json b/farmq-admin/static/novnc/app/locale/pt_BR.json new file mode 100644 index 0000000..aa130f7 --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/pt_BR.json @@ -0,0 +1,72 @@ +{ + "Connecting...": "Conectando...", + "Disconnecting...": "Desconectando...", + "Reconnecting...": "Reconectando...", + "Internal error": "Erro interno", + "Must set host": "É necessário definir o host", + "Connected (encrypted) to ": "Conectado (com criptografia) a ", + "Connected (unencrypted) to ": "Conectado (sem criptografia) a ", + "Something went wrong, connection is closed": "Algo deu errado. A conexão foi encerrada.", + "Failed to connect to server": "Falha ao conectar-se ao servidor", + "Disconnected": "Desconectado", + "New connection has been rejected with reason: ": "A nova conexão foi rejeitada pelo motivo: ", + "New connection has been rejected": "A nova conexão foi rejeitada", + "Credentials are required": "Credenciais são obrigatórias", + "noVNC encountered an error:": "O noVNC encontrou um erro:", + "Hide/Show the control bar": "Esconder/mostrar a barra de controles", + "Drag": "Arrastar", + "Move/Drag Viewport": "Mover/arrastar a janela", + "Keyboard": "Teclado", + "Show Keyboard": "Mostrar teclado", + "Extra keys": "Teclas adicionais", + "Show Extra Keys": "Mostar teclas adicionais", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Pressionar/soltar Ctrl", + "Alt": "Alt", + "Toggle Alt": "Pressionar/soltar Alt", + "Toggle Windows": "Pressionar/soltar Windows", + "Windows": "Windows", + "Send Tab": "Enviar Tab", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "Enviar Esc", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "Enviar Ctrl-Alt-Del", + "Shutdown/Reboot": "Desligar/reiniciar", + "Shutdown/Reboot...": "Desligar/reiniciar...", + "Power": "Ligar", + "Shutdown": "Desligar", + "Reboot": "Reiniciar", + "Reset": "Reiniciar (forçado)", + "Clipboard": "Área de transferência", + "Clear": "Limpar", + "Fullscreen": "Tela cheia", + "Settings": "Configurações", + "Shared Mode": "Modo compartilhado", + "View Only": "Apenas visualizar", + "Clip to Window": "Recortar à janela", + "Scaling Mode:": "Modo de dimensionamento:", + "None": "Nenhum", + "Local Scaling": "Local", + "Remote Resizing": "Remoto", + "Advanced": "Avançado", + "Quality:": "Qualidade:", + "Compression level:": "Nível de compressão:", + "Repeater ID:": "ID do repetidor:", + "WebSocket": "WebSocket", + "Encrypt": "Criptografar", + "Host:": "Host:", + "Port:": "Porta:", + "Path:": "Caminho:", + "Automatic Reconnect": "Reconexão automática", + "Reconnect Delay (ms):": "Atraso da reconexão (ms)", + "Show Dot when No Cursor": "Mostrar ponto quando não há cursor", + "Logging:": "Registros:", + "Version:": "Versão:", + "Disconnect": "Desconectar", + "Connect": "Conectar", + "Username:": "Nome de usuário:", + "Password:": "Senha:", + "Send Credentials": "Enviar credenciais", + "Cancel": "Cancelar" +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/ru.json b/farmq-admin/static/novnc/app/locale/ru.json new file mode 100644 index 0000000..cab9739 --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/ru.json @@ -0,0 +1,72 @@ +{ + "Connecting...": "Подключение...", + "Disconnecting...": "Отключение...", + "Reconnecting...": "Переподключение...", + "Internal error": "Внутренняя ошибка", + "Must set host": "Задайте имя сервера или IP", + "Connected (encrypted) to ": "Подключено (с шифрованием) к ", + "Connected (unencrypted) to ": "Подключено (без шифрования) к ", + "Something went wrong, connection is closed": "Что-то пошло не так, подключение разорвано", + "Failed to connect to server": "Ошибка подключения к серверу", + "Disconnected": "Отключено", + "New connection has been rejected with reason: ": "Новое соединение отклонено по причине: ", + "New connection has been rejected": "Новое соединение отклонено", + "Credentials are required": "Требуются учетные данные", + "noVNC encountered an error:": "Ошибка noVNC: ", + "Hide/Show the control bar": "Скрыть/Показать контрольную панель", + "Drag": "Переместить", + "Move/Drag Viewport": "Переместить окно", + "Keyboard": "Клавиатура", + "Show Keyboard": "Показать клавиатуру", + "Extra keys": "Дополнительные Кнопки", + "Show Extra Keys": "Показать Дополнительные Кнопки", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Переключение нажатия Ctrl", + "Alt": "Alt", + "Toggle Alt": "Переключение нажатия Alt", + "Toggle Windows": "Переключение вкладок", + "Windows": "Вкладка", + "Send Tab": "Передать нажатие Tab", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "Передать нажатие Escape", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "Передать нажатие Ctrl-Alt-Del", + "Shutdown/Reboot": "Выключить/Перезагрузить", + "Shutdown/Reboot...": "Выключить/Перезагрузить...", + "Power": "Питание", + "Shutdown": "Выключить", + "Reboot": "Перезагрузить", + "Reset": "Сброс", + "Clipboard": "Буфер обмена", + "Clear": "Очистить", + "Fullscreen": "Во весь экран", + "Settings": "Настройки", + "Shared Mode": "Общий режим", + "View Only": "Только Просмотр", + "Clip to Window": "В окно", + "Scaling Mode:": "Масштаб:", + "None": "Нет", + "Local Scaling": "Локльный масштаб", + "Remote Resizing": "Удаленная перенастройка размера", + "Advanced": "Дополнительно", + "Quality:": "Качество", + "Compression level:": "Уровень Сжатия", + "Repeater ID:": "Идентификатор ID:", + "WebSocket": "WebSocket", + "Encrypt": "Шифрование", + "Host:": "Сервер:", + "Port:": "Порт:", + "Path:": "Путь:", + "Automatic Reconnect": "Автоматическое переподключение", + "Reconnect Delay (ms):": "Задержка переподключения (мс):", + "Show Dot when No Cursor": "Показать точку вместо курсора", + "Logging:": "Лог:", + "Version:": "Версия", + "Disconnect": "Отключение", + "Connect": "Подключение", + "Username:": "Имя Пользователя", + "Password:": "Пароль:", + "Send Credentials": "Передача Учетных Данных", + "Cancel": "Выход" +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/sv.json b/farmq-admin/static/novnc/app/locale/sv.json new file mode 100644 index 0000000..077ef42 --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/sv.json @@ -0,0 +1,80 @@ +{ + "HTTPS is required for full functionality": "HTTPS krävs för full funktionalitet", + "Connecting...": "Ansluter...", + "Disconnecting...": "Kopplar ner...", + "Reconnecting...": "Återansluter...", + "Internal error": "Internt fel", + "Must set host": "Du måste specifiera en värd", + "Connected (encrypted) to ": "Ansluten (krypterat) till ", + "Connected (unencrypted) to ": "Ansluten (okrypterat) till ", + "Something went wrong, connection is closed": "Något gick fel, anslutningen avslutades", + "Failed to connect to server": "Misslyckades att ansluta till servern", + "Disconnected": "Frånkopplad", + "New connection has been rejected with reason: ": "Ny anslutning har blivit nekad med följande skäl: ", + "New connection has been rejected": "Ny anslutning har blivit nekad", + "Credentials are required": "Användaruppgifter krävs", + "noVNC encountered an error:": "noVNC stötte på ett problem:", + "Hide/Show the control bar": "Göm/Visa kontrollbaren", + "Drag": "Dra", + "Move/Drag Viewport": "Flytta/Dra Vyn", + "Keyboard": "Tangentbord", + "Show Keyboard": "Visa Tangentbord", + "Extra keys": "Extraknappar", + "Show Extra Keys": "Visa Extraknappar", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Växla Ctrl", + "Alt": "Alt", + "Toggle Alt": "Växla Alt", + "Toggle Windows": "Växla Windows", + "Windows": "Windows", + "Send Tab": "Skicka Tab", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "Skicka Escape", + "Ctrl+Alt+Del": "Ctrl+Alt+Del", + "Send Ctrl-Alt-Del": "Skicka Ctrl-Alt-Del", + "Shutdown/Reboot": "Stäng av/Boota om", + "Shutdown/Reboot...": "Stäng av/Boota om...", + "Power": "Ström", + "Shutdown": "Stäng av", + "Reboot": "Boota om", + "Reset": "Återställ", + "Clipboard": "Urklipp", + "Edit clipboard content in the textarea below.": "Redigera urklippets innehåll i fältet nedan.", + "Full Screen": "Fullskärm", + "Settings": "Inställningar", + "Shared Mode": "Delat Läge", + "View Only": "Endast Visning", + "Clip to Window": "Begränsa till Fönster", + "Scaling Mode:": "Skalningsläge:", + "None": "Ingen", + "Local Scaling": "Lokal Skalning", + "Remote Resizing": "Ändra Storlek", + "Advanced": "Avancerat", + "Quality:": "Kvalitet:", + "Compression level:": "Kompressionsnivå:", + "Repeater ID:": "Repeater-ID:", + "WebSocket": "WebSocket", + "Encrypt": "Kryptera", + "Host:": "Värd:", + "Port:": "Port:", + "Path:": "Sökväg:", + "Automatic Reconnect": "Automatisk Återanslutning", + "Reconnect Delay (ms):": "Fördröjning (ms):", + "Show Dot when No Cursor": "Visa prick när ingen muspekare finns", + "Logging:": "Loggning:", + "Version:": "Version:", + "Disconnect": "Koppla från", + "Connect": "Anslut", + "Server identity": "Server-identitet", + "The server has provided the following identifying information:": "Servern har gett följande identifierande information:", + "Fingerprint:": "Fingeravtryck:", + "Please verify that the information is correct and press \"Approve\". Otherwise press \"Reject\".": "Kontrollera att informationen är korrekt och tryck sedan \"Godkänn\". Tryck annars \"Neka\".", + "Approve": "Godkänn", + "Reject": "Neka", + "Credentials": "Användaruppgifter", + "Username:": "Användarnamn:", + "Password:": "Lösenord:", + "Send Credentials": "Skicka Användaruppgifter", + "Cancel": "Avbryt" +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/tr.json b/farmq-admin/static/novnc/app/locale/tr.json new file mode 100644 index 0000000..451c1b8 --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/tr.json @@ -0,0 +1,69 @@ +{ + "Connecting...": "Bağlanıyor...", + "Disconnecting...": "Bağlantı kesiliyor...", + "Reconnecting...": "Yeniden bağlantı kuruluyor...", + "Internal error": "İç hata", + "Must set host": "Sunucuyu kur", + "Connected (encrypted) to ": "Bağlı (şifrelenmiş)", + "Connected (unencrypted) to ": "Bağlandı (şifrelenmemiş)", + "Something went wrong, connection is closed": "Bir şeyler ters gitti, bağlantı kesildi", + "Disconnected": "Bağlantı kesildi", + "New connection has been rejected with reason: ": "Bağlantı aşağıdaki nedenlerden dolayı reddedildi: ", + "New connection has been rejected": "Bağlantı reddedildi", + "Password is required": "Şifre gerekli", + "noVNC encountered an error:": "Bir hata oluştu:", + "Hide/Show the control bar": "Denetim masasını Gizle/Göster", + "Move/Drag Viewport": "Görünümü Taşı/Sürükle", + "viewport drag": "Görüntü penceresini sürükle", + "Active Mouse Button": "Aktif Fare Düğmesi", + "No mousebutton": "Fare düğmesi yok", + "Left mousebutton": "Farenin sol düğmesi", + "Middle mousebutton": "Farenin orta düğmesi", + "Right mousebutton": "Farenin sağ düğmesi", + "Keyboard": "Klavye", + "Show Keyboard": "Klavye Düzenini Göster", + "Extra keys": "Ekstra tuşlar", + "Show Extra Keys": "Ekstra tuşları göster", + "Ctrl": "Ctrl", + "Toggle Ctrl": "Ctrl Değiştir ", + "Alt": "Alt", + "Toggle Alt": "Alt Değiştir", + "Send Tab": "Sekme Gönder", + "Tab": "Sekme", + "Esc": "Esc", + "Send Escape": "Boşluk Gönder", + "Ctrl+Alt+Del": "Ctrl + Alt + Del", + "Send Ctrl-Alt-Del": "Ctrl-Alt-Del Gönder", + "Shutdown/Reboot": "Kapat/Yeniden Başlat", + "Shutdown/Reboot...": "Kapat/Yeniden Başlat...", + "Power": "Güç", + "Shutdown": "Kapat", + "Reboot": "Yeniden Başlat", + "Reset": "Sıfırla", + "Clipboard": "Pano", + "Clear": "Temizle", + "Fullscreen": "Tam Ekran", + "Settings": "Ayarlar", + "Shared Mode": "Paylaşım Modu", + "View Only": "Sadece Görüntüle", + "Clip to Window": "Pencereye Tıkla", + "Scaling Mode:": "Ölçekleme Modu:", + "None": "Bilinmeyen", + "Local Scaling": "Yerel Ölçeklendirme", + "Remote Resizing": "Uzaktan Yeniden Boyutlandırma", + "Advanced": "Gelişmiş", + "Repeater ID:": "Tekralayıcı ID:", + "WebSocket": "WebSocket", + "Encrypt": "Şifrele", + "Host:": "Ana makine:", + "Port:": "Port:", + "Path:": "Yol:", + "Automatic Reconnect": "Otomatik Yeniden Bağlan", + "Reconnect Delay (ms):": "Yeniden Bağlanma Süreci (ms):", + "Logging:": "Giriş yapılıyor:", + "Disconnect": "Bağlantıyı Kes", + "Connect": "Bağlan", + "Password:": "Parola:", + "Cancel": "Vazgeç", + "Canvas not supported.": "Tuval desteklenmiyor." +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/zh_CN.json b/farmq-admin/static/novnc/app/locale/zh_CN.json new file mode 100644 index 0000000..f0aea9a --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/zh_CN.json @@ -0,0 +1,69 @@ +{ + "Connecting...": "连接中...", + "Disconnecting...": "正在断开连接...", + "Reconnecting...": "重新连接中...", + "Internal error": "内部错误", + "Must set host": "请提供主机名", + "Connected (encrypted) to ": "已连接到(加密)", + "Connected (unencrypted) to ": "已连接到(未加密)", + "Something went wrong, connection is closed": "发生错误,连接已关闭", + "Failed to connect to server": "无法连接到服务器", + "Disconnected": "已断开连接", + "New connection has been rejected with reason: ": "连接被拒绝,原因:", + "New connection has been rejected": "连接被拒绝", + "Password is required": "请提供密码", + "noVNC encountered an error:": "noVNC 遇到一个错误:", + "Hide/Show the control bar": "显示/隐藏控制栏", + "Move/Drag Viewport": "拖放显示范围", + "viewport drag": "显示范围拖放", + "Active Mouse Button": "启动鼠标按鍵", + "No mousebutton": "禁用鼠标按鍵", + "Left mousebutton": "鼠标左鍵", + "Middle mousebutton": "鼠标中鍵", + "Right mousebutton": "鼠标右鍵", + "Keyboard": "键盘", + "Show Keyboard": "显示键盘", + "Extra keys": "额外按键", + "Show Extra Keys": "显示额外按键", + "Ctrl": "Ctrl", + "Toggle Ctrl": "切换 Ctrl", + "Alt": "Alt", + "Toggle Alt": "切换 Alt", + "Send Tab": "发送 Tab 键", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "发送 Escape 键", + "Ctrl+Alt+Del": "Ctrl-Alt-Del", + "Send Ctrl-Alt-Del": "发送 Ctrl-Alt-Del 键", + "Shutdown/Reboot": "关机/重新启动", + "Shutdown/Reboot...": "关机/重新启动...", + "Power": "电源", + "Shutdown": "关机", + "Reboot": "重新启动", + "Reset": "重置", + "Clipboard": "剪贴板", + "Clear": "清除", + "Fullscreen": "全屏", + "Settings": "设置", + "Shared Mode": "分享模式", + "View Only": "仅查看", + "Clip to Window": "限制/裁切窗口大小", + "Scaling Mode:": "缩放模式:", + "None": "无", + "Local Scaling": "本地缩放", + "Remote Resizing": "远程调整大小", + "Advanced": "高级", + "Repeater ID:": "中继站 ID", + "WebSocket": "WebSocket", + "Encrypt": "加密", + "Host:": "主机:", + "Port:": "端口:", + "Path:": "路径:", + "Automatic Reconnect": "自动重新连接", + "Reconnect Delay (ms):": "重新连接间隔 (ms):", + "Logging:": "日志级别:", + "Disconnect": "中断连接", + "Connect": "连接", + "Password:": "密码:", + "Cancel": "取消" +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/locale/zh_TW.json b/farmq-admin/static/novnc/app/locale/zh_TW.json new file mode 100644 index 0000000..8ddf813 --- /dev/null +++ b/farmq-admin/static/novnc/app/locale/zh_TW.json @@ -0,0 +1,69 @@ +{ + "Connecting...": "連線中...", + "Disconnecting...": "正在中斷連線...", + "Reconnecting...": "重新連線中...", + "Internal error": "內部錯誤", + "Must set host": "請提供主機資訊", + "Connected (encrypted) to ": "已加密連線到", + "Connected (unencrypted) to ": "未加密連線到", + "Something went wrong, connection is closed": "發生錯誤,連線已關閉", + "Failed to connect to server": "無法連線到伺服器", + "Disconnected": "連線已中斷", + "New connection has been rejected with reason: ": "連線被拒絕,原因:", + "New connection has been rejected": "連線被拒絕", + "Password is required": "請提供密碼", + "noVNC encountered an error:": "noVNC 遇到一個錯誤:", + "Hide/Show the control bar": "顯示/隱藏控制列", + "Move/Drag Viewport": "拖放顯示範圍", + "viewport drag": "顯示範圍拖放", + "Active Mouse Button": "啟用滑鼠按鍵", + "No mousebutton": "無滑鼠按鍵", + "Left mousebutton": "滑鼠左鍵", + "Middle mousebutton": "滑鼠中鍵", + "Right mousebutton": "滑鼠右鍵", + "Keyboard": "鍵盤", + "Show Keyboard": "顯示鍵盤", + "Extra keys": "額外按鍵", + "Show Extra Keys": "顯示額外按鍵", + "Ctrl": "Ctrl", + "Toggle Ctrl": "切換 Ctrl", + "Alt": "Alt", + "Toggle Alt": "切換 Alt", + "Send Tab": "送出 Tab 鍵", + "Tab": "Tab", + "Esc": "Esc", + "Send Escape": "送出 Escape 鍵", + "Ctrl+Alt+Del": "Ctrl-Alt-Del", + "Send Ctrl-Alt-Del": "送出 Ctrl-Alt-Del 快捷鍵", + "Shutdown/Reboot": "關機/重新啟動", + "Shutdown/Reboot...": "關機/重新啟動...", + "Power": "電源", + "Shutdown": "關機", + "Reboot": "重新啟動", + "Reset": "重設", + "Clipboard": "剪貼簿", + "Clear": "清除", + "Fullscreen": "全螢幕", + "Settings": "設定", + "Shared Mode": "分享模式", + "View Only": "僅檢視", + "Clip to Window": "限制/裁切視窗大小", + "Scaling Mode:": "縮放模式:", + "None": "無", + "Local Scaling": "本機縮放", + "Remote Resizing": "遠端調整大小", + "Advanced": "進階", + "Repeater ID:": "中繼站 ID", + "WebSocket": "WebSocket", + "Encrypt": "加密", + "Host:": "主機:", + "Port:": "連接埠:", + "Path:": "路徑:", + "Automatic Reconnect": "自動重新連線", + "Reconnect Delay (ms):": "重新連線間隔 (ms):", + "Logging:": "日誌級別:", + "Disconnect": "中斷連線", + "Connect": "連線", + "Password:": "密碼:", + "Cancel": "取消" +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/app/localization.js b/farmq-admin/static/novnc/app/localization.js new file mode 100644 index 0000000..84341da --- /dev/null +++ b/farmq-admin/static/novnc/app/localization.js @@ -0,0 +1,179 @@ +/* + * noVNC: HTML5 VNC client + * Copyright (C) 2018 The noVNC Authors + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +/* + * Localization Utilities + */ + +export class Localizer { + constructor() { + // Currently configured language + this.language = 'en'; + + // Current dictionary of translations + this.dictionary = undefined; + } + + // Configure suitable language based on user preferences + setup(supportedLanguages) { + this.language = 'en'; // Default: US English + + /* + * Navigator.languages only available in Chrome (32+) and FireFox (32+) + * Fall back to navigator.language for other browsers + */ + let userLanguages; + if (typeof window.navigator.languages == 'object') { + userLanguages = window.navigator.languages; + } else { + userLanguages = [navigator.language || navigator.userLanguage]; + } + + for (let i = 0;i < userLanguages.length;i++) { + const userLang = userLanguages[i] + .toLowerCase() + .replace("_", "-") + .split("-"); + + // Built-in default? + if ((userLang[0] === 'en') && + ((userLang[1] === undefined) || (userLang[1] === 'us'))) { + return; + } + + // First pass: perfect match + for (let j = 0; j < supportedLanguages.length; j++) { + const supLang = supportedLanguages[j] + .toLowerCase() + .replace("_", "-") + .split("-"); + + if (userLang[0] !== supLang[0]) { + continue; + } + if (userLang[1] !== supLang[1]) { + continue; + } + + this.language = supportedLanguages[j]; + return; + } + + // Second pass: fallback + for (let j = 0;j < supportedLanguages.length;j++) { + const supLang = supportedLanguages[j] + .toLowerCase() + .replace("_", "-") + .split("-"); + + if (userLang[0] !== supLang[0]) { + continue; + } + if (supLang[1] !== undefined) { + continue; + } + + this.language = supportedLanguages[j]; + return; + } + } + } + + // Retrieve localised text + get(id) { + if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) { + return this.dictionary[id]; + } else { + return id; + } + } + + // Traverses the DOM and translates relevant fields + // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate + translateDOM() { + const self = this; + + function process(elem, enabled) { + function isAnyOf(searchElement, items) { + return items.indexOf(searchElement) !== -1; + } + + function translateString(str) { + // We assume surrounding whitespace, and whitespace around line + // breaks is just for source formatting + str = str.split("\n").map(s => s.trim()).join(" ").trim(); + return self.get(str); + } + + function translateAttribute(elem, attr) { + const str = translateString(elem.getAttribute(attr)); + elem.setAttribute(attr, str); + } + + function translateTextNode(node) { + const str = translateString(node.data); + node.data = str; + } + + if (elem.hasAttribute("translate")) { + if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) { + enabled = true; + } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) { + enabled = false; + } + } + + if (enabled) { + if (elem.hasAttribute("abbr") && + elem.tagName === "TH") { + translateAttribute(elem, "abbr"); + } + if (elem.hasAttribute("alt") && + isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) { + translateAttribute(elem, "alt"); + } + if (elem.hasAttribute("download") && + isAnyOf(elem.tagName, ["A", "AREA"])) { + translateAttribute(elem, "download"); + } + if (elem.hasAttribute("label") && + isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP", + "OPTION", "TRACK"])) { + translateAttribute(elem, "label"); + } + // FIXME: Should update "lang" + if (elem.hasAttribute("placeholder") && + isAnyOf(elem.tagName, ["INPUT", "TEXTAREA"])) { + translateAttribute(elem, "placeholder"); + } + if (elem.hasAttribute("title")) { + translateAttribute(elem, "title"); + } + if (elem.hasAttribute("value") && + elem.tagName === "INPUT" && + isAnyOf(elem.getAttribute("type"), ["reset", "button", "submit"])) { + translateAttribute(elem, "value"); + } + } + + for (let i = 0; i < elem.childNodes.length; i++) { + const node = elem.childNodes[i]; + if (node.nodeType === node.ELEMENT_NODE) { + process(node, enabled); + } else if (node.nodeType === node.TEXT_NODE && enabled) { + translateTextNode(node); + } + } + } + + process(document.body, true); + } +} + +export const l10n = new Localizer(); +export default l10n.get.bind(l10n); diff --git a/farmq-admin/static/novnc/app/sounds/CREDITS b/farmq-admin/static/novnc/app/sounds/CREDITS new file mode 100644 index 0000000..ec1fb55 --- /dev/null +++ b/farmq-admin/static/novnc/app/sounds/CREDITS @@ -0,0 +1,4 @@ +bell + Copyright: Dr. Richard Boulanger et al + URL: http://www.archive.org/details/Berklee44v12 + License: CC-BY Attribution 3.0 Unported diff --git a/farmq-admin/static/novnc/app/sounds/bell.mp3 b/farmq-admin/static/novnc/app/sounds/bell.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..fdbf149a1e9e58aa6a39adb6c681b910347d161a GIT binary patch literal 4531 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 DLJX+- literal 0 HcmV?d00001 diff --git a/farmq-admin/static/novnc/app/sounds/bell.oga b/farmq-admin/static/novnc/app/sounds/bell.oga new file mode 100644 index 0000000000000000000000000000000000000000..144d2b3670e8e294ee7cfe343355bf90123cb687 GIT binary patch literal 8495 zcmbVx2{@G9+xSCei6qHVvV<7>TF73uK{AYe$)2?sOSY(FU&7e8EJI^WS+e&gJ43di zFhxwVOb9WS?-}0S-}1k%@4K%5xsEgEzR!K`=iJ-5&pGs*oD2YR;O`=+JrhXkQsTa^ zkg=2b-uJR~MvxHXx5`OBU|BsTeoLlDn)y#5%_IXM+bZGH^umY#Q9Mr^GrA0pn>atX zC!*)&#Oda2Yjnh(Q;SnVOzgVYH3WKT4_@ z80x~o;$vy->wxw3!TJW9rav=(8*KXanfb&s%k5`2Ea89G-@&+TQauu#lMHylEt@(A zDY$a6U{Z6-ZK{RoBJ;m-W)5X;49L=D&c8r2W{vz6Wthd+fKJakvY;D= zbq=&8q&K7EMaEDj0;5Hni8}82Gh|U#poc*mD?Xd9lhp>Ka~{s5L{FN4b4`ab7hX0n zg1snY7GG_H;W59z-2!cujrmLVD>x(SQzbmlza z434L;W#lU6N@n*?pi6!s-VG^YkBe7_qMzP@vGSq3pBzcf2>@aAM_&A=IdbJ=FD^=o z7U<=F(=X8{L~6?0F>*_<+Ual7ii2J(Bn^6TTzm72t+&V_$dEG@sA) z&nzc=aM)lRHqM$k{J$ORpU45gpb0yQNu+J8cvtq*+p5&Z2L2N{o{U|IY(0rQ+OK(Z zhlK{#rSvwWKAu<8m(n%3VEOTa&j^!)!8OYfX@?QG!;E$h`iRacz;xSFS>YwTqm5I*CT|^h0{;$Zn7hRGbT@o3M zkBsM!N_CFPENv)o>#baB{6FhIk)z?o16D9{)Vz59Eppz8v)u%v>9vsV#<7eFA3=qB zt1#ijrNE4URxIx@rVB&pnn zE~`0ra#B`M%tOLhk|$yo(^b3m?+Dn_vVK$U85Iww->|hSVN~spSwkUWRYW`A!c_VH zq5`+QlYl+|P-tDz;?Y`{D$xAJGr%1!e=d|KKt2JlmD|1XkR8xs^k*MTzLkhylLxa0 z86Eh?q?*qqNFFLf2E_3Avumx(Cw=pm(q-g|PAZPNGAOB=>lm$L0M9S|8_eX(JOXq1 zO9jAfvUIW0{&Ss8-``;Ik4Cr2I=*N)1Rw6}YIvc?Trne?YVYtQ z_z#$5X&Nqc0sw||MpB~+mKoHr4w;oS*fAZ0x~Kxd^M8XE;A5%iVqpWg=|bacT2m%C zye^~E?GH!+uKuWmzAUZNg*#-p^h#h;9J*is4WdD?76btxl#CJx1gDNBo zS(8*yZC%wuy61J{?MyQrqj|JO>!1(FON1OG`_Kq>j;` zJAsTzq@|^eBB1?1*8*{t%$W=RB+4I+l{E5tjJgJJORNv+;P4*>+?Uy8z&(#eg^v}^ zWK&4Z%pUs3p+dzY#XHjVPpd+BrJyR*yvWn4&gdefmS#omxC8_ZZdkminpCsC7e(V{ zt{GuqQvm?4PEdk>H2Ftzu>-)9SXitqo=sXE(wU8jzgWmk_J-V)$xt71SFZx5ck z%xD1Rdk+BM#w#12u;ha3cc8h*0e>*tt3pYqGI}l)i(S8X#B&(wkY*Sy-pOW12QD2v zvr%$^4Di`c=}G)BDpWa$cyO&@L$Y1z2zpg~wq3t0RX!qCd?6b=l#r7g{F2%=J{D>L zl8p4=mci1Vj);||$O${mh3E99Qx!lPQpr;0Bj}-8=!bN25H2u(33PxUbv_blCMXgP zgYu(_Mq!EKVPIkdrHvLZ4toxRqHB#}#q+}qL1dk6Y#c}e`pyMAHzD2>k{5w%b%oS2 z*_DG@u+T=MSX_lVS>^&=ggZ^_K$-H)T_D94G=P}@10WRo$}>Er=*|0&Uyk(90G?z? zdPoQTh2%(DC>D})afXr3gGeYS8Umw2$pwP^B(FQ_fjRFE>_<9+j+FgKbg0okGe8hD z2dJv!fiNWOPYtFa{_UZxIr9nluA~3WKSF2WQZ0P{=z_NInu2 z9`qko{v(i=l<#>-IiD9C1qYB&{(KT03Q0ny91Xyr)F2G%jD!L^$L$dcf&xDTB9BlE zP|^t<20h~ZhYot0go2uR5I#&Pa z6VYtPe2=D*8q!LR8U2I${{zY1O*7MlDxUw3_yVsFV&S04yfCsTForshWaq?rqPn_c21KPm}EP^i5FbAfcu z1ZwlX88&lm!Ov(5KsrAK5M=9D~z+Hx~Kf8BA!$$(IBUjWFAUJ1UbaVdO@PHz7G^c|n~2S@vElwtq3TSMH^qqe*g`4^_X zoVrWO2>^J<`k-Gf-DxFg%MzC4B6LP2Rwb&zp_6}}XJH)tQnHCbHxkxw8qMpi15t+* zGpKeVi$KqZcn_Ol6zUD3G+>tD1l}o!^`9g^b>}X)4bjmv6ak^{$!~T&I7k0nyPIE( zpY1$GtK*UC+l{9Dsz>wP6vF7kvP;w7{S*2yPA1Ab z%H(vn-+qm{F8`H+?>l3P9C+6Q3l;DXOug4aBkwsb$jHenDy!a7*M#caz5_D^a|BrE z0Yv~vy1;oQGeb6>F5&ER`owcd49Vw_jNmCo8NAGpXp{kR^5bhO2_`2$zPc(ik_G{Q zbaf?-Iwj1tu%)FK8ymngGmt6C@Z^}Z!)+4_TiAwU4pMDX>8jeM~7U zQ_HlcHuKZcv_GTm>NnB^52M`uT!m0x*LhQCHmCg>+o7#rvnw(F##182vkrFlh0FmO zd4Zi-wYT*R#+)#_D@s0-RZA|-zrUZ5dUZ0EOf7rKZL8V!9piJceC#)v+*6(EH-^9B zV@9J*BT)f&i9(n``|Gn#)=j23JA~)^5u#38Q*7H@_R!MZ={4?I-Rum?-+tLPX^M}K zA&8Jq<`KEO^yJ=~zvP)sY+zqrWk}^;J)Zz&cpSABvM2nKG^wV9?p_(vH7-NcdWx+ z`WNzhq#HVJldbQR>{YDb71CC_4=jVV(w7}Zn&~bZBSLv^>|=R_^Q6At7sBwk@jK{I zem=lbOlFd0?~7^i0A$8z1*5N4%VpT0UVatF331nf=8 z>v+ewCVY*0GO@N?TpjISc`0z4Al@J^g z{a8<4-#(ER6703p9XtEg0lv3)us^@QX)}Jv=eZZ?*}szKv3bn}-+Q?!_aJ=9ApXYp zGihQc)my|S;sy`8yELG`qa`JF(mI`)-F7YvDixE)&sp&`{x;;OOF; z&|f&*oRFYt{0`2syzpjyqt{=wO~&;ZPdobIYFSqvyo^pM#qe`5@yZ9q@Ur@}n}^2O z)(=WK8d)xbSIoA{#@!LM!!w6&bkz&^z1q|R+Q5o{h5K|jMjj|(*7r;3%r4UajAm+C zCd`L1Um9afzc4r+UOc1rr2_l%rJMDN>G$F9(A#e|wiLU9sy-ru6NvZ~wz)d9R$HB| z!O{zXsA|?z9e*WB?KcARmy}>SBa!Atz5sxM3_Ualcd{i&= zc^^I1X3_R=`@5_xGjpW0ld;ZWo+x3T&hl%08bNw9m`6-NvHtL9gceX^v&qeT#eV0b zwKa!Pqs5CZ#kh!Ezt!uCK-_|AKZfAcxvM4VWW&TAuIpx!I6Wfx;4&{dEvK?p z-2$iJd%G;zMv4$a+gP_Qh*~g>tS;vE6rmp>|i{+ z>4%?Iv+8cMfe+W~k<$mQ*0`Q%!9H6L0(<>RbL%5TGGI?_lPfLWjoG4>d2_;|$7OY# zD&@iOt;yoX3Mz@0B@gpH+`vVlto?F`%U{=L_8H&6XZAk#&f*UfWbnf*gjN@JfyvR> zdOg*juOlTL*NA-;wO4_YFi*PoK^L{oWCp^k`T7ZR5|JyFR>nAK1=a83fzycQeQ0Eg zwNuDjCp#Hq$Akqo1CtC-^Sp+wV!;{?U)+uRkIeEWA5R55e^Bk!E%K3i!vw)KEvFrQG0YA*^4+i_TWjhg{twaF83;)ew?Zy;FD9fz9 zD&Sx|^Rb83p-<$}wej+AGN_Q%`gIGO_4L7>QmZaU?Lf@_gM**afiTUI^jIF<= zulO&|08{cEKFor=8=t@jk_4|RwyxOn#%3A9`>bb<>x8BXe^kVW{O$VnxroZL>y&mo zZ_|U_Z-nfPF0nRDun+pZRdC3>X>LAp@?42%V}yaRtn}xK2P=A}R@#@|+~@t?XobV( z?FY96;vI;c2JY`IMXq zP1|fK-dLSj^=N3s#ARVTE4L#`-(spZmIUJlg4VM!*!F`v)16}ngbdZoLB)}J-AP{N zgw=OeXRj9=_JUsc3wqZzNfncOptY1^0j;yP!G zoH^69oW!OHv@BDPd3Ga0T=#8!nj~A$2VNo6?_9rU;@4Jl2Y7E9-?mu9NikcNrphsL zctigB9DwM#ch%{wPS8xKC&iW|MU~fi8Nge*Du*F3UWvW4*<~lQ_VJU*I)QHeFZFxV z+tC$5WBgB;)z?Z^*tRQP7gSZ8C^n!8%E(@SLGUcdaQ2`rvz8%5IAK1&E=qGm`Es9hYELovSkJgqaqi7#Xe5u$n>Tl(Ic#~pNJzsFpXQ#*s#=umX6~82?_;dcz z@_p^t;K{nC-?~?8p|Q!gGc&2@u2=IOcn`i(Gjhu+(I77p5Ba(N3L&%buIEA(&2AwF z`&_j4$)DD1R@~Dxzdl5~)H?k}#4GjE1aim8vFypT<)K^}hPczF$icgoEpfnP>XAhZ z7UB35w>kIvoVFJ1(VC*D3cGG>kf_rY#b3>2+4|OpYvDlw-vi&rmbGpL7EM`9(Cs|h zTx~kIYE))h+t7K@RiTQ=g5NQ(gkN{4(Sn3@4W-Z47+atp@|ImR_&T<2p|po++w0RdeD zLaT|3OQ{FBGx_^(^b*_=i)kK~Ut7qF^sR|>fke-rV-?pH%WxF_J0|CFJ`2gLcnv|5 zzgn(2N7NGHCf2CMbKM2?%MbyTLBj~?5v#zhOKb2u_D`#QgeNe5wBlYNtc=4Ug#911 zBUiTjIti0wXW@gW>jCJ81SMgd{ne*6Z8_>}w-P0dWx$f-~DZ)(3IyK{YiW&3%{K>JUlc6{1{kq;3=Ri6jAPeroK zxP0Hka(P+mZPsGrS+)*#S6-L+TkgKQ)KIWFx_sA}e4%nCwDA|YSU{zqR`s*XS+|k^ z3}J<8$Mma}cpRM(^PsTb%uG4U#WIxy;yp;@b;miwK*ZP%)2GSds?^|81?o?RQV~x(PvkUWEHi!nRt>PwWJwLCiN>hYoRQqP3 z?~?Ys*HuiLuw$c)WE^_>N2!_ml_hOd476&#mc2)AbHj7H_i7NfF1lfSy35eVF z;8?kJo5gIVnd0Uxroa{A^PI$vZhJHP{oTe8z4OK8PucqS%XEy>d@S$*Qi?l^*)bxJ zn(dhKx?T?Hww!H)HjzukdOGTzj>vs3>bZv%*xqQh{aNcz4@8)@gD)_--@qX|49NwT z53)J8spr_5j=+2(MzFtcK{<>c=dy? z*E#S#Yb>>W^pn8^|7UGkOv@qV6B&0(VbgOhcw^{%q2AA0slm?f(8~(RrT#qUN8?{i zjFyWV)Tfs_ zHNIptN`UE!LhlBwm0LECz}e&3-dbmsW8LqwUSqCTUqtb}b|3ZKvAF8`bi?C;z@|>{ z47LxSga|>mL>`ZOrR&&E;5`mq=mE2IVGeaj{Z3J&kIu(S{b33S= zi3!RJo^g^02#>IyI=$I85OZqFU*jhZk@uafSQoM0+RSg)95_zfa-%0&+JG7S2gT95 z!AVM%qi2JocLVSd7TBGZnI=D6P?QeC8|0ngwM_mfg4LJ)Fo-42BIL+dho{43zHa8h z%dvTfO8CeY-=VH1yDB^i|A{8e(&*`#7qbEDWG*_*(2nn&;@}f5phK-dFiyoSCKGb) ztLEACpur!8mcH!a@2)6n`@MdawK`%b64IJSSdwem+x@lU@#D+Z&Z3Dw|1PShB_Bon zJh&ZjgR!xpp)#@k_rBuL2(ej-@P;rL?Ad&f)R*SRmtE!w;TWoQQV?8B(#|OIp8U&x zg?s0##*FD@T@%rH0b7lYwJgFQ$}nvSW%pNh@YTw+rFr?!YdG-?+iK@OgXBGM^R&#nJ@o3@Ke(I~Z@U~ox5&6TE$BzC3 q4pSv@4Tp8LC~{y18P49-RO96847`*l^Ef%*L!-C1?mgOf>VE*{SErW% literal 0 HcmV?d00001 diff --git a/farmq-admin/static/novnc/app/styles/Orbitron700.ttf b/farmq-admin/static/novnc/app/styles/Orbitron700.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e28729dc56b0662b5d2506ddfde5a368784f5755 GIT binary patch literal 38580 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 YaN=?Mu0Qj~2_>(2Mry;V1-x;lBPyVI3Q(tVP1 z0J0(?K)^shMTj2=`9GeV`P2R%@?Y!!FCrpJ(m+7KzCRf5f25|nfGjE`Eb@b8{ z(fg4H(hL5)%rdmrxBI~!e&oh}wV;6lT^=u0L4dkA2%8 zA5Q}CD32_S9c+MrD1Z9>pU>kw2K?1lwuV2tv_JNM?|_2*t z|B)F)WY7=)-&l`))o%aPfkt?^3;yF{BuCM%nTC2sdU`vc2G9`TgkyufP=S1Yso;}_ zdU}C+K)8^=GC)ACnbL`pR#NhqBj3kc5f)`pU^vt$x0R~t?p98iMT-cWm#G?Rwfu$1 z$kedV$TXF-G(&&)kV)}zXklTY%OZ;7ij5-0r9~DK3+MI$(^3*PS6#=RG2hSMU&kG) zA_`R)mPM1df+aGgBFGr()W?h?2suuy$}TS;-_>zp)vu!vc#Bxt|CF_tr8nmlA5R0k zPg;s6|2C}9FrQCiGz>UpG#rJu>D#2yADPy2i-DB{L#!@Rca(!!*~=)$*+KE8bdITpe>)0skN-9UxhbM`Gm_bu@cw3dx}}Ja9q2*gm+Oo zABAolua+w~YFIN08N&_miEqWyFu2#yht-3l6n4Qlmr5*EF5rRL!e*jNu$O~gjBCo$ zpktF&N{G50UbBL%^HGIu{5{G%1w7_r`m9UV6VErg@cBAIJbC9@c79rWM>w1B7~ zRl?fA8SW(ZulF{mxFR%obmV`n_E)2@oRZWFO#+*0aj|De46~q z&xHP&$v*yLk#sc86!K^{9-w%G$>kNZU)wEf+xA^6;_7+fbk@N=h zWh2uLdtvh)c4C<++VIIgBeN|~o8`i|!q`4!hKKqHgAdIuvs-Aoi(;`-*didyPz^FZBa7*Ayw)L9hG+Q{?6=3%pH()RgcdSKMP_FWrdtDr9BVO#Wzjl>R|9v%?M2q{|H zT>Q7lrjc{4?C$!^ALo39HJ<`;cMFPq*ktF$gbK5za-4&sQD;;RaT|QPqH<1fzI_gY zwhGP;^X;_vy|)sb+=YxX@23>r9-#;OswKy2 z79U`!GMwz6wc{K=Cab8AHsn)NBn-u917sLU6!?d((cF@yNbD~NF^mj2aFFyqMx^+= z=?y)lV8-ZgwREZg&`@x!1tul+zEh7FVbpexXRn`U!($r2=qpi;I)WKKtn288^+6^X|mcO ztnTYDU`g#563423h`xDMKZVEpKBVUZwIRg9qOD)~?9nSA!cnZAyyJQXR&u1LP}n9*>s5|r0k+3e<@AXk87-_ zrh#qvK)Xsn#djc_bR^|I>cONo%axa^;?B?c)oCR64YH7EM@Z%}5oCOOGzc)dTS$~s z+i6z906LdeK3HGO z*h~=24veEWTSeA#~X zoPG~~<9yY;_1t}je@}eVzxOX8r7-=a$_7A`rMD%} z<@@&S&E1LpS9=pWT6&VY()t1x-1*VJSN@h9{WN`5o!t%niAo`z#_hcJkj88>mB!<< zACk^S?m(6F-0Q0T9>BEG}3lrNt&dPR52I zlyHcV7$Zb#WP&eL@W!Onl^#Fg4&*1f-yKdEqAhyas2KQQ; zG>Xn|6+uvHy>DpAXf<@~n~IXxVyYA(UignE*WWWet~Xx6EI}PjkES_1t{c8hbG%rJ zi~dNj54^Ex@28uH?3);l!d=##+EP_adX}0?WsC+7q^t_C6jR|wiQY6V`;|^1T=nu{ zGsDmm5NB#w7ZwY!k4t+BC)}a49ZnoPfPD1xYO#M^(MnGT=IourN)Feo&3XbRFd8a^ zX*QU=VI@~8E<uXEei6F1JDF2rq%Q^L zmQ_jFZ1mQoIrlag>J|imVmeQRno_{Tf0Ugs02{NKvY<2bAs> zr06g3w#hlWY+g>J_d{Y92*|=I!9~G!iOk;co4Jw$DAOr+OWO&n>jMhrmKdMDDbG>1 zj$hV>;#br5GJsgAL`Cj*pEu&dd(X?i(h-Y+I3fIh@@pv$7bE{5RuE0^U2^`8$vBKl zJ-|ebxQ3TDM`NyXvYy&$1m8CAKBHMj+Qh|gWPab#w?{pu*E-YUC?~SMBv|79&28eB zl+Co89>Jlz=5v3Ii2WW01}~P=cwEFQZtbp9j=W{trz=ZnO(ZRu|F*z3C`iipfrg-b zElyq)jX<6D#|kIznmjJdMdn7sev6W2CNg7+-I4~T%MEjyDZfg5K#W|Ii%ZiQp@U-@ z{Zd6^juBU+U%t=~%DZfrr8AC=94CRLl)lz+^0EPAvfH3xnsI?4;k`@qfw&MHot&FnWcsT zRT^+lSoqtCmz4#;B&Hr6mL6^-vr5@=AZ|ExB06->!VSz#164q$3us+jTCCE1h3x78 zb(apzn`N`@2piU?>&=EPyC-WvsUP0;mAqdV+oBInSP<8{X;`MuGS@U+tu$-cwY30- z-n><**n$})I4I3k4 zMI1gHedvkW61AAw(zpX(Pq6o?GRP(Np4n&@&lNt!jU(l97{#_`K-}3p$mnjjWcwhvZP|%{DUzRwiU)^i&6$=5-R?9@W^C<}Ii?$cA%M8}-Mn z*7Dyy7zZ7hG&jW-sCuIiT4F1B4fMkpeJmvr@yeOM>eHfi=s-r)2*+Fu=V&+S^5vPr zpTHX@SLwAp@%Zrz17=sb=U_|5hrpma^RZW~%||97<=;2zri+W={O%!_N6M07M6DH= z57XTrFW@91T7+Sn-0lmdvzsTDOa!*9kyE&IS`)Kul0K$;WF+PS)?mx?X~33diJ6dl zk%hiqDT$zUSA+nPYJN#$ML5PW-_79GeqVWWNh(OUxU?$ICSdrCNKy9P_@{A`GInwN z$WG0_!wMNw4!GioZvl7?QbBj#M5pL3D<>Fck(e3x@Q164H((vXPSxeyxDg}$F@M`y z5%7a%&zEvq9|N4xGMYSA;7%0T#{vd#hD?lj3wPj?n!B`LBg0l>c3LiA!79jk5%vyu z!{o?${YI#kYBW2p0yM)|BR47`-5%X8J2Uae%!HPhkrTsJ@-lFsuM0Uj6)4~-th0pD zGT(oV5TUR5i*Qong?s%XNVm`HVmUIgi-7jhiT6!d7+>x400qCnpFv zhBuXPk_9n))!RK9h){K*xY&b~nF**>66h;SNZ)@2srejvxhhQR=S_kvU32??+sWy1 zo42#EryE!<-q3NV;Eq+@)8OLR-*e`)vnq9q{XUubXPdcrC?DSZFAXJS?oB+<%H6V} zFm-!B6!ng-o;d5iZ$SHv(us&A^+IIj)6As+5Ay4m>KqvS9@M#L{edeR+maNn8OHWgKR|t7&-`t3}7dH;`XMSaV zI0}ra)4N{EVi~)@-(E4U0aa?Uq)FDRvR{to?4|G`*9j5&hf%w~!K})Z%gR%Wxqyv% zR?II&*?_AoSuOY>Ar9oQxnu`ZU zV9Gx^&w=Lp3|h6tu@W4B5vJHHop&-XwmLh1=ftf%0og4@FLbMa&x(q*B5jai9s1Tu zEXN($bKuBL&}C5{7^GDLkUiX@4;5bFVwN%1gqg-(pWQegz1q<~@|{Yse*xg^9gQoD z@D@v5ZJp!3IMS>!>pL?S?nOB^W9BL5K074Nc)Ju4{E*f7@*PGnQ;~*LF^X-Fmg!NJ zZCyiI^0LKy{PxGYBM2Z9qWBx*MGWfIi@wd1*m-(d0NLzaXKAKa?jq@yDnN5s0WCt2 zN6~=${szaVp@LhO4a9}ojOEa0y_58TkY#?c&Yq)MQzH#loS;(%p7h0y4V`)VznG@t zAEPjXQ%fKGR?dSo7Al*nt5jd+pgbBTH_GlG|e zdn{{=RMA*64^2&V-kPLlGrmt#a7o*$X{{i?W5&SjAZu8}MdAC=({}tbGJUh>HlPkOyBC=cm-FHjm}00Ekg(AN3G5N=SI!b{|FXa zYmF)g@gxb~dMU9Q)~#9-BU0o;Xu+$uCC)G07(vtjnZ31jm^9;>7kugy!UU6*^8Air zO(VJx%t-PWBu~qDlJxOKtHtrsn2hD)I)U4<*3_cWmqPX+3JTR_a(W7rs~tse9c0K! z&O6ZcrBNbZ=2hO1rr9Btk+O?nuT~MKCN-h#6+O1Qx-!=znG3DUgQ3m&LsIV2>+!m7 zyN*}ZvcgmV&M|4zFc-adn|jf9p;8!7j-bJDt7_v8;Q+W$nKUN|tij5ct!6fM&V^Gv zq0-q~CYIaR5JrL7@$J!2H7%l?8BPewwGt&j$T{4$3()NB!{Y*urkT> zAe<^pOUWJ(fx1t=WWrBOm%BaMdcwehgkka8`O`=|uP$Mk)cPr2tZ8n%gMbHE5SOt= zi$FjCWKDQu^qnPX!#D|2-kivlY4eKYoTD@>sN!3KZnq$q`=HyMK6XD&p+Vf9>z-RG zRdz`}e+MmAF=NkoG``bCHL6!jnAdr99NBK5&;4FW^uE!@0;TsEmG@Tj-Kex?cI)lRxbpM% zJ!m>`v|4yWb|A+dbQ5`aj<&+_XfgXL+VD)dNMw#({qfCiHYD5Q$6X3fhMLC#sy*l2 z@-N{1gwY0vEAUdth!v$de-PP&YQ%FlsxwA#XBo<`eO1Gt6Ocg zq*a&-y_`)FofeN=^(YGIVIkj6d*u~$VVua2VOORr4CHg)???)iG6dfI5C3Q-;y&8c z#;Cw~MU^3p#SLjR9+s`49PJvO5;pnBN`X7nCr__Wiu+GjL}wUPrfjRoj>BP2Ousfl z#18Ls&1A9)<<99={w2$+O_$l15~xjWL&yDBNrPWe*d77{m;c10#h;K@edIHbVvpZ%YGL+z*Ep7Cl60Jj@*`-k4cjG4KYCI3Ap{z?6`z%Hr zzG6RPqrQJ$=xnxf5rc3Zn{=c>#fN@voJ2N1Hbobl{odcfsZGYS^V-KcJK=(bJ%S9E zI3x{#Iuf#N<&Z}yU^^5NG?pox=z^|QMN&-~JaT7`39GBcq6fs_YRt}2tT$u<7dO*22e$>>%aO`!)e4tqFYx!gg%QAn*Bmevr@R(11aHyM)=oFj7Q6;+>vUpMeb= zjKRp6Uxuy^vtj6lAne)k?AQhwiN`P`kSq@Q4tYt=oMYw zD~Z9H!OgR4<652CbTelVgBKRwjQt+}(8BAq@~sMJWXTz=C;4w8Dj^u8k zqYIM3sMP7TEl9T{KKhxafKF3;0lx913bsHHWS`_UyzTemuByARTllCwojI_v-1OpurEppKxGx-KSb? zy?`z}w9+b&NejU}u9|Y_PS-@5#yp@Cnfby$h8t=^x{zS07}pLs)FOc%x#gjowaJ3A zZvMOE{9&P7`A?$j4!!XZ$22#yiP1Evz1(sKdq#y~#kAZiE`>&U^A69r3?+qE{B>f+ zH)Kzak{f5nH~6yzrvWXyge?1>4?KpVNMZC2*IYaj;8E{n&r4>n^Fql@lc*{loNQM~ zChkhH-9yFcu7xkk7lfj^E`hScu@;X~r6eKF@rldc$L|KNQ=o7}S~MxnUgvOl79E|P zMD4-Ji7C`<9kb!-X!Oq7`No1$hDda0Glb4kKl)L=hWh*SV?14H2#XlIh@rHkvAB8wks6|r`x6(5Ogr4rGu|2S zBy94d2FVQ>zv-u$-&c*Od4$&rE2~bI$Jf?w{K_qr;;5@@KjTK}E9>iYDRb^-`iizP zh+Ekb12qW(J<81#nf{%NA^`^)*&{Qld(H7vV!!*S*k4E(o{f0uMtJsCiy=<8O=+Sb?4;4)lgvuVBgbg?}R+Q|NPE!cLfs zrR!C3KTcQ39PP)nwdH>KFA_~8=6J53-5}%KFgL9xJdV>RuLC#sWRO^*m^_iI7r;!U zTm4B*dUak6NKM*A`8{PIgYkR%tb97?O_F0jlvzcw$Ud>M^lMW2;G>+1d+(A0E&BWm z8aHt=X_&E%G`fcv2Jy=n#T$9%e&;$z8W-6MHdwu~g%@KLgf@FUWCp7*@P_7;PBS;kLIOx7IgW zvBy2zSTjMJWAfs*C!eMnO`AkPZkB4e$iO#SI8>fJRDCowOG{#h~!q zc|v>b^~lVW5c~-{NB`MF?fflCdD=UcPvbnY9348cH0XzqUAWH=wpeQSUprK}o?W#B zkUr*2@fr&fiv){GJrs`^f)gcJBQvdzq zerNiewwwH8xEts2!QLgoRcKqnE$L-#Xh^I5a2a(Roxgxh-+H9ae(bEB1&Wr# zp?Ac~7F%*&gSH?dP%#_gSM}Y(DM&9RK!?jm{;LJsX8D^W)$*U)3V|BiZ^&Nuy|z9*a1;uEgLGHf9m(0vYdy{Ub1(!xeGq7^3Ta-Z&(Ok& znF)=<|Kek;*2qB(1Yc5#?53w2r`CNHmfp~0(hEjd#McLqyXZaQ@{QKR5Z=&J>fN8( zaUBMMP}J|&P;SQp7rY(nPW_4Q%oLQvL4!(tBAf+@YCVxHVZw#ZC#Cd>WO}wE~CLUaeyp!-Dbf%mw((OGR#B-hs6(zFm^g;P(D#al>c z6wsC3%+#@a-x<&zH-r;o*DaoHsE;r$Gv;jy@7fj)`3ufCeJ2yI4Jm z|2F>3RbTY}0_LQJ7vDw}y9ZZe__j>(^c5bR%kt2TChHO0eKNv5Kps267dwyu{B>vC z1w=}EJ!ai>%A04I#B*Ny&dFjcA6k)Fx}FcKDem&-y}7g6{ej9pp-5Txb2%mS+fPH& zWoa7$Q5IEpGlh$Ky8VMy@GdMg5PoJW#jOm|Yt_T*{yuzn`2;Ma|V%dFx#P)cD z$YwX~T8w*K2%USapP!D(K0T@l185N5vx{jcRW~qz2;1mo(9y+Lw$VCIumfkXGLv3( zZNnM9ZxF0VJeWS*Y(MwgXAQ^E)v2aE5_ZJLJxr0(Z9DJ=t8i{Mn2V*4itd3kA^r|L zIs}e0%y8-BH0!fDf)mL?yrPRiCxHux6(S%X^#fB&Ow(XC)e7`{=vyb79A5Ce zjki8G4GX6l-jy?IsVMHMX@kz3(&4p$qimUY9zTRebrgz9PDp~U#{ zWVYf5(U*H#a^+lq0#lAd=U_Om*h>03&UBX|ZsUELb_KJS&A=`5qjyYHV^m*THBXt2 zm0QY!D1w*M-vgGkaSlbfl;pQ=F!j6y8HETnwQuS?SIZ{Q4pjo<&&MYxF|l!FeHKRWy@rN_9j5h$5|Zu zjGkxUTmY`zbpD{-#(2q;6H^a&Np?m{QB{T#V|3J2$Twm1OLm zYjL0UYcvGAF9>}j9yXAmiMFpk%)DKUwoqXsB}4Ny%{_;cnAO8~Mn~|x=wpX$Rajbz zP?%_%v+`_C*04raw7D9y9yhdYmkcHoO~=nf*BVo$hK`jp!M?Y#|86lst#qP$LY^{p zbs-f%^c9EMz8U>%Kx2<|8U&rme`N504pnip8b>`y%b?N;ZOP!A3(71w{HgR?F=e&rQ^cY2{o8*CgBaUQP9_~q(Ir`wG zXW>!+{%Jf|@_8I$Ie_zDw#N||a|IiNai-O=_xE6uB|49zLRmJ)>P-a8v~A2#ubW zY5cOw<+>i%74q~v$V`UN;y(GI--tA3f2rG9_cF%1P$MF?3sKd1L06a9lgdDm>LqIO zr=(ZI1UJszN|x?4zvdcx6vQ}YAH9U>Kk9}It@t5&=+PPALF@Jo&~ z_z2o$9|JuY-HDZZ9Iib;X?Jzd=Q{GKb9p$1U&j?b+W^&Xw2#MM=7;Hz69jReZIVL2 zu-8#iIUx4MuIBmH@86Fy>W`ld?WyW*b-`Xn zY-Z?>sI0iAm1oBP*=$tm53q&pH?x2B$`o$89ipqfsq*2+(Hc(59)-9zFo5UE?eWYA zj4u|$-ImS@NC_+7@p&%k?0!SW=cs=DTsHsTZ?b?4ZpVHh4BxMN8t<5R1EcA)mwi}} zMSfks>4q!&zmm$~L4u-+p#j-fz!U#J9rx#)C_g`zub%Vo{Yn%#eq^*8SfD5Y2rt)6 zYegP2MR~NZD<1kjgOG(I(Xx0xd1`5EFTSJ0#g)+Ly`4GnUu>cg2@r!lZLESUF#bDo zvnu9M;Sq9L8&s4k!Z7V(kjO$5QQKaWL&3Z+?9bWnS&lQG({B9liWE>G5wgF_?7`|` zvBF3wY#B)B?dtsb$#zOf!!6Lc7Eph(ty3rvnpqbjD$(5v965zxda~9xP%o3q-es0h zjuhmT;27f-3oK-9cJ`H8s(sKBnQ6X??}tuBvKR=2sa0a zN0N(WdaMlLaWj6)3hZXXxQ&R>vze6&y7_H>zel4Y|NOc)UUxxlR)GV!ZPBW~Szn;e zYnvkH6t~9O1qdg&mKP5=QB)V|3AU?XOV&$VGi<7C{8yBiJ+)J2 z4ZsZx!Frvj9PrES!d}vdrtChJJg`kTy)uh2yh+2nPX+v>i=-Y+NN0LSR3Xm?n?eH8 z|FTEGwU5-(c^DeTWdj|N1Y2->8QwQWUeDrxbI51UG9jQeb~ELWK#sKy#}3?7QXhZ` z#eFq2lJ!p}hQX@clPOg?R`DgHJ7}W{R^uHpxU;-Yt1b*y>dvc#B@8NFZP&Zp6G-3W zv`TVGO9kI2f-WyKe*Mfx zyu=9^GjTrce8s)TGYp4U*v=R2#jZT96X3+j`trQ}>zyt?zo7_(5h_wWw?#uDo zhjUik1w71LJ{p83{juBVQE3iDibvkeIb^keA+@E?dg64WrGZLJ!E4Qts0|RX!{*u{ zd0+mFSR+M()-9zJ3F|`M0pRRJhAR{o+~QBm470{7MXE{EhU6LpBz$H$@>U3;nYq^5znV3M7#kdpP0#{4{J?}ONp*C z2vM+psvS-4H4IAaU{xwuTeTTXpeI=VDt4Kk%s96_dDjrSM-q8MEY6(B^br%rXiY;G zAJfU3zGj#gRUrF{Ab%~^4w{XP$_@RSedbW z-D1M!yj0e($>D)FZE_;_gt47f(19;S(`~-Bu(IdPnNcoiEqOJ!it?}p(O?X@kv>r? z1%N+iB$}X$Oy&P;oo&2_%H+;@U#t|L+XKmLqvY@X%O%uYVmMmC;!2>WV8JD^o<#PC z|4}SHIGxmKaqP!cXunBjWn*TUQcBjFD7Qe~4uH1hHspP0IPfxd+AgL zNm;cVKj~l35@vPf!s4{76*s~OqE#d7mX7JzVCDPXcuHFDb})ItOqQRRz39VcIW}{* z#mm}YjbF#-`t6kW-E3IXVVLAJdUCpbfuqi=9@kRuUJu8YRrvwAfyr_Bvvzp;q>VJ~ zfq-tFKYeuFblt8Gzq`N7!p`zcipebu49Fcy%h&RXi9^D0i>X4w)5pq+4MIZ0@rg|g zOvtGWirq)^LP|@^%S&+$h|9@C!qOfaw-E=v=4&BWIG1|8_w7G8viktH*k9;;!yYf_ zyhMa`{8mWH`NDAGSz3hKu>@nRt0cCgltXfeWwhmgv*ZDd;`DRk?flpCkWLxhxuS4= z{N!T?X;kyzXuf*Tg}s)wkY~A|GJPzq;LOR!zgC@HGOFsdpMvMxM782QhA!>iH`uC2 zrd~LBhivacJTrQBMnI?s0~=$}w_qXr!x)gf!@5&Z*aqWY1U~4p!{?D;iUd3IWXx!$ zBr6mI%Ef3;CElnl9i(JN=?~*Kk|`vl#weW+)v(I)--M=0Z5K(LOT{(*Qd{IS*<%Lf z1houA-85C7GV)Z{oVtC=UE@T?F>GPs?Qf)}jY?&JH`qK)2?&LEh9;7v#9=w@Dz?Hj z)Iw3;KxGdQbv7_a)*V#^Hklk&r7w1V54@S5yUb0mfOlB2UFWQzhB#PJoEl~2-w$E6 z#&=v)w{iextlPB#2WeaNDfi<#6CICa-*q`pY@hYtW45neKG?s3VfOkYjbfRnYahmh7sLPvtYkFo%|D`g_r%cfEVrs|t za)PRLNCO>(xNv-yWAp*F@g+FSC#U_m&GaSqd67%9PeA2$qchCc|G_uEl_pwGtJSM|Z!1FVWR&3-0F9C!W! zWs<#B+)KBTROwQo_AIu5=qR?Ba#Z!K0n3BSnLe}7NnV{Ok)3E$PrY$Bo?B(o&bYDA zAD1>6ZC{VPav>45n-E@j-kIsXTYz6Ti_Rk1pijlvWgMEM)Qo0_uXPMt)&Ay!fR~l# zCmOyw%4Xcx^cNiMY^qe-VeWVC-w2(7*{5_|G9X*qRP;JZxm_ba^o{`0ufNPa$edAY z)~ILIrH~#h9U&e=FG%`pl8;@lWn(4RpXH`U4){Wr6aJP%|HI=(B^_`7+WN%uAJL3g zp9g&GxW4&ako4hf&l^6=L+&cn9RNwRCRdSg>+e!$F!|C}dROby#QSGl{*uNGw1@BG zXFN|yuW6*1M%{Jd9)}TNJn}#g4HExbJA&Q>*DX0AiEK~GqATVpSju^7!6ZouOZ4QL z0jDnK6`oS1HRE=xK2NiYE#68#T$)E~>cxAyF_V-?C79+ip0U(0IG=Wx8z*8ZPORJL z6E!*|)lr6dsWMRIscA>w&|;V=&7x{#MNM-O6TE*c|24aSRp04&(qrpi%s~lFTU;oG z(2+rzIrUXa5~+v@N-~`hF3RmY$A|-SO0mEB^z9`J_k?@!6glfW8a(X9(Qxr31`hSD zgv!gFGIiv>MQ}N`_DuQY5kmu~Mur?hd;fI(o-wix15=*SRO01HbyZKc;cQWejp`r1{DYm?xygvEIN-$2r~ zy$W*{02?+&UCCronKtvh=4*JC#PZX2SuQe@jE!oyvMtY~CIQ`3Zbv!y-kCt^f>?{! z=<8GWCpZAi@kt`X=MJJ-1|`By=TYIkymh`V8dTd^g$~f@)pd#`^!}X5;n18h;muq( zWFxiWoHuyv!dKVxC0jRdaQO$8gF61%<8RATFPeucy!sj3X(K&V&*pRqtM1aK^yL00 zM(t{TgoRT$6<6;ySdUELXaF(ntR%dKX`vKg72R04eY!gk&mNN;=Q|=8- zR}uDEO=S@44EoZ^mGPL9Sc}selHN4lMvPf>>vLw>WTP-C&#oPsv!jNTzLwDGP}S#x z7b}(Hd3d*?tJm#0lN%&_FC3#a7>*iNV`q%`j!$y!0Tl{bwKS5^HE%{H_a#Zop@)@M zVWWo?CMB2CIL%z$D#iBUc493}G6GGB5>{S)`&XOBmDV|3+Slpx<($VIG5`{tEaWin_!Z==IRznfc<>+UTcE#CRH9_~(S?jB7|9Q|1<%{)fruWW}1 z3nFQcRb`n)3e|P=tGe0t!uWPAKdIMM%(xJDN0y;M*sI#h>Xr)`hmr2lZPDLzo}bm# zO0W3KpzBGz%IoK!lDH}p6v`R(`|IiZUHo?1^{2A^?{iQ2zxPjw4HUtiT%`>=UxITOuX&6E>s53e!>V~n<`B>*y*;6#&;+o7)^12R9EsjOpC84VX zC6iO?^S6%JYLwN3e3#61CbHh|r(Ebp>z-@Jy6Y1obLus2hf`Zy7G$55QCYfTOJW2_ zl-p!7D#RV@jvjoM%AZd6PWrQyY6FO#DiLiSbXew^S(3(uDeYY#&6ZoN0PwU!_3OvSRLWm6|$IV-$twJK6W4LwJb_z&~{Hb(#w9<<#=3X=U;hW9bS4}Id7|*C}bH!+-LzT+RnA{ zh-^HhC|)I)0vhJ90fxpovoj0U6bF|&rX2v68SO3@9L?=bMGj7{tWw^>D*^90|3cpV z7QM&!I!3<3oYEFGwmpwV+57imqC1J0oNH%BLDRLm4L+k9kBQ?}=E@EujpSh{pRgw` z<84B-?&69XtRcD)p&9Uz=g>M6ar&1m?cVc0*Mzim>IHu8RVSKY$=g638(iIv z$wLyHeV$WIw8}9Y8e`mjhhUs59eDq)7ah9yPZ2OCme zI(s?ahwBD7HO(O_2Pm&JwLy}7***Lhp?iirN2qlKtDzYpx+;fUGGd@{ zKNaTXp6CC&vy1bl)M2kAwcJ0?AFA>Y3$AU%wXHGkqw6WUiU{0?iF!Z9{|4Sxzw63q z%{-5zlG{5cU^xhKDGtpyC9R(3)=7BJR@k|dz5G)NSaFCT84*w@sA zq={ebb!+cfJ`d%Yj?6^UGOkJ*<^PEgI2fmy!_f^HOC%o5oM=b-Prp`p1F1!J%jC7lm_{BVM87ejdFDfZGA#;Y(W^$(g0Y)- zr)~`@?n_T$|2B~fkj_OZg6&N?(#NWa%0@1uk@YdnufItfq?(xPG_vtz^VdxLx4_(h zWmEfGA5CV8DSCw|g1t>=NDt5N#i31NkBK#?2^-4)4cnntp`qK)bU95#JJUO*O@UyLDBM^47m?n#YA7H-z~x|Q=ML#$XusZ5F}@wcLPjDu@)qW zO=CP3B2YE;26)i|dtNV1W?$Wk!)*sj`tHR-d5#XMbL9l7I*ok!0XiQCf=gUdyAZbZ zB$zxUNWD_pzLF?#WYQA~hqgC>l^iF^EOZ&eNdf!0fPu)q8i^7XWss(3(n+F#mXUB} z{!hzA<$Qq7uD{F;_|kptfo9uaRSM8F)_)IG>~{zPoBnYb+ckR|weNjC7Ls~&fjrvTW`o~ zBrWx{$rP{QTo_*W_r`cvp?jgY9ABoGCbtt_VZfCCP_P?3+03{@U=~+!GbQICiiYaz zjp;6-AsDp$ux0(>6hv^#4VY~SIaXPriE|9qU_Z>IQX~NUDjp2F zmiMfzjtv|~mp&;VNue2~XCEjm#Y#2t$qM_M^v`8@J-C-5c$QnLI_&qyug7>E`1wTg zEXd|Q!qu+94YPzwLnX=n)WplmakupAa<#!_=%G|1)?g8+Gs}Y!vM5Q^sRVYIUt7}n z3Fv)W5Lysalob%l@i6v`s*>cjJy0UYUALHixjxmh!2#7^LqTIH_I*QeU(oFk<9;3r z3Uqsg($1i#FKaI{X8Kt=Nl*s4J3&ef)JB?}Zu8+5ykwhEGt_ae@Tdok6y~n`EebhR zL}FDDD#;=n$|)Pa(WIMorZDgYf~J6(B+Hm$$9$Lmkc-mkxNkL3xa%2Y^tni-6}H`{ zVkXDbA+EZBBbH^2i1MG0JJ#y2mmzOdmx_-8(Ke*^{hrC$=yIgbjZUY=+b-65(F7j| z97QlfiqV1^ZcIX@?O!|LR3*eC8CXr<8UHb3|LgpU6LQ@Q0A^si;ET#o4 z>tw*1&@v9tz-$pcqp$ajH0p|;lFq8ysp6=-hiw&UH%wf&_P6#dNh;wPpKsRU&-|N# zi1jZ*QhHY_A9Y*<_;=hfY1ka@BR5HiaH6%TJSCMH@VTUgOsTQJVPPxM3}mDvL?CG~ zihww00WXm`DV#z&)lrIUD~Xtp)vSvwNJ@N#wT@+_^$?M>L#W;UUA!<_hk{e_=gbZCR{GNS=#e{+B`=b@@9_} z))>_c$d!*dgNCH-awjkaXNSAzB*JaYY8a7xtomBebW%*4mZDnmST=Pi%|+)9NXy}( z?yBcI{56kLO?Ud5tFGpX$-wf-T*0CuuksG2(NrN%a8tshCG*H=?`Bv_^N1v_z9Cpm3U7^SEuTPzSkIuGsb3GSN5H*j~7dUXIgMh6J z-xH%z%^)TE-fl(2`po|mcLRw0oZ+(bs@LyL-ue2pHyIGkQxNUwi_U1$JGtz4M*8A% zbTP!iu`}p`?4UO|9f3=59tu%?~^Ew#UbZME@z^f6U z4X=YiFzWPuJ-!G|dNcrZy-{#7VXOc*l<%{I=qbj*HpjTBs!DfXi|ytwl@xZ-5K@95Wzbg4IwU>5Ie}|mw=G$H4u5pEHt<45LiXKJ*!|h z==4EwOk(V;J>LF@JP-SK8tV3E4tM>tum1xxjc;6doNdilPt#Et$MNq|TMEcl_FgI| zSX%Z}5fxArP;hI30%f#7TMD?NM!nGZ5{zCNx2Vw<;9haZk(f9xG|_8swCD8Jll(u= z?>Xl@=Z^us{?20Mf9p@+6AaA3h=~x)gc3$LbBQ35D58lWmN@1ypLh~TB#C5FNF|MQ zGRP#0Y;wpYk9-!ekVOZqrIWvrl)l{B%6 z)vTeJ7Ft=$I@(yz2HNSMlZ|Ymi*7cvg{^F3J3H9PE_P$*97j0HDGqaz<9y;Q7r4(| zF499Ur|IJ%4({=Qr##{@Pw3|b&v?!y2KdcuUh;}TesF>zoD4I{2rkaxW}Go5@ZiJC zBz~rtVVXVcV=uQjz zd|4n1Wswv}p)8gqQY6K)R4h^=Re4P(hG?yZ#*SdJnHu$B zv$jRss$HjT)2`RHYdf@^`Z4{mHCSw>ZoStX+|$JtETyK+dT5JwtJbbG+KWBC#vVnl zqEF#a^eYAwg9@i&D1d2L&kd`7L@}yxDcp)N#kgWZ;Zb-MJ~ce4hbL9^>*!Z;O2;X6 aYg+X)x}OQee;ks?lK=p?0sn=t1dRYj;d3$o literal 0 HcmV?d00001 diff --git a/farmq-admin/static/novnc/app/styles/base.css b/farmq-admin/static/novnc/app/styles/base.css new file mode 100644 index 0000000..06e736a --- /dev/null +++ b/farmq-admin/static/novnc/app/styles/base.css @@ -0,0 +1,922 @@ +/* + * noVNC base CSS + * Copyright (C) 2019 The noVNC Authors + * noVNC is licensed under the MPL 2.0 (see LICENSE.txt) + * This file is licensed under the 2-Clause BSD license (see LICENSE.txt). + */ + +/* + * Z index layers: + * + * 0: Main screen + * 10: Control bar + * 50: Transition blocker + * 60: Connection popups + * 100: Status bar + * ... + * 1000: Javascript crash + * ... + * 10000: Max (used for polyfills) + */ + +/* + * State variables (set on :root): + * + * noVNC_loading: Page is still loading + * noVNC_connecting: Connecting to server + * noVNC_reconnecting: Re-establishing a connection + * noVNC_connected: Connected to server (most common state) + * noVNC_disconnecting: Disconnecting from server + */ + +:root { + font-family: sans-serif; +} + +body { + margin:0; + padding:0; + /*Background image with light grey curve.*/ + background-color:#494949; + background-repeat:no-repeat; + background-position:right bottom; + height:100%; + touch-action: none; +} + +html { + height:100%; +} + +.noVNC_only_touch.noVNC_hidden { + display: none; +} + +.noVNC_disabled { + color: rgb(128, 128, 128); +} + +/* ---------------------------------------- + * Spinner + * ---------------------------------------- + */ + +.noVNC_spinner { + position: relative; +} +.noVNC_spinner, .noVNC_spinner::before, .noVNC_spinner::after { + width: 10px; + height: 10px; + border-radius: 2px; + box-shadow: -60px 10px 0 rgba(255, 255, 255, 0); + animation: noVNC_spinner 1.0s linear infinite; +} +.noVNC_spinner::before { + content: ""; + position: absolute; + left: 0px; + top: 0px; + animation-delay: -0.1s; +} +.noVNC_spinner::after { + content: ""; + position: absolute; + top: 0px; + left: 0px; + animation-delay: 0.1s; +} +@keyframes noVNC_spinner { + 0% { box-shadow: -60px 10px 0 rgba(255, 255, 255, 0); width: 20px; } + 25% { box-shadow: 20px 10px 0 rgba(255, 255, 255, 1); width: 10px; } + 50% { box-shadow: 60px 10px 0 rgba(255, 255, 255, 0); width: 10px; } +} + +/* ---------------------------------------- + * WebKit centering hacks + * ---------------------------------------- + */ + +.noVNC_center { + /* + * This is a workaround because webkit misrenders transforms and + * uses non-integer coordinates, resulting in blurry content. + * Ideally we'd use "top: 50%; transform: translateY(-50%);" on + * the objects instead. + */ + display: flex; + align-items: center; + justify-content: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; +} +.noVNC_center > * { + pointer-events: auto; +} +.noVNC_vcenter { + display: flex !important; + flex-direction: column; + justify-content: center; + position: fixed; + top: 0; + left: 0; + height: 100%; + margin: 0 !important; + padding: 0 !important; + pointer-events: none; +} +.noVNC_vcenter > * { + pointer-events: auto; +} + +/* ---------------------------------------- + * Layering + * ---------------------------------------- + */ + +.noVNC_connect_layer { + z-index: 60; +} + +/* ---------------------------------------- + * Fallback error + * ---------------------------------------- + */ + +#noVNC_fallback_error { + z-index: 1000; + visibility: hidden; + /* Put a dark background in front of everything but the error, + and don't let mouse events pass through */ + background: rgba(0, 0, 0, 0.8); + pointer-events: all; +} +#noVNC_fallback_error.noVNC_open { + visibility: visible; +} + +#noVNC_fallback_error > div { + max-width: calc(100vw - 30px - 30px); + max-height: calc(100vh - 30px - 30px); + overflow: auto; + + padding: 15px; + + transition: 0.5s ease-in-out; + + transform: translateY(-50px); + opacity: 0; + + text-align: center; + font-weight: bold; + color: #fff; + + border-radius: 10px; + box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); + background: rgba(200,55,55,0.8); +} +#noVNC_fallback_error.noVNC_open > div { + transform: translateY(0); + opacity: 1; +} + +#noVNC_fallback_errormsg { + font-weight: normal; +} + +#noVNC_fallback_errormsg .noVNC_message { + display: inline-block; + text-align: left; + font-family: monospace; + white-space: pre-wrap; +} + +#noVNC_fallback_error .noVNC_location { + font-style: italic; + font-size: 0.8em; + color: rgba(255, 255, 255, 0.8); +} + +#noVNC_fallback_error .noVNC_stack { + padding: 10px; + margin: 10px; + font-size: 0.8em; + text-align: left; + font-family: monospace; + white-space: pre; + border: 1px solid rgba(0, 0, 0, 0.5); + background: rgba(0, 0, 0, 0.2); + overflow: auto; +} + +/* ---------------------------------------- + * Control Bar + * ---------------------------------------- + */ + +#noVNC_control_bar_anchor { + /* The anchor is needed to get z-stacking to work */ + position: fixed; + z-index: 10; + + transition: 0.5s ease-in-out; + + /* Edge misrenders animations wihthout this */ + transform: translateX(0); +} +:root.noVNC_connected #noVNC_control_bar_anchor.noVNC_idle { + opacity: 0.8; +} +#noVNC_control_bar_anchor.noVNC_right { + left: auto; + right: 0; +} + +#noVNC_control_bar { + position: relative; + left: -100%; + + transition: 0.5s ease-in-out; + + background-color: rgb(110, 132, 163); + border-radius: 0 10px 10px 0; + + user-select: none; + -webkit-user-select: none; + -webkit-touch-callout: none; /* Disable iOS image long-press popup */ +} +#noVNC_control_bar.noVNC_open { + box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); + left: 0; +} +#noVNC_control_bar::before { + /* This extra element is to get a proper shadow */ + content: ""; + position: absolute; + z-index: -1; + height: 100%; + width: 30px; + left: -30px; + transition: box-shadow 0.5s ease-in-out; +} +#noVNC_control_bar.noVNC_open::before { + box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); +} +.noVNC_right #noVNC_control_bar { + left: 100%; + border-radius: 10px 0 0 10px; +} +.noVNC_right #noVNC_control_bar.noVNC_open { + left: 0; +} +.noVNC_right #noVNC_control_bar::before { + visibility: hidden; +} + +#noVNC_control_bar_handle { + position: absolute; + left: -15px; + top: 0; + transform: translateY(35px); + width: calc(100% + 30px); + height: 50px; + z-index: -1; + cursor: pointer; + border-radius: 5px; + background-color: rgb(83, 99, 122); + background-image: url("../images/handle_bg.svg"); + background-repeat: no-repeat; + background-position: right; + box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5); +} +#noVNC_control_bar_handle:after { + content: ""; + transition: transform 0.5s ease-in-out; + background: url("../images/handle.svg"); + position: absolute; + top: 22px; /* (50px-6px)/2 */ + right: 5px; + width: 5px; + height: 6px; +} +#noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after { + transform: translateX(1px) rotate(180deg); +} +:root:not(.noVNC_connected) #noVNC_control_bar_handle { + display: none; +} +.noVNC_right #noVNC_control_bar_handle { + background-position: left; +} +.noVNC_right #noVNC_control_bar_handle:after { + left: 5px; + right: 0; + transform: translateX(1px) rotate(180deg); +} +.noVNC_right #noVNC_control_bar.noVNC_open #noVNC_control_bar_handle:after { + transform: none; +} +/* Larger touch area for the handle, used when a touch screen is available */ +#noVNC_control_bar_handle div { + position: absolute; + right: -35px; + top: 0; + width: 50px; + height: 100%; + display: none; +} +@media (any-pointer: coarse) { + #noVNC_control_bar_handle div { + display: initial; + } +} +.noVNC_right #noVNC_control_bar_handle div { + left: -35px; + right: auto; +} + +#noVNC_control_bar > .noVNC_scroll { + max-height: 100vh; /* Chrome is buggy with 100% */ + overflow-x: hidden; + overflow-y: auto; + padding: 0 10px; +} + +#noVNC_control_bar > .noVNC_scroll > * { + display: block; + margin: 10px auto; +} + +/* Control bar hint */ +#noVNC_hint_anchor { + position: fixed; + right: -50px; + left: auto; +} +#noVNC_control_bar_anchor.noVNC_right + #noVNC_hint_anchor { + left: -50px; + right: auto; +} +#noVNC_control_bar_hint { + position: relative; + transform: scale(0); + width: 100px; + height: 50%; + max-height: 600px; + + visibility: hidden; + opacity: 0; + transition: 0.2s ease-in-out; + background: transparent; + box-shadow: 0 0 10px black, inset 0 0 10px 10px rgba(110, 132, 163, 0.8); + border-radius: 10px; + transition-delay: 0s; +} +#noVNC_control_bar_hint.noVNC_active { + visibility: visible; + opacity: 1; + transition-delay: 0.2s; + transform: scale(1); +} +#noVNC_control_bar_hint.noVNC_notransition { + transition: none !important; +} + +/* Control bar buttons */ +#noVNC_control_bar .noVNC_button { + padding: 4px 4px; + vertical-align: middle; + border:1px solid rgba(255, 255, 255, 0.2); + border-radius: 6px; + background-color: transparent; + background-image: unset; /* we don't want the gradiant from input.css */ +} +#noVNC_control_bar .noVNC_button.noVNC_selected { + border-color: rgba(0, 0, 0, 0.8); + background-color: rgba(0, 0, 0, 0.5); +} +#noVNC_control_bar .noVNC_button.noVNC_selected:not(:disabled):hover { + border-color: rgba(0, 0, 0, 0.4); + background-color: rgba(0, 0, 0, 0.2); +} +#noVNC_control_bar .noVNC_button:not(:disabled):hover { + background-color: rgba(255, 255, 255, 0.2); +} +#noVNC_control_bar .noVNC_button:not(:disabled):active { + padding-top: 5px; + padding-bottom: 3px; +} +#noVNC_control_bar .noVNC_button.noVNC_hidden { + display: none !important; +} + +/* Android browsers don't properly update hover state if touch events are + * intercepted, like they are when clicking on the remote screen. */ +@media (any-pointer: coarse) { + #noVNC_control_bar .noVNC_button:not(:disabled):hover { + background-color: transparent; + } + #noVNC_control_bar .noVNC_button.noVNC_selected:not(:disabled):hover { + border-color: rgba(0, 0, 0, 0.8); + background-color: rgba(0, 0, 0, 0.5); + } +} + + +/* Panels */ +.noVNC_panel { + transform: translateX(25px); + + transition: 0.5s ease-in-out; + + box-sizing: border-box; /* so max-width don't have to care about padding */ + max-width: calc(100vw - 75px - 25px); /* minus left and right margins */ + max-height: 100vh; /* Chrome is buggy with 100% */ + overflow-x: hidden; + overflow-y: auto; + + visibility: hidden; + opacity: 0; + + padding: 15px; + + background: #fff; + border-radius: 10px; + color: #000; + border: 2px solid #E0E0E0; + box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); +} +.noVNC_panel.noVNC_open { + visibility: visible; + opacity: 1; + transform: translateX(75px); +} +.noVNC_right .noVNC_vcenter { + left: auto; + right: 0; +} +.noVNC_right .noVNC_panel { + transform: translateX(-25px); +} +.noVNC_right .noVNC_panel.noVNC_open { + transform: translateX(-75px); +} + +.noVNC_panel > * { + display: block; + margin: 10px auto; +} +.noVNC_panel > *:first-child { + margin-top: 0 !important; +} +.noVNC_panel > *:last-child { + margin-bottom: 0 !important; +} + +.noVNC_panel hr { + border: none; + border-top: 1px solid rgb(192, 192, 192); +} + +.noVNC_panel label { + display: block; + white-space: nowrap; + margin: 5px; +} + +.noVNC_panel li { + margin: 5px; +} + +.noVNC_panel .noVNC_heading { + background-color: rgb(110, 132, 163); + border-radius: 5px; + padding: 5px; + /* Compensate for padding in image */ + padding-right: 8px; + color: white; + font-size: 20px; + white-space: nowrap; +} +.noVNC_panel .noVNC_heading img { + vertical-align: bottom; +} + +.noVNC_submit { + float: right; +} + +/* Expanders */ +.noVNC_expander { + cursor: pointer; +} +.noVNC_expander::before { + content: url("../images/expander.svg"); + display: inline-block; + margin-right: 5px; + transition: 0.2s ease-in-out; +} +.noVNC_expander.noVNC_open::before { + transform: rotateZ(90deg); +} +.noVNC_expander ~ * { + margin: 5px; + margin-left: 10px; + padding: 5px; + background: rgba(0, 0, 0, 0.05); + border-radius: 5px; +} +.noVNC_expander:not(.noVNC_open) ~ * { + display: none; +} + +/* Control bar content */ + +#noVNC_control_bar .noVNC_logo { + font-size: 13px; +} + +.noVNC_logo + hr { + /* Remove all but top border */ + border: none; + border-top: 1px solid rgba(255, 255, 255, 0.2); +} + +:root:not(.noVNC_connected) #noVNC_view_drag_button { + display: none; +} + +/* noVNC Touch Device only buttons */ +:root:not(.noVNC_connected) #noVNC_mobile_buttons { + display: none; +} +@media not all and (any-pointer: coarse) { + /* FIXME: The button for the virtual keyboard is the only button in this + group of "mobile buttons". It is bad to assume that no touch + devices have physical keyboards available. Hopefully we can get + a media query for this: + https://github.com/w3c/csswg-drafts/issues/3871 */ + :root.noVNC_connected #noVNC_mobile_buttons { + display: none; + } +} + +/* Extra manual keys */ +:root:not(.noVNC_connected) #noVNC_toggle_extra_keys_button { + display: none; +} + +#noVNC_modifiers { + background-color: rgb(92, 92, 92); + border: none; + padding: 10px; +} + +/* Shutdown/Reboot */ +:root:not(.noVNC_connected) #noVNC_power_button { + display: none; +} +#noVNC_power { +} +#noVNC_power_buttons { + display: none; +} + +#noVNC_power input[type=button] { + width: 100%; +} + +/* Clipboard */ +:root:not(.noVNC_connected) #noVNC_clipboard_button { + display: none; +} +#noVNC_clipboard_text { + width: 360px; + min-width: 150px; + height: 160px; + min-height: 70px; + + box-sizing: border-box; + max-width: 100%; + /* minus approximate height of title, height of subtitle, and margin */ + max-height: calc(100vh - 10em - 25px); +} + +/* Settings */ +#noVNC_settings { +} +#noVNC_settings ul { + list-style: none; + padding: 0px; +} +#noVNC_setting_port { + width: 80px; +} +#noVNC_setting_path { + width: 100px; +} + +/* Version */ + +.noVNC_version_wrapper { + font-size: small; +} + +.noVNC_version { + margin-left: 1rem; +} + +/* Connection Controls */ +:root:not(.noVNC_connected) #noVNC_disconnect_button { + display: none; +} + +/* ---------------------------------------- + * Status Dialog + * ---------------------------------------- + */ + +#noVNC_status { + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 100; + transform: translateY(-100%); + + cursor: pointer; + + transition: 0.5s ease-in-out; + + visibility: hidden; + opacity: 0; + + padding: 5px; + + display: flex; + flex-direction: row; + justify-content: center; + align-content: center; + + line-height: 25px; + word-wrap: break-word; + color: #fff; + + border-bottom: 1px solid rgba(0, 0, 0, 0.9); +} +#noVNC_status.noVNC_open { + transform: translateY(0); + visibility: visible; + opacity: 1; +} + +#noVNC_status::before { + content: ""; + display: inline-block; + width: 25px; + height: 25px; + margin-right: 5px; +} + +#noVNC_status.noVNC_status_normal { + background: rgba(128,128,128,0.9); +} +#noVNC_status.noVNC_status_normal::before { + content: url("../images/info.svg") " "; +} +#noVNC_status.noVNC_status_error { + background: rgba(200,55,55,0.9); +} +#noVNC_status.noVNC_status_error::before { + content: url("../images/error.svg") " "; +} +#noVNC_status.noVNC_status_warn { + background: rgba(180,180,30,0.9); +} +#noVNC_status.noVNC_status_warn::before { + content: url("../images/warning.svg") " "; +} + +/* ---------------------------------------- + * Connect Dialog + * ---------------------------------------- + */ + +#noVNC_connect_dlg { + transition: 0.5s ease-in-out; + + transform: scale(0, 0); + visibility: hidden; + opacity: 0; +} +#noVNC_connect_dlg.noVNC_open { + transform: scale(1, 1); + visibility: visible; + opacity: 1; +} +#noVNC_connect_dlg .noVNC_logo { + transition: 0.5s ease-in-out; + padding: 10px; + margin-bottom: 10px; + + font-size: 80px; + text-align: center; + + border-radius: 5px; +} +@media (max-width: 440px) { + #noVNC_connect_dlg { + max-width: calc(100vw - 100px); + } + #noVNC_connect_dlg .noVNC_logo { + font-size: calc(25vw - 30px); + } +} +#noVNC_connect_dlg div { + padding: 12px; + + background-color: rgb(110, 132, 163); + border-radius: 12px; + text-align: center; + font-size: 20px; + + box-shadow: 6px 6px 0px rgba(0, 0, 0, 0.5); +} +#noVNC_connect_button { + width: 100%; + padding: 5px 30px; + + cursor: pointer; + + border-color: rgb(83, 99, 122); + border-radius: 5px; + + background: linear-gradient(to top, rgb(110, 132, 163), rgb(99, 119, 147)); + color: white; + + /* This avoids it jumping around when :active */ + vertical-align: middle; +} +#noVNC_connect_button:hover { + background: linear-gradient(to top, rgb(110, 132, 163), rgb(105, 125, 155)); +} + +#noVNC_connect_button img { + vertical-align: bottom; + height: 1.3em; +} + +/* ---------------------------------------- + * Server verification Dialog + * ---------------------------------------- + */ + +#noVNC_verify_server_dlg { + position: relative; + + transform: translateY(-50px); +} +#noVNC_verify_server_dlg.noVNC_open { + transform: translateY(0); +} +#noVNC_fingerprint_block { + margin: 10px; +} + +/* ---------------------------------------- + * Password Dialog + * ---------------------------------------- + */ + +#noVNC_credentials_dlg { + position: relative; + + transform: translateY(-50px); +} +#noVNC_credentials_dlg.noVNC_open { + transform: translateY(0); +} +#noVNC_username_block.noVNC_hidden, +#noVNC_password_block.noVNC_hidden { + display: none; +} + + +/* ---------------------------------------- + * Main Area + * ---------------------------------------- + */ + +/* Transition screen */ +#noVNC_transition { + transition: 0.5s ease-in-out; + + display: flex; + opacity: 0; + visibility: hidden; + + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + + color: white; + background: rgba(0, 0, 0, 0.5); + z-index: 50; + + /*display: flex;*/ + align-items: center; + justify-content: center; + flex-direction: column; +} +:root.noVNC_loading #noVNC_transition, +:root.noVNC_connecting #noVNC_transition, +:root.noVNC_disconnecting #noVNC_transition, +:root.noVNC_reconnecting #noVNC_transition { + opacity: 1; + visibility: visible; +} +:root:not(.noVNC_reconnecting) #noVNC_cancel_reconnect_button { + display: none; +} +#noVNC_transition_text { + font-size: 1.5em; +} + +/* Main container */ +#noVNC_container { + width: 100%; + height: 100%; + background-color: #313131; + border-bottom-right-radius: 800px 600px; + /*border-top-left-radius: 800px 600px;*/ + + /* If selection isn't disabled, long-pressing stuff in the sidebar + can accidentally select the container or the canvas. This can + happen when attempting to move the handle. */ + user-select: none; + -webkit-user-select: none; +} + +#noVNC_keyboardinput { + width: 1px; + height: 1px; + background-color: #fff; + color: #fff; + border: 0; + position: absolute; + left: -40px; + z-index: -1; + ime-mode: disabled; +} + +/*Default noVNC logo.*/ +/* From: http://fonts.googleapis.com/css?family=Orbitron:700 */ +@font-face { + font-family: 'Orbitron'; + font-style: normal; + font-weight: 700; + src: local('?'), url('Orbitron700.woff') format('woff'), + url('Orbitron700.ttf') format('truetype'); +} + +.noVNC_logo { + color:yellow; + font-family: 'Orbitron', 'OrbitronTTF', sans-serif; + line-height:90%; + text-shadow: 0.1em 0.1em 0 black; +} +.noVNC_logo span{ + color:green; +} + +#noVNC_bell { + display: none; +} + +/* ---------------------------------------- + * Media sizing + * ---------------------------------------- + */ + +@media screen and (max-width: 640px){ + #noVNC_logo { + font-size: 150px; + } +} + +@media screen and (min-width: 321px) and (max-width: 480px) { + #noVNC_logo { + font-size: 110px; + } +} + +@media screen and (max-width: 320px) { + #noVNC_logo { + font-size: 90px; + } +} diff --git a/farmq-admin/static/novnc/app/styles/input.css b/farmq-admin/static/novnc/app/styles/input.css new file mode 100644 index 0000000..eaf083c --- /dev/null +++ b/farmq-admin/static/novnc/app/styles/input.css @@ -0,0 +1,281 @@ +/* + * noVNC general input element CSS + * Copyright (C) 2022 The noVNC Authors + * noVNC is licensed under the MPL 2.0 (see LICENSE.txt) + * This file is licensed under the 2-Clause BSD license (see LICENSE.txt). + */ + +/* + * Common for all inputs + */ +input, input::file-selector-button, button, select, textarea { + /* Respect standard font settings */ + font: inherit; + + /* Disable default rendering */ + appearance: none; + background: none; + + padding: 5px; + border: 1px solid rgb(192, 192, 192); + border-radius: 5px; + color: black; + --bg-gradient: linear-gradient(to top, rgb(255, 255, 255) 80%, rgb(240, 240, 240)); + background-image: var(--bg-gradient); +} + +/* + * Buttons + */ +input[type=button], +input[type=color], +input[type=image], +input[type=reset], +input[type=submit], +input::file-selector-button, +button, +select { + border-bottom-width: 2px; + + /* This avoids it jumping around when :active */ + vertical-align: middle; + margin-top: 0; + + padding-left: 20px; + padding-right: 20px; + + /* Disable Chrome's touch tap highlight */ + -webkit-tap-highlight-color: transparent; +} + +/* + * Select dropdowns + */ +select { + --select-arrow: url('data:image/svg+xml;utf8, \ + \ + \ + '); + background-image: var(--select-arrow), var(--bg-gradient); + background-position: calc(100% - 7px), left top; + background-repeat: no-repeat; + padding-right: calc(2*7px + 8px); + padding-left: 7px; +} +/* FIXME: :active isn't set when the   + Perftest:  + Realtime:   + +   + +

+ + Results:
+ + +

+ +
+
Loading
+
+ + diff --git a/farmq-admin/static/novnc/utils/.eslintrc b/farmq-admin/static/novnc/utils/.eslintrc new file mode 100644 index 0000000..b7dc129 --- /dev/null +++ b/farmq-admin/static/novnc/utils/.eslintrc @@ -0,0 +1,8 @@ +{ + "env": { + "node": true + }, + "rules": { + "no-console": 0 + } +} \ No newline at end of file diff --git a/farmq-admin/static/novnc/utils/README.md b/farmq-admin/static/novnc/utils/README.md new file mode 100644 index 0000000..05d75c9 --- /dev/null +++ b/farmq-admin/static/novnc/utils/README.md @@ -0,0 +1,14 @@ +## WebSockets Proxy/Bridge + +Websockify has been forked out into its own project. `novnc_proxy` will +automatically download it here if it is not already present and not +installed as system-wide. + +For more detailed description and usage information please refer to +the [websockify README](https://github.com/novnc/websockify/blob/master/README.md). + +The other versions of websockify (C, Node.js) and the associated test +programs have been moved to +[websockify](https://github.com/novnc/websockify). Websockify was +formerly named wsproxy. + diff --git a/farmq-admin/static/novnc/utils/b64-to-binary.pl b/farmq-admin/static/novnc/utils/b64-to-binary.pl new file mode 100755 index 0000000..280e28c --- /dev/null +++ b/farmq-admin/static/novnc/utils/b64-to-binary.pl @@ -0,0 +1,17 @@ +#!/usr/bin/env perl +use MIME::Base64; + +for (<>) { + unless (/^'([{}])(\d+)\1(.+?)',$/) { + print; + next; + } + + my ($dir, $amt, $b64) = ($1, $2, $3); + + my $decoded = MIME::Base64::decode($b64) or die "Could not base64-decode line `$_`"; + + my $decoded_escaped = join "", map { "\\x$_" } unpack("(H2)*", $decoded); + + print "'${dir}${amt}${dir}${decoded_escaped}',\n"; +} diff --git a/farmq-admin/static/novnc/utils/convert.js b/farmq-admin/static/novnc/utils/convert.js new file mode 100755 index 0000000..aeba49d --- /dev/null +++ b/farmq-admin/static/novnc/utils/convert.js @@ -0,0 +1,140 @@ +#!/usr/bin/env node + +const path = require('path'); +const program = require('commander'); +const fs = require('fs'); +const fse = require('fs-extra'); +const babel = require('@babel/core'); + +program + .option('-m, --with-source-maps [type]', 'output source maps when not generating a bundled app (type may be empty for external source maps, inline for inline source maps, or both) ') + .option('--clean', 'clear the lib folder before building') + .parse(process.argv); + +// the various important paths +const paths = { + main: path.resolve(__dirname, '..'), + core: path.resolve(__dirname, '..', 'core'), + vendor: path.resolve(__dirname, '..', 'vendor'), + libDirBase: path.resolve(__dirname, '..', 'lib'), +}; + +// util.promisify requires Node.js 8.x, so we have our own +function promisify(original) { + return function promiseWrap() { + const args = Array.prototype.slice.call(arguments); + return new Promise((resolve, reject) => { + original.apply(this, args.concat((err, value) => { + if (err) return reject(err); + resolve(value); + })); + }); + }; +} + +const writeFile = promisify(fs.writeFile); + +const readdir = promisify(fs.readdir); +const lstat = promisify(fs.lstat); + +const ensureDir = promisify(fse.ensureDir); + +const babelTransformFile = promisify(babel.transformFile); + +// walkDir *recursively* walks directories trees, +// calling the callback for all normal files found. +function walkDir(basePath, cb, filter) { + return readdir(basePath) + .then((files) => { + const paths = files.map(filename => path.join(basePath, filename)); + return Promise.all(paths.map(filepath => lstat(filepath) + .then((stats) => { + if (filter !== undefined && !filter(filepath, stats)) return; + + if (stats.isSymbolicLink()) return; + if (stats.isFile()) return cb(filepath); + if (stats.isDirectory()) return walkDir(filepath, cb, filter); + }))); + }); +} + +function makeLibFiles(sourceMaps) { + // NB: we need to make a copy of babelOpts, since babel sets some defaults on it + const babelOpts = () => ({ + plugins: [], + presets: [ + [ '@babel/preset-env', + { modules: 'commonjs' } ] + ], + ast: false, + sourceMaps: sourceMaps, + }); + + fse.ensureDirSync(paths.libDirBase); + + const outFiles = []; + + const handleDir = (vendorRewrite, inPathBase, filename) => Promise.resolve() + .then(() => { + const outPath = path.join(paths.libDirBase, path.relative(inPathBase, filename)); + + if (path.extname(filename) !== '.js') { + return; // skip non-javascript files + } + return Promise.resolve() + .then(() => ensureDir(path.dirname(outPath))) + .then(() => { + const opts = babelOpts(); + // Adjust for the fact that we move the core files relative + // to the vendor directory + if (vendorRewrite) { + opts.plugins.push(["import-redirect", + {"root": paths.libDirBase, + "redirect": { "vendor/(.+)": "./vendor/$1"}}]); + } + + return babelTransformFile(filename, opts) + .then((res) => { + console.log(`Writing ${outPath}`); + const {map} = res; + let {code} = res; + if (sourceMaps === true) { + // append URL for external source map + code += `\n//# sourceMappingURL=${path.basename(outPath)}.map\n`; + } + outFiles.push(`${outPath}`); + return writeFile(outPath, code) + .then(() => { + if (sourceMaps === true || sourceMaps === 'both') { + console.log(` and ${outPath}.map`); + outFiles.push(`${outPath}.map`); + return writeFile(`${outPath}.map`, JSON.stringify(map)); + } + }); + }); + }); + }); + + Promise.resolve() + .then(() => { + const handler = handleDir.bind(null, false, paths.main); + return walkDir(paths.vendor, handler); + }) + .then(() => { + const handler = handleDir.bind(null, true, paths.core); + return walkDir(paths.core, handler); + }) + .catch((err) => { + console.error(`Failure converting modules: ${err}`); + process.exit(1); + }); +} + +let options = program.opts(); + +if (options.clean) { + console.log(`Removing ${paths.libDirBase}`); + fse.removeSync(paths.libDirBase); +} + +makeLibFiles(options.withSourceMaps); diff --git a/farmq-admin/static/novnc/utils/genkeysymdef.js b/farmq-admin/static/novnc/utils/genkeysymdef.js new file mode 100755 index 0000000..f539a0b --- /dev/null +++ b/farmq-admin/static/novnc/utils/genkeysymdef.js @@ -0,0 +1,127 @@ +#!/usr/bin/env node +/* + * genkeysymdef: X11 keysymdef.h to JavaScript converter + * Copyright (C) 2018 The noVNC Authors + * Licensed under MPL 2.0 (see LICENSE.txt) + */ + +"use strict"; + +const fs = require('fs'); + +let showHelp = process.argv.length === 2; +let filename; + +for (let i = 2; i < process.argv.length; ++i) { + switch (process.argv[i]) { + case "--help": + case "-h": + showHelp = true; + break; + case "--file": + case "-f": + default: + filename = process.argv[i]; + } +} + +if (!filename) { + showHelp = true; + console.log("Error: No filename specified\n"); +} + +if (showHelp) { + console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings"); + console.log("Usage: node parse.js [options] filename:"); + console.log(" -h [ --help ] Produce this help message"); + console.log(" filename The keysymdef.h file to parse"); + process.exit(0); +} + +const buf = fs.readFileSync(filename); +const str = buf.toString('utf8'); + +const re = /^#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/)?\s*$/m; + +const arr = str.split('\n'); + +const codepoints = {}; + +for (let i = 0; i < arr.length; ++i) { + const result = re.exec(arr[i]); + if (result) { + const keyname = result[1]; + const keysym = parseInt(result[2], 16); + const remainder = result[3]; + + const unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder); + if (unicodeRes) { + const unicode = parseInt(unicodeRes[1], 16); + // The first entry is the preferred one + if (!codepoints[unicode]) { + codepoints[unicode] = { keysym: keysym, name: keyname }; + } + } + } +} + +let out = +"/*\n" + +" * Mapping from Unicode codepoints to X11/RFB keysyms\n" + +" *\n" + +" * This file was automatically generated from keysymdef.h\n" + +" * DO NOT EDIT!\n" + +" */\n" + +"\n" + +"/* Functions at the bottom */\n" + +"\n" + +"const codepoints = {\n"; + +function toHex(num) { + let s = num.toString(16); + if (s.length < 4) { + s = ("0000" + s).slice(-4); + } + return "0x" + s; +} + +for (let codepoint in codepoints) { + codepoint = parseInt(codepoint); + + // Latin-1? + if ((codepoint >= 0x20) && (codepoint <= 0xff)) { + continue; + } + + // Handled by the general Unicode mapping? + if ((codepoint | 0x01000000) === codepoints[codepoint].keysym) { + continue; + } + + out += " " + toHex(codepoint) + ": " + + toHex(codepoints[codepoint].keysym) + + ", // XK_" + codepoints[codepoint].name + "\n"; +} + +out += +"};\n" + +"\n" + +"export default {\n" + +" lookup(u) {\n" + +" // Latin-1 is one-to-one mapping\n" + +" if ((u >= 0x20) && (u <= 0xff)) {\n" + +" return u;\n" + +" }\n" + +"\n" + +" // Lookup table (fairly random)\n" + +" const keysym = codepoints[u];\n" + +" if (keysym !== undefined) {\n" + +" return keysym;\n" + +" }\n" + +"\n" + +" // General mapping as final fallback\n" + +" return 0x01000000 | u;\n" + +" },\n" + +"};"; + +console.log(out); diff --git a/farmq-admin/static/novnc/utils/novnc_proxy b/farmq-admin/static/novnc/utils/novnc_proxy new file mode 100755 index 0000000..ea3ea70 --- /dev/null +++ b/farmq-admin/static/novnc/utils/novnc_proxy @@ -0,0 +1,214 @@ +#!/usr/bin/env bash + +# Copyright (C) 2018 The noVNC Authors +# Licensed under MPL 2.0 or any later version (see LICENSE.txt) + +usage() { + if [ "$*" ]; then + echo "$*" + echo + fi + echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT] [--ssl-only]" + echo + echo "Starts the WebSockets proxy and a mini-webserver and " + echo "provides a cut-and-paste URL to go to." + echo + echo " --listen PORT Port for proxy/webserver to listen on" + echo " Default: 6080" + echo " --vnc VNC_HOST:PORT VNC server host:port proxy target" + echo " Default: localhost:5900" + echo " --cert CERT Path to combined cert/key file, or just" + echo " the cert file if used with --key" + echo " Default: self.pem" + echo " --key KEY Path to key file, when not combined with cert" + echo " --web WEB Path to web files (e.g. vnc.html)" + echo " Default: ./" + echo " --ssl-only Disable non-https connections." + echo " " + echo " --file-only Disable directory listing in web server." + echo " " + echo " --record FILE Record traffic to FILE.session.js" + echo " " + echo " --syslog SERVER Can be local socket such as /dev/log, or a UDP host:port pair." + echo " " + echo " --heartbeat SEC send a ping to the client every SEC seconds" + echo " --timeout SEC after SEC seconds exit when not connected" + echo " --idle-timeout SEC server exits after SEC seconds if there are no" + echo " " + echo " --web-auth enable authentication" + echo " --auth-plugin CLASS authentication plugin to use" + echo " --auth-source ARG plugin configuration" + echo " " + echo " active connections" + echo " " + exit 2 +} + +NAME="$(basename $0)" +REAL_NAME="$(readlink -f $0)" +HERE="$(cd "$(dirname "$REAL_NAME")" && pwd)" +PORT="6080" +VNC_DEST="localhost:5900" +CERT="" +KEY="" +WEB="" +proxy_pid="" +SSLONLY="" +RECORD_ARG="" +SYSLOG_ARG="" +HEARTBEAT_ARG="" +IDLETIMEOUT_ARG="" +TIMEOUT_ARG="" +WEBAUTH_ARG="" +AUTHPLUGIN_ARG="" +AUTHSOURCE_ARG="" +FILEONLY_ARG="" + + +die() { + echo "$*" + exit 1 +} + +cleanup() { + trap - TERM QUIT INT EXIT + trap "true" CHLD # Ignore cleanup messages + echo + if [ -n "${proxy_pid}" ]; then + echo "Terminating WebSockets proxy (${proxy_pid})" + kill ${proxy_pid} + fi +} + +# Process Arguments + +# Arguments that only apply to chrooter itself +while [ "$*" ]; do + param=$1; shift; OPTARG=$1 + case $param in + --listen) PORT="${OPTARG}"; shift ;; + --vnc) VNC_DEST="${OPTARG}"; shift ;; + --cert) CERT="${OPTARG}"; shift ;; + --key) KEY="${OPTARG}"; shift ;; + --web) WEB="${OPTARG}"; shift ;; + --ssl-only) SSLONLY="--ssl-only" ;; + --file-only) FILEONLY_ARG="--file-only" ;; + --record) RECORD_ARG="--record ${OPTARG}"; shift ;; + --syslog) SYSLOG_ARG="--syslog ${OPTARG}"; shift ;; + --heartbeat) HEARTBEAT_ARG="--heartbeat ${OPTARG}"; shift ;; + --idle-timeout) IDLETIMEOUT_ARG="--idle-timeout ${OPTARG}"; shift ;; + --timeout) TIMEOUT_ARG="--timeout ${OPTARG}"; shift ;; + --web-auth) WEBAUTH_ARG="--web-auth" ;; + --auth-plugin) AUTHPLUGIN_ARG="--auth-plugin ${OPTARG}"; shift ;; + --auth-source) AUTHSOURCE_ARG="--auth-source ${OPTARG}"; shift ;; + -h|--help) usage ;; + -*) usage "Unknown chrooter option: ${param}" ;; + *) break ;; + esac +done + +# Sanity checks +if bash -c "exec 7<>/dev/tcp/localhost/${PORT}" &> /dev/null; then + exec 7<&- + exec 7>&- + die "Port ${PORT} in use. Try --listen PORT" +else + exec 7<&- + exec 7>&- +fi + +trap "cleanup" TERM QUIT INT EXIT + +# Find vnc.html +if [ -n "${WEB}" ]; then + if [ ! -e "${WEB}/vnc.html" ]; then + die "Could not find ${WEB}/vnc.html" + fi +elif [ -e "$(pwd)/vnc.html" ]; then + WEB=$(pwd) +elif [ -e "${HERE}/../vnc.html" ]; then + WEB=${HERE}/../ +elif [ -e "${HERE}/vnc.html" ]; then + WEB=${HERE} +elif [ -e "${HERE}/../share/novnc/vnc.html" ]; then + WEB=${HERE}/../share/novnc/ +else + die "Could not find vnc.html" +fi + +# Find self.pem +if [ -n "${CERT}" ]; then + if [ ! -e "${CERT}" ]; then + die "Could not find ${CERT}" + fi +elif [ -e "$(pwd)/self.pem" ]; then + CERT="$(pwd)/self.pem" +elif [ -e "${HERE}/../self.pem" ]; then + CERT="${HERE}/../self.pem" +elif [ -e "${HERE}/self.pem" ]; then + CERT="${HERE}/self.pem" +else + echo "Warning: could not find self.pem" +fi + +# Check key file +if [ -n "${KEY}" ]; then + if [ ! -e "${KEY}" ]; then + die "Could not find ${KEY}" + fi +fi + +# try to find websockify (prefer local, try global, then download local) +if [[ -d ${HERE}/websockify ]]; then + WEBSOCKIFY=${HERE}/websockify/run + + if [[ ! -x $WEBSOCKIFY ]]; then + echo "The path ${HERE}/websockify exists, but $WEBSOCKIFY either does not exist or is not executable." + echo "If you intended to use an installed websockify package, please remove ${HERE}/websockify." + exit 1 + fi + + echo "Using local websockify at $WEBSOCKIFY" +else + WEBSOCKIFY_FROMSYSTEM=$(which websockify 2>/dev/null) + WEBSOCKIFY_FROMSNAP=${HERE}/../usr/bin/python2-websockify + [ -f $WEBSOCKIFY_FROMSYSTEM ] && WEBSOCKIFY=$WEBSOCKIFY_FROMSYSTEM + [ -f $WEBSOCKIFY_FROMSNAP ] && WEBSOCKIFY=$WEBSOCKIFY_FROMSNAP + + if [ ! -f "$WEBSOCKIFY" ]; then + echo "No installed websockify, attempting to clone websockify..." + WEBSOCKIFY=${HERE}/websockify/run + git clone https://github.com/novnc/websockify ${HERE}/websockify + + if [[ ! -e $WEBSOCKIFY ]]; then + echo "Unable to locate ${HERE}/websockify/run after downloading" + exit 1 + fi + + echo "Using local websockify at $WEBSOCKIFY" + else + echo "Using installed websockify at $WEBSOCKIFY" + fi +fi + +echo "Starting webserver and WebSockets proxy on port ${PORT}" +#${HERE}/websockify --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} & +${WEBSOCKIFY} ${SYSLOG_ARG} ${SSLONLY} ${FILEONLY_ARG} --web ${WEB} ${CERT:+--cert ${CERT}} ${KEY:+--key ${KEY}} ${PORT} ${VNC_DEST} ${HEARTBEAT_ARG} ${IDLETIMEOUT_ARG} ${RECORD_ARG} ${TIMEOUT_ARG} ${WEBAUTH_ARG} ${AUTHPLUGIN_ARG} ${AUTHSOURCE_ARG} & +proxy_pid="$!" +sleep 1 +if [ -z "$proxy_pid" ] || ! ps -eo pid= | grep -w "$proxy_pid" > /dev/null; then + proxy_pid= + echo "Failed to start WebSockets proxy" + exit 1 +fi + +echo -e "\n\nNavigate to this URL:\n" +if [ "x$SSLONLY" == "x" ]; then + echo -e " http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n" +else + echo -e " https://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n" +fi + +echo -e "Press Ctrl-C to exit\n\n" + +wait ${proxy_pid} diff --git a/farmq-admin/static/novnc/utils/u2x11 b/farmq-admin/static/novnc/utils/u2x11 new file mode 100755 index 0000000..fd3e4ba --- /dev/null +++ b/farmq-admin/static/novnc/utils/u2x11 @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# +# Convert "U+..." commented entries in /usr/include/X11/keysymdef.h +# into JavaScript for use by noVNC. Note this is likely to produce +# a few duplicate properties with clashing values, that will need +# resolving manually. +# +# Colin Dean +# + +regex="^#define[ \t]+XK_[A-Za-z0-9_]+[ \t]+0x([0-9a-fA-F]+)[ \t]+\/\*[ \t]+U\+([0-9a-fA-F]+)[ \t]+[^*]+.[ \t]+\*\/[ \t]*$" +echo "unicodeTable = {" +while read line; do + if echo "${line}" | egrep -qs "${regex}"; then + + x11=$(echo "${line}" | sed -r "s/${regex}/\1/") + vnc=$(echo "${line}" | sed -r "s/${regex}/\2/") + + if echo "${vnc}" | egrep -qs "^00[2-9A-F][0-9A-F]$"; then + : # skip ISO Latin-1 (U+0020 to U+00FF) as 1-to-1 mapping + else + # note 1-to-1 is possible (e.g. for Euro symbol, U+20AC) + echo " 0x${vnc} : 0x${x11}," + fi + fi +done < /usr/include/X11/keysymdef.h | uniq +echo "};" + diff --git a/farmq-admin/static/novnc/utils/validate b/farmq-admin/static/novnc/utils/validate new file mode 100755 index 0000000..a6b5507 --- /dev/null +++ b/farmq-admin/static/novnc/utils/validate @@ -0,0 +1,45 @@ +#!/bin/bash + +set -e + +RET=0 + +OUT=`mktemp` + +for fn in "$@"; do + echo "Validating $fn..." + echo + + case $fn in + *.html) + type="text/html" + ;; + *.css) + type="text/css" + ;; + *) + echo "Unknown format!" + echo + RET=1 + continue + ;; + esac + + curl --silent \ + --header "Content-Type: ${type}; charset=utf-8" \ + --data-binary @${fn} \ + https://validator.w3.org/nu/?out=text > $OUT + cat $OUT + echo + + # We don't fail the check for warnings as some warnings are + # not relevant for us, and we don't currently have a way to + # ignore just those + if grep -q -s -E "^Error:" $OUT; then + RET=1 + fi +done + +rm $OUT + +exit $RET diff --git a/farmq-admin/static/novnc/vendor/pako/LICENSE b/farmq-admin/static/novnc/vendor/pako/LICENSE new file mode 100644 index 0000000..d082ae3 --- /dev/null +++ b/farmq-admin/static/novnc/vendor/pako/LICENSE @@ -0,0 +1,21 @@ +(The MIT License) + +Copyright (C) 2014-2016 by Vitaly Puzrin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/farmq-admin/static/novnc/vendor/pako/README.md b/farmq-admin/static/novnc/vendor/pako/README.md new file mode 100644 index 0000000..755df64 --- /dev/null +++ b/farmq-admin/static/novnc/vendor/pako/README.md @@ -0,0 +1,6 @@ +This is an ES6-modules-compatible version of +https://github.com/nodeca/pako, based on pako version 1.0.3. + +It's more-or-less a direct translation of the original, with unused parts +removed, and the dynamic support for non-typed arrays removed (since ES6 +modules don't work well with dynamic exports). diff --git a/farmq-admin/static/novnc/vnc.html b/farmq-admin/static/novnc/vnc.html new file mode 100644 index 0000000..24a118d --- /dev/null +++ b/farmq-admin/static/novnc/vnc.html @@ -0,0 +1,341 @@ + + + + + + noVNC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
noVNC encountered an error:
+
+
+
+
+ + +
+ +
+
+ +
+ +

no
VNC

+ +
+ + + + + +
+ +
+ + + +
+
+ + + + + + +
+
+ + + +
+
+
+ Power +
+ + + +
+
+ + + +
+
+
+ Clipboard +
+

+ Edit clipboard content in the textarea below. +

+ +
+
+ + + + + + +
+
+
+ Settings +
+
    +
  • + +
  • +
  • + +
  • +

  • +
  • + +
  • +
  • + + +
  • +

  • +
  • +
    Advanced
    +
      +
    • + + +
    • +
    • + + +
    • +

    • +
    • + + +
    • +
    • +
      WebSocket
      +
        +
      • + +
      • +
      • + + +
      • +
      • + + +
      • +
      • + + +
      • +
      +
    • +

    • +
    • + +
    • +
    • + + +
    • +

    • +
    • + +
    • +

    • + +
    • + +
    • +
    +
  • +

  • +
  • + Version: + +
  • +
+
+
+ + + + +
+
+ +
+ +
+
+
+
+ + +
+ + +
+
+ +
+ +
+
+
+ + +
+
+
+ Server identity +
+
+ The server has provided the following identifying information: +
+
+ Fingerprint: + +
+
+ Please verify that the information is correct and press + "Approve". Otherwise press "Reject". +
+
+ + +
+
+
+ + +
+
+
+ Credentials +
+
+ + +
+
+ + +
+
+ +
+
+
+ + +
+
+
+ +
+
+
+ + +
+ + +
+ + + + diff --git a/farmq-admin/static/novnc/vnc_lite.html b/farmq-admin/static/novnc/vnc_lite.html new file mode 100644 index 0000000..e725a2d --- /dev/null +++ b/farmq-admin/static/novnc/vnc_lite.html @@ -0,0 +1,187 @@ + + + + + + noVNC + + + + + + + +
+
Loading
+
Send CtrlAltDel
+
+
+ +
+ + diff --git a/farmq-admin/templates/base.html b/farmq-admin/templates/base.html index 29b1a99..96ed54e 100644 --- a/farmq-admin/templates/base.html +++ b/farmq-admin/templates/base.html @@ -146,7 +146,7 @@