From 2648918dda05a4d5073560cf803e57dad763ecb8 Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 21 Jun 2024 02:01:38 +0300 Subject: [PATCH] feat: added multilanguage for portal --- .gitignore | 4 +- gulpfile.js | 142 ++++ package.json | 17 + src/PortalTask.h | 16 +- src_data/{static => }/fonts/iconly.eot | Bin src_data/fonts/iconly.svg | 78 ++ src_data/{static => }/fonts/iconly.ttf | Bin src_data/{static => }/fonts/iconly.woff | Bin src_data/{static => }/fonts/iconly.woff2 | Bin src_data/{static => images}/favicon.ico | Bin src_data/locales/en.json | 331 ++++++++ src_data/locales/ru.json | 331 ++++++++ src_data/network.html | 211 ----- src_data/{ => pages}/dashboard.html | 110 +-- src_data/{ => pages}/index.html | 106 ++- src_data/pages/network.html | 220 +++++ src_data/pages/settings.html | 833 +++++++++++++++++++ src_data/pages/upgrade.html | 111 +++ src_data/scripts/i18n.min.js | 1 + src_data/scripts/lang.js | 128 +++ src_data/{static/app.js => scripts/utils.js} | 18 +- src_data/settings.html | 833 ------------------- src_data/{static => styles}/app.css | 107 +-- src_data/styles/iconly.css | 68 ++ src_data/{static => styles}/pico.min.css | 0 src_data/upgrade.html | 108 --- tools/build.py | 4 + 27 files changed, 2469 insertions(+), 1308 deletions(-) create mode 100644 gulpfile.js create mode 100644 package.json rename src_data/{static => }/fonts/iconly.eot (100%) create mode 100644 src_data/fonts/iconly.svg rename src_data/{static => }/fonts/iconly.ttf (100%) rename src_data/{static => }/fonts/iconly.woff (100%) rename src_data/{static => }/fonts/iconly.woff2 (100%) rename src_data/{static => images}/favicon.ico (100%) create mode 100644 src_data/locales/en.json create mode 100644 src_data/locales/ru.json delete mode 100644 src_data/network.html rename src_data/{ => pages}/dashboard.html (79%) rename src_data/{ => pages}/index.html (57%) create mode 100644 src_data/pages/network.html create mode 100644 src_data/pages/settings.html create mode 100644 src_data/pages/upgrade.html create mode 100644 src_data/scripts/i18n.min.js create mode 100644 src_data/scripts/lang.js rename src_data/{static/app.js => scripts/utils.js} (97%) delete mode 100644 src_data/settings.html rename src_data/{static => styles}/app.css (50%) create mode 100644 src_data/styles/iconly.css rename src_data/{static => styles}/pico.min.css (100%) delete mode 100644 src_data/upgrade.html diff --git a/.gitignore b/.gitignore index d4b07d4..6d729a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .pio .vscode build/*.bin -data/**/*.gz +data/* secrets.ini +node_modules +package-lock.json !.gitkeep \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..d635dbe --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,142 @@ +const { src, dest, series, parallel } = require('gulp'); +const concat = require('gulp-concat'); +const gzip = require('gulp-gzip'); +const postcss = require('gulp-postcss'); +const cssnano = require('cssnano'); +const terser = require('gulp-terser'); +const jsonminify = require('gulp-jsonminify'); +const htmlmin = require('gulp-html-minifier-terser'); + +// Paths for tasks +let paths = { + styles: { + dest: 'data/static/', + bundles: { + 'app.css': [ + 'src_data/styles/pico.min.css', + 'src_data/styles/iconly.css', + 'src_data/styles/app.css' + ] + } + }, + scripts: { + dest: 'data/static/', + bundles: { + 'app.js': [ + 'src_data/scripts/i18n.min.js', + 'src_data/scripts/lang.js', + 'src_data/scripts/utils.js' + ] + } + }, + json: [ + { + src: 'src_data/locales/*.json', + dest: 'data/static/locales/' + } + ], + static: [ + { + src: 'src_data/fonts/*.*', + dest: 'data/static/fonts/' + }, + { + src: 'src_data/images/*.*', + dest: 'data/static/images/' + } + ], + pages: { + src: 'src_data/pages/*.html', + dest: 'data/pages/' + } +}; + + + +// Tasks +const styles = (cb) => { + for (let name in paths.styles.bundles) { + const items = paths.styles.bundles[name]; + + src(items) + .pipe(postcss([ + cssnano({ preset: 'advanced' }) + ])) + .pipe(concat(name)) + .pipe(gzip({ + append: true + })) + .pipe(dest(paths.styles.dest)); + } + + cb(); +} + +const scripts = (cb) => { + for (let name in paths.scripts.bundles) { + const items = paths.scripts.bundles[name]; + + src(items) + .pipe(terser().on('error', console.error)) + .pipe(concat(name)) + .pipe(gzip({ + append: true + })) + .pipe(dest(paths.scripts.dest)); + } + + cb(); +} + +const jsonFiles = (cb) => { + for (let i in paths.json) { + const item = paths.json[i]; + + src(item.src) + .pipe(jsonminify()) + .pipe(gzip({ + append: true + })) + .pipe(dest(item.dest)); + } + + cb(); +} + +const staticFiles = (cb) => { + for (let i in paths.static) { + const item = paths.static[i]; + + src(item.src, { encoding: false }) + .pipe(gzip({ + append: true + })) + .pipe(dest(item.dest)); + } + + cb(); +} + +const pages = () => { + return src(paths.pages.src) + .pipe(htmlmin({ + html5: true, + caseSensitive: true, + collapseWhitespace: true, + collapseInlineTagWhitespace: true, + conservativeCollapse: true, + removeComments: true, + minifyJS: true + })) + .pipe(gzip({ + append: true + })) + .pipe(dest(paths.pages.dest)); +} + +exports.build_styles = styles; +exports.build_scripts = scripts; +exports.build_json = jsonFiles; +exports.build_static = staticFiles; +exports.build_pages = pages; +exports.build_all = parallel(styles, scripts, jsonFiles, staticFiles, pages); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..62380e7 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "otgateway", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "devDependencies": { + "cssnano": "^7.0.2", + "cssnano-preset-advanced": "^7.0.2", + "gulp": "^5.0.0", + "gulp-concat": "^2.6.1", + "gulp-gzip": "^1.4.2", + "gulp-html-minifier-terser": "^7.1.0", + "gulp-jsonminify": "^1.1.0", + "gulp-postcss": "^10.0.0", + "gulp-terser": "^2.1.0" + } +} diff --git a/src/PortalTask.h b/src/PortalTask.h index 05b3115..1c30b26 100644 --- a/src/PortalTask.h +++ b/src/PortalTask.h @@ -77,7 +77,7 @@ protected: #endif // index page - /*auto indexPage = (new DynamicPage("/", &LittleFS, "/index.html")) + /*auto indexPage = (new DynamicPage("/", &LittleFS, "/pages/index.html")) ->setTemplateCallback([](const char* var) -> String { String result; @@ -88,10 +88,10 @@ protected: return result; }); this->webServer->addHandler(indexPage);*/ - this->webServer->addHandler(new StaticPage("/", &LittleFS, "/index.html", PORTAL_CACHE)); + this->webServer->addHandler(new StaticPage("/", &LittleFS, "/pages/index.html", PORTAL_CACHE)); // dashboard page - auto dashboardPage = (new StaticPage("/dashboard.html", &LittleFS, "/dashboard.html", PORTAL_CACHE)) + auto dashboardPage = (new StaticPage("/dashboard.html", &LittleFS, "/pages/dashboard.html", PORTAL_CACHE)) ->setBeforeSendCallback([this]() { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { this->webServer->requestAuthentication(DIGEST_AUTH); @@ -117,7 +117,7 @@ protected: }); // network settings page - auto networkPage = (new StaticPage("/network.html", &LittleFS, "/network.html", PORTAL_CACHE)) + auto networkPage = (new StaticPage("/network.html", &LittleFS, "/pages/network.html", PORTAL_CACHE)) ->setBeforeSendCallback([this]() { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { this->webServer->requestAuthentication(DIGEST_AUTH); @@ -129,7 +129,7 @@ protected: this->webServer->addHandler(networkPage); // settings page - auto settingsPage = (new StaticPage("/settings.html", &LittleFS, "/settings.html", PORTAL_CACHE)) + auto settingsPage = (new StaticPage("/settings.html", &LittleFS, "/pages/settings.html", PORTAL_CACHE)) ->setBeforeSendCallback([this]() { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { this->webServer->requestAuthentication(DIGEST_AUTH); @@ -141,7 +141,7 @@ protected: this->webServer->addHandler(settingsPage); // upgrade page - auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/upgrade.html", PORTAL_CACHE)) + auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/pages/upgrade.html", PORTAL_CACHE)) ->setBeforeSendCallback([this]() { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { this->webServer->requestAuthentication(DIGEST_AUTH); @@ -250,6 +250,7 @@ protected: fsNetworkSettings.update(); network->setHostname(networkSettings.hostname) ->setStaCredentials(networkSettings.sta.ssid, networkSettings.sta.password, networkSettings.sta.channel) + ->setApCredentials(networkSettings.ap.ssid, networkSettings.ap.password, networkSettings.ap.channel) ->setUseDhcp(networkSettings.useDhcp) ->setStaticConfig( networkSettings.staticConfig.ip, @@ -326,6 +327,7 @@ protected: fsNetworkSettings.update(); network->setHostname(networkSettings.hostname) ->setStaCredentials(networkSettings.sta.ssid, networkSettings.sta.password, networkSettings.sta.channel) + ->setApCredentials(networkSettings.ap.ssid, networkSettings.ap.password, networkSettings.ap.channel) ->setUseDhcp(networkSettings.useDhcp) ->setStaticConfig( networkSettings.staticConfig.ip, @@ -573,7 +575,7 @@ protected: } }); - this->webServer->serveStatic("/favicon.ico", LittleFS, "/static/favicon.ico", PORTAL_CACHE); + this->webServer->serveStatic("/favicon.ico", LittleFS, "/static/images/favicon.ico", PORTAL_CACHE); this->webServer->serveStatic("/static", LittleFS, "/static", PORTAL_CACHE); } diff --git a/src_data/static/fonts/iconly.eot b/src_data/fonts/iconly.eot similarity index 100% rename from src_data/static/fonts/iconly.eot rename to src_data/fonts/iconly.eot diff --git a/src_data/fonts/iconly.svg b/src_data/fonts/iconly.svg new file mode 100644 index 0000000..8eed6c2 --- /dev/null +++ b/src_data/fonts/iconly.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src_data/static/fonts/iconly.ttf b/src_data/fonts/iconly.ttf similarity index 100% rename from src_data/static/fonts/iconly.ttf rename to src_data/fonts/iconly.ttf diff --git a/src_data/static/fonts/iconly.woff b/src_data/fonts/iconly.woff similarity index 100% rename from src_data/static/fonts/iconly.woff rename to src_data/fonts/iconly.woff diff --git a/src_data/static/fonts/iconly.woff2 b/src_data/fonts/iconly.woff2 similarity index 100% rename from src_data/static/fonts/iconly.woff2 rename to src_data/fonts/iconly.woff2 diff --git a/src_data/static/favicon.ico b/src_data/images/favicon.ico similarity index 100% rename from src_data/static/favicon.ico rename to src_data/images/favicon.ico diff --git a/src_data/locales/en.json b/src_data/locales/en.json new file mode 100644 index 0000000..f86c061 --- /dev/null +++ b/src_data/locales/en.json @@ -0,0 +1,331 @@ +{ + "values": { + "logo": "OpenTherm Gateway", + "nav": { + "license": "License", + "source": "Source code", + "help": "Help", + "issues": "Issues & questions", + "releases": "Releases" + }, + + "button": { + "upgrade": "Upgrade", + "restart": "Restart", + "save": "Save", + "saved": "Saved", + "refresh": "Refresh", + "restore": "Restore", + "restored": "Restored", + "backup": "Backup", + "wait": "Please wait...", + "uploading": "Uploading...", + "success": "Success", + "error": "Error" + }, + + "index": { + "title": "OpenTherm Gateway", + + "section": { + "network": "Network", + "system": "System" + }, + + "system": { + "build": { + "title": "Build", + "version": "Version", + "date": "Date", + "sdk": "Core/SDK" + }, + "uptime": "Uptime", + "memory": { + "title": "Free memory", + "maxFreeBlock": "max free block", + "min": "min" + }, + "board": "Board", + "chip": { + "model": "Chip", + "cores": "Cores", + "freq": "frequency" + }, + "flash": { + "size": "Flash size", + "realSize": "real size" + }, + "lastResetReason": "Last reset reason" + } + }, + + "dashboard": { + "name": "Dashboard", + "title": "Dashboard - OpenTherm Gateway", + + "section": { + "control": "Control", + "states": "States and sensors", + "otDiag": "OpenTherm diagnostic" + }, + + "thermostat": { + "heating": "Heating", + "dhw": "DHW", + "temp.current": "Current", + "enable": "Enable", + "turbo": "Turbo mode" + }, + + "state": { + "ot": "OpenTherm connected", + "mqtt": "MQTT connected", + "emergency": "Emergency", + "heating": "Heating", + "dhw": "DHW", + "flame": "Flame", + "fault": "Fault", + "diag": "Diagnostic", + "extpump": "External pump", + "modulation": "Modulation", + "pressure": "Pressure", + "dhwFlowRate": "DHW flow rate", + "faultCode": "Fault code", + "indoorTemp": "Indoor temp", + "outdoorTemp": "Outdoor temp", + "heatingTemp": "Heating temp", + "heatingSetpointTemp": "Heating setpoint temp", + "heatingReturnTemp": "Heating return temp", + "dhwTemp": "DHW temp", + "exhaustTemp": "Exhaust temp" + } + }, + + "network": { + "title": "Network - OpenTherm Gateway", + "name": "Network settings", + + "section": { + "static": "Static settings", + "availableNetworks": "Available networks", + "staSettings": "WiFi settings", + "apSettings": "AP settings" + }, + + "scan": { + "pos": "#", + "info": "Info" + }, + + "wifi": { + "ssid": "SSID", + "password": "Password", + "channel": "Channel", + "signal": "Signal", + "connected": "Connected" + }, + + "params": { + "hostname": "Hostname", + "dhcp": "Use DHCP", + "mac": "MAC", + "ip": "IP", + "subnet": "Subnet", + "gateway": "Gateway", + "dns": "DNS" + }, + + "sta": { + "channel.note": "set 0 for auto select" + } + }, + + "settings": { + "title": "Settings - OpenTherm Gateway", + "name": "Settings", + + "section": { + "portal": "Portal settings", + "system": "System settings", + "diag": "Diagnostic", + "heating": "Heating settings", + "dhw": "DHW settings", + "emergency": "Emergency mode settings", + "emergency.events": "Events", + "emergency.regulators": "Using regulators", + "equitherm": "Equitherm settings", + "pid": "PID settings", + "ot": "OpenTherm settings", + "ot.options": "Options", + "mqtt": "MQTT settings", + "outdorSensor": "Outdoor sensor settings", + "indoorSensor": "Indoor sensor settings", + "extPump": "External pump settings" + }, + + "enable": "Enable", + "note": { + "restart": "After changing these settings, the device must be restarted for the changes to take effect.", + "blankNotUse": "blank - not use" + }, + + "temp": { + "min": "Minimum temperature", + "max": "Maximum temperature" + }, + + "portal": { + "login": "Login", + "password": "Password", + "auth": "Require authentication" + }, + + "system": { + "unit": "Unit system", + "metric": "Metric (celsius, liters, bar)", + "imperial": "Imperial (fahrenheit, gallons, psi)", + "statusLedGpio": "Status LED GPIO", + "debug": "Debug mode", + "serial": { + "enable": "Enable Serial port", + "baud": { + "title": "Serial port baud rate", + "note": "Available: 9600, 19200, 38400, 57600, 74880, 115200" + } + }, + "telnet": { + "enable": "Enable Telnet", + "port": { + "title": "Telnet port", + "note": "Default: 23" + } + } + }, + + "heating": { + "hyst": "Hysteresis", + "maxMod": "Max modulation level" + }, + + "emergency": { + "desc": "! Emergency mode can be useful only when using Equitherm and/or PID (when normal work) and when reporting indoor/outdoor temperature via MQTT or API. In this mode, sensor values that are reported via MQTT/API are not used.", + + "target": { + "title": "Target temperature", + "note": "Indoor temperature if Equitherm or PID is enabled
Heat carrier temperature if Equitherm and PID is disabled" + }, + "treshold": "Treshold time (sec)", + + "events": { + "network": "On network fault", + "mqtt": "On MQTT fault" + }, + + "regulators": { + "equitherm": "Equitherm (requires at least an external/boiler outdoor sensor)", + "pid": "PID (requires at least an external/BLE indoor sensor)" + } + }, + + "equitherm": { + "n": "N factor", + "k": "K factor", + "t": { + "title": "T factor", + "note": "Not used if PID is enabled" + } + }, + + "pid": { + "p": "P factor", + "i": "I factor", + "d": "D factor", + "dt": "DT in seconds" + }, + + "ot": { + "inGpio": "In GPIO", + "outGpio": "Out GPIO", + "ledGpio": "RX LED GPIO", + "memberIdCode": "Master MemberID code", + + "options": { + "dhwPresent": "DHW present", + "summerWinterMode": "Summer/winter mode", + "heatingCh2Enabled": "Heating CH2 always enabled", + "heatingCh1ToCh2": "Duplicate heating CH1 to CH2", + "dhwToCh2": "Duplicate DHW to CH2", + "dhwBlocking": "DHW blocking", + "modulationSyncWithHeating": "Sync modulation with heating", + "getMinMaxTemp": "Get min/max temp from boiler" + }, + + "faultState": { + "gpio": "Fault state GPIO", + "note": "Can be useful to switch on another boiler via relay. Blank - not use.", + "invert": "Invert fault state" + }, + + "nativeHeating": { + "title": "Native heating control (boiler)", + "note": "Works ONLY if the boiler requires the desired room temperature and regulates the temperature of the coolant itself. Not compatible with PID and Equitherm regulators and hysteresis in firmware." + } + }, + + "mqtt": { + "homeAssistantDiscovery": "Home Assistant Discovery", + "server": "Server", + "port": "Port", + "user": "User", + "password": "Password", + "prefix": "Prefix", + "interval": "Publish interval (sec)" + }, + + "tempSensor": { + "source": { + "type": "Source type", + "boiler": "From boiler via OpenTherm", + "manual": "Manual via MQTT/API", + "ext": "External (DS18B20)", + "ble": "BLE device (ONLY for some ESP32 which support BLE)" + }, + "gpio": "GPIO", + "offset": "Temp offset (calibration)", + "bleAddress": { + "title": "BLE address", + "note": "ONLY for some ESP32 which support BLE" + } + }, + + "extPump": { + "use": "Use external pump", + "gpio": "Relay GPIO", + "postCirculationTime": "Post circulation time (min)", + "antiStuckInterval": "Anti stuck interval (days)", + "antiStuckTime": "Anti stuck time (min)" + } + }, + + "upgrade": { + "title": "Upgrade - OpenTherm Gateway", + "name": "Upgrade", + + "section": { + "backupAndRestore": "Backup & restore", + "backupAndRestore.desc": "In this section you can save and restore a backup of ALL settings.", + "upgrade": "Upgrade", + "upgrade.desc": "In this section you can upgrade the firmware and filesystem of your device.
Latest releases can be downloaded from the Releases page of the project repository." + }, + + "note": { + "disclaimer1": "After a successful upgrade the filesystem, ALL settings will be reset to default values! Save backup before upgrading.", + "disclaimer2": "After a successful upgrade, the device will automatically reboot after 10 seconds." + }, + + "settingsFile": "Settings file", + "fw": "Firmware", + "fs": "Filesystem" + } + } +} \ No newline at end of file diff --git a/src_data/locales/ru.json b/src_data/locales/ru.json new file mode 100644 index 0000000..4349836 --- /dev/null +++ b/src_data/locales/ru.json @@ -0,0 +1,331 @@ +{ + "values": { + "logo": "OpenTherm Gateway", + "nav": { + "license": "Лицензия", + "source": "Исходный код", + "help": "Помощь", + "issues": "Проблемы и вопросы", + "releases": "Релизы" + }, + + "button": { + "upgrade": "Обновить", + "restart": "Перезагрузка", + "save": "Сохранить", + "saved": "Сохранено", + "refresh": "Обновить", + "restore": "Восстановить настройки", + "restored": "Восстановлено", + "backup": "Сохранить настройки", + "wait": "Пожалуйста, подождите...", + "uploading": "Загрузка...", + "success": "Успешно", + "error": "Ошибка" + }, + + "index": { + "title": "OpenTherm Gateway", + + "section": { + "network": "Сеть", + "system": "Система" + }, + + "system": { + "build": { + "title": "Билд", + "version": "Версия", + "date": "Дата", + "sdk": "Ядро/SDK" + }, + "uptime": "Аптайм", + "memory": { + "title": "ОЗУ", + "maxFreeBlock": "макс. блок", + "min": "мин." + }, + "board": "Плата", + "chip": { + "model": "Чип", + "cores": "Кол-во ядер", + "freq": "частота" + }, + "flash": { + "size": "Размер ПЗУ", + "realSize": "реальный размер" + }, + "lastResetReason": "Причина перезагрузки" + } + }, + + "dashboard": { + "name": "Дашборд", + "title": "Дашборд - OpenTherm Gateway", + + "section": { + "control": "Управление", + "states": "Состояние и сенсоры", + "otDiag": "Диагностика OpenTherm" + }, + + "thermostat": { + "heating": "Отопление", + "dhw": "ГВС", + "temp.current": "Текущая", + "enable": "Вкл", + "turbo": "Турбо" + }, + + "state": { + "ot": "OpenTherm подключение", + "mqtt": "MQTT подключение", + "emergency": "Аварийный режим", + "heating": "Отопление", + "dhw": "ГВС", + "flame": "Пламя", + "fault": "Ошибка", + "diag": "Диагностика", + "extpump": "Внешний насос", + "modulation": "Уровень модуляции", + "pressure": "Давление", + "dhwFlowRate": "Расход ГВС", + "faultCode": "Код ошибки", + "indoorTemp": "Внутренняя темп.", + "outdoorTemp": "Наружная темп.", + "heatingTemp": "Темп. отопления", + "heatingSetpointTemp": "Уставка темп. отопления", + "heatingReturnTemp": "Темп. обратки отопления", + "dhwTemp": "Темп. ГВС", + "exhaustTemp": "Темп. выхлопных газов" + } + }, + + "network": { + "title": "Сеть - OpenTherm Gateway", + "name": "Настройки сети", + + "section": { + "static": "Статические параметры", + "availableNetworks": "Доступные сети", + "staSettings": "Настройки подключения", + "apSettings": "Настройки точки доступа" + }, + + "scan": { + "pos": "#", + "info": "Инфо" + }, + + "wifi": { + "ssid": "Имя сети", + "password": "Пароль", + "channel": "Канал", + "signal": "Сигнал", + "connected": "Подключено" + }, + + "params": { + "hostname": "Имя хоста", + "dhcp": "Использовать DHCP", + "mac": "MAC адрес", + "ip": "IP адрес", + "subnet": "Адрес подсети", + "gateway": "Адрес шлюза", + "dns": "DNS адрес" + }, + + "sta": { + "channel.note": "установите 0 для автоматического выбора" + } + }, + + "settings": { + "title": "Настройки - OpenTherm Gateway", + "name": "Настройки", + + "section": { + "portal": "Настройки портала", + "system": "Системные настройки", + "diag": "Диагностика", + "heating": "Настройки отопления", + "dhw": "Настройки ГВС", + "emergency": "Настройки аварийного режима", + "emergency.events": "События", + "emergency.regulators": "Используемые регуляторы", + "equitherm": "Настройки ПЗА", + "pid": "Настройки ПИД", + "ot": "Настройки OpenTherm", + "ot.options": "Опции", + "mqtt": "Настройки MQTT", + "outdorSensor": "Настройки наружного датчика температуры", + "indoorSensor": "Настройки внутреннего датчика температуры", + "extPump": "Настройки дополнительного насоса" + }, + + "enable": "Вкл", + "note": { + "restart": "После изменения этих настроек устройство необходимо перезагрузить, чтобы изменения вступили в силу.", + "blankNotUse": "пусто - не использовать" + }, + + "temp": { + "min": "Мин. температура", + "max": "Макс. температура" + }, + + "portal": { + "login": "Логин", + "password": "Пароль", + "auth": "Требовать аутентификацию" + }, + + "system": { + "unit": "Система единиц", + "metric": "Метрическая (цильсии, литры, бары)", + "imperial": "Imperial (фаренгейты, галлоны, psi)", + "statusLedGpio": "Статус LED GPIO", + "debug": "Отладка", + "serial": { + "enable": "Вкл. Serial порт", + "baud": { + "title": "Скорость Serial порта", + "note": "Доступно: 9600, 19200, 38400, 57600, 74880, 115200" + } + }, + "telnet": { + "enable": "Вкл. Telnet", + "port": { + "title": "Telnet порт", + "note": "По умолчанию: 23" + } + } + }, + + "heating": { + "hyst": "Гистерезис", + "maxMod": "Макс. уровень модуляции" + }, + + "emergency": { + "desc": "! Аварийный режим может быть полезен только при использовании ПЗА и/или ПИД и при передачи наружной/внутренней температуры через MQTT или API. В этом режиме значения датчиков, передаваемые через MQTT/API, не используются.", + + "target": { + "title": "Целевая температура", + "note": "Целевая внутренняя температура если ПЗА и/или ПИД включены
Целевая температура теплоносителя если ПЗА и ПИД выключены" + }, + "treshold": "Пороговое время включения (сек)", + + "events": { + "network": "При отключении сети", + "mqtt": "При отключении MQTT" + }, + + "regulators": { + "equitherm": "ПЗА (требуется внешний или подключенный к котлу датчик наружной температуры)", + "pid": "ПИД (требуется внешний/BLE датчик внутренней температуры)" + } + }, + + "equitherm": { + "n": "Коэффициент N", + "k": "Коэффициент K", + "t": { + "title": "Коэффициент T", + "note": "Не используется, если ПИД включен" + } + }, + + "pid": { + "p": "Коэффициент P", + "i": "Коэффициент I", + "d": "Коэффициент D", + "dt": "DT в секундах" + }, + + "ot": { + "inGpio": "Вход GPIO", + "outGpio": "Выход GPIO", + "ledGpio": "RX LED GPIO", + "memberIdCode": "Master MemberID код", + + "options": { + "dhwPresent": "Контур ГВС", + "summerWinterMode": "Летний/зимний режим", + "heatingCh2Enabled": "Канал 2 отопления всегда вкл.", + "heatingCh1ToCh2": "Дублировать параметры отопления канала 1 в канал 2", + "dhwToCh2": "Дублировать параметры ГВС в канал 2", + "dhwBlocking": "DHW blocking", + "modulationSyncWithHeating": "Синхронизировать модуляцию с отоплением", + "getMinMaxTemp": "Получать мин. и макс. температуру от котла" + }, + + "faultState": { + "gpio": "Fault state GPIO", + "note": "Can be useful to switch on another boiler via relay. Blank - not use.", + "invert": "Invert fault state" + }, + + "nativeHeating": { + "title": "Передать управление отоплением котлу", + "note": "Работает ТОЛЬКО если котел требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД и ПЗА, а также с гистерезисом встроенного ПО." + } + }, + + "mqtt": { + "homeAssistantDiscovery": "Home Assistant Discovery", + "server": "Адрес сервера", + "port": "Порт", + "user": "Имя пользователя", + "password": "Пароль", + "prefix": "Префикс", + "interval": "Интервал публикации (в секундах)" + }, + + "tempSensor": { + "source": { + "type": "Источник данных", + "boiler": "От котла через OpenTherm", + "manual": "Вручную через MQTT/API", + "ext": "Внешний датчик (DS18B20)", + "ble": "BLE устройство (ТОЛЬКО для некоторых плат ESP32 с поддержкой BLE)" + }, + "gpio": "GPIO", + "offset": "Смещение температуры (калибровка)", + "bleAddress": { + "title": "BLE адрес", + "note": "ТОЛЬКО для некоторых плат ESP32 с поддержкой BLE" + } + }, + + "extPump": { + "use": "Использовать доп. насос", + "gpio": "GPIO реле", + "postCirculationTime": "Время постциркуляции (в минутах)", + "antiStuckInterval": "Интервал защиты от блокировки (в днях)", + "antiStuckTime": "Время работы насоса (в минутах)" + } + }, + + "upgrade": { + "title": "Обновление - OpenTherm Gateway", + "name": "Обновление", + + "section": { + "backupAndRestore": "Резервное копирование и восстановление", + "backupAndRestore.desc": "В этом разделе вы можете сохранить и восстановить резервную копию ВСЕХ настроек.", + "upgrade": "Обновление", + "upgrade.desc": "В этом разделе вы можете обновить прошивку и файловую систему вашего устройства.
Последнюю версию можно загрузить со страницы Релизы в репозитории проекта." + }, + + "note": { + "disclaimer1": "После успешного обновления файловой системы ВСЕ настройки будут сброшены на стандартные! Создайте резервную копию ПЕРЕД обновлением.", + "disclaimer2": "После успешного обновления устройство автоматически перезагрузится через 10 секунд." + }, + + "settingsFile": "Файл настроек", + "fw": "Прошивка", + "fs": "Файловая система" + } + } +} \ No newline at end of file diff --git a/src_data/network.html b/src_data/network.html deleted file mode 100644 index 06e3a89..0000000 --- a/src_data/network.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - - Network - OpenTherm Gateway - - - - - -
- -
- -
-
-
-
-

Network settings

-

-
- -
- -
-
- -
-
-
-

Available networks

-

-
- -
-
- - - - - - - - - -
#SSIDInfo
-
- - -
- -
-
-
-

WiFi settings

-

-
- -
- -
-
-
- -
-
-
-

AP settings

-

-
- -
- -
-
-
- - - - - - - - diff --git a/src_data/dashboard.html b/src_data/pages/dashboard.html similarity index 79% rename from src_data/dashboard.html rename to src_data/pages/dashboard.html index 3ca5d28..adfe22d 100644 --- a/src_data/dashboard.html +++ b/src_data/pages/dashboard.html @@ -3,19 +3,26 @@ - Dashboard - OpenTherm Gateway - - + dashboard.title +
@@ -23,43 +30,43 @@
-

Dashboard

+

dashboard.name

@@ -78,7 +85,7 @@
-

System

+

index.section.system

@@ -86,37 +93,53 @@ - + - - + + - - + + - - + + - - + + - +
@@ -125,17 +148,20 @@ - + \ No newline at end of file diff --git a/src_data/pages/network.html b/src_data/pages/network.html new file mode 100644 index 0000000..6005ac5 --- /dev/null +++ b/src_data/pages/network.html @@ -0,0 +1,220 @@ + + + + + + network.title + + + + +
+ +
+ +
+
+
+
+

network.name

+

+
+ +
+ +
+
+ +
+
+
+

network.section.availableNetworks

+

+
+ +
+
+ + + + + + + + + +
network.scan.posnetwork.wifi.ssidnetwork.scan.info
+
+ + +
+ +
+
+
+

network.section.staSettings

+

+
+ +
+ +
+
+
+ +
+
+
+

network.section.apSettings

+

+
+ +
+ +
+
+
+ + + + + + + \ No newline at end of file diff --git a/src_data/pages/settings.html b/src_data/pages/settings.html new file mode 100644 index 0000000..2a45e9e --- /dev/null +++ b/src_data/pages/settings.html @@ -0,0 +1,833 @@ + + + + + + settings.title + + + + +
+ +
+ +
+
+
+

settings.name

+

+
+ +
+ settings.section.portal +
+
+ +
+
+ +
+ +
+ settings.section.system +
+
+ +
+
+ + +
+ +
+ settings.section.heating +
+
+ +
+
+ +
+ +
+ settings.section.dhw +
+
+ +
+
+ +
+ +
+ settings.section.emergency +
+
+ +
+
+ +
+ +
+ settings.section.equitherm +
+
+ +
+
+ +
+ +
+ settings.section.pid +
+
+ +
+
+ +
+ +
+ settings.section.ot +
+
+ +
+
+ +
+ +
+ settings.section.mqtt +
+
+ +
+
+ +
+ +
+ settings.section.outdorSensor +
+
+ +
+
+ +
+ +
+ settings.section.indoorSensor +
+
+ +
+
+ +
+ +
+ settings.section.extPump +
+
+ +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/src_data/pages/upgrade.html b/src_data/pages/upgrade.html new file mode 100644 index 0000000..d438df2 --- /dev/null +++ b/src_data/pages/upgrade.html @@ -0,0 +1,111 @@ + + + + + + upgrade.title + + + + +
+ +
+ +
+
+
+
+

upgrade.section.backupAndRestore

+

upgrade.section.backupAndRestore.desc

+
+ +
+ + +
+ + +
+
+
+
+ +
+
+
+

upgrade.section.upgrade

+

upgrade.section.upgrade.desc

+
+ +
+
+ + + +
+ +
    +
  • upgrade.note.disclaimer1
  • +
  • upgrade.note.disclaimer2
  • +
+ + +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/src_data/scripts/i18n.min.js b/src_data/scripts/i18n.min.js new file mode 100644 index 0000000..507ce34 --- /dev/null +++ b/src_data/scripts/i18n.min.js @@ -0,0 +1 @@ +(function(){var e,t,n,r=function(e,t){return function(){return e.apply(t,arguments)}};e=function(){function e(){this.translate=r(this.translate,this);this.data={values:{},contexts:[]};this.globalContext={}}e.prototype.translate=function(e,t,n,r,i){var s,o,u,a;if(i==null){i=this.globalContext}u=function(e){var t;t=typeof e;return t==="function"||t==="object"&&!!e};if(u(t)){s=null;a=null;o=t;i=n||this.globalContext}else{if(typeof t==="number"){s=null;a=t;o=n;i=r||this.globalContext}else{s=t;if(typeof n==="number"){a=n;o=r;i=i}else{a=null;o=n;i=r||this.globalContext}}}if(u(e)){if(u(e["i18n"])){e=e["i18n"]}return this.translateHash(e,i)}else{return this.translateText(e,a,o,i,s)}};e.prototype.add=function(e){var t,n,r,i,s,o,u,a;if(e.values!=null){o=e.values;for(n in o){r=o[n];this.data.values[n]=r}}if(e.contexts!=null){u=e.contexts;a=[];for(i=0,s=u.length;i=s[0]||s[0]===null)&&(t<=s[1]||s[1]===null)){i=this.applyFormatting(s[2].replace("-%n",String(-t)),t,n);return this.applyFormatting(i.replace("%n",String(t)),t,n)}}}}return null};e.prototype.getContextData=function(e,t){var n,r,i,s,o,u,a,f;if(e.contexts==null){return null}a=e.contexts;for(o=0,u=a.length;o { + await this.setLocale(element.target.value); + this.translatePage(); + }); + } + + async setLocale(newLocale) { + if (this.currentLocale == newLocale) { + return; + } + + i18n.translator.reset(); + i18n.translator.add(await this.fetchTranslations(newLocale)); + + this.currentLocale = newLocale; + localStorage.setItem('locale', this.currentLocale); + + if (document.documentElement) { + document.documentElement.setAttribute("lang", this.currentLocale); + } + + if (this.switcher.value != this.currentLocale) { + this.switcher.value = this.currentLocale; + } + } + + async fetchTranslations(locale) { + const response = await fetch(`/static/locales/${locale}.json`); + const data = await response.json(); + + if (data.values instanceof Object) { + data.values = this.flattenKeys({keys: data.values, prefix: ''}); + } + + return data; + } + + translatePage() { + document + .querySelectorAll("[data-i18n]") + .forEach((element) => this.translateElement(element)); + } + + translateElement(element) { + let key = element.getAttribute("data-i18n"); + if (!key && element.innerHTML) { + key = element.innerHTML; + element.setAttribute("data-i18n", key); + } + + if (!key) { + return; + } + + const arg = element.getAttribute("data-i18n-arg") || null; + const options = JSON.parse(element.getAttribute("data-i18n-options")) || null; + + element.innerHTML = i18n(key, arg, options); + } + + + localeIsSupported(locale) { + return locale !== null && this.supportedLocales.indexOf(locale) > -1; + } + + getSuitableLocale(locales) { + return locales.find(this.localeIsSupported) || this.defaultLocale; + } + + browserLocales(codeOnly = false) { + return navigator.languages.map((locale) => + codeOnly ? locale.split("-")[0] : locale, + ); + } + + flattenKeys({ keys, prefix }) { + let result = {}; + for (let key in keys) { + const type = typeof keys[key]; + if (type === 'string') { + result[`${prefix}${key}`] = keys[key]; + } + else if (type === 'object') { + result = { ...result, ...this.flattenKeys({ keys: keys[key], prefix: `${prefix}${key}.` }) } + } + } + return result; + } +} + diff --git a/src_data/static/app.js b/src_data/scripts/utils.js similarity index 97% rename from src_data/static/app.js rename to src_data/scripts/utils.js index 76f2000..c294880 100644 --- a/src_data/static/app.js +++ b/src_data/scripts/utils.js @@ -22,14 +22,14 @@ function setupForm(formSelector, onResultCallback = null, noCastItems = []) { event.preventDefault(); if (button) { - button.textContent = 'Please wait...'; + button.textContent = i18n("button.wait"); button.setAttribute('disabled', true); button.setAttribute('aria-busy', true); } const onSuccess = (result) => { if (button) { - button.textContent = 'Saved'; + button.textContent = i18n('button.saved'); button.classList.add('success'); button.removeAttribute('aria-busy'); @@ -43,7 +43,7 @@ function setupForm(formSelector, onResultCallback = null, noCastItems = []) { const onFailed = () => { if (button) { - button.textContent = 'Error'; + button.textContent = i18n('button.error'); button.classList.add('failed'); button.removeAttribute('aria-busy'); @@ -110,7 +110,7 @@ function setupNetworkScanForm(formSelector, tableSelector) { } if (button) { - button.innerHTML = 'Please wait...'; + button.innerHTML = i18n('button.wait'); button.setAttribute('disabled', true); button.setAttribute('aria-busy', true); } @@ -270,14 +270,14 @@ function setupRestoreBackupForm(formSelector) { event.preventDefault(); if (button) { - button.textContent = 'Please wait...'; + button.textContent = i18n('button.wait'); button.setAttribute('disabled', true); button.setAttribute('aria-busy', true); } const onSuccess = (response) => { if (button) { - button.textContent = 'Restored'; + button.textContent = i18n('button.restored'); button.classList.add('success'); button.removeAttribute('aria-busy'); @@ -291,7 +291,7 @@ function setupRestoreBackupForm(formSelector) { const onFailed = (response) => { if (button) { - button.textContent = 'Error'; + button.textContent = i18n('button.error'); button.classList.add('failed'); button.removeAttribute('aria-busy'); @@ -437,7 +437,7 @@ function setupUpgradeForm(formSelector) { const onFailed = (response) => { if (button) { - button.textContent = 'Error'; + button.textContent = i18n('button.error'); button.classList.add('failed'); button.removeAttribute('aria-busy'); @@ -456,7 +456,7 @@ function setupUpgradeForm(formSelector) { hide('.upgrade-filesystem-result'); if (button) { - button.textContent = 'Uploading...'; + button.textContent = i18n('button.uploading'); button.setAttribute('disabled', true); button.setAttribute('aria-busy', true); } diff --git a/src_data/settings.html b/src_data/settings.html deleted file mode 100644 index f030234..0000000 --- a/src_data/settings.html +++ /dev/null @@ -1,833 +0,0 @@ - - - - - - - Settings - OpenTherm Gateway - - - - - -
- -
- -
-
-
-

Settings

-

-
- -
- Portal settings -
-
- -
-
- -
- -
- System settings -
-
- -
-
- - -
- -
- Heating settings -
-
- -
-
- -
- -
- DHW settings -
-
- -
-
- -
- -
- Emergency mode settings -
-
- -
-
- -
- -
- Equitherm settings -
-
- -
-
- -
- -
- PID settings -
-
- -
-
- -
- -
- OpenTherm settings -
-
- -
-
- -
- -
- MQTT settings -
-
- -
-
- -
- -
- Outdoor sensor settings -
-
- -
-
- -
- -
- Indoor sensor settings -
-
- -
-
- -
- -
- External pump settings -
-
- -
-
-
-
- - - - - - - - diff --git a/src_data/static/app.css b/src_data/styles/app.css similarity index 50% rename from src_data/static/app.css rename to src_data/styles/app.css index 8e9bc06..38d290b 100644 --- a/src_data/static/app.css +++ b/src_data/styles/app.css @@ -25,9 +25,9 @@ --pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.5); } - .container { - max-width: 1000px; - } + .container { + max-width: 1000px; + } } @media (min-width: 1536px) { @@ -36,77 +36,82 @@ --pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.75); } - .container { - max-width: 1000px; - } + .container { + max-width: 1000px; + } } -header, main, footer { - padding-top: 1rem !important; - padding-bottom: 1rem !important; +header, +main, +footer { + padding-top: 1rem !important; + padding-bottom: 1rem !important; } article { - margin-bottom: 1rem; + margin-bottom: 1rem; } footer { - text-align: center; + text-align: center; } -nav li a:has(> div.logo) { - margin-bottom: 0; +/*nav li a:has(> div.logo) { + margin-bottom: 0; +}*/ +nav li :where(a,[role=link]) { + margin: 0; } -details > div { - padding: 0 var(--pico-form-element-spacing-horizontal); +details>div { + padding: 0 var(--pico-form-element-spacing-horizontal); } pre { - padding: 0.5rem; + padding: 0.5rem; } .hidden { - display: none !important; + display: none !important; } button.success { - background-color: var(--pico-form-element-valid-border-color); - border-color: var(--pico-form-element-valid-border-color); + background-color: var(--pico-form-element-valid-border-color); + border-color: var(--pico-form-element-valid-border-color); } button.failed { - background-color: var(--pico-form-element-invalid-border-color); - border-color: var(--pico-form-element-invalid-border-color); + background-color: var(--pico-form-element-invalid-border-color); + border-color: var(--pico-form-element-invalid-border-color); } tr.network:hover { - --pico-background-color: var(--pico-primary-focus); - cursor: pointer; + --pico-background-color: var(--pico-primary-focus); + cursor: pointer; } .primary { - border: 0.25rem solid var(--pico-form-element-invalid-border-color); - padding: 1rem; - margin-bottom: 1rem; + border: 0.25rem solid var(--pico-form-element-invalid-border-color); + padding: 1rem; + margin-bottom: 1rem; } .logo { - display: inline-block; - padding: calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal); - vertical-align: baseline; - line-height: var(--pico-line-height); - background-color: var(--pico-code-kbd-background-color); - border-radius: var(--pico-border-radius); + display: inline-block; + padding: calc(var(--pico-nav-link-spacing-vertical) - var(--pico-border-width) * 2) var(--pico-nav-link-spacing-horizontal); + vertical-align: baseline; + line-height: var(--pico-line-height); + background-color: var(--pico-code-kbd-background-color); + border-radius: var(--pico-border-radius); color: var(--pico-code-kbd-color); font-weight: bolder; - font-size: 1.3rem; + font-size: 1.3rem; font-family: var(--pico-font-family-monospace); } .thermostat { - display: grid; + display: grid; grid-template-columns: 0.5fr 2fr 0.5fr; grid-template-rows: 0.25fr 1fr 0.25fr; gap: 0px 0px; @@ -118,8 +123,8 @@ tr.network:hover { "thermostat-minus thermostat-temp thermostat-plus" "thermostat-control thermostat-control thermostat-control"; - border: .25rem solid var(--pico-blockquote-border-color); - padding: 0.5rem; + border: .25rem solid var(--pico-blockquote-border-color); + padding: 0.5rem; } .thermostat-header { @@ -127,14 +132,14 @@ tr.network:hover { align-self: end; grid-area: thermostat-header; - font-size: 1rem; + font-size: 1rem; font-weight: bold; border-bottom: .25rem solid var(--pico-primary-hover-border); - margin: 0 0 1rem 0; + margin: 0 0 1rem 0; } .thermostat-temp { - display: grid; + display: grid; grid-template-columns: 1fr; grid-template-rows: 1fr 0.5fr; gap: 0px 0px; @@ -150,7 +155,7 @@ tr.network:hover { align-self: center; grid-area: thermostat-temp-target; - font-weight: bold; + font-weight: bold; font-size: 1.75rem; } @@ -159,7 +164,7 @@ tr.network:hover { align-self: start; grid-area: thermostat-temp-current; - color: var(--pico-secondary); + color: var(--pico-secondary); font-size: 0.85rem; } @@ -180,15 +185,19 @@ tr.network:hover { align-self: start; grid-area: thermostat-control; - margin: 1.25rem 0; + margin: 1.25rem 0; } -[class*=" icons-"],[class=icons],[class^=icons-] {font-size: 1.35rem; } -*:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]):has(+ * > [class*=" icons-"], + * > [class=icons], + * > [class^=icons-]) { margin: 0 0.5rem 0 0; } -[data-tooltip]:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]) { border: 0!important; } +[class*=" icons-"], +[class=icons], +[class^=icons-] { + font-size: 1.35rem; +} +*:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]):has(+ * > [class*=" icons-"], + * > [class=icons], + * > [class^=icons-]) { + margin: 0 0.5rem 0 0; +} -/*! - * Icons icon font. Generated by Iconly: https://iconly.io/ - */ - @font-face{font-display:auto;font-family:"Icons";font-style:normal;font-weight:400;src:url(./fonts/iconly.eot?1717885802370);src:url(./fonts/iconly.eot?#iefix) format("embedded-opentype"),url(./fonts/iconly.woff2?1717885802370) format("woff2"),url(./fonts/iconly.woff?1717885802370) format("woff"),url(./fonts/iconly.ttf?1717885802370) format("truetype")}[class*=" icons-"],[class=icons],[class^=icons-]{display:inline-block;font-family:"Icons"!important;font-weight:400;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.icons-plus:before{content:"\e000"}.icons-minus:before{content:"\e001"}.icons-unlocked:before{content:"\e002"}.icons-locked:before{content:"\e003"}.icons-wifi-strength-1:before{content:"\e004"}.icons-wifi-strength-0:before{content:"\e005"}.icons-wifi-strength-2:before{content:"\e006"}.icons-wifi-strength-3:before{content:"\e008"}.icons-down:before{content:"\e009"}.icons-wifi-strength-4:before{content:"\e00a"}.icons-up:before{content:"\e00c"} +[data-tooltip]:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]) { + border: 0 !important; +} \ No newline at end of file diff --git a/src_data/styles/iconly.css b/src_data/styles/iconly.css new file mode 100644 index 0000000..08f9389 --- /dev/null +++ b/src_data/styles/iconly.css @@ -0,0 +1,68 @@ +/*! + * Icons icon font. Generated by Iconly: https://iconly.io/ + */ + +@font-face { + font-display: auto; + font-family: "Icons"; + font-style: normal; + font-weight: 400; + src: url("/static/fonts/iconly.eot?1718563596894"); + src: url("/static/fonts/iconly.eot?#iefix") format("embedded-opentype"), url("/static/fonts/iconly.woff2?1718563596894") format("woff2"), url("/static/fonts/iconly.woff?1718563596894") format("woff"), url("/static/fonts/iconly.ttf?1718563596894") format("truetype"), url("/static/fonts/iconly.svg?1718563596894#Icons") format("svg"); +} + +[class="icons"], [class^="icons-"], [class*=" icons-"] { + display: inline-block; + font-family: "Icons" !important; + font-weight: 400; + font-style: normal; + font-variant: normal; + text-rendering: auto; + line-height: 1; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; +} + +.icons-plus:before { + content: "\e000"; +} + +.icons-minus:before { + content: "\e001"; +} + +.icons-unlocked:before { + content: "\e002"; +} + +.icons-locked:before { + content: "\e003"; +} + +.icons-wifi-strength-1:before { + content: "\e004"; +} + +.icons-wifi-strength-0:before { + content: "\e005"; +} + +.icons-wifi-strength-2:before { + content: "\e006"; +} + +.icons-wifi-strength-3:before { + content: "\e008"; +} + +.icons-down:before { + content: "\e009"; +} + +.icons-wifi-strength-4:before { + content: "\e00a"; +} + +.icons-up:before { + content: "\e00c"; +} diff --git a/src_data/static/pico.min.css b/src_data/styles/pico.min.css similarity index 100% rename from src_data/static/pico.min.css rename to src_data/styles/pico.min.css diff --git a/src_data/upgrade.html b/src_data/upgrade.html deleted file mode 100644 index 830ab72..0000000 --- a/src_data/upgrade.html +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - Upgrade - OpenTherm Gateway - - - - - -
- -
- -
-
-
-
-

Backup & restore

-

- In this section you can save and restore a backup of ALL settings. -

-
- -
- - -
- - -
-
-
-
- -
-
-
-

Upgrade

-

- In this section you can upgrade the firmware and filesystem of your device.
- Latest releases can be downloaded from the Releases page of the project repository. -

-
- -
-
- - - -
- -
    -
  • After a successful upgrade the filesystem, ALL settings will be reset to default values! Save backup before upgrading.
  • -
  • After a successful upgrade, the device will automatically reboot after 10 seconds.
  • -
- - -
-
-
-
- - - - - - - - diff --git a/tools/build.py b/tools/build.py index 932f6c1..8d1b7dc 100644 --- a/tools/build.py +++ b/tools/build.py @@ -14,6 +14,9 @@ def post_build(source, target, env): def before_buildfs(source, target, env): + env.Execute("npm install --silent") + env.Execute("npx gulp build_all --no-deprecation") +""" src = os.path.join(env["PROJECT_DIR"], "src_data") dst = os.path.join(env["PROJECT_DIR"], "data") @@ -32,6 +35,7 @@ def before_buildfs(source, target, env): shutil.copyfileobj(f_in, f_out) print("Compressed '%s' to '%s'" % (src_path, dst_path)) +""" def after_buildfs(source, target, env): copy_to_build_dir({