Explorar o código

Merge branch 'main' of https://github.com/jomjol/AI-on-the-edge-device

jomjol hai 11 meses
pai
achega
063c4827d0

+ 47 - 33
README.md

@@ -251,17 +251,17 @@ There are some ideas and feature requests which are not currently being pursued
                 </a>
                 </a>
             </td>
             </td>
             <td align="center">
             <td align="center">
-                <a href="https://github.com/Slider0007">
-                    <img src="https://avatars.githubusercontent.com/u/115730895?v=4" width="100;" alt="Slider0007"/>
+                <a href="https://github.com/SybexX">
+                    <img src="https://avatars.githubusercontent.com/u/587201?v=4" width="100;" alt="SybexX"/>
                     <br />
                     <br />
-                    <sub><b>Slider0007</b></sub>
+                    <sub><b>michael</b></sub>
                 </a>
                 </a>
             </td>
             </td>
             <td align="center">
             <td align="center">
-                <a href="https://github.com/SybexX">
-                    <img src="https://avatars.githubusercontent.com/u/587201?v=4" width="100;" alt="SybexX"/>
+                <a href="https://github.com/Slider0007">
+                    <img src="https://avatars.githubusercontent.com/u/115730895?v=4" width="100;" alt="Slider0007"/>
                     <br />
                     <br />
-                    <sub><b>michael</b></sub>
+                    <sub><b>Slider0007</b></sub>
                 </a>
                 </a>
             </td>
             </td>
             <td align="center">
             <td align="center">
@@ -368,6 +368,13 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>parhedberg</b></sub>
                     <sub><b>parhedberg</b></sub>
                 </a>
                 </a>
             </td>
             </td>
+            <td align="center">
+                <a href="https://github.com/fsck-block">
+                    <img src="https://avatars.githubusercontent.com/u/58307481?v=4" width="100;" alt="fsck-block"/>
+                    <br />
+                    <sub><b>fsck-block</b></sub>
+                </a>
+            </td>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/slovdahl">
                 <a href="https://github.com/slovdahl">
                     <img src="https://avatars.githubusercontent.com/u/1417619?v=4" width="100;" alt="slovdahl"/>
                     <img src="https://avatars.githubusercontent.com/u/1417619?v=4" width="100;" alt="slovdahl"/>
@@ -389,13 +396,6 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>LordGuilly</b></sub>
                     <sub><b>LordGuilly</b></sub>
                 </a>
                 </a>
             </td>
             </td>
-            <td align="center">
-                <a href="https://github.com/bilalmirza74">
-                    <img src="https://avatars.githubusercontent.com/u/84387676?v=4" width="100;" alt="bilalmirza74"/>
-                    <br />
-                    <sub><b>Bilal Mirza</b></sub>
-                </a>
-            </td>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/muggenhor">
                 <a href="https://github.com/muggenhor">
                     <img src="https://avatars.githubusercontent.com/u/484066?v=4" width="100;" alt="muggenhor"/>
                     <img src="https://avatars.githubusercontent.com/u/484066?v=4" width="100;" alt="muggenhor"/>
@@ -406,10 +406,17 @@ There are some ideas and feature requests which are not currently being pursued
 		</tr>
 		</tr>
 		<tr>
 		<tr>
             <td align="center">
             <td align="center">
-                <a href="https://github.com/ppisljar">
-                    <img src="https://avatars.githubusercontent.com/u/13629809?v=4" width="100;" alt="ppisljar"/>
+                <a href="https://github.com/bilalmirza74">
+                    <img src="https://avatars.githubusercontent.com/u/84387676?v=4" width="100;" alt="bilalmirza74"/>
                     <br />
                     <br />
-                    <sub><b>Peter Pisljar</b></sub>
+                    <sub><b>Bilal Mirza</b></sub>
+                </a>
+            </td>
+            <td align="center">
+                <a href="https://github.com/AngryApostrophe">
+                    <img src="https://avatars.githubusercontent.com/u/89547888?v=4" width="100;" alt="AngryApostrophe"/>
+                    <br />
+                    <sub><b>AngryApostrophe</b></sub>
                 </a>
                 </a>
             </td>
             </td>
             <td align="center">
             <td align="center">
@@ -426,6 +433,13 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>Ranjana761</b></sub>
                     <sub><b>Ranjana761</b></sub>
                 </a>
                 </a>
             </td>
             </td>
+            <td align="center">
+                <a href="https://github.com/SURYANSH-RAI">
+                    <img src="https://avatars.githubusercontent.com/u/79277130?v=4" width="100;" alt="SURYANSH-RAI"/>
+                    <br />
+                    <sub><b>SURYANSH RAI</b></sub>
+                </a>
+            </td>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/SkylightXD">
                 <a href="https://github.com/SkylightXD">
                     <img src="https://avatars.githubusercontent.com/u/16561545?v=4" width="100;" alt="SkylightXD"/>
                     <img src="https://avatars.githubusercontent.com/u/16561545?v=4" width="100;" alt="SkylightXD"/>
@@ -433,6 +447,8 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>SkylightXD</b></sub>
                     <sub><b>SkylightXD</b></sub>
                 </a>
                 </a>
             </td>
             </td>
+		</tr>
+		<tr>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/ottk3">
                 <a href="https://github.com/ottk3">
                     <img src="https://avatars.githubusercontent.com/u/5236802?v=4" width="100;" alt="ottk3"/>
                     <img src="https://avatars.githubusercontent.com/u/5236802?v=4" width="100;" alt="ottk3"/>
@@ -447,8 +463,6 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>Tobias Bieniek</b></sub>
                     <sub><b>Tobias Bieniek</b></sub>
                 </a>
                 </a>
             </td>
             </td>
-		</tr>
-		<tr>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/tkopczuk">
                 <a href="https://github.com/tkopczuk">
                     <img src="https://avatars.githubusercontent.com/u/101632?v=4" width="100;" alt="tkopczuk"/>
                     <img src="https://avatars.githubusercontent.com/u/101632?v=4" width="100;" alt="tkopczuk"/>
@@ -477,6 +491,8 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>flox_x</b></sub>
                     <sub><b>flox_x</b></sub>
                 </a>
                 </a>
             </td>
             </td>
+		</tr>
+		<tr>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/gneluka">
                 <a href="https://github.com/gneluka">
                     <img src="https://avatars.githubusercontent.com/u/32097881?v=4" width="100;" alt="gneluka"/>
                     <img src="https://avatars.githubusercontent.com/u/32097881?v=4" width="100;" alt="gneluka"/>
@@ -491,8 +507,6 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>kalwados</b></sub>
                     <sub><b>kalwados</b></sub>
                 </a>
                 </a>
             </td>
             </td>
-		</tr>
-		<tr>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/kub3let">
                 <a href="https://github.com/kub3let">
                     <img src="https://avatars.githubusercontent.com/u/95883234?v=4" width="100;" alt="kub3let"/>
                     <img src="https://avatars.githubusercontent.com/u/95883234?v=4" width="100;" alt="kub3let"/>
@@ -521,13 +535,8 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>smartboart</b></sub>
                     <sub><b>smartboart</b></sub>
                 </a>
                 </a>
             </td>
             </td>
-            <td align="center">
-                <a href="https://github.com/AngryApostrophe">
-                    <img src="https://avatars.githubusercontent.com/u/89547888?v=4" width="100;" alt="AngryApostrophe"/>
-                    <br />
-                    <sub><b>AngryApostrophe</b></sub>
-                </a>
-            </td>
+		</tr>
+		<tr>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/wetneb">
                 <a href="https://github.com/wetneb">
                     <img src="https://avatars.githubusercontent.com/u/309908?v=4" width="100;" alt="wetneb"/>
                     <img src="https://avatars.githubusercontent.com/u/309908?v=4" width="100;" alt="wetneb"/>
@@ -535,8 +544,6 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>Antonin Delpeuch</b></sub>
                     <sub><b>Antonin Delpeuch</b></sub>
                 </a>
                 </a>
             </td>
             </td>
-		</tr>
-		<tr>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/adarazs">
                 <a href="https://github.com/adarazs">
                     <img src="https://avatars.githubusercontent.com/u/6269603?v=4" width="100;" alt="adarazs"/>
                     <img src="https://avatars.githubusercontent.com/u/6269603?v=4" width="100;" alt="adarazs"/>
@@ -572,6 +579,8 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>Dave</b></sub>
                     <sub><b>Dave</b></sub>
                 </a>
                 </a>
             </td>
             </td>
+		</tr>
+		<tr>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/FarukhS52">
                 <a href="https://github.com/FarukhS52">
                     <img src="https://avatars.githubusercontent.com/u/129654632?v=4" width="100;" alt="FarukhS52"/>
                     <img src="https://avatars.githubusercontent.com/u/129654632?v=4" width="100;" alt="FarukhS52"/>
@@ -579,8 +588,6 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>Farookh Zaheer Siddiqui</b></sub>
                     <sub><b>Farookh Zaheer Siddiqui</b></sub>
                 </a>
                 </a>
             </td>
             </td>
-		</tr>
-		<tr>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/hex7c0">
                 <a href="https://github.com/hex7c0">
                     <img src="https://avatars.githubusercontent.com/u/4419146?v=4" width="100;" alt="hex7c0"/>
                     <img src="https://avatars.githubusercontent.com/u/4419146?v=4" width="100;" alt="hex7c0"/>
@@ -616,6 +623,8 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>Joerg Rosenkranz</b></sub>
                     <sub><b>Joerg Rosenkranz</b></sub>
                 </a>
                 </a>
             </td>
             </td>
+		</tr>
+		<tr>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/Innovatorcloudy">
                 <a href="https://github.com/Innovatorcloudy">
                     <img src="https://avatars.githubusercontent.com/u/183274513?v=4" width="100;" alt="Innovatorcloudy"/>
                     <img src="https://avatars.githubusercontent.com/u/183274513?v=4" width="100;" alt="Innovatorcloudy"/>
@@ -623,8 +632,6 @@ There are some ideas and feature requests which are not currently being pursued
                     <sub><b>KrishCode</b></sub>
                     <sub><b>KrishCode</b></sub>
                 </a>
                 </a>
             </td>
             </td>
-		</tr>
-		<tr>
             <td align="center">
             <td align="center">
                 <a href="https://github.com/myxor">
                 <a href="https://github.com/myxor">
                     <img src="https://avatars.githubusercontent.com/u/1397377?v=4" width="100;" alt="myxor"/>
                     <img src="https://avatars.githubusercontent.com/u/1397377?v=4" width="100;" alt="myxor"/>
@@ -652,6 +659,13 @@ There are some ideas and feature requests which are not currently being pursued
                     <br />
                     <br />
                     <sub><b>Michael Geissler</b></sub>
                     <sub><b>Michael Geissler</b></sub>
                 </a>
                 </a>
+            </td>
+            <td align="center">
+                <a href="https://github.com/ppisljar">
+                    <img src="https://avatars.githubusercontent.com/u/13629809?v=4" width="100;" alt="ppisljar"/>
+                    <br />
+                    <sub><b>Peter Pisljar</b></sub>
+                </a>
             </td>
             </td>
 		</tr>
 		</tr>
 	<tbody>
 	<tbody>

+ 61 - 84
code/components/jomjol_fileserver_ota/server_file.cpp

@@ -6,11 +6,8 @@
    software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    CONDITIONS OF ANY KIND, either express or implied.
    CONDITIONS OF ANY KIND, either express or implied.
 */
 */
-
-
 #include "server_file.h"
 #include "server_file.h"
 
 
-
 #include <stdio.h>
 #include <stdio.h>
 #include <string.h>
 #include <string.h>
 #include <string>
 #include <string>
@@ -58,7 +55,6 @@ struct file_server_data {
     char scratch[SERVER_FILER_SCRATCH_BUFSIZE];
     char scratch[SERVER_FILER_SCRATCH_BUFSIZE];
 };
 };
 
 
-
 #include <iostream>
 #include <iostream>
 #include <sys/types.h>
 #include <sys/types.h>
 #include <dirent.h>
 #include <dirent.h>
@@ -67,11 +63,9 @@ using namespace std;
 
 
 string SUFFIX_ZW = "_0xge";
 string SUFFIX_ZW = "_0xge";
 
 
-
 static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file);
 static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file);
 static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file);
 static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file);
 
 
-
 esp_err_t get_numbers_file_handler(httpd_req_t *req)
 esp_err_t get_numbers_file_handler(httpd_req_t *req)
 {
 {
     std::string ret = flowctrl.getNumbersName();
     std::string ret = flowctrl.getNumbersName();
@@ -87,7 +81,6 @@ esp_err_t get_numbers_file_handler(httpd_req_t *req)
     return ESP_OK;
     return ESP_OK;
 }
 }
 
 
-
 esp_err_t get_data_file_handler(httpd_req_t *req)
 esp_err_t get_data_file_handler(httpd_req_t *req)
 {
 {
     struct dirent *entry;
     struct dirent *entry;
@@ -131,7 +124,6 @@ esp_err_t get_data_file_handler(httpd_req_t *req)
     return ESP_OK;
     return ESP_OK;
 }
 }
 
 
-
 esp_err_t get_tflite_file_handler(httpd_req_t *req)
 esp_err_t get_tflite_file_handler(httpd_req_t *req)
 {
 {
     struct dirent *entry;
     struct dirent *entry;
@@ -175,12 +167,11 @@ esp_err_t get_tflite_file_handler(httpd_req_t *req)
     return ESP_OK;
     return ESP_OK;
 }
 }
 
 
-
 /* Send HTTP response with a run-time generated html consisting of
 /* Send HTTP response with a run-time generated html consisting of
  * a list of all files and folders under the requested path.
  * a list of all files and folders under the requested path.
  * In case of SPIFFS this returns empty list when path is any
  * In case of SPIFFS this returns empty list when path is any
  * string other than '/', since SPIFFS doesn't support directories */
  * string other than '/', since SPIFFS doesn't support directories */
-static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const char* uripath, bool readonly)
+static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const char *uripath, bool readonly)
 {
 {
     char entrypath[FILE_PATH_MAX];
     char entrypath[FILE_PATH_MAX];
     char entrysize[16];
     char entrysize[16];
@@ -192,82 +183,85 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const
     char dirpath_corrected[FILE_PATH_MAX];
     char dirpath_corrected[FILE_PATH_MAX];
     strcpy(dirpath_corrected, dirpath);
     strcpy(dirpath_corrected, dirpath);
 
 
-    file_server_data * server_data = (file_server_data *) req->user_ctx;
-    if ((strlen(dirpath_corrected)-1) > strlen(server_data->base_path))      // if dirpath is not mountpoint, the last "\" needs to be removed
-        dirpath_corrected[strlen(dirpath_corrected)-1] = '\0';
+    file_server_data *server_data = (file_server_data *)req->user_ctx;
+
+    if ((strlen(dirpath_corrected) - 1) > strlen(server_data->base_path)) {
+        // if dirpath is not mountpoint, the last "\" needs to be removed
+        dirpath_corrected[strlen(dirpath_corrected) - 1] = '\0';
+    }
 
 
-    DIR *dir = opendir(dirpath_corrected);
+    DIR *pdir = opendir(dirpath_corrected);
 
 
     const size_t dirpath_len = strlen(dirpath);
     const size_t dirpath_len = strlen(dirpath);
     ESP_LOGD(TAG, "Dirpath: <%s>, Pathlength: %d", dirpath, dirpath_len);
     ESP_LOGD(TAG, "Dirpath: <%s>, Pathlength: %d", dirpath, dirpath_len);
 
 
-    /* Retrieve the base path of file storage to construct the full path */
+    // Retrieve the base path of file storage to construct the full path
     strlcpy(entrypath, dirpath, sizeof(entrypath));
     strlcpy(entrypath, dirpath, sizeof(entrypath));
     ESP_LOGD(TAG, "entrypath: <%s>", entrypath);
     ESP_LOGD(TAG, "entrypath: <%s>", entrypath);
 
 
-    if (!dir) {
+    if (!pdir) {
         LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to stat dir: " + std::string(dirpath) + "!");
         LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to stat dir: " + std::string(dirpath) + "!");
-        /* Respond with 404 Not Found */
+        // Respond with 404 Not Found
         httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, get404());
         httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, get404());
         return ESP_FAIL;
         return ESP_FAIL;
     }
     }
 
 
     httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
     httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
 
 
-    /* Send HTML file header */
-    httpd_resp_sendstr_chunk(req, "<!DOCTYPE html><html><body>");
-
-/////////////////////////////////////////////////
-    if (!readonly) {
-        FILE *fd = fopen("/sdcard/html/file_server.html", "r");
-        char *chunk = ((struct file_server_data *)req->user_ctx)->scratch;
-        size_t chunksize;
-        do {
-            chunksize = fread(chunk, 1, SERVER_FILER_SCRATCH_BUFSIZE, fd);
-            //        ESP_LOGD(TAG, "Chunksize %d", chunksize);
-            if (chunksize > 0){
-                if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
-                fclose(fd);
-                LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "File sending failed!");
-                return ESP_FAIL;
-                }
-            }
-        } while (chunksize != 0);
-        fclose(fd);
-        //    ESP_LOGI(TAG, "File sending complete");
-    }
-///////////////////////////////
+    // Send HTML file header
+    httpd_resp_sendstr_chunk(req, "<!DOCTYPE html><html lang=\"en\" xml:lang=\"en\"><head>");
+    httpd_resp_sendstr_chunk(req, "<link href=\"/file_server.css\" rel=\"stylesheet\">");
+    httpd_resp_sendstr_chunk(req, "<link href=\"/firework.css\" rel=\"stylesheet\">");
+    httpd_resp_sendstr_chunk(req, "<script type=\"text/javascript\" src=\"/jquery-3.6.0.min.js\"></script>");
+    httpd_resp_sendstr_chunk(req, "<script type=\"text/javascript\" src=\"/firework.js\"></script></head>");
+
+    httpd_resp_sendstr_chunk(req, "<body>");
+
+    httpd_resp_sendstr_chunk(req, "<table class=\"fixed\" border=\"0\" width=100% style=\"font-family: arial\">");
+    httpd_resp_sendstr_chunk(req, "<tr><td style=\"vertical-align: top;width: 300px;\"><h2>Fileserver</h2></td>"
+                                  "<td rowspan=\"2\"><table border=\"0\" style=\"width:100%\"><tr><td style=\"width:80px\">"
+                                  "<label for=\"newfile\">Source</label></td><td colspan=\"2\">"
+                                  "<input id=\"newfile\" type=\"file\" onchange=\"setpath()\" style=\"width:100%;\"></td></tr>"
+                                  "<tr><td><label for=\"filepath\">Destination</label></td><td>"
+                                  "<input id=\"filepath\" type=\"text\" style=\"width:94%;\"></td><td>"
+                                  "<button id=\"upload\" type=\"button\" class=\"button\" onclick=\"upload()\">Upload</button></td></tr>"
+                                  "</table></td></tr><tr></tr><tr><td colspan=\"2\">"
+                                  "<button style=\"font-size:16px; padding: 5px 10px\" id=\"dirup\" type=\"button\" onclick=\"dirup()\""
+                                  "disabled>&#129145; Directory up</button><span style=\"padding-left:15px\" id=\"currentpath\">"
+                                  "</span></td></tr>");
+    httpd_resp_sendstr_chunk(req, "</table>");
+
+    httpd_resp_sendstr_chunk(req, "<script type=\"text/javascript\" src=\"/file_server.js\"></script>");
+    httpd_resp_sendstr_chunk(req, "<script type=\"text/javascript\">initFileServer();</script>");
 
 
     std::string _zw = std::string(dirpath);
     std::string _zw = std::string(dirpath);
     _zw = _zw.substr(8, _zw.length() - 8);
     _zw = _zw.substr(8, _zw.length() - 8);
-    _zw = "/delete/" + _zw + "?task=deldircontent"; 
+    _zw = "/delete/" + _zw + "?task=deldircontent";
 
 
+    // Send file-list table definition and column labels
+    httpd_resp_sendstr_chunk(req, "<table id=\"files_table\">"
+                                  "<col width=\"800px\"><col width=\"300px\"><col width=\"300px\"><col width=\"100px\">"
+                                  "<thead><tr><th>Name</th><th>Type</th><th>Size</th>");
 
 
-    /* Send file-list table definition and column labels */
-    httpd_resp_sendstr_chunk(req,
-        "<table id=\"files_table\">"
-        "<col width=\"800px\" /><col width=\"300px\" /><col width=\"300px\" /><col width=\"100px\" />"
-        "<thead><tr><th>Name</th><th>Type</th><th>Size</th>");
     if (!readonly) {
     if (!readonly) {
-        httpd_resp_sendstr_chunk(req, "<th>"
-            "<form method=\"post\" action=\"");
+        httpd_resp_sendstr_chunk(req, "<th><form method=\"post\" action=\"");
         httpd_resp_sendstr_chunk(req, _zw.c_str());
         httpd_resp_sendstr_chunk(req, _zw.c_str());
-        httpd_resp_sendstr_chunk(req,
-            "\"><button type=\"submit\">DELETE ALL!</button></form>"
-            "</th></tr>");
+        httpd_resp_sendstr_chunk(req, "\"><button type=\"submit\">DELETE ALL!</button></form></th></tr>");
     }
     }
+
     httpd_resp_sendstr_chunk(req, "</thead><tbody>\n");
     httpd_resp_sendstr_chunk(req, "</thead><tbody>\n");
 
 
-    /* Iterate over all files / folders and fetch their names and sizes */
-    while ((entry = readdir(dir)) != NULL) {
-        if (strcmp("wlan.ini", entry->d_name) != 0 )        // wlan.ini soll nicht angezeigt werden!
-        {
+    // Iterate over all files / folders and fetch their names and sizes
+    while ((entry = readdir(pdir)) != NULL) {
+        // wlan.ini soll nicht angezeigt werden!
+        if (strcmp("wlan.ini", entry->d_name) != 0) {
             entrytype = (entry->d_type == DT_DIR ? "directory" : "file");
             entrytype = (entry->d_type == DT_DIR ? "directory" : "file");
 
 
             strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len);
             strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len);
             ESP_LOGD(TAG, "Entrypath: %s", entrypath);
             ESP_LOGD(TAG, "Entrypath: %s", entrypath);
+
             if (stat(entrypath, &entry_stat) == -1) {
             if (stat(entrypath, &entry_stat) == -1) {
-                LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to stat " + string(entrytype) + ": " + string(entry->d_name));
+                LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to stat " + std::string(entrytype) + ": " + std::string(entry->d_name));
                 continue;
                 continue;
             }
             }
 
 
@@ -283,22 +277,25 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const
                 }
                 }
             }
             }
 
 
-            ESP_LOGI(TAG, "Found %s: %s (%s bytes)", entrytype, entry->d_name, entrysize);
+            ESP_LOGD(TAG, "Found %s: %s (%s bytes)", entrytype, entry->d_name, entrysize);
 
 
-            /* Send chunk of HTML file containing table entries with file name and size */
+            // Send chunk of HTML file containing table entries with file name and size
             httpd_resp_sendstr_chunk(req, "<tr><td><a href=\"");
             httpd_resp_sendstr_chunk(req, "<tr><td><a href=\"");
             httpd_resp_sendstr_chunk(req, "/fileserver");
             httpd_resp_sendstr_chunk(req, "/fileserver");
             httpd_resp_sendstr_chunk(req, uripath);
             httpd_resp_sendstr_chunk(req, uripath);
             httpd_resp_sendstr_chunk(req, entry->d_name);
             httpd_resp_sendstr_chunk(req, entry->d_name);
+
             if (entry->d_type == DT_DIR) {
             if (entry->d_type == DT_DIR) {
                 httpd_resp_sendstr_chunk(req, "/");
                 httpd_resp_sendstr_chunk(req, "/");
             }
             }
+
             httpd_resp_sendstr_chunk(req, "\">");
             httpd_resp_sendstr_chunk(req, "\">");
             httpd_resp_sendstr_chunk(req, entry->d_name);
             httpd_resp_sendstr_chunk(req, entry->d_name);
             httpd_resp_sendstr_chunk(req, "</a></td><td>");
             httpd_resp_sendstr_chunk(req, "</a></td><td>");
             httpd_resp_sendstr_chunk(req, entrytype);
             httpd_resp_sendstr_chunk(req, entrytype);
             httpd_resp_sendstr_chunk(req, "</td><td>");
             httpd_resp_sendstr_chunk(req, "</td><td>");
             httpd_resp_sendstr_chunk(req, entrysize);
             httpd_resp_sendstr_chunk(req, entrysize);
+
             if (!readonly) {
             if (!readonly) {
                 httpd_resp_sendstr_chunk(req, "</td><td>");
                 httpd_resp_sendstr_chunk(req, "</td><td>");
                 httpd_resp_sendstr_chunk(req, "<form method=\"post\" action=\"/delete");
                 httpd_resp_sendstr_chunk(req, "<form method=\"post\" action=\"/delete");
@@ -306,31 +303,28 @@ static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath, const
                 httpd_resp_sendstr_chunk(req, entry->d_name);
                 httpd_resp_sendstr_chunk(req, entry->d_name);
                 httpd_resp_sendstr_chunk(req, "\"><button type=\"submit\">Delete</button></form>");
                 httpd_resp_sendstr_chunk(req, "\"><button type=\"submit\">Delete</button></form>");
             }
             }
+
             httpd_resp_sendstr_chunk(req, "</td></tr>\n");
             httpd_resp_sendstr_chunk(req, "</td></tr>\n");
         }
         }
     }
     }
-    closedir(dir);
 
 
-    /* Finish the file list table */
+    closedir(pdir);
+
+    // Finish the file list table
     httpd_resp_sendstr_chunk(req, "</tbody></table>");
     httpd_resp_sendstr_chunk(req, "</tbody></table>");
 
 
-    /* Send remaining chunk of HTML file to complete it */
+    // Send remaining chunk of HTML file to complete it
     httpd_resp_sendstr_chunk(req, "</body></html>");
     httpd_resp_sendstr_chunk(req, "</body></html>");
 
 
-    /* Send empty chunk to signal HTTP response completion */
+    // Send empty chunk to signal HTTP response completion
     httpd_resp_sendstr_chunk(req, NULL);
     httpd_resp_sendstr_chunk(req, NULL);
     return ESP_OK;
     return ESP_OK;
 }
 }
-/*
-#define IS_FILE_EXT(filename, ext) \
-    (strcasecmp(&filename[strlen(filename) - sizeof(ext) + 1], ext) == 0)
-*/
 
 
 static esp_err_t logfileact_get_full_handler(httpd_req_t *req) {
 static esp_err_t logfileact_get_full_handler(httpd_req_t *req) {
     return send_logfile(req, true);
     return send_logfile(req, true);
 }
 }
 
 
-
 static esp_err_t logfileact_get_last_part_handler(httpd_req_t *req) {
 static esp_err_t logfileact_get_last_part_handler(httpd_req_t *req) {
     return send_logfile(req, false);
     return send_logfile(req, false);
 }
 }
@@ -339,7 +333,6 @@ static esp_err_t datafileact_get_full_handler(httpd_req_t *req) {
     return send_datafile(req, true);
     return send_datafile(req, true);
 }
 }
 
 
-
 static esp_err_t datafileact_get_last_part_handler(httpd_req_t *req) {
 static esp_err_t datafileact_get_last_part_handler(httpd_req_t *req) {
     return send_datafile(req, false);
     return send_datafile(req, false);
 }
 }
@@ -424,7 +417,6 @@ static esp_err_t send_datafile(httpd_req_t *req, bool send_full_file)
     return ESP_OK;
     return ESP_OK;
 }
 }
 
 
-
 static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file)
 static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file)
 {
 {
     LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "log_get_last_part_handler");
     LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "log_get_last_part_handler");
@@ -510,7 +502,6 @@ static esp_err_t send_logfile(httpd_req_t *req, bool send_full_file)
     return ESP_OK;
     return ESP_OK;
 }
 }
 
 
-
 /* Handler to download a file kept on the server */
 /* Handler to download a file kept on the server */
 static esp_err_t download_get_handler(httpd_req_t *req)
 static esp_err_t download_get_handler(httpd_req_t *req)
 {
 {
@@ -528,7 +519,6 @@ static esp_err_t download_get_handler(httpd_req_t *req)
 //    filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
 //    filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
 //                                             req->uri, sizeof(filepath));
 //                                             req->uri, sizeof(filepath));
 
 
-
     if (!filename) {
     if (!filename) {
         LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Filename is too long");
         LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Filename is too long");
         /* Respond with 414 Error */
         /* Respond with 414 Error */
@@ -759,7 +749,6 @@ static esp_err_t upload_post_handler(httpd_req_t *req)
     httpd_resp_set_hdr(req, "Location", directory.c_str());
     httpd_resp_set_hdr(req, "Location", directory.c_str());
     httpd_resp_sendstr(req, "File uploaded successfully");
     httpd_resp_sendstr(req, "File uploaded successfully");
 
 
-
     return ESP_OK;
     return ESP_OK;
 }
 }
 
 
@@ -770,7 +759,6 @@ static esp_err_t delete_post_handler(httpd_req_t *req)
     char filepath[FILE_PATH_MAX];
     char filepath[FILE_PATH_MAX];
     struct stat file_stat;
     struct stat file_stat;
 
 
-
 //////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////
     char _query[200];
     char _query[200];
     char _valuechar[30];    
     char _valuechar[30];    
@@ -893,13 +881,11 @@ static esp_err_t delete_post_handler(httpd_req_t *req)
         }
         }
     }
     }
 
 
-
     httpd_resp_set_hdr(req, "Location", directory.c_str());
     httpd_resp_set_hdr(req, "Location", directory.c_str());
     httpd_resp_sendstr(req, "File successfully deleted");
     httpd_resp_sendstr(req, "File successfully deleted");
     return ESP_OK;
     return ESP_OK;
 }
 }
 
 
-
 void delete_all_in_directory(std::string _directory)
 void delete_all_in_directory(std::string _directory)
 {
 {
     struct dirent *entry;
     struct dirent *entry;
@@ -1137,8 +1123,6 @@ void unzip(std::string _in_zip_file, std::string _target_directory){
     ESP_LOGD(TAG, "Success.");
     ESP_LOGD(TAG, "Success.");
 }
 }
 
 
-
-
 void register_server_file_uri(httpd_handle_t server, const char *base_path)
 void register_server_file_uri(httpd_handle_t server, const char *base_path)
 {
 {
     static struct file_server_data *server_data = NULL;
     static struct file_server_data *server_data = NULL;
@@ -1164,8 +1148,6 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
     strlcpy(server_data->base_path, base_path,
     strlcpy(server_data->base_path, base_path,
             sizeof(server_data->base_path));
             sizeof(server_data->base_path));
 
 
-
-
     /* URI handler for getting uploaded files */
     /* URI handler for getting uploaded files */
 //    char zw[sizeof(serverprefix)+1];
 //    char zw[sizeof(serverprefix)+1];
 //    strcpy(zw, serverprefix);
 //    strcpy(zw, serverprefix);
@@ -1180,7 +1162,6 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
     };
     };
     httpd_register_uri_handler(server, &file_download);
     httpd_register_uri_handler(server, &file_download);
 
 
-
     httpd_uri_t file_datafileact = {
     httpd_uri_t file_datafileact = {
         .uri       = "/datafileact",  // Match all URIs of type /path/to/file
         .uri       = "/datafileact",  // Match all URIs of type /path/to/file
         .method    = HTTP_GET,
         .method    = HTTP_GET,
@@ -1189,7 +1170,6 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
     };
     };
     httpd_register_uri_handler(server, &file_datafileact);
     httpd_register_uri_handler(server, &file_datafileact);
 
 
-
     httpd_uri_t file_datafile_last_part_handle = {
     httpd_uri_t file_datafile_last_part_handle = {
         .uri       = "/data",  // Match all URIs of type /path/to/file
         .uri       = "/data",  // Match all URIs of type /path/to/file
         .method    = HTTP_GET,
         .method    = HTTP_GET,
@@ -1206,7 +1186,6 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
     };
     };
     httpd_register_uri_handler(server, &file_logfileact);
     httpd_register_uri_handler(server, &file_logfileact);
 
 
-
     httpd_uri_t file_logfile_last_part_handle = {
     httpd_uri_t file_logfile_last_part_handle = {
         .uri       = "/log",  // Match all URIs of type /path/to/file
         .uri       = "/log",  // Match all URIs of type /path/to/file
         .method    = HTTP_GET,
         .method    = HTTP_GET,
@@ -1215,7 +1194,6 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
     };
     };
     httpd_register_uri_handler(server, &file_logfile_last_part_handle);
     httpd_register_uri_handler(server, &file_logfile_last_part_handle);
 
 
-
     /* URI handler for uploading files to server */
     /* URI handler for uploading files to server */
     httpd_uri_t file_upload = {
     httpd_uri_t file_upload = {
         .uri       = "/upload/*",   // Match all URIs of type /upload/path/to/file
         .uri       = "/upload/*",   // Match all URIs of type /upload/path/to/file
@@ -1233,5 +1211,4 @@ void register_server_file_uri(httpd_handle_t server, const char *base_path)
         .user_ctx  = server_data    // Pass server data as context
         .user_ctx  = server_data    // Pass server data as context
     };
     };
     httpd_register_uri_handler(server, &file_delete);
     httpd_register_uri_handler(server, &file_delete);
-
 }
 }

+ 59 - 0
code/components/openmetrics/openmetrics.cpp

@@ -1,4 +1,6 @@
 #include "openmetrics.h"
 #include "openmetrics.h"
+#include "functional"
+#include "esp_log.h"
 
 
 /**
 /**
  * create a singe metric from the given input
  * create a singe metric from the given input
@@ -10,10 +12,66 @@ std::string createMetric(const std::string &metricName, const std::string &help,
            metricName + " " + value + "\n";
            metricName + " " + value + "\n";
 }
 }
 
 
+typedef struct sequence_metric {
+    const char *name;
+    const char *help;
+    const char *type;
+    std::function<std::string(NumberPost *number)> valueFunc;
+} sequence_metric_t;
+
+
+sequence_metric_t sequenceMetrics[4] = {
+    { "flow_value",     "current value of meter readout",     "gauge", [](NumberPost *number)-> std::string {return number->ReturnValue;} },
+    { "flow_raw_value", "current raw value of meter readout", "gauge", [](NumberPost *number)-> std::string {return number->ReturnRawValue;} },
+    { "flow_pre_value", "previous value of meter readout",    "gauge", [](NumberPost *number)-> std::string {return number->ReturnPreValue;} },
+    { "flow_error",     "Error message text != 'no error'",   "gauge", [](NumberPost *number)-> std::string {return number->ErrorMessageText.compare("no error") == 0 ? "0" : "1";} },
+};
+
+std::string createSequenceMetrics(std::string prefix, const std::vector<NumberPost *> &numbers)
+{
+    std::string result;
+    for (int i = 0; i<sizeof(sequenceMetrics)/sizeof(sequence_metric_t);i++) 
+    {
+        std::string res;
+        for (const auto &number : numbers)
+        {
+            std::string value = sequenceMetrics[i].valueFunc(number); 
+            if (value.find("N") != std::string::npos) {
+                value = "NaN";
+            }
+            ESP_LOGD("METRICS", "metric=%s, name=%s, value = %s ",sequenceMetrics[i].name,number->name.c_str(), value.c_str());
+
+            // only valid data is reported (https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#missing-data)
+            if (value.length() > 0)
+            {
+                auto label = number->name;
+                // except newline, double quote, and backslash (https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#abnf)
+                // to keep it simple, these characters are just removed from the label
+                replaceAll(label, "\\", "");
+                replaceAll(label, "\"", "");
+                replaceAll(label, "\n", "");
+
+                res += prefix + "_" + sequenceMetrics[i].name + "{sequence=\"" + label + "\"} " + value + "\n";
+            }
+        }
+        // prepend metadata if a valid metric was created
+        if (res.length() > 0)
+        {
+            res = "# HELP " + prefix + "_" + sequenceMetrics[i].name + " " + sequenceMetrics[i].help + "\n"
+                + "# TYPE " + prefix + "_" + sequenceMetrics[i].name + " " + sequenceMetrics[i].type + "\n"
+                + res;
+        }
+        result += res;
+    }
+
+    return result;
+}
+
 /**
 /**
  * Generate the MetricFamily from all available sequences
  * Generate the MetricFamily from all available sequences
  * @returns the string containing the text wire format of the MetricFamily
  * @returns the string containing the text wire format of the MetricFamily
  **/
  **/
+/*
 std::string createSequenceMetrics(std::string prefix, const std::vector<NumberPost *> &numbers)
 std::string createSequenceMetrics(std::string prefix, const std::vector<NumberPost *> &numbers)
 {
 {
     std::string res;
     std::string res;
@@ -41,3 +99,4 @@ std::string createSequenceMetrics(std::string prefix, const std::vector<NumberPo
     }
     }
     return res;
     return res;
 }
 }
+*/

+ 41 - 6
code/test/components/openmetrics/test_openmetrics.cpp

@@ -37,23 +37,58 @@ void test_createSequenceMetrics()
     NumberPost *number_1 = new NumberPost;
     NumberPost *number_1 = new NumberPost;
     number_1->name = "main";
     number_1->name = "main";
     number_1->ReturnValue = "123.456";
     number_1->ReturnValue = "123.456";
+    number_1->ReturnRawValue = "N23.456";
+    number_1->ReturnPreValue = "986.543";
+    number_1->ErrorMessageText = "";
     NUMBERS.push_back(number_1);
     NUMBERS.push_back(number_1);
 
 
     const std::string metricNamePrefix = "ai_on_the_edge_device";
     const std::string metricNamePrefix = "ai_on_the_edge_device";
-    const std::string metricName = metricNamePrefix + "_flow_value";
+    const std::string metricName1 = metricNamePrefix + "_flow_value";
+    const std::string metricName2 = metricNamePrefix + "_flow_raw_value";
+    const std::string metricName3 = metricNamePrefix + "_flow_pre_value";
+    const std::string metricName4 = metricNamePrefix + "_flow_error";
+
+    std::string expected1 ;
+    expected1 = "# HELP " + metricName1 + " current value of meter readout\n# TYPE " + metricName1 + " gauge\n" +
+                metricName1 + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnValue + "\n";
+    
+    expected1 += "# HELP " + metricName2 + " current raw value of meter readout\n# TYPE " + metricName2 + " gauge\n" +
+                metricName2 + "{sequence=\"" + number_1->name + "\"} " + "NaN" + "\n";
+
+    expected1 += "# HELP " + metricName3 + " previous value of meter readout\n# TYPE " + metricName3 + " gauge\n" +
+                metricName3 + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnPreValue + "\n";
+    
+    expected1 += "# HELP " + metricName4 + " Error message text != 'no error'\n# TYPE " + metricName4 + " gauge\n" +
+                metricName4 + "{sequence=\"" + number_1->name + "\"} " + "1" + "\n";
 
 
-    std::string expected1 = "# HELP " + metricName + " current value of meter readout\n# TYPE " + metricName + " gauge\n" +
-                             metricName + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnValue + "\n";
     TEST_ASSERT_EQUAL_STRING(expected1.c_str(), createSequenceMetrics(metricNamePrefix, NUMBERS).c_str());
     TEST_ASSERT_EQUAL_STRING(expected1.c_str(), createSequenceMetrics(metricNamePrefix, NUMBERS).c_str());
 
 
     NumberPost *number_2 = new NumberPost;
     NumberPost *number_2 = new NumberPost;
     number_2->name = "secondary";
     number_2->name = "secondary";
     number_2->ReturnValue = "1.0";
     number_2->ReturnValue = "1.0";
+    number_2->ReturnRawValue = "01.000";
+    number_2->ReturnPreValue = "0.987";
+    number_2->ErrorMessageText = "no error";
     NUMBERS.push_back(number_2);
     NUMBERS.push_back(number_2);
 
 
-    std::string expected2 = "# HELP " + metricName + " current value of meter readout\n# TYPE " + metricName + " gauge\n" +
-                             metricName + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnValue + "\n" +
-                             metricName + "{sequence=\"" + number_2->name + "\"} " + number_2->ReturnValue + "\n";
+    std::string expected2 ;
+    expected2 = "# HELP " + metricName1 + " current value of meter readout\n# TYPE " + metricName1 + " gauge\n" +
+                metricName1 + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnValue + "\n" +
+                metricName1 + "{sequence=\"" + number_2->name + "\"} " + number_2->ReturnValue + "\n";
+    
+    expected2 += "# HELP " + metricName2 + " current raw value of meter readout\n# TYPE " + metricName2 + " gauge\n" +
+                metricName2 + "{sequence=\"" + number_1->name + "\"} " + "NaN" + "\n" +
+                metricName2 + "{sequence=\"" + number_2->name + "\"} " + number_2->ReturnRawValue + "\n";
+
+    expected2 += "# HELP " + metricName3 + " previous value of meter readout\n# TYPE " + metricName3 + " gauge\n" +
+                metricName3 + "{sequence=\"" + number_1->name + "\"} " + number_1->ReturnPreValue + "\n" +
+                metricName3 + "{sequence=\"" + number_2->name + "\"} " + number_2->ReturnPreValue + "\n";
+
+    expected2 += "# HELP " + metricName4 + " Error message text != 'no error'\n# TYPE " + metricName4 + " gauge\n" +
+                metricName4 + "{sequence=\"" + number_1->name + "\"} " + "1" + "\n" +
+                metricName4 + "{sequence=\"" + number_2->name + "\"} " + "0" + "\n";
+
+    
     TEST_ASSERT_EQUAL_STRING(expected2.c_str(), createSequenceMetrics(metricNamePrefix, NUMBERS).c_str());
     TEST_ASSERT_EQUAL_STRING(expected2.c_str(), createSequenceMetrics(metricNamePrefix, NUMBERS).c_str());
 }
 }
 
 

+ 222 - 222
sd-card/html/backup.html

@@ -1,222 +1,222 @@
-<!DOCTYPE html>
-<html lang="en" xml:lang="en"> 
-<head>
-    <title>Backup/Restore Configuration</title>
-    <meta charset="UTF-8" />
-
-    <style>
-        h1 {font-size: 2em;}
-        h2 {font-size: 1.5em; margin-block-start: 0.0em; margin-block-end: 0.2em;}
-        h3 {font-size: 1.2em;}
-        p {font-size: 1em;}
-
-        input[type=number] {
-            width: 138px;
-            padding: 10px 5px;
-            display: inline-block;
-            border: 1px solid #ccc;
-            font-size: 16px; 
-        }
-
-        .button {
-            padding: 5px 10px;
-            width: 205px;
-            font-size: 16px;
-        }
-    </style>
-
-</head>
-
-<body style="font-family: arial; padding: 0px 10px;">
-    <h2>Backup Configuration</h2>
-    <p>With the following action the <a href="/fileserver/config/" target="_self">config</a> folder on the SD-card gets zipped and provided as a download.</p>
-
-    <button class="button" id="startBackup" type="button" onclick="startBackup()">Create Backup</button>
-    <p id=progress></p>
-    <hr>
-    <h2>Restore Configuration</h2>
-    <p>Use the <a href="/fileserver/config/" target="_self">File Server</a> to upload individual files.</p>
-</body>
-
-
-<script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
-<script type="text/javascript" src="jszip.min.js?v=$COMMIT_HASH"></script>
-<script type="text/javascript" src="FileSaver.min.js?v=$COMMIT_HASH"></script>
-<script>
-
-function startBackup() {  
-    document.getElementById("progress").innerHTML = "Creating backup...<br>\n";
-    
-    // Get hostname
-    try {
-        var xhttp = new XMLHttpRequest();
-        xhttp.open("GET", getDomainname() + "/info?type=Hostname", false);
-        xhttp.send();
-        hostname = xhttp.responseText;
-    }
-    catch(err) {
-        setStatus("<span style=\"color: red\">Failed to fetch hostname: " + err.message + "!</span>");
-        return;
-    }
-    
-    // get date/time
-    var dateTime = new Date().toJSON().slice(0,10) + "_" + new Date().toJSON().slice(11,19).replaceAll(":", "-");
-    
-    zipFilename = hostname + "_" + dateTime + ".zip";
-    console.log(zipFilename);
-
-    // Get files list
-    setStatus("Fetching File List...");
-    try {
-        var xhttp = new XMLHttpRequest();
-        xhttp.open("GET", getDomainname() + "/fileserver/config/", false);
-        xhttp.send();
-        
-        var parser = new DOMParser();
-        var content = parser.parseFromString(xhttp.responseText, 'text/html');    }
-    catch(err) {
-        setStatus("Failed to fetch files list: " + err.message);
-        return;
-    }
-    
-    const list = content.querySelectorAll("a");
-    
-    var urls = [];
-    
-    for (a of list) {
-        url = a.getAttribute("href");
-        urls.push(getDomainname() + url);
-    }
-    
-    // Pack as zip and download
-    try {
-        backup(urls, zipFilename);
-        }
-    catch(err) {
-        setStatus("<span style=\"color: red\">Failed to zip files: " + err.message + "!</span>");
-        return;
-    }
-}
-
-
-function fetchFiles(urls, filesData, index, retry, zipFilename) {
-    url = urls[index];
-
-//    console.log(url + " started (" + index + "/" + urls.length + ")");
-    if (retry == 0) {
-        setStatus("&nbsp;- " + getFilenameFromUrl(urls[index]) + " (" + (index+1) + "/" + urls.length + ")...");
-    }
-    else {
-        setStatus("<span style=\"color: gray\">&nbsp;&nbsp;&nbsp;Retrying (" + retry + ")...</span>");
-    }
-
-    const xhr = new XMLHttpRequest();
-    xhr.open('GET', url, true);
-    xhr.responseType = "blob";
-
-    if (retry == 0) { // Short timeout on first retry
-        xhr.timeout = 2000; // time in milliseconds
-    }
-    else if (retry == 1) { // longer timeout
-        xhr.timeout = 5000; // time in milliseconds
-    }
-    else if (retry == 2) { // longer timeout
-        xhr.timeout = 10000; // time in milliseconds
-    }
-    else if (retry == 3) { // longer timeout
-        xhr.timeout = 20000; // time in milliseconds
-    }
-    else { // very long timeout
-        xhr.timeout = 30000; // time in milliseconds
-    }
-
-    xhr.onload = () => { // Request finished
-        //console.log(url + " done");
-
-        filesData[index] = xhr.response;
-
-        if (index == urls.length - 1) {
-            setStatus("Fetched all files");
-            generateZipFile(urls, filesData, zipFilename);
-            return;
-        }
-        else { // Next file
-            fetchFiles(urls, filesData, index+1, 0, zipFilename);
-        }
-    };
-
-    xhr.onprogress = (e) => { // XMLHttpRequest progress ... extend timeout
-        xhr.timeout = xhr.timeout + 500;
-    };
-
-    xhr.onerror = (e) => { // XMLHttpRequest error loading
-        console.log("Error on fetching " + url + "!");
-        if (retry > 5) {
-            setStatus("<span style=\"color: red\">Backup failed, please restart the device and try again!</span>");
-        }
-        else {
-            fetchFiles(urls, filesData, index, retry+1, zipFilename);
-        }
-    };
-
-    xhr.ontimeout = (e) => { // XMLHttpRequest timed out
-        console.log("Timeout on fetching " + url + "!");
-        if (retry > 5) {
-            setStatus("<span style=\"color: red\">Backup failed, please restart the device and try again!</span>");
-        }
-        else {
-            fetchFiles(urls, filesData, index, retry+1, zipFilename);
-        }
-    };
-
-    xhr.send(null);
-}
-
-
-function generateZipFile(urls, filesData, zipFilename) {
-    setStatus("Creating Zip File...");
-
-    var zip = new JSZip();
-
-    for (var i = 0; i < urls.length; i++) {        
-        zip.file(getFilenameFromUrl(urls[i]), filesData[i]);
-    }
-
-    zip.generateAsync({type:"blob"})
-    .then(function(content) {
-        saveAs(content, zipFilename);
-    });
-
-    setStatus("Backup completed");
-}
-
-
-const backup = (urls, zipFilename) => {
-    if(!urls) return;
-
-    /* Testing */
-    /*len = urls.length;
-    for (i = 0; i < len - 3; i++) {
-        urls.pop();
-    }*/
-
-    console.log(urls);
-
-    urlIndex = 0;
-    setStatus("Fetching files...");
-    fetchFiles(urls, [], 0, 0, zipFilename);
-};
-
-
-function setStatus(status) {
-    console.log(status);
-    document.getElementById("progress").innerHTML += status + "<br>\n";
-}
-
-function getFilenameFromUrl(url) {
-    return filename = url.substring(url.lastIndexOf('/')+1);
-}
-
-</script>
-
-</html>
+<!DOCTYPE html>
+<html lang="en" xml:lang="en"> 
+<head>
+    <title>Backup/Restore Configuration</title>
+    <meta charset="UTF-8" />
+
+    <style>
+        h1 {font-size: 2em;}
+        h2 {font-size: 1.5em; margin-block-start: 0.0em; margin-block-end: 0.2em;}
+        h3 {font-size: 1.2em;}
+        p {font-size: 1em;}
+
+        input[type=number] {
+            width: 138px;
+            padding: 10px 5px;
+            display: inline-block;
+            border: 1px solid #ccc;
+            font-size: 16px; 
+        }
+
+        .button {
+            padding: 5px 10px;
+            width: 205px;
+            font-size: 16px;
+        }
+    </style>
+
+</head>
+
+<body style="font-family: arial; padding: 0px 10px;">
+    <h2>Backup Configuration</h2>
+    <p>With the following action the <a href="/fileserver/config/" target="_self">config</a> folder on the SD-card gets zipped and provided as a download.</p>
+
+    <button class="button" id="startBackup" type="button" onclick="startBackup()">Create Backup</button>
+    <p id=progress></p>
+    <hr>
+    <h2>Restore Configuration</h2>
+    <p>Use the <a href="/fileserver/config/" target="_self">File Server</a> to upload individual files.</p>
+</body>
+
+
+<script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
+<script type="text/javascript" src="jszip.min.js?v=$COMMIT_HASH"></script>
+<script type="text/javascript" src="FileSaver.min.js?v=$COMMIT_HASH"></script>
+<script>
+
+function startBackup() {  
+    document.getElementById("progress").innerHTML = "Creating backup...<br>\n";
+    
+    // Get hostname
+    try {
+        var xhttp = new XMLHttpRequest();
+        xhttp.open("GET", getDomainname() + "/info?type=Hostname", false);
+        xhttp.send();
+        hostname = xhttp.responseText;
+    }
+    catch(err) {
+        setStatus("<span style=\"color: red\">Failed to fetch hostname: " + err.message + "!</span>");
+        return;
+    }
+    
+    // get date/time
+    var dateTime = new Date().toJSON().slice(0,10) + "_" + new Date().toJSON().slice(11,19).replaceAll(":", "-");
+    
+    zipFilename = hostname + "_" + dateTime + ".zip";
+    console.log(zipFilename);
+
+    // Get files list
+    setStatus("Fetching File List...");
+    try {
+        var xhttp = new XMLHttpRequest();
+        xhttp.open("GET", getDomainname() + "/fileserver/config/", false);
+        xhttp.send();
+        
+        var parser = new DOMParser();
+        var content = parser.parseFromString(xhttp.responseText, 'text/html');    }
+    catch(err) {
+        setStatus("Failed to fetch files list: " + err.message);
+        return;
+    }
+    
+    const list = content.querySelectorAll("a");
+    
+    var urls = [];
+    
+    for (a of list) {
+        url = a.getAttribute("href");
+        urls.push(getDomainname() + url);
+    }
+    
+    // Pack as zip and download
+    try {
+        backup(urls, zipFilename);
+        }
+    catch(err) {
+        setStatus("<span style=\"color: red\">Failed to zip files: " + err.message + "!</span>");
+        return;
+    }
+}
+
+
+function fetchFiles(urls, filesData, index, retry, zipFilename) {
+    url = urls[index];
+
+//    console.log(url + " started (" + index + "/" + urls.length + ")");
+    if (retry == 0) {
+        setStatus("&nbsp;- " + getFilenameFromUrl(urls[index]) + " (" + (index+1) + "/" + urls.length + ")...");
+    }
+    else {
+        setStatus("<span style=\"color: gray\">&nbsp;&nbsp;&nbsp;Retrying (" + retry + ")...</span>");
+    }
+
+    const xhr = new XMLHttpRequest();
+    xhr.open('GET', url, true);
+    xhr.responseType = "blob";
+
+    if (retry == 0) { // Short timeout on first retry
+        xhr.timeout = 2000; // time in milliseconds
+    }
+    else if (retry == 1) { // longer timeout
+        xhr.timeout = 5000; // time in milliseconds
+    }
+    else if (retry == 2) { // longer timeout
+        xhr.timeout = 10000; // time in milliseconds
+    }
+    else if (retry == 3) { // longer timeout
+        xhr.timeout = 20000; // time in milliseconds
+    }
+    else { // very long timeout
+        xhr.timeout = 30000; // time in milliseconds
+    }
+
+    xhr.onload = () => { // Request finished
+        //console.log(url + " done");
+
+        filesData[index] = xhr.response;
+
+        if (index == urls.length - 1) {
+            setStatus("Fetched all files");
+            generateZipFile(urls, filesData, zipFilename);
+            return;
+        }
+        else { // Next file
+            fetchFiles(urls, filesData, index+1, 0, zipFilename);
+        }
+    };
+
+    xhr.onprogress = (e) => { // XMLHttpRequest progress ... extend timeout
+        xhr.timeout = xhr.timeout + 500;
+    };
+
+    xhr.onerror = (e) => { // XMLHttpRequest error loading
+        console.log("Error on fetching " + url + "!");
+        if (retry > 5) {
+            setStatus("<span style=\"color: red\">Backup failed, please restart the device and try again!</span>");
+        }
+        else {
+            fetchFiles(urls, filesData, index, retry+1, zipFilename);
+        }
+    };
+
+    xhr.ontimeout = (e) => { // XMLHttpRequest timed out
+        console.log("Timeout on fetching " + url + "!");
+        if (retry > 5) {
+            setStatus("<span style=\"color: red\">Backup failed, please restart the device and try again!</span>");
+        }
+        else {
+            fetchFiles(urls, filesData, index, retry+1, zipFilename);
+        }
+    };
+
+    xhr.send(null);
+}
+
+
+function generateZipFile(urls, filesData, zipFilename) {
+    setStatus("Creating Zip File...");
+
+    var zip = new JSZip();
+
+    for (var i = 0; i < urls.length; i++) {        
+        zip.file(getFilenameFromUrl(urls[i]), filesData[i]);
+    }
+
+    zip.generateAsync({type:"blob"})
+    .then(function(content) {
+        saveAs(content, zipFilename);
+    });
+
+    setStatus("Backup completed");
+}
+
+
+const backup = (urls, zipFilename) => {
+    if(!urls) return;
+
+    /* Testing */
+    /*len = urls.length;
+    for (i = 0; i < len - 3; i++) {
+        urls.pop();
+    }*/
+
+    console.log(urls);
+
+    urlIndex = 0;
+    setStatus("Fetching files...");
+    fetchFiles(urls, [], 0, 0, zipFilename);
+};
+
+
+function setStatus(status) {
+    console.log(status);
+    document.getElementById("progress").innerHTML += status + "<br>\n";
+}
+
+function getFilenameFromUrl(url) {
+    return filename = url.substring(url.lastIndexOf('/')+1);
+}
+
+</script>
+
+</html>

+ 207 - 0
sd-card/html/data_export.html

@@ -0,0 +1,207 @@
+<!DOCTYPE html>
+<html lang="en" xml:lang="en"> 
+<head>
+    <title>Data Export (CSV)</title>
+    <meta charset="UTF-8"/>
+
+    <style>
+        h1 {font-size: 2em;}
+        h2 {font-size: 1.5em; margin-block-start: 0.0em; margin-block-end: 0.2em;}
+        h3 {font-size: 1.2em;}
+        p {font-size: 1em;}
+
+        input[type=number] {
+            width: 138px;
+            padding: 10px 5px;
+            display: inline-block;
+            border: 1px solid #ccc;
+            font-size: 16px; 
+        }
+
+        .button {
+            padding: 5px 10px;
+            width: 300px;
+            font-size: 16px;
+        }
+    </style>
+</head>
+
+<body style="font-family: arial; padding: 0px 10px;">
+    <h2>Data Export(CSV)</h2>
+    <p>With the following action the <a href="/fileserver/log/data/" target="_self">data</a> folder on the SD-card gets zipped and provided as a download.</p>
+    <button class="button" id="startExportData" type="button" onclick="startExportData()">Export Data</button>
+    <p></p>
+    <hr>
+    <p id=progress></p>
+
+<script type="text/javascript" src="common.js?v=$COMMIT_HASH"></script>
+<script type="text/javascript" src="jszip.min.js?v=$COMMIT_HASH"></script>
+<script type="text/javascript" src="FileSaver.min.js?v=$COMMIT_HASH"></script>
+
+<script type="text/javascript">
+function startExportData() {  
+    document.getElementById("progress").innerHTML = "Creating Export Data...<br>\n";
+    
+    // Get hostname
+    try {
+        var xhttp = new XMLHttpRequest();
+        xhttp.open("GET", getDomainname() + "/info?type=Hostname", false);
+        xhttp.send();
+        hostname = xhttp.responseText;
+    }
+    catch(err) {
+        setStatus("<span style=\"color: red\">Failed to fetch hostname: " + err.message + "!</span>");
+        return;
+    }
+    
+    // get date/time
+    var dateTime = new Date().toJSON().slice(0,10) + "_" + new Date().toJSON().slice(11,19).replaceAll(":", "-");
+    
+    zipFilename = hostname + "_" + dateTime + "_csv" + ".zip";
+    console.log(zipFilename);
+
+    // Get files list
+    setStatus("Fetching File List...");
+    try {
+        var xhttp = new XMLHttpRequest();
+        xhttp.open("GET", getDomainname() + "/fileserver/log/data/", false);
+        xhttp.send();
+        
+        var parser = new DOMParser();
+        var content = parser.parseFromString(xhttp.responseText, 'text/html');
+    }
+    catch(err) {
+        setStatus("Failed to fetch files list: " + err.message);
+        return;
+    }
+    
+    const list = content.querySelectorAll("a");
+    
+    var urls = [];
+    
+    for (a of list) {
+        url = a.getAttribute("href");
+        urls.push(getDomainname() + url);
+    }
+    
+    // Pack as zip and download
+    try {
+        startExportZip(urls, zipFilename);
+        }
+    catch(err) {
+        setStatus("<span style=\"color: red\">Failed to zip files: " + err.message + "!</span>");
+        return;
+    }
+}
+
+function fetchFiles(urls, filesData, index, retry, zipFilename) {
+    url = urls[index];
+
+    if (retry == 0) {
+        setStatus("&nbsp;- " + getFilenameFromUrl(urls[index]) + " (" + (index+1) + "/" + urls.length + ")...");
+    }
+    else {
+        setStatus("<span style=\"color: gray\">&nbsp;&nbsp;&nbsp;Retrying (" + retry + ")...</span>");
+    }
+
+    const xhr = new XMLHttpRequest();
+    xhr.open('GET', url, true);
+    xhr.responseType = "blob";
+
+    if (retry == 0) { // Short timeout on first retry
+        xhr.timeout = 2000; // time in milliseconds
+    }
+    else if (retry == 1) { // longer timeout
+        xhr.timeout = 5000;
+    }
+    else if (retry == 2) { // longer timeout
+        xhr.timeout = 10000;
+    }
+    else if (retry == 3) { // longer timeout
+        xhr.timeout = 20000;
+    }
+    else { // very long timeout
+        xhr.timeout = 30000;
+    }
+
+    xhr.onload = () => { // Request finished
+        //console.log(url + " done");
+
+        filesData[index] = xhr.response;
+
+        if (index == urls.length - 1) {
+            setStatus("Fetched all files");
+            generateZipFile(urls, filesData, zipFilename);
+            return;
+        }
+        else { // Next file
+            fetchFiles(urls, filesData, index+1, 0, zipFilename);
+        }
+    };
+
+    xhr.onprogress = (e) => { // XMLHttpRequest progress ... extend timeout
+        xhr.timeout = xhr.timeout + 500;
+    };
+
+    xhr.onerror = (e) => { // XMLHttpRequest error loading
+        console.log("Error on fetching " + url + "!");
+        if (retry > 5) {
+            setStatus("<span style=\"color: red\">Data Export failed, please restart the device and try again!</span>");
+        }
+        else {
+            fetchFiles(urls, filesData, index, retry+1, zipFilename);
+        }
+    };
+
+    xhr.ontimeout = (e) => { // XMLHttpRequest timed out
+        console.log("Timeout on fetching " + url + "!");
+        if (retry > 5) {
+            setStatus("<span style=\"color: red\">Data Export failed, please restart the device and try again!</span>");
+        }
+        else {
+            fetchFiles(urls, filesData, index, retry+1, zipFilename);
+        }
+    };
+
+    xhr.send(null);
+}
+
+function generateZipFile(urls, filesData, zipFilename) {
+    setStatus("Creating Zip File...");
+
+    var zip = new JSZip();
+
+    for (var i = 0; i < urls.length; i++) {        
+        zip.file(getFilenameFromUrl(urls[i]), filesData[i]);
+    }
+
+    zip.generateAsync({type:"blob"})
+    .then(function(content) {
+        saveAs(content, zipFilename);
+    });
+
+    setStatus("Data Export completed");
+}
+
+const startExportZip = (urls, zipFilename) => {
+    if(!urls) { return; }
+
+    console.log(urls);
+
+    urlIndex = 0;
+    setStatus("Fetching files...");
+    fetchFiles(urls, [], 0, 0, zipFilename);
+};
+
+function setStatus(status) {
+    console.log(status);
+    document.getElementById("progress").innerHTML += status + "<br>\n";
+}
+
+function getFilenameFromUrl(url) {
+    return filename = url.substring(url.lastIndexOf('/')+1);
+}
+
+</script>
+</body>
+</html>

+ 50 - 45
sd-card/html/edit_alignment.html

@@ -79,6 +79,18 @@
             transform: translate(-50%,-50%);
             transform: translate(-50%,-50%);
             -ms-transform: translate(-50%,-50%);
             -ms-transform: translate(-50%,-50%);
         }
         }
+        
+        #reboot_button {
+            float: none;
+            background-color: #f44336;
+            color: white;
+            padding: 5px;
+            border-radius:
+            5px; font-weight: bold;
+            text-align: center;
+            text-decoration: none;
+            display: inline-block;
+        }
     </style>
     </style>
 
 
     <link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
     <link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
@@ -188,13 +200,11 @@
             param;
             param;
     
     
         function doReboot() {
         function doReboot() {
-            if (confirm("Are you sure you want to reboot? Did you save your changes?")) {
-                var stringota = domainname + "/reboot";
-                window.location = stringota;
-                window.location.href = stringota;
-                window.location.assign(stringota);
-                window.location.replace(stringota);
-            }
+            var stringota = domainname + "/reboot";
+            window.location = stringota;
+            window.location.href = stringota;
+            window.location.assign(stringota);
+            window.location.replace(stringota);
         }
         }
     
     
         function ChangeSelection(){
         function ChangeSelection(){
@@ -215,50 +225,45 @@
             document.getElementById("overlay").style.display = "block";
             document.getElementById("overlay").style.display = "block";
             document.getElementById("overlaytext").innerHTML = "Save Alignment Marker...";
             document.getElementById("overlaytext").innerHTML = "Save Alignment Marker...";
 
 
-            if (confirm("Are you sure you want to save the new alignment marker configuration?")) {
-                function sleep(ms) {
-                    return new Promise(resolve => setTimeout(resolve, ms));
-                }
+            function sleep(ms) {
+                return new Promise(resolve => setTimeout(resolve, ms));
+            }
 
 
-                async function task() {
-                    while (true) {
-                        WriteConfigININew();
-		
-                        if (neueref1 == 1 && neueref2 == 1) {
-                            UpdateConfigReferences(domainname);
-                        }
-                        else if (neueref1 == 1) {
-                            var anzneueref = 1;
-                            UpdateConfigReference(anzneueref, domainname);
-                        }
-                        else if (neueref2 == 1) {
-                            var anzneueref = 2;
-                            UpdateConfigReference(anzneueref, domainname);
-                        }
+            async function task() {
+                while (true) {
+                    WriteConfigININew();
+    
+                    if (neueref1 == 1 && neueref2 == 1) {
+                        UpdateConfigReferences(domainname);
+                    }
+                    else if (neueref1 == 1) {
+                        var anzneueref = 1;
+                        UpdateConfigReference(anzneueref, domainname);
+                    }
+                    else if (neueref2 == 1) {
+                        var anzneueref = 2;
+                        UpdateConfigReference(anzneueref, domainname);
+                    }
 
 
-                        SaveConfigToServer(domainname);
-				
-                        document.getElementById("updatemarker").disabled = false;
-                        // document.getElementById("savemarker").disabled = true;
-                        // document.getElementById("enhancecontrast").disabled = true;
+                    SaveConfigToServer(domainname);
+            
+                    document.getElementById("updatemarker").disabled = false;
+                    // document.getElementById("savemarker").disabled = true;
+                    // document.getElementById("enhancecontrast").disabled = true;
 
 
-                        EnDisableItem(false, "savemarker", true);
-                        EnDisableItem(false, "enhancecontrast", true);
+                    EnDisableItem(false, "savemarker", true);
+                    EnDisableItem(false, "enhancecontrast", true);
 
 
-                        document.getElementById("overlay").style.display = "none";
-                        firework.launch('Alignment marker saved. They will get applied after next reboot', 'success', 5000);
-                        return;
-                    }
+                    document.getElementById("overlay").style.display = "none";
+                    firework.launch('Alignment marker saved. They will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
+                    return;
                 }
                 }
-
-                setTimeout(function () {
-                    // Delay so the overlay gets shown
-                    task();
-                }, 1);
-            }
-            else {
-                document.getElementById("overlay").style.display = "none";
             }
             }
+
+            setTimeout(function () {
+                // Delay so the overlay gets shown
+                task();
+            }, 1);
         }
         }
 
 
         function EnhanceContrast() {
         function EnhanceContrast() {

+ 26 - 16
sd-card/html/edit_analog.html

@@ -5,6 +5,20 @@
     <meta charset="UTF-8" />
     <meta charset="UTF-8" />
     <title>Analog ROI</title>
     <title>Analog ROI</title>
 
 
+    <style>
+        #reboot_button {
+            float: none;
+            background-color: #f44336;
+            color: white;
+            padding: 5px;
+            border-radius:
+            5px; font-weight: bold;
+            text-align: center;
+            text-decoration: none;
+            display: inline-block;
+        }
+    </style>
+
     <link href="edit_style.css" rel="stylesheet">
     <link href="edit_style.css" rel="stylesheet">
     <link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
     <link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
 
 
@@ -185,13 +199,11 @@ The following settings are only used for easier setup, they are <b>not</b> persi
         domainname = getDomainname();
         domainname = getDomainname();
 
 
     function doReboot() {
     function doReboot() {
-        if (confirm("Are you sure you want to reboot? Did you save your changes?")) {
-            var stringota = getDomainname() + "/reboot";
-            window.location = stringota;
-            window.location.href = stringota;
-            window.location.assign(stringota);
-            window.location.replace(stringota);
-        }
+        var stringota = getDomainname() + "/reboot";
+        window.location = stringota;
+        window.location.href = stringota;
+        window.location.assign(stringota);
+        window.location.replace(stringota);
     }
     }
     
     
     function EnDisableAnalog() {
     function EnDisableAnalog() {
@@ -331,16 +343,14 @@ The following settings are only used for easier setup, they are <b>not</b> persi
     }
     }
 
 
     function SaveToConfig() {
     function SaveToConfig() {
-        if (confirm("Are you sure you want to save the new analog ROI configuration?")) {
-            //_zwcat = getConfigCategory();
-            cofcat["Analog"]["enabled"] = document.getElementById("Category_Analog_enabled").checked;
-            WriteConfigININew();
-            SaveConfigToServer(domainname);
-            UpdateROIs();
-            document.getElementById("saveroi").disabled = true;
+        //_zwcat = getConfigCategory();
+        cofcat["Analog"]["enabled"] = document.getElementById("Category_Analog_enabled").checked;
+        WriteConfigININew();
+        SaveConfigToServer(domainname);
+        UpdateROIs();
+        document.getElementById("saveroi").disabled = true;
 
 
-            firework.launch('Configuration saved. It will get applied after next reboot', 'success', 5000);
-        }
+        firework.launch('Configuration saved. It will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
     }
     }
 
 
     function ShowMultiplier() {
     function ShowMultiplier() {

+ 23 - 22
sd-card/html/edit_config_raw.html

@@ -20,6 +20,18 @@
 	textarea {
 	textarea {
 		font-size: 15px;
 		font-size: 15px;
 	}
 	}
+
+	#reboot_button {
+		float: none;
+		background-color: #f44336;
+		color: white;
+		padding: 5px;
+		border-radius:
+		5px; font-weight: bold;
+		text-align: center;
+		text-decoration: none;
+		display: inline-block;
+	}
 </style>
 </style>
 
 
 <link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
 <link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
@@ -38,15 +50,8 @@
 		</td>
 		</td>
 	</table>
 	</table>
 
 
-	<table>
-		<td>
-			<button class="button" onclick="saveTextAsFile()">Save Config</button>
-		</td>
-	    <td>
-	        <button class="button" id="reboot" type="button" onclick="doReboot()">Reboot to activate changes</button>
-		</td>
-	</table>
-
+	<hr>
+	<button class="button" onclick="saveTextAsFile()">Save Config</button>
 
 
 	<script type="text/javascript" src="readconfigparam.js?v=$COMMIT_HASH"></script>
 	<script type="text/javascript" src="readconfigparam.js?v=$COMMIT_HASH"></script>
 	<script type="text/javascript" src="readconfigcommon.js?v=$COMMIT_HASH"></script>
 	<script type="text/javascript" src="readconfigcommon.js?v=$COMMIT_HASH"></script>
@@ -64,23 +69,19 @@
 
 
 	function saveTextAsFile()
 	function saveTextAsFile()
 	{
 	{
-		if (confirm("Are you sure you want to save the configuration?")) {
-			FileDeleteOnServer("/config/config.ini", domainname);
-			var textToSave = document.getElementById("inputTextToSave").value;
-			FileSendContent(textToSave, "/config/config.ini", domainname);
+		FileDeleteOnServer("/config/config.ini", domainname);
+		var textToSave = document.getElementById("inputTextToSave").value;
+		FileSendContent(textToSave, "/config/config.ini", domainname);
 
 
-			firework.launch('Configuration saved. It will get applied after next reboot', 'success', 5000);
-		}
+		firework.launch('Configuration saved. It will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
 	}
 	}
 
 
 	function doReboot() {
 	function doReboot() {
-		if (confirm("Are you sure you want to reboot?")) {
-			var stringota = "/reboot";
-			window.location = stringota;
-			window.location.href = stringota;
-			window.location.assign(stringota);
-			window.location.replace(stringota);
-		}
+		var stringota = "/reboot";
+		window.location = stringota;
+		window.location.href = stringota;
+		window.location.assign(stringota);
+		window.location.replace(stringota);
 	}
 	}
 	
 	
 	LoadConfigNeu();
 	LoadConfigNeu();

+ 36 - 33
sd-card/html/edit_config_template.html

@@ -183,7 +183,19 @@
         color: white;
         color: white;
         transform: translate(-50%,-50%);
         transform: translate(-50%,-50%);
         -ms-transform: translate(-50%,-50%);
         -ms-transform: translate(-50%,-50%);
-    }	
+    }
+
+	#reboot_button {
+		float: none;
+		background-color: #f44336;
+		color: white;
+		padding: 5px;
+		border-radius:
+		5px; font-weight: bold;
+		text-align: center;
+		text-decoration: none;
+		display: inline-block;
+	}
 </style>
 </style>
 
 
 <link rel="stylesheet" href="mkdocs_theme.css?v=$COMMIT_HASH" />
 <link rel="stylesheet" href="mkdocs_theme.css?v=$COMMIT_HASH" />
@@ -897,7 +909,7 @@
 
 
 		<tr style="margin-top:12px">
 		<tr style="margin-top:12px">
 			<td class="indent1" style="padding-top:25px" colspan="3">
 			<td class="indent1" style="padding-top:25px" colspan="3">
-				<b>Parameter per number sequence:</b>
+				<b>The following parameters are configurable individually for each number sequence:</b>
                 <select 
                 <select 
 					style="font-weight: bold; margin-left:17px" id="Numbers_value1" onchange="numberChanged()">
 					style="font-weight: bold; margin-left:17px" id="Numbers_value1" onchange="numberChanged()">
 				</select>
 				</select>
@@ -1188,8 +1200,8 @@
 					<option value="energy_mwh">Energymeter (Value: MWh, Rate: MW)</option>
 					<option value="energy_mwh">Energymeter (Value: MWh, Rate: MW)</option>
 					<option value="energy_gj">Energymeter (Value: GJ, Rate: GJ/h)</option>
 					<option value="energy_gj">Energymeter (Value: GJ, Rate: GJ/h)</option>
 					<option value="temperature_c">Thermometer (Value: °C, Rate: °C/min)</option>
 					<option value="temperature_c">Thermometer (Value: °C, Rate: °C/min)</option>
-					<option value="temperature_c">Thermometer (Value: °F, Rate: °F/min)</option>
-					<option value="temperature_c">Thermometer (Value: K, Rate: K/min)</option>
+					<option value="temperature_f">Thermometer (Value: °F, Rate: °F/min)</option>
+					<option value="temperature_k">Thermometer (Value: K, Rate: K/min)</option>
 				</select>
 				</select>
 			</td>
 			</td>
 			<td>$TOOLTIP_MQTT_MeterType</td>
 			<td>$TOOLTIP_MQTT_MeterType</td>
@@ -1208,7 +1220,7 @@
 		
 		
 		<tr class="MQTTItem" style="margin-top:12px">
 		<tr class="MQTTItem" style="margin-top:12px">
 			<td class="indent1" style="padding-top:25px" colspan="3">
 			<td class="indent1" style="padding-top:25px" colspan="3">
-				<b>Parameter per number sequence:</b>
+				<b>The following parameters are configurable individually for each number sequence:</b>
                 <select
                 <select
 					style="font-weight: bold; margin-left:17px" id="NumbersMQTTIdx_value1" onchange="numberMQTTIdxChanged()">
 					style="font-weight: bold; margin-left:17px" id="NumbersMQTTIdx_value1" onchange="numberMQTTIdxChanged()">
                 </select>
                 </select>
@@ -1937,11 +1949,11 @@
 
 
 
 
 		<!------------- Autotimer ------------------>
 		<!------------- Autotimer ------------------>
-		<!--
 		<tr style="border-bottom: 2px solid lightgray;">
 		<tr style="border-bottom: 2px solid lightgray;">
 			<td colspan="3" style="padding-left: 0px; padding-bottom: 3px;"><h4>Auto Timer</h4></td>
 			<td colspan="3" style="padding-left: 0px; padding-bottom: 3px;"><h4>Auto Timer</h4></td>
 		</tr>
 		</tr>
-
+		
+		<!--
 		<tr class="expert" unused_id="ex13">
 		<tr class="expert" unused_id="ex13">
 			<td class="indent1">
 			<td class="indent1">
 			    <class id="AutoTimer_AutoStart_text" style="color:black;">Automatic Round Start</class>
 			    <class id="AutoTimer_AutoStart_text" style="color:black;">Automatic Round Start</class>
@@ -2110,14 +2122,9 @@
 		</tr>
 		</tr>
 	</table>
 	</table>
 
 
-	<table style="padding-top:10px">
-		<td>
-			<button class="button" onclick="saveTextAsFile()">Save Config</button>
-		</td>
-	    <td>
-	        <button class="button" id="reboot" type="button" onclick="doReboot()">Reboot to activate changes</button>
-		</td>
-	</table>
+	<hr>
+	<button class="button" onclick="saveTextAsFile()">Save Config</button>
+
 </div>
 </div>
 
 
 
 
@@ -2673,18 +2680,16 @@ function saveTextAsFile() {
         return;
         return;
     }
     }
 
 
-    if (confirm("Are you sure you want to save the configuration?")) {
-        ReadParameterAll();
-        WriteConfigININew();
-        SaveConfigToServer(domainname);
+	ReadParameterAll();
+	WriteConfigININew();
+	SaveConfigToServer(domainname);
 
 
-        firework.launch('Configuration saved. It will get applied after the next reboot!', 'success', 5000);
+	firework.launch('Configuration saved. It will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
 
 
-        if (changeCamValue == 1) {
-            camSettingsSet();
-            firework.launch('You have changed the camera settings, so creating a new reference image and updating the alignment marks is mandatory!', 'success', 10000);		
-        }	    
-    }
+	if (changeCamValue == 1) {
+		camSettingsSet();
+		firework.launch('You have changed the camera settings, creating a new reference image and updating the alignment marks is mandatory!', 'success', 5000);
+	}
 }
 }
 
 
 function camSettingsSet(){
 function camSettingsSet(){
@@ -2875,7 +2880,7 @@ function camSettingsSet(){
             
             
             if (xhttp.responseText == "CamSettingsSet") {
             if (xhttp.responseText == "CamSettingsSet") {
                 document.getElementById("overlay").style.display = "none";
                 document.getElementById("overlay").style.display = "none";
-                firework.launch('Cam Settings saved', 'success', 2000);
+                firework.launch('Cam Settings saved', 'success', 5000);
                 return;
                 return;
             }
             }
             else {
             else {
@@ -2903,13 +2908,11 @@ function camSettingsSet(){
 }
 }
 	
 	
 function doReboot() {
 function doReboot() {
-    if (confirm("Are you sure you want to reboot?")) {
-        var stringota = domainname + "/reboot";
-        window.location = stringota;
-        window.location.href = stringota;
-        window.location.assign(stringota);
-        window.location.replace(stringota);
-    }
+	var stringota = domainname + "/reboot";
+	window.location = stringota;
+	window.location.href = stringota;
+	window.location.assign(stringota);
+	window.location.replace(stringota);
 }
 }
 
 
 function FormatDecimalValue(_param, _cat, _name) {
 function FormatDecimalValue(_param, _cat, _name) {

+ 25 - 16
sd-card/html/edit_digits.html

@@ -4,6 +4,19 @@
 <head>
 <head>
     <meta charset="UTF-8" />
     <meta charset="UTF-8" />
     <title>Digit ROI</title>
     <title>Digit ROI</title>
+    <style>
+        #reboot_button {
+            float: none;
+            background-color: #f44336;
+            color: white;
+            padding: 5px;
+            border-radius:
+            5px; font-weight: bold;
+            text-align: center;
+            text-decoration: none;
+            display: inline-block;
+        }
+    </style>
 
 
     <link href="edit_style.css" rel="stylesheet">
     <link href="edit_style.css" rel="stylesheet">
     <link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
     <link href="firework.css?v=$COMMIT_HASH" rel="stylesheet">
@@ -204,13 +217,11 @@
         domainname = getDomainname();
         domainname = getDomainname();
 
 
     function doReboot() {
     function doReboot() {
-        if (confirm("Are you sure you want to reboot? Did you save your changes?")) {
-            var stringota = getDomainname() + "/reboot";
-            window.location = stringota;
-            window.location.href = stringota;
-            window.location.assign(stringota);
-            window.location.replace(stringota);
-        }
+        var stringota = getDomainname() + "/reboot";
+        window.location = stringota;
+        window.location.href = stringota;
+        window.location.assign(stringota);
+        window.location.replace(stringota);
     }
     }
 
 
     function EnDisableDigits() {
     function EnDisableDigits() {
@@ -359,16 +370,14 @@
     }
     }
 
 
     function SaveToConfig() {
     function SaveToConfig() {
-        if (confirm("Are you sure you want to save the new digit ROI configuration?")) {
-            // _zwcat = getConfigCategory();
-            cofcat["Digits"]["enabled"] = document.getElementById("Category_Digits_enabled").checked;
-            WriteConfigININew();
-            SaveConfigToServer(domainname);
-            UpdateROIs();
-            document.getElementById("saveroi").disabled = true;
+        // _zwcat = getConfigCategory();
+        cofcat["Digits"]["enabled"] = document.getElementById("Category_Digits_enabled").checked;
+        WriteConfigININew();
+        SaveConfigToServer(domainname);
+        UpdateROIs();
+        document.getElementById("saveroi").disabled = true;
 
 
-            firework.launch('Configuration saved. It will get applied after next reboot', 'success', 5000);
-        }
+        firework.launch('Configuration saved. It will get applied after the next reboot!<br><br>\n<a id="reboot_button" onclick="doReboot()">reboot now</a>', 'success', 5000);
     }
     }
 
 
     function ShowMultiplier() {
     function ShowMultiplier() {

+ 7 - 17
sd-card/html/edit_reference.html

@@ -395,22 +395,12 @@
 
 
     <script type="text/javascript">
     <script type="text/javascript">
         var canvas = document.getElementById('canvas'),
         var canvas = document.getElementById('canvas'),
-            domainname = getDomainname(),
-            context = canvas.getContext('2d'),
-            imageObj = new Image(),
-            isActReference = false,
-            param,
-			category;
-
-        function doReboot() {
-            if (confirm("Are you sure you want to reboot? Did you save the config?")) {
-               var stringota = domainname + "/reboot";
-               window.location = stringota;
-               window.location.href = stringota;
-               window.location.assign(stringota);
-               window.location.replace(stringota);
-            }
-        }
+        domainname = getDomainname(),
+        context = canvas.getContext('2d'),
+        imageObj = new Image(),
+        isActReference = false,
+        param,
+        category;
 
 
         function cameraParameterChanged() {
         function cameraParameterChanged() {
             document.getElementById("savereferenceimage").disabled = true;
             document.getElementById("savereferenceimage").disabled = true;
@@ -738,7 +728,7 @@
             
             
                     if (xhttp.responseText == "CamSettingsSet") {
                     if (xhttp.responseText == "CamSettingsSet") {
 						document.getElementById("overlay").style.display = "none";
 						document.getElementById("overlay").style.display = "none";
-                        firework.launch('Cam Settings saved', 'success', 2000);
+                        firework.launch('Cam Settings saved', 'success', 5000);
                         return;
                         return;
                     }
                     }
                     else {
                     else {

+ 50 - 0
sd-card/html/file_server.css

@@ -0,0 +1,50 @@
+h1 {font-size: 2em;}
+h2 {font-size: 1.5em; margin-block-start: 0.0em; margin-block-end: 0.2em;}
+h3 {font-size: 1.2em;}
+p {font-size: 1em;}
+
+#files_table {
+  font-family: Arial, Helvetica, sans-serif;
+  border-collapse: collapse;
+  width: 100%;
+}
+
+#files_table td, #files_table th {
+  border: 1px solid #ddd;
+  padding: 8px;
+}
+
+#files_table tr:nth-child(even){
+  background-color: #f2f2f2;
+}
+
+#files_table tr:hover {
+  background-color: #ddd;
+}
+
+#files_table th {
+  padding-top: 12px;
+  padding-bottom: 12px;
+  text-align: left;
+  background-color:lightgrey;
+  color: black;
+}
+
+input[type=file] {
+  padding: 5px 0px;
+  display: inline-block;
+  font-size: 16px; 
+}
+
+input[type=text] {
+  padding: 5px 10px;
+  display: inline-block;
+  border: 1px solid #ccc;
+  font-size: 16px; 
+}
+
+.button {
+  padding: 4px 10px;
+  width: 100px;
+  font-size: 16px;	
+}

+ 0 - 206
sd-card/html/file_server.html

@@ -1,206 +0,0 @@
-<!DOCTYPE html>
-<html lang="en" xml:lang="en">
-
-    <head>
-        <link href="/firework.css?v=$COMMIT_HASH" rel="stylesheet">
-        <script type="text/javascript" src="/jquery-3.6.0.min.js?v=$COMMIT_HASH"></script>
-        <script type="text/javascript" src="/firework.js?v=$COMMIT_HASH"></script>
-	
-        <style>
-            h1 {font-size: 2em;}
-            h2 {font-size: 1.5em; margin-block-start: 0.0em; margin-block-end: 0.2em;}
-            h3 {font-size: 1.2em;}
-            p {font-size: 1em;}
-
-            #files_table {
-                font-family: Arial, Helvetica, sans-serif;
-                border-collapse: collapse;
-                width: 100%;
-            }
-
-            #files_table td, #files_table th {
-                border: 1px solid #ddd;
-                padding: 8px;
-            }
-
-            #files_table tr:nth-child(even){
-                background-color: #f2f2f2;
-            }
-
-            #files_table tr:hover {
-                background-color: #ddd;
-            }
-
-            #files_table th {
-                padding-top: 12px;
-                padding-bottom: 12px;
-                text-align: left;
-                background-color:lightgrey;
-                color: black;
-            }
-
-            input[type=file] {
-                padding: 5px 0px;
-                display: inline-block;
-                font-size: 16px; 
-            }
-
-            input[type=text] {
-                padding: 5px 10px;
-                display: inline-block;
-                border: 1px solid #ccc;
-                font-size: 16px; 
-            }
-
-            .button {
-                padding: 4px 10px;
-                width: 100px;
-                font-size: 16px;	
-		    }
-        </style>
-    </head>
-    
-    </body>
-        <table class="fixed" border="0" width=100% style="font-family: arial">
-            <tr>
-                <td style="vertical-align: top;width: 300px;">
-                    <h2>Fileserver</h2>
-                </td>
-                <td rowspan="2">
-                    <table border="0" style="width:100%">
-                        <tr>
-                            <td style="width:80px">
-                                <label for="newfile">Source</label>
-                            </td>
-                            <td colspan="2">
-                                <input id="newfile" type="file" onchange="setpath()" style="width:100%;">
-                            </td>
-                        </tr>
-                        
-                        <tr>
-                            <td>
-                                <label for="filepath">Destination</label>
-                            </td>
-                            <td>
-                                <input id="filepath" type="text" style="width:94%;">
-                            </td>
-                            <td>
-                                <button id="upload" type="button" class="button" onclick="upload()">Upload</button>
-                            </td>
-                        </tr>
-                    </table>
-                </td>
-            </tr>
-            <tr></tr>
-            <tr>
-                <td colspan="2">
-                    <button style="font-size:16px; padding: 5px 10px" id="dirup" type="button" onclick="dirup()" disabled>&#129145; Directory up</button>
-                    <span style="padding-left:15px" id="currentpath"></span>
-                </td>
-            </tr>
-        </table>
-
-        <script type="text/javascript" src="/common.js?v=$COMMIT_HASH"></script>
-
-        <script type="text/javascript">
-        function setpath() {
-            var fileserverpraefix = "/fileserver";
-            var anz_zeichen_fileserver = fileserverpraefix.length;
-            var default_path = window.location.pathname.substring(anz_zeichen_fileserver) + document.getElementById("newfile").files[0].name;
-            document.getElementById("filepath").value = default_path;
-        }
-
-        function dirup() {
-            var str = window.location.href;
-            str = str.substring(0, str.length-1);
-            var zw = str.indexOf("/");
-            var found = zw;
-            while (zw >= 0)
-            {
-                zw = str.indexOf("/", found+1);  
-                if (zw >= 0)
-                    found = zw;
-            }
-            var res = str.substring(0, found+1);
-
-            window.location.href = res;	
-        }
-
-
-        function upload() {
-            var filePath = document.getElementById("filepath").value;
-            var upload_path = "/upload/" + filePath;
-            var fileInput = document.getElementById("newfile").files;
-
-            /* Max size of an individual file. Make sure this
-            * value is same as that set in file_server.c */
-            var MAX_FILE_SIZE = 8000*1024;
-            var MAX_FILE_SIZE_STR = "8000KB";
-
-            if (fileInput.length == 0) {
-                firework.launch('No file selected!', 'danger', 30000);
-            } else if (filePath.length == 0) {
-                firework.launch('File path on server is not set!', 'danger', 30000);
-            } else if (filePath.length > 100) {
-                firework.launch('Filename is to long! Max 100 characters.', 'danger', 30000);
-            } else if (filePath.indexOf(' ') >= 0) {
-                firework.launch('File path on server cannot have spaces!', 'danger', 30000);
-            } else if (filePath[filePath.length-1] == '/') {
-                firework.launch('File name not specified after path!', 'danger', 30000);
-            } else if (fileInput[0].size > MAX_FILE_SIZE) {
-                firework.launch("File size must be less than " + MAX_FILE_SIZE_STR + "!", 'danger', 30000);
-            } else {
-                document.getElementById("newfile").disabled = true;
-                document.getElementById("filepath").disabled = true;
-                document.getElementById("upload").disabled = true;
-
-                var file = fileInput[0];
-                var xhttp = new XMLHttpRequest();
-                xhttp.onreadystatechange = function() {
-                    if (xhttp.readyState == 4) {
-                        if (xhttp.status == 200) {
-                            document.open();
-                            document.write(xhttp.responseText);
-                            document.close();
-                            firework.launch('File upload completed', 'success', 5000);
-                        } else if (xhttp.status == 0) {
-                            firework.launch('Server closed the connection abruptly!', 'danger', 30000);
-                            UpdatePage(false);
-                        } else {
-                            firework.launch('An error occured: ' + xhttp.responseText, 'danger', 30000);
-                            UpdatePage(false);
-                        }
-                    }
-                };
-                xhttp.open("POST", upload_path, true);
-                xhttp.send(file);
-            }
-        }
-
-
-        function checkAtRootLevel(res) {
-            if (getPath() == "/fileserver/") { // Already at root level
-                document.getElementById("dirup").disabled = true;
-                console.log("Already on sd-card root level!");
-                return true;
-            }
-
-            document.getElementById("dirup").disabled = false;
-            return false;
-        }
-
-
-        function getPath() {
-            return window.location.pathname.replace(/\/+$/, '') + "/"
-        }
-
-        checkAtRootLevel();
-
-        console.log("Current path: " + getPath().replace("/fileserver", ""));
-        document.getElementById("currentpath").innerHTML = "Current path: <b>" + getPath().replace("/fileserver", "") + "</b>";
-
-        document.cookie = "page=" + getPath() + "; path=/";
-
-        </script>
-    </body>
-</html>

+ 93 - 0
sd-card/html/file_server.js

@@ -0,0 +1,93 @@
+function setpath() {
+  var fileserverpraefix = "/fileserver";
+  var anz_zeichen_fileserver = fileserverpraefix.length;
+  var default_path = window.location.pathname.substring(anz_zeichen_fileserver) + document.getElementById("newfile").files[0].name;
+  document.getElementById("filepath").value = default_path;
+}
+
+function dirup() {
+  var str = window.location.href;
+  str = str.substring(0, str.length-1);
+  var zw = str.indexOf("/");
+  var found = zw;
+  while (zw >= 0) {
+    zw = str.indexOf("/", found+1);  
+    if (zw >= 0) { 
+      found = zw; 
+    }
+  }
+  var res = str.substring(0, found+1);
+  window.location.href = res;	
+}
+
+function upload() {
+  var filePath = document.getElementById("filepath").value;
+  var upload_path = "/upload/" + filePath;
+  var fileInput = document.getElementById("newfile").files;
+
+  // Max size of an individual file. Make sure this value is same as that set in file_server.c
+  var MAX_FILE_SIZE = 8000*1024;
+  var MAX_FILE_SIZE_STR = "8000KB";
+
+  if (fileInput.length == 0) {
+    firework.launch('No file selected!', 'danger', 30000);
+  } else if (filePath.length == 0) {
+    firework.launch('File path on server is not set!', 'danger', 30000);
+  } else if (filePath.length > 100) {
+    firework.launch('Filename is to long! Max 100 characters.', 'danger', 30000);
+  } else if (filePath.indexOf(' ') >= 0) {
+    firework.launch('File path on server cannot have spaces!', 'danger', 30000);
+  } else if (filePath[filePath.length-1] == '/') {
+    firework.launch('File name not specified after path!', 'danger', 30000);
+  } else if (fileInput[0].size > MAX_FILE_SIZE) {
+    firework.launch("File size must be less than " + MAX_FILE_SIZE_STR + "!", 'danger', 30000);
+  } else {
+    document.getElementById("newfile").disabled = true;
+    document.getElementById("filepath").disabled = true;
+    document.getElementById("upload").disabled = true;
+
+    var file = fileInput[0];
+    var xhttp = new XMLHttpRequest();
+    xhttp.onreadystatechange = function() {
+      if (xhttp.readyState == 4) {
+        if (xhttp.status == 200) {
+          document.open();
+          document.write(xhttp.responseText);
+          document.close();
+          firework.launch('File upload completed', 'success', 5000);
+        } else if (xhttp.status == 0) {
+          firework.launch('Server closed the connection abruptly!', 'danger', 30000);
+          UpdatePage(false);
+        } else {
+          firework.launch('An error occured: ' + xhttp.responseText, 'danger', 30000);
+          UpdatePage(false);
+        }
+      }
+    };
+    xhttp.open("POST", upload_path, true);
+    xhttp.send(file);
+  }
+}
+
+function checkAtRootLevel(res) {
+  if (getPath() == "/fileserver/") { 
+    // Already at root level
+    document.getElementById("dirup").disabled = true;
+    console.log("Already on sd-card root level!");
+    return true;
+  }
+
+  document.getElementById("dirup").disabled = false;
+  return false;
+}
+
+function getPath() {
+  return window.location.pathname.replace(/\/+$/, '') + "/"
+}
+
+function initFileServer() {
+  checkAtRootLevel();
+  console.log("Current path: " + getPath().replace("/fileserver", ""));
+  document.getElementById("currentpath").innerHTML = "Current path: <b>" + getPath().replace("/fileserver", "") + "</b>";
+  document.cookie = "page=" + getPath() + "; path=/";
+}

+ 1 - 0
sd-card/html/index.html

@@ -142,6 +142,7 @@
             <li><a href="#" onclick="loadPage('graph.html?v=$COMMIT_HASH');">Data Graph</a></li>
             <li><a href="#" onclick="loadPage('graph.html?v=$COMMIT_HASH');">Data Graph</a></li>
             <li><a href="#" onclick="loadPage('data.html?v=$COMMIT_HASH');">Data Table</a></li>
             <li><a href="#" onclick="loadPage('data.html?v=$COMMIT_HASH');">Data Table</a></li>
             <li><a href="#" onclick="loadPage(getDomainname() + '/fileserver/log/data/');">Data Files</a></li>
             <li><a href="#" onclick="loadPage(getDomainname() + '/fileserver/log/data/');">Data Files</a></li>
+            <li><a href="#" onclick="loadPage('data_export.html?v=$COMMIT_HASH');">Data Export</a></li>
         </ul>
         </ul>
     </li>
     </li>
 
 

+ 5 - 7
sd-card/html/reboot_page.html

@@ -34,13 +34,11 @@ p {font-size: 1em;}
 
 
 <script>
 <script>
 function doReboot() {
 function doReboot() {
-	// if (confirm("Are you sure you want to reboot the ESP32?")) {
-		var stringota = getDomainname() + "/reboot";
-		window.location = stringota;
-		window.location.href = stringota;
-		window.location.assign(stringota);
-		window.location.replace(stringota);
-	// }
+	var stringota = getDomainname() + "/reboot";
+	window.location = stringota;
+	window.location.href = stringota;
+	window.location.assign(stringota);
+	window.location.replace(stringota);
 }
 }
 </script>
 </script>
 
 

BIN=BIN
sd-card/html/searchicon.png


+ 2 - 2
sd-card/html/timezones.html

@@ -8,7 +8,7 @@
 }
 }
 
 
 #myInput {
 #myInput {
-  background-image: url('/css/searchicon.png');
+  background-image: url('searchicon.png');
   background-position: 10px 10px;
   background-position: 10px 10px;
   background-repeat: no-repeat;
   background-repeat: no-repeat;
   width: 100%;
   width: 100%;
@@ -539,4 +539,4 @@
     
     
     </body>
     </body>
     </html>
     </html>
-    
+    

+ 2 - 2
sd-card/html/wlan_config.html

@@ -29,7 +29,7 @@
 //    var xhttp = new XMLHttpRequest();
 //    var xhttp = new XMLHttpRequest();
 //    xhttp.onreadystatechange = function() {if (xhttp.readyState == 4) {if (xhttp.status == 200) {document.reload();}}};
 //    xhttp.onreadystatechange = function() {if (xhttp.readyState == 4) {if (xhttp.status == 200) {document.reload();}}};
 if (!file.name.includes("remote-setup")){
 if (!file.name.includes("remote-setup")){
-    if (!confirm("The zip file name should contain \"...remote-setup...\". Are you sure that you have downloaded the correct file?"))
+    if (!confirm("The zip file name should contain \"...remote-setup...\". Are you sure you have downloaded the correct file?"))
         return;
         return;
 }
 }
 
 
@@ -41,7 +41,7 @@ if (!file.name.includes("remote-setup")){
         var file = document.getElementById("newfile").files[0];
         var file = document.getElementById("newfile").files[0];
         if (!file.name.includes("remote-setup"))
         if (!file.name.includes("remote-setup"))
         {
         {
-            if (!confirm("The zip file name should contain \"...remote-setup...\". Are you sure that you have downloaded the correct file?"))
+            if (!confirm("The zip file name should contain \"...remote-setup...\". Are you sure you have downloaded the correct file?"))
                 return;
                 return;
         }
         }