added file manager, upload animation, unsaved config indicator
This commit is contained in:
parent
e7556b7ec9
commit
1ce4d74a65
@ -956,6 +956,8 @@ fn wait_infinity(wait_type: WaitType, reboot_now: Arc<AtomicBool>) -> ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if reboot_now.load(std::sync::atomic::Ordering::Relaxed) {
|
if reboot_now.load(std::sync::atomic::Ordering::Relaxed) {
|
||||||
|
//ensure clean http answer
|
||||||
|
Delay::new_default().delay_ms(500);
|
||||||
BOARD_ACCESS.lock().unwrap().deep_sleep( 1);
|
BOARD_ACCESS.lock().unwrap().deep_sleep( 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1067,9 +1069,17 @@ fn get_version() -> VersionInfo {
|
|||||||
let branch = env!("VERGEN_GIT_BRANCH").to_owned();
|
let branch = env!("VERGEN_GIT_BRANCH").to_owned();
|
||||||
let hash = &env!("VERGEN_GIT_SHA")[0..8];
|
let hash = &env!("VERGEN_GIT_SHA")[0..8];
|
||||||
|
|
||||||
|
let running_partition = unsafe { esp_ota_get_running_partition() };
|
||||||
|
let address = unsafe { (*running_partition).address };
|
||||||
|
let partition = if address > 20000 {
|
||||||
|
"ota_1"
|
||||||
|
} else {
|
||||||
|
"ota_0"
|
||||||
|
};
|
||||||
return VersionInfo {
|
return VersionInfo {
|
||||||
git_hash: (branch + "@" + hash),
|
git_hash: (branch + "@" + hash),
|
||||||
build_time: env!("VERGEN_BUILD_TIMESTAMP").to_owned(),
|
build_time: env!("VERGEN_BUILD_TIMESTAMP").to_owned(),
|
||||||
|
partition: partition.to_owned()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1077,4 +1087,5 @@ fn get_version() -> VersionInfo {
|
|||||||
struct VersionInfo {
|
struct VersionInfo {
|
||||||
git_hash: String,
|
git_hash: String,
|
||||||
build_time: String,
|
build_time: String,
|
||||||
|
partition: String,
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ use esp_idf_svc::wifi::EspWifi;
|
|||||||
use measurements::Temperature;
|
use measurements::Temperature;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use plant_ctrl2::sipo::ShiftRegister40;
|
use plant_ctrl2::sipo::ShiftRegister40;
|
||||||
use esp_idf_sys::{esp_deep_sleep, esp_sleep_enable_ext1_wakeup, esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW};
|
use esp_idf_sys::{esp_deep_sleep, esp_sleep_enable_ext1_wakeup, esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_LOW, esp_spiffs_info};
|
||||||
use esp_idf_sys::esp_restart;
|
use esp_idf_sys::esp_restart;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
@ -182,6 +182,8 @@ pub struct FileInfo {
|
|||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
pub struct FileList {
|
pub struct FileList {
|
||||||
|
total: usize,
|
||||||
|
used: usize,
|
||||||
files: Vec<FileInfo>,
|
files: Vec<FileInfo>,
|
||||||
file_system_corrupt: Option<String>,
|
file_system_corrupt: Option<String>,
|
||||||
iter_error: Option<String>,
|
iter_error: Option<String>,
|
||||||
@ -232,19 +234,8 @@ impl PlantCtrlBoard<'_> {
|
|||||||
|
|
||||||
pub fn list_files(&self, filename: &str) -> FileList {
|
pub fn list_files(&self, filename: &str) -> FileList {
|
||||||
let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap();
|
let storage = CString::new(SPIFFS_PARTITION_NAME).unwrap();
|
||||||
let error = unsafe {
|
|
||||||
esp! {
|
|
||||||
esp_spiffs_check(storage.as_ptr())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut file_system_corrupt = match error {
|
let mut file_system_corrupt = Option::None;
|
||||||
OkStd(_) => None,
|
|
||||||
Err(err) => {
|
|
||||||
println!("Corrupt spiffs {err:?}");
|
|
||||||
Some(format!("{err:?}"))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut iter_error = None;
|
let mut iter_error = None;
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
@ -280,8 +271,15 @@ impl PlantCtrlBoard<'_> {
|
|||||||
file_system_corrupt = Some(format!("{err:?}"));
|
file_system_corrupt = Some(format!("{err:?}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut total:usize = 0;
|
||||||
|
let mut used:usize = 0;
|
||||||
|
unsafe {
|
||||||
|
esp_spiffs_info(storage.as_ptr(), &mut total, &mut used);
|
||||||
|
}
|
||||||
|
|
||||||
return FileList {
|
return FileList {
|
||||||
|
total,
|
||||||
|
used,
|
||||||
file_system_corrupt,
|
file_system_corrupt,
|
||||||
files: result,
|
files: result,
|
||||||
iter_error,
|
iter_error,
|
||||||
|
@ -5,7 +5,7 @@ use std::{
|
|||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
espota::OtaUpdate, get_version, map_range_moisture, plant_hal::FileInfo, BOARD_ACCESS,
|
espota::OtaUpdate, get_version, map_range_moisture, plant_hal::{FileInfo, PLANT_COUNT}, BOARD_ACCESS,
|
||||||
};
|
};
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
@ -159,6 +159,7 @@ fn get_battery_state(
|
|||||||
fn get_version_web(
|
fn get_version_web(
|
||||||
_request: &mut Request<&mut EspHttpConnection>,
|
_request: &mut Request<&mut EspHttpConnection>,
|
||||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||||
|
|
||||||
anyhow::Ok(Some(serde_json::to_string(&get_version())?))
|
anyhow::Ok(Some(serde_json::to_string(&get_version())?))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,6 +198,7 @@ fn list_files(
|
|||||||
fn ota(
|
fn ota(
|
||||||
request: &mut Request<&mut EspHttpConnection>,
|
request: &mut Request<&mut EspHttpConnection>,
|
||||||
) -> Result<Option<std::string::String>, anyhow::Error> {
|
) -> Result<Option<std::string::String>, anyhow::Error> {
|
||||||
|
let mut board = BOARD_ACCESS.lock().unwrap();
|
||||||
let mut ota = OtaUpdate::begin()?;
|
let mut ota = OtaUpdate::begin()?;
|
||||||
println!("start ota");
|
println!("start ota");
|
||||||
|
|
||||||
@ -204,12 +206,22 @@ fn ota(
|
|||||||
const BUFFER_SIZE: usize = 512;
|
const BUFFER_SIZE: usize = 512;
|
||||||
let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
|
let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
|
||||||
let mut total_read: usize = 0;
|
let mut total_read: usize = 0;
|
||||||
|
let mut lastiter = 0;
|
||||||
loop {
|
loop {
|
||||||
let read = request.read(&mut buffer)?;
|
let read = request.read(&mut buffer)?;
|
||||||
total_read += read;
|
total_read += read;
|
||||||
println!("received {read} bytes ota {total_read}");
|
println!("received {read} bytes ota {total_read}");
|
||||||
let to_write = &buffer[0..read];
|
let to_write = &buffer[0..read];
|
||||||
|
|
||||||
|
let iter = (total_read/1024)%8;
|
||||||
|
if iter != lastiter {
|
||||||
|
for i in 0..PLANT_COUNT {
|
||||||
|
board.fault(i, iter==i);
|
||||||
|
|
||||||
|
}
|
||||||
|
lastiter = iter;
|
||||||
|
}
|
||||||
|
|
||||||
ota.write(to_write)?;
|
ota.write(to_write)?;
|
||||||
println!("wrote {read} bytes ota {total_read}");
|
println!("wrote {read} bytes ota {total_read}");
|
||||||
if read == 0 {
|
if read == 0 {
|
||||||
@ -222,6 +234,8 @@ fn ota(
|
|||||||
|
|
||||||
let mut finalizer = ota.finalize()?;
|
let mut finalizer = ota.finalize()?;
|
||||||
println!("changing boot partition");
|
println!("changing boot partition");
|
||||||
|
board.set_restart_to_conf(true);
|
||||||
|
drop(board);
|
||||||
finalizer.set_as_boot_partition()?;
|
finalizer.set_as_boot_partition()?;
|
||||||
finalizer.restart();
|
finalizer.restart();
|
||||||
}
|
}
|
||||||
@ -413,10 +427,11 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
|||||||
server
|
server
|
||||||
.fn_handler("/file", Method::Post, move |mut request| {
|
.fn_handler("/file", Method::Post, move |mut request| {
|
||||||
let filename = query_param(request.uri(), "filename").unwrap();
|
let filename = query_param(request.uri(), "filename").unwrap();
|
||||||
let file_handle = BOARD_ACCESS
|
let lock = BOARD_ACCESS
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.get_file_handle(&filename, true);
|
let file_handle =
|
||||||
|
lock.get_file_handle(&filename, true);
|
||||||
match file_handle {
|
match file_handle {
|
||||||
//TODO get free filesystem size, check against during write if not to large
|
//TODO get free filesystem size, check against during write if not to large
|
||||||
|
|
||||||
@ -424,13 +439,21 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
|||||||
const BUFFER_SIZE: usize = 512;
|
const BUFFER_SIZE: usize = 512;
|
||||||
let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
|
let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
|
||||||
let mut total_read: usize = 0;
|
let mut total_read: usize = 0;
|
||||||
|
let mut lastiter = 0;
|
||||||
loop {
|
loop {
|
||||||
|
|
||||||
|
let iter = (total_read/1024)%8;
|
||||||
|
if iter != lastiter {
|
||||||
|
for i in 0..PLANT_COUNT {
|
||||||
|
lock.fault(i, iter==i);
|
||||||
|
}
|
||||||
|
lastiter = iter;
|
||||||
|
}
|
||||||
|
|
||||||
let read = request.read(&mut buffer)?;
|
let read = request.read(&mut buffer)?;
|
||||||
total_read += read;
|
total_read += read;
|
||||||
println!("sending {read} bytes of {total_read} for upload {filename}");
|
|
||||||
let to_write = &buffer[0..read];
|
let to_write = &buffer[0..read];
|
||||||
std::io::Write::write(&mut file_handle, to_write)?;
|
std::io::Write::write(&mut file_handle, to_write)?;
|
||||||
println!("wrote {read} bytes of {total_read} for upload {filename}");
|
|
||||||
if read == 0 {
|
if read == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -444,6 +467,7 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
|||||||
cors_response(request, 500, &error_text)?;
|
cors_response(request, 500, &error_text)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
drop(lock);
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -466,6 +490,11 @@ pub fn httpd(reboot_now: Arc<AtomicBool>) -> Box<EspHttpServer<'static>> {
|
|||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
server
|
||||||
|
.fn_handler("/file", Method::Options, |request| {
|
||||||
|
cors_response(request, 200, "")
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
server
|
server
|
||||||
.fn_handler("/flashbattery", Method::Post, move |request| {
|
.fn_handler("/flashbattery", Method::Post, move |request| {
|
||||||
@ -529,6 +558,7 @@ fn cors_response(
|
|||||||
let headers = [
|
let headers = [
|
||||||
("Access-Control-Allow-Origin", "*"),
|
("Access-Control-Allow-Origin", "*"),
|
||||||
("Access-Control-Allow-Headers", "*"),
|
("Access-Control-Allow-Headers", "*"),
|
||||||
|
("Access-Control-Allow-Methods", "*"),
|
||||||
];
|
];
|
||||||
let mut response = request.into_response(status, None, &headers)?;
|
let mut response = request.into_response(status, None, &headers)?;
|
||||||
response.write(body.as_bytes())?;
|
response.write(body.as_bytes())?;
|
||||||
|
10
rust/src_webpack/package-lock.json
generated
10
rust/src_webpack/package-lock.json
generated
@ -6,6 +6,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"copy-webpack-plugin": "^12.0.2",
|
"copy-webpack-plugin": "^12.0.2",
|
||||||
|
"fast-equals": "^5.2.2",
|
||||||
"source-map-loader": "^4.0.1"
|
"source-map-loader": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -1765,6 +1766,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-equals": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fast-glob": {
|
"node_modules/fast-glob": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"copy-webpack-plugin": "^12.0.2",
|
"copy-webpack-plugin": "^12.0.2",
|
||||||
|
"fast-equals": "^5.2.2",
|
||||||
"source-map-loader": "^4.0.1"
|
"source-map-loader": "^4.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,19 @@ interface NetworkConfig {
|
|||||||
base_topic: string
|
base_topic: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FileList {
|
||||||
|
total: number,
|
||||||
|
used: number,
|
||||||
|
files: FileInfo[],
|
||||||
|
file_system_corrupt: string,
|
||||||
|
iter_error: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileInfo{
|
||||||
|
filename: string,
|
||||||
|
size: number,
|
||||||
|
}
|
||||||
|
|
||||||
interface NightLampConfig {
|
interface NightLampConfig {
|
||||||
night_lamp_hour_start: number,
|
night_lamp_hour_start: number,
|
||||||
night_lamp_hour_end: number,
|
night_lamp_hour_end: number,
|
||||||
@ -64,7 +77,8 @@ interface Moistures {
|
|||||||
|
|
||||||
interface VersionInfo {
|
interface VersionInfo {
|
||||||
git_hash: string,
|
git_hash: string,
|
||||||
build_time: string
|
build_time: string,
|
||||||
|
partition: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BatteryState {
|
interface BatteryState {
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
|
<style>
|
||||||
|
.powerflexkey {
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
.powerflexvalue {
|
||||||
|
text-wrap: nowrap;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer">
|
||||||
<div class="subtitle">
|
<div class="subtitle">
|
||||||
Battery:
|
Battery:
|
||||||
|
72
rust/src_webpack/src/fileview.html
Normal file
72
rust/src_webpack/src/fileview.html
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<style>
|
||||||
|
.filecheckbox {
|
||||||
|
margin: 0px;
|
||||||
|
min-width: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.filekey {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filevalue {
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 25%;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filenumberbox {
|
||||||
|
min-width: 50px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filetitle {
|
||||||
|
border-top-style: dotted;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.fileentryouter {
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="subtitle">Files:</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="filekey">Total Size</div>
|
||||||
|
<div id="filetotalsize" class="filevalue"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="filekey">Used Size</div>
|
||||||
|
<div id="fileusedsize" class="filevalue"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="filekey">Free Size</div>
|
||||||
|
<div id="filefreesize" class="filevalue"></div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div class="flexcontainer" style="border-left-style: double; border-right-style: double; border-top-style: double;">
|
||||||
|
<div class="subtitle" >Upload:</div>
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer" style="border-left-style: double; border-right-style: double;">
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="filekey">
|
||||||
|
File:
|
||||||
|
</div>
|
||||||
|
<input id="fileuploadfile" class="filevalue" type="file">
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="filekey">
|
||||||
|
Name:
|
||||||
|
</div>
|
||||||
|
<input id="fileuploadname" class="filevalue" type="text">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer" style="border-left-style: double; border-right-style: double; border-bottom-style: double;">
|
||||||
|
<button id="fileuploadbtn" class="subtitle">Upload</button>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="flexcontainer" style="border-left-style: double; border-right-style: double; border-top-style: double;">
|
||||||
|
<div class="subtitle">List:</div>
|
||||||
|
</div>
|
||||||
|
<div id="fileList" class="flexcontainer" style="border-left-style: double; border-right-style: double; border-bottom-style: double;">
|
||||||
|
</div>
|
97
rust/src_webpack/src/fileview.ts
Normal file
97
rust/src_webpack/src/fileview.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { Controller } from "./main";
|
||||||
|
|
||||||
|
const regex = /[^a-zA-Z0-9_.]/g;
|
||||||
|
|
||||||
|
function sanitize(str:string){
|
||||||
|
return str.replaceAll(regex, '_')
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FileView {
|
||||||
|
readonly fileListView: HTMLElement;
|
||||||
|
readonly controller: Controller;
|
||||||
|
readonly filefreesize: HTMLElement;
|
||||||
|
readonly filetotalsize: HTMLElement;
|
||||||
|
readonly fileusedsize: HTMLElement;
|
||||||
|
|
||||||
|
constructor(controller: Controller) {
|
||||||
|
(document.getElementById("fileview") as HTMLElement).innerHTML = require('./fileview.html') as string;
|
||||||
|
this.fileListView = document.getElementById("fileList") as HTMLElement
|
||||||
|
this.filefreesize = document.getElementById("filefreesize") as HTMLElement
|
||||||
|
this.filetotalsize = document.getElementById("filetotalsize") as HTMLElement
|
||||||
|
this.fileusedsize = document.getElementById("fileusedsize") as HTMLElement
|
||||||
|
|
||||||
|
let fileuploadfile = document.getElementById("fileuploadfile") as HTMLInputElement
|
||||||
|
let fileuploadname = document.getElementById("fileuploadname") as HTMLInputElement
|
||||||
|
let fileuploadbtn = document.getElementById("fileuploadbtn") as HTMLInputElement
|
||||||
|
fileuploadfile.onchange = () => {
|
||||||
|
var selectedFile = fileuploadfile.files?.[0];
|
||||||
|
if (selectedFile == null) {
|
||||||
|
//TODO error dialog here
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileuploadname.value = sanitize(selectedFile.name)
|
||||||
|
};
|
||||||
|
|
||||||
|
fileuploadname.onchange = () => {
|
||||||
|
let input = fileuploadname.value
|
||||||
|
let clean = sanitize(fileuploadname.value)
|
||||||
|
if (input != clean){
|
||||||
|
fileuploadname.value = clean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileuploadbtn.onclick = () => {
|
||||||
|
var selectedFile = fileuploadfile.files?.[0];
|
||||||
|
if (selectedFile == null) {
|
||||||
|
//TODO error dialog here
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.uploadFile(selectedFile, selectedFile.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this.controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFileList(fileList: FileList, public_url: string) {
|
||||||
|
this.filetotalsize.innerText = Math.floor(fileList.total / 1024) + "kB"
|
||||||
|
this.fileusedsize.innerText = Math.ceil(fileList.used / 1024) + "kB"
|
||||||
|
this.filefreesize.innerText = Math.ceil((fileList.total - fileList.used) / 1024) + "kB"
|
||||||
|
|
||||||
|
//fast clear
|
||||||
|
this.fileListView.textContent = ""
|
||||||
|
for (let i = 0; i < fileList.files.length; i++) {
|
||||||
|
let file = fileList.files[i]
|
||||||
|
new FileEntry(this.controller, i, file, this.fileListView, public_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileEntry {
|
||||||
|
view: HTMLElement;
|
||||||
|
constructor(controller: Controller, fileid: number, fileinfo: FileInfo, parent: HTMLElement, public_url: string) {
|
||||||
|
this.view = document.createElement("div") as HTMLElement
|
||||||
|
parent.appendChild(this.view)
|
||||||
|
this.view.classList.add("fileentryouter")
|
||||||
|
|
||||||
|
const template = require('./fileviewentry.html') as string;
|
||||||
|
const fileRaw = template.replaceAll("${fileid}", String(fileid));
|
||||||
|
this.view.innerHTML = fileRaw
|
||||||
|
|
||||||
|
let name = document.getElementById("file_" + fileid + "_name") as HTMLElement;
|
||||||
|
let size = document.getElementById("file_" + fileid + "_size") as HTMLElement;
|
||||||
|
let deleteBtn = document.getElementById("file_" + fileid + "_delete") as HTMLButtonElement;
|
||||||
|
deleteBtn.onclick = () => {
|
||||||
|
controller.deleteFile(fileinfo.filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
let downloadBtn = document.getElementById("file_" + fileid + "_download") as HTMLAnchorElement;
|
||||||
|
downloadBtn.href = public_url + "/file?filename=" + fileinfo.filename
|
||||||
|
downloadBtn.download = fileinfo.filename
|
||||||
|
|
||||||
|
name.innerText = fileinfo.filename;
|
||||||
|
size.innerText = fileinfo.size.toString()
|
||||||
|
}
|
||||||
|
}
|
11
rust/src_webpack/src/fileviewentry.html
Normal file
11
rust/src_webpack/src/fileviewentry.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<div class="flexcontainer">
|
||||||
|
<div id="file_${fileid}_name" class="filetitle">Name</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="filekey">Size</div>
|
||||||
|
<div id = "file_${fileid}_size" class="filevalue"></div>
|
||||||
|
<a id = "file_${fileid}_download" class="filevalue" target="_blank">Download</a>
|
||||||
|
<button id = "file_${fileid}_delete" class="filevalue">Delete</button>
|
||||||
|
</div>
|
||||||
|
|
@ -1,6 +1,7 @@
|
|||||||
<style>
|
<style>
|
||||||
.progressPane {
|
.progressPane {
|
||||||
display: block;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -8,27 +9,21 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background-color: lightgrey;
|
background-color: grey;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progressPaneCenter {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 48%;
|
|
||||||
position: absolute;
|
|
||||||
height: 4%;
|
|
||||||
width: 50%;
|
|
||||||
margin-left: 25%;
|
|
||||||
margin-right: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #c9c9c9;
|
background-color: #555;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progressSpacer{
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.progress:after {
|
.progress:after {
|
||||||
content: attr(data-label);
|
content: attr(data-label);
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
@ -40,13 +35,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.progress .value {
|
.progress .value {
|
||||||
background-color: #7cc4ff;
|
background-color: darkcyan;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress .valueIndeterminate {
|
.progress .valueIndeterminate {
|
||||||
background-color: #7cc4ff;
|
background-color: darkcyan;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
animation: indeterminateAnimation 1s infinite linear;
|
animation: indeterminateAnimation 1s infinite linear;
|
||||||
@ -73,8 +68,13 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
.flexcontainer-rev{
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap-reverse;
|
||||||
|
}
|
||||||
.subcontainer {
|
.subcontainer {
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
|
max-width: 900px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
@ -100,7 +100,7 @@
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (min-width: 900px) {
|
@media (min-width: 1100px) {
|
||||||
.plantcontainer {
|
.plantcontainer {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
min-width: 20%;
|
min-width: 20%;
|
||||||
@ -109,7 +109,7 @@
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (min-width: 1800px) {
|
@media (min-width: 2150px) {
|
||||||
.plantcontainer {
|
.plantcontainer {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
@ -132,75 +132,6 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.powerflexkey {
|
|
||||||
min-width: 150px;
|
|
||||||
}
|
|
||||||
.powerflexvalue {
|
|
||||||
text-wrap: nowrap;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.basicnetworkkey{
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
.basicnetworkvalue{
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
.basicnetworkkeyssid1{
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
.basicnetworkkeyssid2{
|
|
||||||
min-width: 50px;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mqttkey{
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
.mqttvalue{
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.otakey{
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
.otavalue{
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
.otaform {
|
|
||||||
min-width: 100px;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
.otachooser {
|
|
||||||
min-width: 100px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightcheckbox{
|
|
||||||
margin: 0px;
|
|
||||||
min-width: 20px
|
|
||||||
}
|
|
||||||
.lightkey{
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
.lightvalue{
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
.lightnumberbox{
|
|
||||||
min-width: 75px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tankcheckbox {
|
|
||||||
min-width: 20px;
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
.tankkey{
|
|
||||||
min-width: 250px;
|
|
||||||
}
|
|
||||||
.tankvalue{
|
|
||||||
flex-grow: 1;
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@ -208,11 +139,11 @@
|
|||||||
|
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<div style="display:flex; flex-wrap: wrap;">
|
<div style="display:flex; flex-wrap: wrap;">
|
||||||
<div id="firmwareview" style="border-width: 1px; border-style: solid; flex-grow: 1; min-width: 350px">
|
<div id="firmwareview" class="subcontainer">
|
||||||
</div>
|
</div>
|
||||||
<div id="timeview" style="border-width: 1px; border-style: solid; flex-grow: 1;">
|
<div id="timeview" class="subcontainer">
|
||||||
</div>
|
</div>
|
||||||
<div id="batteryview" style="border-width: 1px; border-style: solid; flex-grow: 1;">
|
<div id="batteryview" class="subcontainer">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -224,31 +155,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h2>config</h2>
|
|
||||||
<h3>Plants:</h3>
|
<h3>Plants:</h3>
|
||||||
<button id="measure_moisture">Measure Moisture</button>
|
<button id="measure_moisture">Measure Moisture</button>
|
||||||
<div id="plants" class="plantlist"></div>
|
<div id="plants" class="plantlist"></div>
|
||||||
|
|
||||||
|
<div class="flexcontainer-rev">
|
||||||
|
<div>
|
||||||
|
<textarea id="json" cols=50 rows=10></textarea>
|
||||||
<button id="submit">Submit</button>
|
<button id="submit">Submit</button>
|
||||||
<div id="submit_status"></div>
|
<div id="submit_status"></div>
|
||||||
<br>
|
</div>
|
||||||
<textarea id="json" cols=50 rows=10></textarea>
|
<div id="fileview" class="subcontainer">
|
||||||
<script src="bundle.js"></script>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<button id="exit">Exit</button>
|
<button id="exit">Exit</button>
|
||||||
<button id="reboot">Reboot</button>
|
<button id="reboot">Reboot</button>
|
||||||
|
|
||||||
|
<script src="bundle.js"></script>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="progressPane" class="progressPane">
|
<div id="progressPane" class="progressPane">
|
||||||
<div class="progressPaneCenter">
|
<div class="progressSpacer"></div>>
|
||||||
<div id="progressPaneBar" class="progress" data-label="50% Complete">
|
<div id="progressPaneBar" class="progress" data-label="50% Complete">
|
||||||
<span id="progressPaneSpan" class="value" style="width:100%;"></span>
|
<span id="progressPaneSpan" class="value" style="width:100%;"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="progressSpacer"></div>>
|
||||||
</div>
|
</div>
|
@ -1,4 +1,6 @@
|
|||||||
|
|
||||||
|
import { deepEqual } from 'fast-equals';
|
||||||
|
|
||||||
declare var PUBLIC_URL: string;
|
declare var PUBLIC_URL: string;
|
||||||
console.log("Url is " + PUBLIC_URL);
|
console.log("Url is " + PUBLIC_URL);
|
||||||
|
|
||||||
@ -14,8 +16,69 @@ import { SubmitView } from "./submitView";
|
|||||||
import { ProgressView } from "./progress";
|
import { ProgressView } from "./progress";
|
||||||
import { OTAView } from "./ota";
|
import { OTAView } from "./ota";
|
||||||
import { BatteryView } from "./batteryview";
|
import { BatteryView } from "./batteryview";
|
||||||
|
import { FileView } from './fileview';
|
||||||
|
|
||||||
export class Controller {
|
export class Controller {
|
||||||
|
updateFileList() {
|
||||||
|
fetch(PUBLIC_URL + "/files")
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(json => json as FileList)
|
||||||
|
.then(filelist => {
|
||||||
|
controller.fileview.setFileList(filelist, PUBLIC_URL)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
uploadFile(file: File, name:string) {
|
||||||
|
var current = 0;
|
||||||
|
var max = 100;
|
||||||
|
controller.progressview.addProgress("file_upload", (current / max) * 100, "Uploading File " + name + "(" + current + "/" + max + ")")
|
||||||
|
var ajax = new XMLHttpRequest();
|
||||||
|
ajax.upload.addEventListener("progress", event => {
|
||||||
|
current = event.loaded / 1000;
|
||||||
|
max = event.total / 1000;
|
||||||
|
controller.progressview.addProgress("file_upload", (current / max) * 100, "Uploading File " + name + "(" + current + "/" + max + ")")
|
||||||
|
}, false);
|
||||||
|
ajax.addEventListener("load", () => {
|
||||||
|
controller.progressview.removeProgress("file_upload")
|
||||||
|
controller.updateFileList()
|
||||||
|
}, false);
|
||||||
|
ajax.addEventListener("error", () => {
|
||||||
|
alert("Error upload")
|
||||||
|
controller.progressview.removeProgress("file_upload")
|
||||||
|
controller.updateFileList()
|
||||||
|
}, false);
|
||||||
|
ajax.addEventListener("abort", () => {
|
||||||
|
alert("abort upload")
|
||||||
|
controller.progressview.removeProgress("file_upload")
|
||||||
|
controller.updateFileList()
|
||||||
|
}, false);
|
||||||
|
ajax.open("POST", PUBLIC_URL + "/file?filename="+name);
|
||||||
|
ajax.send(file);
|
||||||
|
}
|
||||||
|
deleteFile(name:string) {
|
||||||
|
controller.progressview.addIndeterminate("file_delete", "Deleting " + name);
|
||||||
|
var ajax = new XMLHttpRequest();
|
||||||
|
ajax.open("DELETE", PUBLIC_URL + "/file?filename="+name);
|
||||||
|
ajax.send();
|
||||||
|
ajax.addEventListener("error", () => {
|
||||||
|
controller.progressview.removeProgress("file_delete")
|
||||||
|
alert("Error delete")
|
||||||
|
controller.updateFileList()
|
||||||
|
}, false);
|
||||||
|
ajax.addEventListener("abort", () => {
|
||||||
|
controller.progressview.removeProgress("file_delete")
|
||||||
|
alert("Error upload")
|
||||||
|
controller.updateFileList()
|
||||||
|
}, false);
|
||||||
|
ajax.addEventListener("load", () => {
|
||||||
|
controller.progressview.removeProgress("file_delete")
|
||||||
|
controller.updateFileList()
|
||||||
|
}, false);
|
||||||
|
controller.updateFileList()
|
||||||
|
}
|
||||||
|
|
||||||
updateRTCData() {
|
updateRTCData() {
|
||||||
fetch(PUBLIC_URL + "/time")
|
fetch(PUBLIC_URL + "/time")
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@ -81,12 +144,16 @@ export class Controller {
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(loaded => {
|
.then(loaded => {
|
||||||
var currentConfig = loaded as PlantControllerConfig;
|
var currentConfig = loaded as PlantControllerConfig;
|
||||||
this.setConfig(currentConfig);
|
controller.setInitialConfig(currentConfig);
|
||||||
|
controller.setConfig(currentConfig);
|
||||||
//sync json view initially
|
//sync json view initially
|
||||||
this.configChanged();
|
this.configChanged();
|
||||||
controller.progressview.removeProgress("get_config")
|
controller.progressview.removeProgress("get_config")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
setInitialConfig(currentConfig: PlantControllerConfig) {
|
||||||
|
this.initialConfig = currentConfig
|
||||||
|
}
|
||||||
uploadConfig(json: string, statusCallback: (status: string) => void) {
|
uploadConfig(json: string, statusCallback: (status: string) => void) {
|
||||||
controller.progressview.addIndeterminate("set_config", "Uploading Config")
|
controller.progressview.addIndeterminate("set_config", "Uploading Config")
|
||||||
fetch(PUBLIC_URL + "/set_config", {
|
fetch(PUBLIC_URL + "/set_config", {
|
||||||
@ -96,6 +163,8 @@ export class Controller {
|
|||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
.then(text => statusCallback(text))
|
.then(text => statusCallback(text))
|
||||||
controller.progressview.removeProgress("set_config")
|
controller.progressview.removeProgress("set_config")
|
||||||
|
//load from remote to be clean
|
||||||
|
controller.downloadConfig()
|
||||||
}
|
}
|
||||||
syncRTCFromBrowser() {
|
syncRTCFromBrowser() {
|
||||||
controller.progressview.addIndeterminate("write_rtc", "Writing RTC")
|
controller.progressview.addIndeterminate("write_rtc", "Writing RTC")
|
||||||
@ -113,10 +182,17 @@ export class Controller {
|
|||||||
|
|
||||||
configChanged() {
|
configChanged() {
|
||||||
const current = controller.getConfig();
|
const current = controller.getConfig();
|
||||||
var pretty = JSON.stringify(current, undefined, 1);
|
var pretty = JSON.stringify(current, undefined, 0);
|
||||||
console.log(pretty)
|
var initial = JSON.stringify(this.initialConfig, undefined, 0);
|
||||||
controller.submitView.setJson(pretty);
|
controller.submitView.setJson(pretty);
|
||||||
|
|
||||||
|
if (deepEqual(current, controller.initialConfig)) {
|
||||||
|
document.title = "PlantCtrl"
|
||||||
|
} else {
|
||||||
|
document.title = "*PlantCtrl"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
testPlant(plantId: number) {
|
testPlant(plantId: number) {
|
||||||
@ -261,8 +337,7 @@ export class Controller {
|
|||||||
setTimeout(this.waitForReboot, 1000)
|
setTimeout(this.waitForReboot, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initialConfig: PlantControllerConfig | null = null
|
||||||
|
|
||||||
readonly rebootBtn: HTMLButtonElement
|
readonly rebootBtn: HTMLButtonElement
|
||||||
readonly exitBtn: HTMLButtonElement
|
readonly exitBtn: HTMLButtonElement
|
||||||
readonly timeView: TimeView;
|
readonly timeView: TimeView;
|
||||||
@ -274,6 +349,7 @@ export class Controller {
|
|||||||
readonly firmWareView: OTAView;
|
readonly firmWareView: OTAView;
|
||||||
readonly progressview: ProgressView;
|
readonly progressview: ProgressView;
|
||||||
readonly batteryView: BatteryView;
|
readonly batteryView: BatteryView;
|
||||||
|
readonly fileview: FileView;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.timeView = new TimeView(this)
|
this.timeView = new TimeView(this)
|
||||||
this.plantViews = new PlantViews(this)
|
this.plantViews = new PlantViews(this)
|
||||||
@ -284,6 +360,7 @@ export class Controller {
|
|||||||
this.submitView = new SubmitView(this)
|
this.submitView = new SubmitView(this)
|
||||||
this.firmWareView = new OTAView(this)
|
this.firmWareView = new OTAView(this)
|
||||||
this.progressview = new ProgressView(this)
|
this.progressview = new ProgressView(this)
|
||||||
|
this.fileview = new FileView(this)
|
||||||
this.rebootBtn = document.getElementById("reboot") as HTMLButtonElement
|
this.rebootBtn = document.getElementById("reboot") as HTMLButtonElement
|
||||||
this.rebootBtn.onclick = () => {
|
this.rebootBtn.onclick = () => {
|
||||||
controller.reboot();
|
controller.reboot();
|
||||||
@ -300,5 +377,6 @@ controller.updateBatteryData();
|
|||||||
controller.downloadConfig();
|
controller.downloadConfig();
|
||||||
//controller.measure_moisture();
|
//controller.measure_moisture();
|
||||||
controller.version();
|
controller.version();
|
||||||
|
controller.updateFileList();
|
||||||
controller.progressview.removeProgress("rebooting");
|
controller.progressview.removeProgress("rebooting");
|
||||||
|
|
||||||
|
@ -1,3 +1,25 @@
|
|||||||
|
<style>
|
||||||
|
.basicnetworkkey{
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
.basicnetworkvalue{
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.basicnetworkkeyssid1{
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.basicnetworkkeyssid2{
|
||||||
|
min-width: 50px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mqttkey{
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
.mqttvalue{
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<div>
|
<div>
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer">
|
||||||
<div class="subcontainer">
|
<div class="subcontainer">
|
||||||
@ -14,7 +36,7 @@
|
|||||||
|
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer">
|
||||||
<label class="basicnetworkkey" for="ssid">Station Mode:</label>
|
<label class="basicnetworkkey" for="ssid">Station Mode:</label>
|
||||||
<input class="basicnetworkkeyssid1" type="text" id="ssid" list="ssidlist">
|
<input class="basicnetworkkeyssid1" type="search" id="ssid" list="ssidlist">
|
||||||
<datalist id="ssidlist">
|
<datalist id="ssidlist">
|
||||||
<option value="Not scanned yet">
|
<option value="Not scanned yet">
|
||||||
</datalist>
|
</datalist>
|
||||||
|
@ -1,5 +1,22 @@
|
|||||||
|
<style>
|
||||||
|
.lightcheckbox{
|
||||||
|
margin: 0px;
|
||||||
|
min-width: 20px
|
||||||
|
}
|
||||||
|
.lightkey{
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
.lightvalue{
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.lightnumberbox{
|
||||||
|
min-width: 50px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div class="subtitle">Light:</div>
|
<div class="subtitle">Light:</div>
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer" style="text-decoration-line: line-through;">
|
||||||
<div class="lightkey">Enable Nightlight</div>
|
<div class="lightkey">Enable Nightlight</div>
|
||||||
<input class="lightcheckbox" type="checkbox" id="night_lamp_enabled" checked="false">
|
<input class="lightcheckbox" type="checkbox" id="night_lamp_enabled" checked="false">
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,19 @@
|
|||||||
|
<style>
|
||||||
|
.otakey{
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
.otavalue{
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.otaform {
|
||||||
|
min-width: 100px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.otachooser {
|
||||||
|
min-width: 100px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer">
|
||||||
<div class="subtitle">
|
<div class="subtitle">
|
||||||
Current Firmware
|
Current Firmware
|
||||||
@ -11,6 +27,10 @@
|
|||||||
<span class="otakey">Buildhash:</span>
|
<span class="otakey">Buildhash:</span>
|
||||||
<span class="otavalue" id="firmware_githash"></span>
|
<span class="otavalue" id="firmware_githash"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<span class="otakey">Partition:</span>
|
||||||
|
<span class="otavalue" id="firmware_partition"></span>
|
||||||
|
</div>
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer">
|
||||||
<form class="otaform" id="upload_form" method="post">
|
<form class="otaform" id="upload_form" method="post">
|
||||||
<input class="otachooser" type="file" name="file1" id="firmware_file"><br>
|
<input class="otachooser" type="file" name="file1" id="firmware_file"><br>
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import { Controller } from "./main";
|
import { Controller } from "./main";
|
||||||
|
|
||||||
export class OTAView {
|
export class OTAView {
|
||||||
file1Upload: HTMLInputElement;
|
readonly file1Upload: HTMLInputElement;
|
||||||
firmware_buildtime: HTMLDivElement;
|
readonly firmware_buildtime: HTMLDivElement;
|
||||||
firmware_githash: HTMLDivElement;
|
readonly firmware_githash: HTMLDivElement;
|
||||||
|
readonly firmware_partition: HTMLDivElement;
|
||||||
|
|
||||||
constructor(controller: Controller) {
|
constructor(controller: Controller) {
|
||||||
(document.getElementById("firmwareview") as HTMLElement).innerHTML = require("./ota.html")
|
(document.getElementById("firmwareview") as HTMLElement).innerHTML = require("./ota.html")
|
||||||
|
|
||||||
this.firmware_buildtime = document.getElementById("firmware_buildtime") as HTMLDivElement;
|
this.firmware_buildtime = document.getElementById("firmware_buildtime") as HTMLDivElement;
|
||||||
this.firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement;
|
this.firmware_githash = document.getElementById("firmware_githash") as HTMLDivElement;
|
||||||
|
this.firmware_partition = document.getElementById("firmware_partition") as HTMLDivElement;
|
||||||
|
|
||||||
|
|
||||||
const file = document.getElementById("firmware_file") as HTMLInputElement;
|
const file = document.getElementById("firmware_file") as HTMLInputElement;
|
||||||
this.file1Upload = file
|
this.file1Upload = file
|
||||||
@ -26,5 +29,6 @@ export class OTAView {
|
|||||||
setVersion(versionInfo: VersionInfo) {
|
setVersion(versionInfo: VersionInfo) {
|
||||||
this.firmware_buildtime.innerText = versionInfo.build_time;
|
this.firmware_buildtime.innerText = versionInfo.build_time;
|
||||||
this.firmware_githash.innerText = versionInfo.git_hash;
|
this.firmware_githash.innerText = versionInfo.git_hash;
|
||||||
|
this.firmware_partition.innerText = versionInfo.partition;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,65 +1,88 @@
|
|||||||
|
<style>
|
||||||
|
.plantsensorkey{
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
.plantsensorvalue{
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plantkey{
|
||||||
|
min-width: 175px;
|
||||||
|
}
|
||||||
|
.plantvalue{
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.plantcheckbox{
|
||||||
|
min-width: 20px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div style="font-weight: bold; text-align: center; flex-grow: 1;"
|
<div class="subtitle"
|
||||||
id="plant_${plantId}_header">Plant ${plantId}</div>
|
id="plant_${plantId}_header">
|
||||||
|
Plant ${plantId}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flexcontainer">
|
||||||
<div>
|
<div class="plantkey">
|
||||||
<div class="row">
|
|
||||||
<div class="col-1"></div>
|
|
||||||
<button class="col-10" id="plant_${plantId}_test">Test</button>
|
|
||||||
<div class="col-1"></div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">Live:</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-7">Sensor A:</div>
|
|
||||||
<span class="col-4" id="plant_${plantId}_moisture_a">loading</span>
|
|
||||||
<div class="col-7">Sensor B:</div>
|
|
||||||
<span class="col-4" id="plant_${plantId}_moisture_b">loading</span>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-7">
|
|
||||||
Mode:
|
Mode:
|
||||||
</div>
|
</div>
|
||||||
<select class="col-4" id="plant_${plantId}_mode">
|
<select class="plantvalue" id="plant_${plantId}_mode">
|
||||||
<option value="OFF">Off</option>
|
<option value="OFF">Off</option>
|
||||||
<option value="TargetMoisture">Target</option>
|
<option value="TargetMoisture">Target</option>
|
||||||
<option value="TimerOnly">Timer</option>
|
<option value="TimerOnly">Timer</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="flexcontainer">
|
||||||
<div class="col-7">Target Moisture:</div>
|
<div class="plantkey">Target Moisture:</div>
|
||||||
<input class="col-4" id="plant_${plantId}_target_moisture" type="number" min="0" max="100" placeholder="0">
|
<input class="plantvalue" id="plant_${plantId}_target_moisture" type="number" min="0" max="100" placeholder="0">
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="flexcontainer">
|
||||||
<div class="col-7">Pump Time (s):</div>
|
<div class="plantkey">Pump Time (s):</div>
|
||||||
<input class="col-4" id="plant_${plantId}_pump_time_s" type="number" min="0" max="600" placeholder="30">
|
<input class="plantvalue" id="plant_${plantId}_pump_time_s" type="number" min="0" max="600" placeholder="30">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="flexcontainer">
|
||||||
<div class="col-7">Pump Cooldown (m):</div>
|
<div class="plantkey">Pump Cooldown (m):</div>
|
||||||
<input class="col-4" id="plant_${plantId}_pump_cooldown_min" type="number" min="0" max="600" placeholder="30">
|
<input class="plantvalue" id="plant_${plantId}_pump_cooldown_min" type="number" min="0" max="600" placeholder="30">
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="flexcontainer">
|
||||||
<div class="col-7">"Pump Hour Start":</div>
|
<div class="plantkey">"Pump Hour Start":</div>
|
||||||
<select class="col-4" id="plant_${plantId}_pump_hour_start">10</select>
|
<select class="plantvalue" id="plant_${plantId}_pump_hour_start">10</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="flexcontainer">
|
||||||
<div class="col-7">"Pump Hour End":</div>
|
<div class="plantkey">"Pump Hour End":</div>
|
||||||
<select class="col-4" id="plant_${plantId}_pump_hour_end">19</select>
|
<select class="plantvalue" id="plant_${plantId}_pump_hour_end">19</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="flexcontainer">
|
||||||
<div class="col-7">Sensor B installed:</div>
|
<div class="plantkey">Warn Pump Count:</div>
|
||||||
<input class="col-4" id="plant_${plantId}_sensor_b" type="checkbox">
|
<input class="plantvalue" id="plant_${plantId}_max_consecutive_pump_count" type="number" min="1" , max="50" ,
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-7">Warn Pump Count:</div>
|
|
||||||
<input class="col-4" id="plant_${plantId}_max_consecutive_pump_count" type="number" min="1" , max="50" ,
|
|
||||||
placeholder="10">
|
placeholder="10">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="plantkey">Sensor B installed:</div>
|
||||||
|
<input class="plantcheckbox" id="plant_${plantId}_sensor_b" type="checkbox">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<button class="subtitle" id="plant_${plantId}_test">Test Pump</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="subtitle">Live:</div>
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<span class="plantsensorkey">Sensor A:</span>
|
||||||
|
<span class="plantsensorvalue" id="plant_${plantId}_moisture_a">loading</span>
|
||||||
|
</div>
|
||||||
|
<div class="flexcontainer">
|
||||||
|
<div class="plantsensorkey">Sensor B:</div>
|
||||||
|
<span class="plantsensorvalue" id="plant_${plantId}_moisture_b">loading</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
@ -144,7 +144,6 @@ export class PlantView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setConfig(plantConfig: PlantConfig) {
|
setConfig(plantConfig: PlantConfig) {
|
||||||
console.log("apply config to ui plant " + this.plantId + " config: " + JSON.stringify(plantConfig))
|
|
||||||
this.mode.value = plantConfig.mode;
|
this.mode.value = plantConfig.mode;
|
||||||
this.targetMoisture.value = plantConfig.target_moisture.toString();
|
this.targetMoisture.value = plantConfig.target_moisture.toString();
|
||||||
this.pumpTimeS.value = plantConfig.pump_time_s.toString();
|
this.pumpTimeS.value = plantConfig.pump_time_s.toString();
|
||||||
|
@ -44,14 +44,14 @@ export class ProgressView{
|
|||||||
|
|
||||||
addIndeterminate(id:string, displayText:string){
|
addIndeterminate(id:string, displayText:string){
|
||||||
this.progresses.set(id, new ProgressInfo(displayText,0,true))
|
this.progresses.set(id, new ProgressInfo(displayText,0,true))
|
||||||
this.progressPane.style.display = "block"
|
this.progressPane.style.display = "flex"
|
||||||
this.updateView();
|
this.updateView();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addProgress(id:string, value:number, displayText:string) {
|
addProgress(id:string, value:number, displayText:string) {
|
||||||
this.progresses.set(id, new ProgressInfo(displayText,value, false))
|
this.progresses.set(id, new ProgressInfo(displayText,value, false))
|
||||||
this.progressPane.style.display = "block"
|
this.progressPane.style.display = "flex"
|
||||||
this.updateView();
|
this.updateView();
|
||||||
}
|
}
|
||||||
removeProgress(id:string){
|
removeProgress(id:string){
|
||||||
|
@ -1,7 +1,21 @@
|
|||||||
|
<style>
|
||||||
|
.tankcheckbox {
|
||||||
|
min-width: 20px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
.tankkey{
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
.tankvalue{
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer">
|
||||||
<div class="subtitle">Tank:</div>
|
<div class="subtitle">Tank:</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flexcontainer">
|
<div class="flexcontainer" style="text-decoration-line: line-through;">
|
||||||
<span class="tankkey">Enable Tank Sensor</span>
|
<span class="tankkey">Enable Tank Sensor</span>
|
||||||
<input class="tankcheckbox" type="checkbox" id="tank_sensor_enabled">
|
<input class="tankcheckbox" type="checkbox" id="tank_sensor_enabled">
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,8 @@ const isDevServer = process.env.WEBPACK_SERVE;
|
|||||||
console.log("Dev server is " + isDevServer);
|
console.log("Dev server is " + isDevServer);
|
||||||
var host;
|
var host;
|
||||||
if (isDevServer){
|
if (isDevServer){
|
||||||
host = 'http://10.23.43.24';
|
//ensure no trailing /
|
||||||
|
host = 'http://192.168.1.172';
|
||||||
} else {
|
} else {
|
||||||
host = '';
|
host = '';
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user