diff --git a/rust/src/main.rs b/rust/src/main.rs index f29c654..d7c5ead 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -293,7 +293,7 @@ fn safe_main() -> anyhow::Result<()> { board.general_fault(true); } } - if (config.network.mqtt_url.is_some()){ + if (config.network.mqtt_url.is_some()) { match board.mqtt(&config) { Ok(_) => { println!("Mqtt connection ready"); @@ -319,8 +319,11 @@ fn safe_main() -> anyhow::Result<()> { match board.wifi_ap(Some(config.network.ap_ssid.clone())) { Ok(_) => { println!("Started ap, continuing") - }, - Err(err) => println!("Could not start config override ap mode due to {}", err.to_string()), + } + Err(err) => println!( + "Could not start config override ap mode due to {}", + err.to_string() + ), } } diff --git a/rust/src/plant_hal.rs b/rust/src/plant_hal.rs index 221ac7a..658938c 100644 --- a/rust/src/plant_hal.rs +++ b/rust/src/plant_hal.rs @@ -515,7 +515,8 @@ impl PlantCtrlBoard<'_> { } pub fn wifi_ap(&mut self, ap_ssid: Option>) -> Result<()> { - let ssid = ap_ssid.unwrap_or(heapless::String::from_str("PlantCtrl Emergency Mode").unwrap()); + let ssid = + ap_ssid.unwrap_or(heapless::String::from_str("PlantCtrl Emergency Mode").unwrap()); let apconfig = AccessPointConfiguration { ssid, auth_method: AuthMethod::None, @@ -644,7 +645,6 @@ impl PlantCtrlBoard<'_> { } pub fn get_rtc_time(&mut self) -> Result> { - match self.rtc.datetime() { OkStd(rtc_time) => { return Ok(rtc_time.and_utc()); @@ -757,7 +757,6 @@ impl PlantCtrlBoard<'_> { bail!("Mqtt url was empty") } - let last_will_topic = format!("{}/state", base_topic); let mqtt_client_config = MqttClientConfiguration { lwt: Some(LwtConfiguration { @@ -835,20 +834,19 @@ impl PlantCtrlBoard<'_> { } esp_idf_svc::mqtt::client::EventPayload::BeforeConnect => { println!("Mqtt before connect") - }, + } esp_idf_svc::mqtt::client::EventPayload::Subscribed(_) => { println!("Mqtt subscribed") - }, + } esp_idf_svc::mqtt::client::EventPayload::Unsubscribed(_) => { println!("Mqtt unsubscribed") - }, + } esp_idf_svc::mqtt::client::EventPayload::Published(_) => { println!("Mqtt published") - }, + } esp_idf_svc::mqtt::client::EventPayload::Deleted(_) => { println!("Mqtt deleted") - }, - + } } })?; diff --git a/rust/src/webserver/webserver.rs b/rust/src/webserver/webserver.rs index 2cd9cd6..8e5f74e 100644 --- a/rust/src/webserver/webserver.rs +++ b/rust/src/webserver/webserver.rs @@ -39,6 +39,10 @@ struct VersionInfo<'a> { struct LoadData<'a> { rtc: &'a str, native: &'a str, +} + +#[derive(Serialize, Debug)] +struct Moistures { moisture_a: Vec, moisture_b: Vec, } @@ -67,18 +71,10 @@ fn write_time( anyhow::Ok(None) } -fn get_data( +fn get_live_moisture( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { let mut board = BOARD_ACCESS.lock().unwrap(); - let native = board - .time() - .and_then(|t| Ok(t.to_rfc3339())) - .unwrap_or("error".to_string()); - let rtc = board - .get_rtc_time() - .and_then(|t| Ok(t.to_rfc3339())) - .unwrap_or("error".to_string()); let mut a: Vec = Vec::new(); let mut b: Vec = Vec::new(); @@ -107,11 +103,31 @@ fn get_data( } } + let data = Moistures { + moisture_a: a, + moisture_b: b, + }; + let json = serde_json::to_string(&data)?; + + anyhow::Ok(Some(json)) +} + +fn get_data( + _request: &mut Request<&mut EspHttpConnection>, +) -> Result, anyhow::Error> { + let mut board = BOARD_ACCESS.lock().unwrap(); + let native = board + .time() + .and_then(|t| Ok(t.to_rfc3339())) + .unwrap_or("error".to_string()); + let rtc = board + .get_rtc_time() + .and_then(|t| Ok(t.to_rfc3339())) + .unwrap_or("error".to_string()); + let data = LoadData { rtc: rtc.as_str(), native: native.as_str(), - moisture_a: a, - moisture_b: b, }; let json = serde_json::to_string(&data)?; @@ -145,8 +161,11 @@ fn set_config( fn get_version( _request: &mut Request<&mut EspHttpConnection>, ) -> Result, anyhow::Error> { - let version_info = VersionInfo { - git_hash: env!("VERGEN_GIT_DESCRIBE"), + let branch = env!("VERGEN_GIT_BRANCH").to_owned(); + let hash = &env!("VERGEN_GIT_SHA")[0..8]; + + let version_info: VersionInfo<'_> = VersionInfo { + git_hash: &(branch + "@" + hash), build_time: env!("VERGEN_BUILD_TIMESTAMP"), }; anyhow::Ok(Some(serde_json::to_string(&version_info)?)) @@ -282,10 +301,15 @@ pub fn httpd(_reboot_now: Arc) -> Box> { .unwrap(); server - .fn_handler("/data", Method::Get, |request| { + .fn_handler("/time", Method::Get, |request| { handle_error_to500(request, get_data) }) .unwrap(); + server + .fn_handler("/moisture", Method::Get, |request| { + handle_error_to500(request, get_live_moisture) + }) + .unwrap(); server .fn_handler("/time", Method::Post, |request| { handle_error_to500(request, write_time) @@ -311,6 +335,11 @@ pub fn httpd(_reboot_now: Arc) -> Box> { handle_error_to500(request, ota) }) .unwrap(); + server + .fn_handler("/ota", Method::Options, |request| { + cors_response(request, 200, "") + }) + .unwrap(); server .fn_handler("/get_config", Method::Get, move |request| { handle_error_to500(request, get_config) @@ -476,6 +505,14 @@ pub fn httpd(_reboot_now: Arc) -> Box> { }) .unwrap(); server + .fn_handler("/bootstrap-grid.css", Method::Get, |request| { + request + .into_ok_response()? + .write(include_bytes!("bootstrap-grid.css"))?; + anyhow::Ok(()) + }) + .unwrap(); + server } fn cors_response( @@ -483,7 +520,10 @@ fn cors_response( status: u16, body: &str, ) -> Result<(), anyhow::Error> { - let headers = [("Access-Control-Allow-Origin", "*")]; + let headers = [ + ("Access-Control-Allow-Origin", "*"), + ("Access-Control-Allow-Headers", "*") + ]; let mut response = request.into_response(status, None, &headers)?; response.write(body.as_bytes())?; response.flush()?; diff --git a/rust/src_webpack/package-lock.json b/rust/src_webpack/package-lock.json index 3016441..b1b970a 100644 --- a/rust/src_webpack/package-lock.json +++ b/rust/src_webpack/package-lock.json @@ -9,6 +9,9 @@ }, "devDependencies": { "copy-webpack-plugin": "^12.0.2", + "css-loader": "^7.1.2", + "html-webpack-harddisk-plugin": "^2.0.0", + "html-webpack-plugin": "^5.6.3", "raw-loader": "^4.0.2", "ts-loader": "^9.5.1", "typescript": "^5.3.3", @@ -304,6 +307,13 @@ "@types/send": "*" } }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -747,6 +757,16 @@ "ansi-html": "bin/ansi-html" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -863,6 +883,13 @@ "multicast-dns": "^7.2.5" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -959,6 +986,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001687", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz", @@ -1029,6 +1067,29 @@ "node": ">=6.0" } }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -1273,6 +1334,85 @@ "node": ">= 8" } }, + "node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -1385,6 +1525,86 @@ "node": ">=6" } }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1431,6 +1651,16 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/envinfo": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", @@ -1910,6 +2140,16 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -1973,6 +2213,105 @@ ], "license": "MIT" }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-webpack-harddisk-plugin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-webpack-harddisk-plugin/-/html-webpack-harddisk-plugin-2.0.0.tgz", + "integrity": "sha512-fWKH72FyaQ5K/j+kYy6LnQsQucSDnsEkghmB6g29TtpJ4sxHYFduEeUV1hfDqyDpCRW+bP7yacjQ+1ikeIDqeg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13" + }, + "peerDependencies": { + "html-webpack-plugin": "^5.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", + "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, "node_modules/http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -2065,6 +2404,19 @@ "node": ">=0.10.0" } }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2395,6 +2747,23 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2534,6 +2903,25 @@ "multicast-dns": "cli.js" } }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", @@ -2549,6 +2937,17 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -2575,6 +2974,19 @@ "node": ">=0.10.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -2691,6 +3103,17 @@ "node": ">=6" } }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2701,6 +3124,17 @@ "node": ">= 0.8" } }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2775,6 +3209,130 @@ "node": ">=8" } }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -2959,6 +3517,30 @@ "node": ">= 10.13.0" } }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -3409,9 +3991,10 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -3557,6 +4140,19 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -3829,6 +4425,13 @@ "dev": true, "license": "MIT" }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/rust/src_webpack/package.json b/rust/src_webpack/package.json index 5f9810c..e9f3ebe 100644 --- a/rust/src_webpack/package.json +++ b/rust/src_webpack/package.json @@ -1,6 +1,7 @@ { "devDependencies": { - "copy-webpack-plugin": "^12.0.2", + "html-webpack-harddisk-plugin": "^2.0.0", + "html-webpack-plugin": "^5.6.3", "raw-loader": "^4.0.2", "ts-loader": "^9.5.1", "typescript": "^5.3.3", diff --git a/rust/src_webpack/public/bootstrap-grid.css b/rust/src_webpack/public/bootstrap-grid.css new file mode 100644 index 0000000..5a71a41 --- /dev/null +++ b/rust/src_webpack/public/bootstrap-grid.css @@ -0,0 +1,2050 @@ +/*! + * Bootstrap Grid v4.0.0 (https://getbootstrap.com) + * Copyright 2011-2018 The Bootstrap Authors + * Copyright 2011-2018 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +@-ms-viewport { + width: device-width; +} + +html { + box-sizing: border-box; + -ms-overflow-style: scrollbar; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +.container { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +@media (min-width: 576px) { + .container { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 720px; + } +} + +@media (min-width: 992px) { + .container { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1140px; + } +} + +.container-fluid { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +.row { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; +} + +.no-gutters { + margin-right: 0; + margin-left: 0; +} + +.no-gutters > .col, +.no-gutters > [class*="col-"] { + padding-right: 0; + padding-left: 0; +} + +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, +.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, +.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, +.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, +.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, +.col-xl-auto { + position: relative; + width: 100%; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} + +.col { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; +} + +.col-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; +} + +.col-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; +} + +.col-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; +} + +.col-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} + +.col-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} + +.col-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; +} + +.col-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.col-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; +} + +.col-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; +} + +.col-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; +} + +.col-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; +} + +.col-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; +} + +.col-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} + +.order-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; +} + +.order-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; +} + +.order-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; +} + +.order-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; +} + +.order-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; +} + +.order-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; +} + +.order-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; +} + +.order-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; +} + +.order-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; +} + +.order-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; +} + +.order-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; +} + +.order-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; +} + +.order-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; +} + +.order-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; +} + +.order-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; +} + +.offset-1 { + margin-left: 8.333333%; +} + +.offset-2 { + margin-left: 16.666667%; +} + +.offset-3 { + margin-left: 25%; +} + +.offset-4 { + margin-left: 33.333333%; +} + +.offset-5 { + margin-left: 41.666667%; +} + +.offset-6 { + margin-left: 50%; +} + +.offset-7 { + margin-left: 58.333333%; +} + +.offset-8 { + margin-left: 66.666667%; +} + +.offset-9 { + margin-left: 75%; +} + +.offset-10 { + margin-left: 83.333333%; +} + +.offset-11 { + margin-left: 91.666667%; +} + +@media (min-width: 576px) { + .col-sm { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-sm-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-sm-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-sm-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-sm-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-sm-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-sm-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-sm-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-sm-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-sm-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-sm-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-sm-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-sm-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-sm-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-sm-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .order-sm-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; + } + .order-sm-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; + } + .order-sm-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-sm-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-sm-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-sm-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-sm-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-sm-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-sm-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-sm-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-sm-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-sm-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-sm-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-sm-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + .offset-sm-0 { + margin-left: 0; + } + .offset-sm-1 { + margin-left: 8.333333%; + } + .offset-sm-2 { + margin-left: 16.666667%; + } + .offset-sm-3 { + margin-left: 25%; + } + .offset-sm-4 { + margin-left: 33.333333%; + } + .offset-sm-5 { + margin-left: 41.666667%; + } + .offset-sm-6 { + margin-left: 50%; + } + .offset-sm-7 { + margin-left: 58.333333%; + } + .offset-sm-8 { + margin-left: 66.666667%; + } + .offset-sm-9 { + margin-left: 75%; + } + .offset-sm-10 { + margin-left: 83.333333%; + } + .offset-sm-11 { + margin-left: 91.666667%; + } +} + +@media (min-width: 768px) { + .col-md { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-md-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-md-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-md-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-md-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-md-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-md-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-md-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-md-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-md-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-md-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-md-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-md-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-md-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-md-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .order-md-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; + } + .order-md-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; + } + .order-md-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-md-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-md-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-md-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-md-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-md-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-md-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-md-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-md-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-md-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-md-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-md-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + .offset-md-0 { + margin-left: 0; + } + .offset-md-1 { + margin-left: 8.333333%; + } + .offset-md-2 { + margin-left: 16.666667%; + } + .offset-md-3 { + margin-left: 25%; + } + .offset-md-4 { + margin-left: 33.333333%; + } + .offset-md-5 { + margin-left: 41.666667%; + } + .offset-md-6 { + margin-left: 50%; + } + .offset-md-7 { + margin-left: 58.333333%; + } + .offset-md-8 { + margin-left: 66.666667%; + } + .offset-md-9 { + margin-left: 75%; + } + .offset-md-10 { + margin-left: 83.333333%; + } + .offset-md-11 { + margin-left: 91.666667%; + } +} + +@media (min-width: 992px) { + .col-lg { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-lg-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-lg-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-lg-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-lg-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-lg-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-lg-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-lg-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-lg-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-lg-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-lg-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-lg-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-lg-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-lg-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-lg-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .order-lg-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; + } + .order-lg-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; + } + .order-lg-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-lg-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-lg-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-lg-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-lg-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-lg-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-lg-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-lg-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-lg-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-lg-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-lg-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-lg-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + .offset-lg-0 { + margin-left: 0; + } + .offset-lg-1 { + margin-left: 8.333333%; + } + .offset-lg-2 { + margin-left: 16.666667%; + } + .offset-lg-3 { + margin-left: 25%; + } + .offset-lg-4 { + margin-left: 33.333333%; + } + .offset-lg-5 { + margin-left: 41.666667%; + } + .offset-lg-6 { + margin-left: 50%; + } + .offset-lg-7 { + margin-left: 58.333333%; + } + .offset-lg-8 { + margin-left: 66.666667%; + } + .offset-lg-9 { + margin-left: 75%; + } + .offset-lg-10 { + margin-left: 83.333333%; + } + .offset-lg-11 { + margin-left: 91.666667%; + } +} + +@media (min-width: 1200px) { + .col-xl { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-xl-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-xl-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-xl-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-xl-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-xl-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-xl-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-xl-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-xl-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-xl-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-xl-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-xl-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-xl-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-xl-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-xl-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .order-xl-last { + -webkit-box-ordinal-group: 14; + -ms-flex-order: 13; + order: 13; + } + .order-xl-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0; + } + .order-xl-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-xl-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-xl-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-xl-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-xl-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-xl-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-xl-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-xl-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-xl-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-xl-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-xl-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-xl-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + .offset-xl-0 { + margin-left: 0; + } + .offset-xl-1 { + margin-left: 8.333333%; + } + .offset-xl-2 { + margin-left: 16.666667%; + } + .offset-xl-3 { + margin-left: 25%; + } + .offset-xl-4 { + margin-left: 33.333333%; + } + .offset-xl-5 { + margin-left: 41.666667%; + } + .offset-xl-6 { + margin-left: 50%; + } + .offset-xl-7 { + margin-left: 58.333333%; + } + .offset-xl-8 { + margin-left: 66.666667%; + } + .offset-xl-9 { + margin-left: 75%; + } + .offset-xl-10 { + margin-left: 83.333333%; + } + .offset-xl-11 { + margin-left: 91.666667%; + } +} + +.d-none { + display: none !important; +} + +.d-inline { + display: inline !important; +} + +.d-inline-block { + display: inline-block !important; +} + +.d-block { + display: block !important; +} + +.d-table { + display: table !important; +} + +.d-table-row { + display: table-row !important; +} + +.d-table-cell { + display: table-cell !important; +} + +.d-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; +} + +.d-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; +} + +@media (min-width: 576px) { + .d-sm-none { + display: none !important; + } + .d-sm-inline { + display: inline !important; + } + .d-sm-inline-block { + display: inline-block !important; + } + .d-sm-block { + display: block !important; + } + .d-sm-table { + display: table !important; + } + .d-sm-table-row { + display: table-row !important; + } + .d-sm-table-cell { + display: table-cell !important; + } + .d-sm-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-sm-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 768px) { + .d-md-none { + display: none !important; + } + .d-md-inline { + display: inline !important; + } + .d-md-inline-block { + display: inline-block !important; + } + .d-md-block { + display: block !important; + } + .d-md-table { + display: table !important; + } + .d-md-table-row { + display: table-row !important; + } + .d-md-table-cell { + display: table-cell !important; + } + .d-md-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-md-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 992px) { + .d-lg-none { + display: none !important; + } + .d-lg-inline { + display: inline !important; + } + .d-lg-inline-block { + display: inline-block !important; + } + .d-lg-block { + display: block !important; + } + .d-lg-table { + display: table !important; + } + .d-lg-table-row { + display: table-row !important; + } + .d-lg-table-cell { + display: table-cell !important; + } + .d-lg-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-lg-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 1200px) { + .d-xl-none { + display: none !important; + } + .d-xl-inline { + display: inline !important; + } + .d-xl-inline-block { + display: inline-block !important; + } + .d-xl-block { + display: block !important; + } + .d-xl-table { + display: table !important; + } + .d-xl-table-row { + display: table-row !important; + } + .d-xl-table-cell { + display: table-cell !important; + } + .d-xl-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-xl-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media print { + .d-print-none { + display: none !important; + } + .d-print-inline { + display: inline !important; + } + .d-print-inline-block { + display: inline-block !important; + } + .d-print-block { + display: block !important; + } + .d-print-table { + display: table !important; + } + .d-print-table-row { + display: table-row !important; + } + .d-print-table-cell { + display: table-cell !important; + } + .d-print-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-print-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +.flex-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; +} + +.flex-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; +} + +.flex-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; +} + +.flex-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; +} + +.flex-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; +} + +.flex-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; +} + +.flex-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; +} + +.justify-content-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; +} + +.justify-content-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; +} + +.justify-content-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; +} + +.justify-content-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; +} + +.justify-content-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; +} + +.align-items-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; +} + +.align-items-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; +} + +.align-items-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; +} + +.align-items-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; +} + +.align-items-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; +} + +.align-content-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; +} + +.align-content-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; +} + +.align-content-center { + -ms-flex-line-pack: center !important; + align-content: center !important; +} + +.align-content-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; +} + +.align-content-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; +} + +.align-content-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; +} + +.align-self-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; +} + +.align-self-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; +} + +.align-self-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; +} + +.align-self-center { + -ms-flex-item-align: center !important; + align-self: center !important; +} + +.align-self-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; +} + +.align-self-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; +} + +@media (min-width: 576px) { + .flex-sm-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-sm-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-sm-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-sm-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-sm-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-sm-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-sm-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-sm-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-sm-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-sm-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-sm-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-sm-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-sm-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-sm-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-sm-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-sm-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-sm-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-sm-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-sm-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-sm-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-sm-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-sm-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-sm-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-sm-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-sm-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-sm-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-sm-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-sm-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-sm-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 768px) { + .flex-md-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-md-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-md-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-md-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-md-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-md-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-md-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-md-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-md-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-md-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-md-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-md-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-md-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-md-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-md-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-md-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-md-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-md-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-md-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-md-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-md-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-md-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-md-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-md-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-md-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-md-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-md-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-md-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-md-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 992px) { + .flex-lg-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-lg-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-lg-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-lg-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-lg-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-lg-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-lg-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-lg-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-lg-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-lg-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-lg-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-lg-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-lg-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-lg-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-lg-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-lg-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-lg-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-lg-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-lg-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-lg-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-lg-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-lg-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-lg-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-lg-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-lg-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-lg-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-lg-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-lg-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-lg-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 1200px) { + .flex-xl-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-xl-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-xl-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-xl-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-xl-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-xl-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-xl-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-xl-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-xl-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-xl-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-xl-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-xl-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-xl-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-xl-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-xl-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-xl-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-xl-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-xl-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-xl-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-xl-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-xl-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-xl-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-xl-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-xl-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-xl-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-xl-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-xl-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-xl-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-xl-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} +/*# sourceMappingURL=bootstrap-grid.css.map */ \ No newline at end of file diff --git a/rust/src_webpack/src/api.ts b/rust/src_webpack/src/api.ts index 4984f1a..7c3e4f0 100644 --- a/rust/src_webpack/src/api.ts +++ b/rust/src_webpack/src/api.ts @@ -52,9 +52,12 @@ interface SetTime { time: string } -interface GetData { +interface GetTime { rtc: string, - native: string, + native: string +} + +interface Moistures { moisture_a: [number], moisture_b: [number], } diff --git a/rust/src_webpack/src/index.html b/rust/src_webpack/src/index.html deleted file mode 100644 index 90fe598..0000000 --- a/rust/src_webpack/src/index.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - -

Current Firmware

-
-
Buildtime loading
-
Build githash loading
-
-

Time

-
-
Esp time
-
Rtc time
-
Rtc time
-
Store Browser time into esp and rtc
- -
- -

firmeware OTA v3

- -
-
- -

-

-

-
- -
-
remote ip
-

WIFI

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

config

- -
-

Mqtt:

-
- MQTT Url - -
-
- Base Topic - -
- - -

Tank:

-
- - Enable Tank Sensor -
-
- - Allow Pumping if Sensor Error -
- - -
- - Tank Size mL -
-
- - Tank Warn Percent (mapped in relation to empty and full) -
-
- - Tank Empty Percent (% max move) -
-
- - Tank Full Percent (% max move) -
- -

Light:

-
- Start - - Stop - -
-
- - Light only when dark -
- -

Battery Firmeware (bq34z100 may be R2)

-
-
- -
-

-

-
-
- -

Plants:

-
-
- -
-
- - - - - \ No newline at end of file diff --git a/rust/src_webpack/src/index.ts b/rust/src_webpack/src/index.ts deleted file mode 100644 index 9fae4dd..0000000 --- a/rust/src_webpack/src/index.ts +++ /dev/null @@ -1,249 +0,0 @@ - -declare var PUBLIC_URL: string; -console.log("Url is " + PUBLIC_URL); - - -import { TimeView } from "./timeview"; -import { PlantView, PlantViews } from "./plant"; -import { NetworkConfigView } from "./network"; -import { NightLampView } from "./nightmode"; -import { TankConfigView } from "./tanks"; - - -export class SubmitView{ - json: HTMLInputElement; - submitFormBtn: HTMLButtonElement; - submit_status: HTMLElement; - - constructor(controller: Controller){ - this.json = document.getElementById('json') as HTMLInputElement - this.submitFormBtn = document.getElementById("submit") as HTMLButtonElement - this.submit_status = document.getElementById("submit_status") as HTMLElement - this.submitFormBtn.onclick = () => { - controller.uploadConfig(this.json.value, (status:string) => { - this.submit_status.innerHTML = status; - }); - } - } - - setJson(pretty: string) { - this.json.value = pretty - } -} - -export class Controller { - downloadConfig() { - fetch(PUBLIC_URL + "/get_config") - .then(response => response.json()) - .then(loaded => { - var currentConfig = loaded as PlantControllerConfig; - this.setConfig(currentConfig); - //sync json view initially - this.configChanged(); - }) - } - uploadConfig(json: string, statusCallback: (status: string) => void) { - fetch(PUBLIC_URL + "/set_config", { - method: "POST", - body: json, - }) - .then(response => response.text()) - .then(text => statusCallback(text)) - } - readonly timeView: TimeView; - readonly plantViews: PlantViews; - readonly networkView: NetworkConfigView; - readonly tankView: TankConfigView; - readonly nightLampView: NightLampView; - readonly submitView: SubmitView; - constructor() { - this.timeView = new TimeView(this) - this.plantViews = new PlantViews(this) - this.networkView = new NetworkConfigView(this, PUBLIC_URL) - this.tankView = new TankConfigView(this) - this.nightLampView = new NightLampView(this) - this.submitView = new SubmitView(this) - } - - syncRTCFromBrowser(){ - var value: SetTime = { - time: new Date().toISOString() - } - var pretty = JSON.stringify(value, undefined, 1); - fetch(PUBLIC_URL + "/time", { - method: "POST", - body: pretty - }) - } - - configChanged() { - const current = controller.getConfig(); - var pretty = JSON.stringify(current, undefined, 1); - console.log(pretty) - controller.submitView.setJson(pretty); - } - - testPlant(plantId: number) { - var body: TestPump = { - pump: plantId - } - var pretty = JSON.stringify(body, undefined, 1); - fetch(PUBLIC_URL + "/pumptest", { - method: "POST", - body: pretty - }) - .then(response => response.text()) - .then(text => console.log(text)) - } - - updateRealTimeData() { - fetch(PUBLIC_URL + "/data") - .then(response => response.json()) - .then(json => json as GetData) - .then(time => { - - controller.timeView.update(time.native, time.rtc) - controller.plantViews.update(time.moisture_a, time.moisture_b) - setTimeout(controller.updateRealTimeData, 1000); - }) - .catch(error => { - console.log(error); - setTimeout(controller.updateRealTimeData, 10000); - }); - } - - getConfig(): PlantControllerConfig{ - return { - network: controller.networkView.getConfig(), - tank: controller.tankView.getConfig(), - night_lamp: controller.nightLampView.getConfig(), - plants: controller.plantViews.getConfig() - } - } - - scanWifi() { - var ajax = new XMLHttpRequest(); - ajax.responseType = 'json'; - ajax.onreadystatechange = () => { - if (ajax.readyState === 4) { - this.networkView.setScanResult(ajax.response as SSIDList) - } - }; - ajax.onerror = (evt) => { - alert("Failed to start see console") - } - ajax.open("POST", PUBLIC_URL + "/wifiscan"); - ajax.send(); - } - - setConfig(current: PlantControllerConfig) { - this.tankView.setConfig(current.tank); - this.networkView.setConfig(current.network); - this.nightLampView.setConfig(current.night_lamp); - this.plantViews.setConfig(current.plants); - } -} -const controller = new Controller(); -setTimeout(controller.updateRealTimeData, 1000); -controller.downloadConfig(); - - - -export function uploadFile() { - var file1 = document.getElementById("file1") as HTMLInputElement; - var loaded_n_total = document.getElementById("loaded_n_total") as HTMLElement; - var progressBar = document.getElementById("progressBar") as HTMLProgressElement; - var status = document.getElementById("status") as HTMLElement; - var answer = document.getElementById("answer") as HTMLElement; - - if (file1.files == null) { - //TODO error dialog here - return - } - - var file = file1.files[0]; - var ajax = new XMLHttpRequest(); - - - ajax.upload.addEventListener("progress", event => { - loaded_n_total.innerHTML = "Uploaded " + event.loaded + " bytes of " + event.total; - var percent = (event.loaded / event.total) * 100; - progressBar.value = Math.round(percent); - status.innerHTML = Math.round(percent) + "%"; - answer.innerHTML = "in progress"; - }, false); - ajax.addEventListener("load", () => { - status.innerHTML = ajax.responseText; - answer.innerHTML = "finished"; - progressBar.value = 0; - }, false); - ajax.addEventListener("error", () => { - status.innerHTML = ajax.responseText; - answer.innerHTML = "failed"; - }, false); - ajax.addEventListener("abort", () => { - status.innerHTML = ajax.responseText; - answer.innerHTML = "aborted"; - }, false); - ajax.open("POST", PUBLIC_URL + "/ota"); - ajax.send(file); -} - -let file1Upload = document.getElementById("file1") as HTMLInputElement; -file1Upload.onchange = uploadFile; - -let firmware_buildtime = document.getElementById("firmware_buildtime") as HTMLDivElement; -let firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement; - -document.addEventListener('DOMContentLoaded', function () { - fetch(PUBLIC_URL + "/version") - .then(response => response.json()) - .then(json => json as VersionInfo) - .then(versionInfo => { - firmware_buildtime.innerText = versionInfo.build_time; - firmware_githash.innerText = versionInfo.git_hash; - }) -}, false); - - -let battery_flash_button = document.getElementById("battery_flash_button") as HTMLButtonElement; -let battery_flash_file = document.getElementById("battery_flash_file") as HTMLInputElement; -let battery_flash_message = document.getElementById("battery_flash_message") as HTMLElement; -let battery_flash_progressBar = document.getElementById("battery_flash_progressBar") as HTMLProgressElement; -let battery_flash_loaded_n_total = document.getElementById("battery_flash_loaded_n_total") as HTMLElement; -let battery_flash_status = document.getElementById("battery_flash_status") as HTMLElement; - - -var ajax = new XMLHttpRequest(); - -ajax.upload.addEventListener("progress", event => { - battery_flash_loaded_n_total.innerHTML = "Uploaded " + event.loaded + " bytes of " + event.total; - var percent = (event.loaded / event.total) * 100; - battery_flash_progressBar.value = Math.round(percent); - battery_flash_status.innerHTML = Math.round(percent) + "%"; - battery_flash_message.innerHTML = "in progress"; -}, false); -ajax.addEventListener("load", () => { - battery_flash_status.innerHTML = ajax.responseText; - battery_flash_message.innerHTML = "finished"; - battery_flash_progressBar.value = 0; -}, false); -ajax.addEventListener("error", () => { - battery_flash_status.innerHTML = ajax.responseText; - battery_flash_message.innerHTML = "failed"; -}, false); -ajax.addEventListener("abort", () => { - battery_flash_status.innerHTML = ajax.responseText; - battery_flash_message.innerHTML = "aborted"; -}, false); - - - - -battery_flash_button.onclick = async function () { - //ajax.open("POST", "/flashbattery"); - //ajax.send(battery_flash_file.files[0]); - - ajax.open("POST", PUBLIC_URL + "/flashbattery?flash=true"); - ajax.send(battery_flash_file.files![0]); -}; diff --git a/rust/src_webpack/src/main.html b/rust/src_webpack/src/main.html new file mode 100644 index 0000000..eddf968 --- /dev/null +++ b/rust/src_webpack/src/main.html @@ -0,0 +1,157 @@ +
+ + + + +
+
+
+ +
+
+ +
+ + +

Current Firmware

+
+
Buildtime loading
+
Build githash loading
+
+ +

firmeware OTA v3

+ +
+
+ +

+

+

+
+ +
+
+ +
+
+ + +

config

+ +
+

Tank:

+
+ + Enable Tank Sensor +
+
+ + Allow Pumping if Sensor Error +
+ + +
+ + Tank Size mL +
+
+ + Tank Warn Percent (mapped in relation to empty and full) +
+
+ + Tank Empty Percent (% max move) +
+
+ + Tank Full Percent (% max move) +
+ +

Light:

+ Enable Nightlight +
+ Start + + Stop + +
+
+ + Light only when dark +
+ +

Plants:

+ +
+
+ +
+
+ + +
\ No newline at end of file diff --git a/rust/src_webpack/src/main.ts b/rust/src_webpack/src/main.ts new file mode 100644 index 0000000..bd39e43 --- /dev/null +++ b/rust/src_webpack/src/main.ts @@ -0,0 +1,244 @@ + +declare var PUBLIC_URL: string; +console.log("Url is " + PUBLIC_URL); + +document.body.innerHTML = require('./main.html') as string; + + +import { TimeView } from "./timeview"; +import { PlantView, PlantViews } from "./plant"; +import { NetworkConfigView } from "./network"; +import { NightLampView } from "./nightmode"; +import { TankConfigView } from "./tanks"; +import { SubmitView } from "./submitView"; +import { ProgressView } from "./progress"; +import { OTAView } from "./ota"; + +export class Controller { + updateRTCData() { + fetch(PUBLIC_URL + "/time") + .then(response => response.json()) + .then(json => json as GetTime) + .then(time => { + controller.timeView.update(time.native, time.rtc) + }) + .catch(error => { + controller.timeView.update("n/a","n/a") + console.log(error); + }); + } + uploadNewFirmware(file: File) { + var current = 0; + var max = 100; + controller.progressview.addProgress("ota_upload", (current/max) *100 , "Uploading firmeware ("+current+"/" + max+")") + var ajax = new XMLHttpRequest(); + ajax.upload.addEventListener("progress", event => { + current = event.loaded/1000; + max = event.total/1000; + controller.progressview.addProgress("ota_upload", (current/max) *100 , "Uploading firmeware ("+current+"/" + max+")") + }, false); + ajax.addEventListener("load", () => { + //TODO wait for reboot here! + controller.progressview.removeProgress("ota_upload") + }, false); + ajax.addEventListener("error", () => { + alert("Error ota") + controller.progressview.removeProgress("ota_upload") + }, false); + ajax.addEventListener("abort", () => { + alert("abort ota") + controller.progressview.removeProgress("ota_upload") + }, false); + ajax.open("POST", PUBLIC_URL + "/ota"); + ajax.send(file); + } + version() { + controller.progressview.addIndeterminate("version", "Getting buildVersion") + fetch(PUBLIC_URL + "/version") + .then(response => response.json()) + .then(json => json as VersionInfo) + .then(versionInfo => { + controller.progressview.removeProgress("version") + controller.firmWareView.setVersion(versionInfo); + }) + } + downloadConfig() { + controller.progressview.addIndeterminate("get_config", "Downloading Config") + fetch(PUBLIC_URL + "/get_config") + .then(response => response.json()) + .then(loaded => { + var currentConfig = loaded as PlantControllerConfig; + this.setConfig(currentConfig); + //sync json view initially + this.configChanged(); + controller.progressview.removeProgress("get_config") + }) + } + uploadConfig(json: string, statusCallback: (status: string) => void) { + controller.progressview.addIndeterminate("set_config", "Uploading Config") + fetch(PUBLIC_URL + "/set_config", { + method: "POST", + body: json, + }) + .then(response => response.text()) + .then(text => statusCallback(text)) + controller.progressview.removeProgress("set_config") + } + syncRTCFromBrowser(){ + controller.progressview.addIndeterminate("write_rtc", "Writing RTC") + var value: SetTime = { + time: new Date().toISOString() + } + var pretty = JSON.stringify(value, undefined, 1); + fetch(PUBLIC_URL + "/time", { + method: "POST", + body: pretty + }).then( + _ => controller.progressview.removeProgress("write_rtc") + ) + } + + configChanged() { + const current = controller.getConfig(); + var pretty = JSON.stringify(current, undefined, 1); + console.log(pretty) + controller.submitView.setJson(pretty); + } + + + testPlant(plantId: number) { + let counter = 0 + let limit = 30 + controller.progressview.addProgress("test_pump", counter/limit*100, "Testing pump " + (plantId+1) + " for " + (limit-counter)+"s") + + let timerId: string | number | NodeJS.Timeout | undefined + function updateProgress(){ + counter++; + controller.progressview.addProgress("test_pump", counter/limit*100, "Testing pump " + (plantId+1) + " for " + (limit-counter)+"s") + timerId = setTimeout(updateProgress, 1000); + + } + timerId = setTimeout(updateProgress, 1000); + + var body: TestPump = { + pump: plantId + } + var pretty = JSON.stringify(body, undefined, 1); + + fetch(PUBLIC_URL + "/pumptest", { + method: "POST", + body: pretty + }) + .then(response => response.text()) + .then( + text => { + clearTimeout(timerId); + controller.progressview.removeProgress("test_pump"); + } + ) + } + + getConfig(): PlantControllerConfig{ + return { + network: controller.networkView.getConfig(), + tank: controller.tankView.getConfig(), + night_lamp: controller.nightLampView.getConfig(), + plants: controller.plantViews.getConfig() + } + } + + scanWifi() { + let counter = 0 + let limit = 5 + controller.progressview.addProgress("scan_ssid", counter/limit*100, "Scanning for SSIDs for " + (limit-counter)+"s") + + let timerId: string | number | NodeJS.Timeout | undefined + function updateProgress(){ + counter++; + controller.progressview.addProgress("scan_ssid", counter/limit*100, "Scanning for SSIDs for " + (limit-counter)+"s") + timerId = setTimeout(updateProgress, 1000); + + } + timerId = setTimeout(updateProgress, 1000); + + + var ajax = new XMLHttpRequest(); + ajax.responseType = 'json'; + ajax.onreadystatechange = () => { + if (ajax.readyState === 4) { + clearTimeout(timerId); + controller.progressview.removeProgress("scan_ssid"); + this.networkView.setScanResult(ajax.response as SSIDList) + } + }; + ajax.onerror = (evt) => { + clearTimeout(timerId); + controller.progressview.removeProgress("scan_ssid"); + alert("Failed to start see console") + } + ajax.open("POST", PUBLIC_URL + "/wifiscan"); + ajax.send(); + } + + setConfig(current: PlantControllerConfig) { + this.tankView.setConfig(current.tank); + this.networkView.setConfig(current.network); + this.nightLampView.setConfig(current.night_lamp); + this.plantViews.setConfig(current.plants); + } + + measure_moisture (){ + let counter = 0 + let limit = 2 + controller.progressview.addProgress("measure_moisture", counter/limit*100, "Measure Moisture " + (limit-counter)+"s") + + let timerId: string | number | NodeJS.Timeout | undefined + function updateProgress(){ + counter++; + controller.progressview.addProgress("measure_moisture", counter/limit*100, "Measure Moisture " + (limit-counter)+"s") + timerId = setTimeout(updateProgress, 1000); + + } + timerId = setTimeout(updateProgress, 1000); + + + fetch(PUBLIC_URL + "/moisture") + .then(response => response.json()) + .then(json => json as Moistures) + .then(time => { + controller.plantViews.update(time.moisture_a, time.moisture_b) + clearTimeout(timerId); + controller.progressview.removeProgress("measure_moisture"); + }) + .catch(error => { + clearTimeout(timerId); + controller.progressview.removeProgress("measure_moisture"); + console.log(error); + }); + } + + + readonly timeView: TimeView; + readonly plantViews: PlantViews; + readonly networkView: NetworkConfigView; + readonly tankView: TankConfigView; + readonly nightLampView: NightLampView; + readonly submitView: SubmitView; + readonly firmWareView : OTAView; + readonly progressview: ProgressView; + constructor() { + this.timeView = new TimeView(this) + this.plantViews = new PlantViews(this) + this.networkView = new NetworkConfigView(this, PUBLIC_URL) + this.tankView = new TankConfigView(this) + this.nightLampView = new NightLampView(this) + this.submitView = new SubmitView(this) + this.firmWareView = new OTAView(this) + this.progressview = new ProgressView(this) + } +} +const controller = new Controller(); +controller.updateRTCData(); +controller.downloadConfig(); +controller.measure_moisture(); +controller.version(); diff --git a/rust/src_webpack/src/network.html b/rust/src_webpack/src/network.html new file mode 100644 index 0000000..968b418 --- /dev/null +++ b/rust/src_webpack/src/network.html @@ -0,0 +1,32 @@ +

Basic network

+Api Redirection to: +remote ip +
+AccessPoint Mode (or fallback) +
+ + +
+
+Station Mode: +
+ + + + +
+ + +
+
+Mqtt Reporting +
+
+ MQTT Url + +
+
+ Base Topic + +
\ No newline at end of file diff --git a/rust/src_webpack/src/network.ts b/rust/src_webpack/src/network.ts index da7ef7b..e7870fe 100644 --- a/rust/src_webpack/src/network.ts +++ b/rust/src_webpack/src/network.ts @@ -1,4 +1,4 @@ -import { Controller } from "."; +import { Controller } from "./main"; export class NetworkConfigView { setScanResult(ssidList: SSIDList) { @@ -17,6 +17,8 @@ export class NetworkConfigView { private readonly ssidlist: HTMLElement; constructor(controller: Controller, publicIp: string) { + (document.getElementById("network_view") as HTMLElement).innerHTML = require('./network.html') as string; + (document.getElementById("remote_ip") as HTMLElement).innerText = publicIp; this.ap_ssid = (document.getElementById("ap_ssid") as HTMLInputElement); diff --git a/rust/src_webpack/src/nightmode.ts b/rust/src_webpack/src/nightmode.ts index 10105d0..eb7e9e0 100644 --- a/rust/src_webpack/src/nightmode.ts +++ b/rust/src_webpack/src/nightmode.ts @@ -1,4 +1,4 @@ -import { Controller } from "."; +import { Controller } from "./main"; export class NightLampView { private readonly night_lamp_only_when_dark: HTMLInputElement; diff --git a/rust/src_webpack/src/ota.ts b/rust/src_webpack/src/ota.ts new file mode 100644 index 0000000..0ab1026 --- /dev/null +++ b/rust/src_webpack/src/ota.ts @@ -0,0 +1,28 @@ +import { Controller } from "./main"; + +export class OTAView { + file1Upload: HTMLInputElement; + firmware_buildtime: HTMLDivElement; + firmware_githash: HTMLDivElement; + + constructor(controller: Controller) { + this.firmware_buildtime = document.getElementById("firmware_buildtime") as HTMLDivElement; + this.firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement; + + const file = document.getElementById("firmware_file") as HTMLInputElement; + this.file1Upload = file + this.file1Upload.onchange = () => { + var selectedFile = file.files?.[0]; + if (selectedFile == null) { + //TODO error dialog here + return + } + controller.uploadNewFirmware(selectedFile); + }; + } + + setVersion(versionInfo: VersionInfo) { + this.firmware_buildtime.innerText = versionInfo.build_time; + this.firmware_githash.innerText = versionInfo.git_hash; + } +} \ No newline at end of file diff --git a/rust/src_webpack/src/plant.ts b/rust/src_webpack/src/plant.ts index 43a0989..e832efd 100644 --- a/rust/src_webpack/src/plant.ts +++ b/rust/src_webpack/src/plant.ts @@ -2,9 +2,22 @@ const PLANT_COUNT = 8; -import { Controller } from "."; +import { Controller } from "./main"; export class PlantViews { + private readonly measure_moisture: HTMLButtonElement; + private readonly plants: PlantView[] = [] + private readonly plantsDiv: HTMLDivElement + + constructor(syncConfig:Controller) { + this.measure_moisture = document.getElementById("measure_moisture") as HTMLButtonElement + this.measure_moisture.onclick = syncConfig.measure_moisture + this.plantsDiv = document.getElementById("plants") as HTMLDivElement; + for (let plantId = 0; plantId < PLANT_COUNT; plantId++) { + this.plants[plantId] = new PlantView(plantId, this.plantsDiv, syncConfig); + } + } + getConfig(): PlantConfig[] { const rv: PlantConfig[] = []; for (let i = 0; i < PLANT_COUNT; i++) { @@ -27,15 +40,6 @@ export class PlantViews { plantView.setConfig(plantConfig) } } - private readonly plants: PlantView[] = [] - private readonly plantsDiv: HTMLDivElement - - constructor(syncConfig:Controller) { - this.plantsDiv = document.getElementById("plants") as HTMLDivElement; - for (let plantId = 0; plantId < PLANT_COUNT; plantId++) { - this.plants[plantId] = new PlantView(plantId, this.plantsDiv, syncConfig); - } - } } export class PlantView { @@ -62,6 +66,7 @@ export class PlantView { const template = require('./plant.html') as string; const plantRaw = template.replaceAll("${plantId}", String(plantId)); this.plantDiv.innerHTML = plantRaw + this.plantDiv.classList.add("col-auto" ) parent.appendChild(this.plantDiv) this.header = document.getElementById("plant_"+plantId+"_header")! diff --git a/rust/src_webpack/src/progress.ts b/rust/src_webpack/src/progress.ts new file mode 100644 index 0000000..6336dcc --- /dev/null +++ b/rust/src_webpack/src/progress.ts @@ -0,0 +1,62 @@ +import { Controller } from "./main"; + +class ProgressInfo{ + displayText:string; + percentValue:number; + indeterminate:boolean; + constructor(displayText:string, percentValue: number, indeterminate:boolean ){ + this.displayText = displayText + this.percentValue = percentValue <0 ? 0 : percentValue > 100? 100: percentValue + this.indeterminate = indeterminate + } + } + +export class ProgressView{ + progressPane: HTMLElement; + progress: HTMLElement; + progressPaneSpan: HTMLSpanElement; + progresses: Map = new Map; + progressPaneBar: HTMLDivElement; + constructor(controller:Controller){ + this.progressPane = document.getElementById("progressPane") as HTMLElement; + this.progress = document.getElementById("progress") as HTMLElement; + this.progressPaneSpan = document.getElementById("progressPaneSpan") as HTMLSpanElement; + this.progressPaneBar = document.getElementById("progressPaneBar") as HTMLDivElement; + + } + + updateView() { + if (this.progresses.size == 0){ + this.progressPane.style.display = "none" + } else{ + const first = this.progresses.entries().next().value![1] + this.progressPaneBar.setAttribute("data-label", first.displayText) + if (first.indeterminate){ + this.progressPaneSpan.className = "valueIndeterminate" + this.progressPaneSpan.style.width = "100%" + + } else { + this.progressPaneSpan.className = "value" + this.progressPaneSpan.style.width = first.percentValue+"%" + } + } + } + + addIndeterminate(id:string, displayText:string){ + this.progresses.set(id, new ProgressInfo(displayText,0,true)) + this.progressPane.style.display = "block" + this.updateView(); + + } + + addProgress(id:string, value:number, displayText:string) { + this.progresses.set(id, new ProgressInfo(displayText,value, false)) + this.progressPane.style.display = "block" + this.updateView(); + } + removeProgress(id:string){ + this.progresses.delete(id) + this.updateView(); + + } +} \ No newline at end of file diff --git a/rust/src_webpack/src/submitView.ts b/rust/src_webpack/src/submitView.ts new file mode 100644 index 0000000..9fa63d8 --- /dev/null +++ b/rust/src_webpack/src/submitView.ts @@ -0,0 +1,22 @@ +import { Controller } from "./main"; + +export class SubmitView{ + json: HTMLInputElement; + submitFormBtn: HTMLButtonElement; + submit_status: HTMLElement; + + constructor(controller: Controller){ + this.json = document.getElementById('json') as HTMLInputElement + this.submitFormBtn = document.getElementById("submit") as HTMLButtonElement + this.submit_status = document.getElementById("submit_status") as HTMLElement + this.submitFormBtn.onclick = () => { + controller.uploadConfig(this.json.value, (status:string) => { + this.submit_status.innerHTML = status; + }); + } + } + + setJson(pretty: string) { + this.json.value = pretty + } + } \ No newline at end of file diff --git a/rust/src_webpack/src/tanks.ts b/rust/src_webpack/src/tanks.ts index 7400c9b..afc70ac 100644 --- a/rust/src_webpack/src/tanks.ts +++ b/rust/src_webpack/src/tanks.ts @@ -1,4 +1,4 @@ -import { Controller } from "."; +import { Controller } from "./main"; export class TankConfigView { private readonly tank_useable_ml: HTMLInputElement; diff --git a/rust/src_webpack/src/timeview.html b/rust/src_webpack/src/timeview.html new file mode 100644 index 0000000..cb1d492 --- /dev/null +++ b/rust/src_webpack/src/timeview.html @@ -0,0 +1,7 @@ +

Time

+AutoRefresh: +
Esp time
+
Rtc time
+
Rtc time
+
+ \ No newline at end of file diff --git a/rust/src_webpack/src/timeview.ts b/rust/src_webpack/src/timeview.ts index 91ff24a..473f26c 100644 --- a/rust/src_webpack/src/timeview.ts +++ b/rust/src_webpack/src/timeview.ts @@ -1,17 +1,33 @@ -import { Controller } from "."; +import { Controller } from "./main"; export class TimeView { esp_time: HTMLDivElement rtc_time: HTMLDivElement browser_time: HTMLDivElement sync: HTMLButtonElement + auto_refresh: HTMLInputElement; + controller: Controller; + timer: NodeJS.Timeout | undefined; constructor(controller:Controller) { + (document.getElementById("timeview") as HTMLElement).innerHTML = require("./timeview.html") + + this.auto_refresh = document.getElementById("timeview_auto_refresh") as HTMLInputElement; this.esp_time = document.getElementById("timeview_esp_time") as HTMLDivElement; this.rtc_time = document.getElementById("timeview_rtc_time") as HTMLDivElement; this.browser_time = document.getElementById("timeview_browser_time") as HTMLDivElement; this.sync = document.getElementById("timeview_time_upload") as HTMLButtonElement; this.sync.onclick = controller.syncRTCFromBrowser; + this.controller = controller; + + this.auto_refresh.onchange = () => { + if(this.timer){ + clearTimeout(this.timer) + } + if(this.auto_refresh.checked){ + controller.updateRTCData() + } + } } update(native: string, rtc: string) { @@ -19,5 +35,13 @@ export class TimeView { this.rtc_time.innerText = rtc; var date = new Date(); this.browser_time.innerText = date.toISOString(); + if(this.auto_refresh.checked){ + this.timer = setTimeout(this.controller.updateRTCData, 1000); + } else { + if(this.timer){ + clearTimeout(this.timer) + } + } + } } \ No newline at end of file diff --git a/rust/src_webpack/webpack.config.js b/rust/src_webpack/webpack.config.js index 1fd1936..eac058f 100644 --- a/rust/src_webpack/webpack.config.js +++ b/rust/src_webpack/webpack.config.js @@ -1,6 +1,8 @@ const webpack = require('webpack'); -const CopyPlugin = require("copy-webpack-plugin"); const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin'); +const CopyPlugin = require("copy-webpack-plugin"); const isDevServer = process.env.WEBPACK_SERVE; console.log("Dev server is " + isDevServer); @@ -13,31 +15,35 @@ if (isDevServer){ module.exports = { mode: "development", - entry: ['./src/index.ts'], + entry: ['./src/main.ts'], devtool: 'inline-source-map', plugins: [ - new CopyPlugin({ - patterns: [ - { - from: "src/index.html", - to: "index.html" - } - ] - }), new webpack.DefinePlugin({ PUBLIC_URL: JSON.stringify(host), }), new webpack.EnvironmentPlugin({ redirect: 'true' - }) - + }), + new HtmlWebpackPlugin({ + alwaysWriteToDisk: true, + title: "PlantCtrl", + }), + new HtmlWebpackHarddiskPlugin(), + new CopyPlugin({ + patterns: [ + { + from: "public/bootstrap-grid.css", + to: "bootstrap-grid.css", + } + ], + }), ], module: { rules: [ { test: /\.html$/, type: 'asset/source', - }, + }, { test: /\.tsx?$/, use: 'ts-loader',