From 20ae646561f204004960fb050a2f4bb189bce9ff Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Thu, 5 Jan 2023 01:44:38 +0100 Subject: [PATCH] webapp: Allow download of individual config files --- include/WebApi_config.h | 1 + src/WebApi_config.cpp | 40 +++++++++++++++++++++++++- webapp/src/types/Config.ts | 7 +++++ webapp/src/views/ConfigAdminView.vue | 43 ++++++++++++++++++++++------ 4 files changed, 82 insertions(+), 9 deletions(-) create mode 100644 webapp/src/types/Config.ts diff --git a/include/WebApi_config.h b/include/WebApi_config.h index 42bea04..e022af6 100644 --- a/include/WebApi_config.h +++ b/include/WebApi_config.h @@ -11,6 +11,7 @@ public: private: void onConfigGet(AsyncWebServerRequest* request); void onConfigDelete(AsyncWebServerRequest* request); + void onConfigListGet(AsyncWebServerRequest* request); void onConfigUploadFinish(AsyncWebServerRequest* request); void onConfigUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final); diff --git a/src/WebApi_config.cpp b/src/WebApi_config.cpp index bd08745..a2233d2 100644 --- a/src/WebApi_config.cpp +++ b/src/WebApi_config.cpp @@ -22,6 +22,7 @@ void WebApiConfigClass::init(AsyncWebServer* server) _server->on("/api/config/get", HTTP_GET, std::bind(&WebApiConfigClass::onConfigGet, this, _1)); _server->on("/api/config/delete", HTTP_POST, std::bind(&WebApiConfigClass::onConfigDelete, this, _1)); + _server->on("/api/config/list", HTTP_GET, std::bind(&WebApiConfigClass::onConfigListGet, this, _1)); _server->on("/api/config/upload", HTTP_POST, std::bind(&WebApiConfigClass::onConfigUploadFinish, this, _1), std::bind(&WebApiConfigClass::onConfigUpload, this, _1, _2, _3, _4, _5, _6)); @@ -37,7 +38,17 @@ void WebApiConfigClass::onConfigGet(AsyncWebServerRequest* request) return; } - request->send(LittleFS, CONFIG_FILENAME, String(), true); + String requestFile = CONFIG_FILENAME; + if (request->hasParam("file")) { + String name = "/" + request->getParam("file")->value(); + if (LittleFS.exists(name)) { + requestFile = name; + } else { + request->send(404); + } + } + + request->send(LittleFS, requestFile, String(), true); } void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request) @@ -106,6 +117,33 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request) ESP.restart(); } +void WebApiConfigClass::onConfigListGet(AsyncWebServerRequest* request) +{ + if (!WebApi.checkCredentials(request)) { + return; + } + + AsyncJsonResponse* response = new AsyncJsonResponse(); + JsonObject root = response->getRoot(); + JsonArray data = root.createNestedArray(F("configs")); + + File rootfs = LittleFS.open("/"); + File file = rootfs.openNextFile(); + while (file) { + if (file.isDirectory()) { + continue; + } + JsonObject obj = data.createNestedObject(); + obj["name"] = String(file.name()); + + file = rootfs.openNextFile(); + } + file.close(); + + response->setLength(); + request->send(response); +} + void WebApiConfigClass::onConfigUploadFinish(AsyncWebServerRequest* request) { if (!WebApi.checkCredentials(request)) { diff --git a/webapp/src/types/Config.ts b/webapp/src/types/Config.ts new file mode 100644 index 0000000..74f8311 --- /dev/null +++ b/webapp/src/types/Config.ts @@ -0,0 +1,7 @@ +export interface ConfigFileInfo { + name: string; +} + +export interface ConfigFileList { + configs: Array; +} \ No newline at end of file diff --git a/webapp/src/views/ConfigAdminView.vue b/webapp/src/views/ConfigAdminView.vue index eb90a35..f3690cd 100644 --- a/webapp/src/views/ConfigAdminView.vue +++ b/webapp/src/views/ConfigAdminView.vue @@ -5,9 +5,22 @@ - {{ $t('configadmin.BackupConfig') }} - +
+
+ {{ $t('configadmin.BackupConfig') }} +
+
+ +
+
+ +
+
@@ -89,6 +102,7 @@ import BasePage from '@/components/BasePage.vue'; import BootstrapAlert from "@/components/BootstrapAlert.vue"; import CardElement from '@/components/CardElement.vue'; +import type { ConfigFileList } from '@/types/Config'; import { authHeader, handleResponse, isLoggedIn } from '@/utils/authentication'; import * as bootstrap from 'bootstrap'; import { @@ -119,14 +133,15 @@ export default defineComponent({ UploadError: "", UploadSuccess: false, file: {} as Blob, + fileList: {} as ConfigFileList, + backupFileSelect: "", }; }, mounted() { - if (!isLoggedIn()) { - this.$router.push({ path: "/login", query: { returnUrl: this.$router.currentRoute.value.fullPath } }); - } this.modalFactoryReset = new bootstrap.Modal('#factoryReset'); - this.loading = false; + }, + created() { + this.getFileList(); }, methods: { onFactoryResetModal() { @@ -154,8 +169,20 @@ export default defineComponent({ ) this.modalFactoryReset.hide(); }, + getFileList() { + this.loading = true; + fetch("/api/config/list", { headers: authHeader() }) + .then((response) => handleResponse(response, this.$emitter, this.$router)) + .then((data) => { + this.fileList = data; + if (this.fileList.configs) { + this.backupFileSelect = this.fileList.configs[0].name; + } + this.loading = false; + }); + }, downloadConfig() { - fetch("/api/config/get", { headers: authHeader() }) + fetch("/api/config/get?file=" + this.backupFileSelect, { headers: authHeader() }) .then(res => res.blob()) .then(blob => { var file = window.URL.createObjectURL(blob);