webapp: Allow download of individual config files

This commit is contained in:
Thomas Basler 2023-01-05 01:44:38 +01:00
parent 7c33688167
commit 20ae646561
4 changed files with 82 additions and 9 deletions

View File

@ -11,6 +11,7 @@ public:
private: private:
void onConfigGet(AsyncWebServerRequest* request); void onConfigGet(AsyncWebServerRequest* request);
void onConfigDelete(AsyncWebServerRequest* request); void onConfigDelete(AsyncWebServerRequest* request);
void onConfigListGet(AsyncWebServerRequest* request);
void onConfigUploadFinish(AsyncWebServerRequest* request); void onConfigUploadFinish(AsyncWebServerRequest* request);
void onConfigUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final); void onConfigUpload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final);

View File

@ -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/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/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, _server->on("/api/config/upload", HTTP_POST,
std::bind(&WebApiConfigClass::onConfigUploadFinish, this, _1), std::bind(&WebApiConfigClass::onConfigUploadFinish, this, _1),
std::bind(&WebApiConfigClass::onConfigUpload, this, _1, _2, _3, _4, _5, _6)); std::bind(&WebApiConfigClass::onConfigUpload, this, _1, _2, _3, _4, _5, _6));
@ -37,7 +38,17 @@ void WebApiConfigClass::onConfigGet(AsyncWebServerRequest* request)
return; 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) void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
@ -106,6 +117,33 @@ void WebApiConfigClass::onConfigDelete(AsyncWebServerRequest* request)
ESP.restart(); 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) void WebApiConfigClass::onConfigUploadFinish(AsyncWebServerRequest* request)
{ {
if (!WebApi.checkCredentials(request)) { if (!WebApi.checkCredentials(request)) {

View File

@ -0,0 +1,7 @@
export interface ConfigFileInfo {
name: string;
}
export interface ConfigFileList {
configs: Array<ConfigFileInfo>;
}

View File

@ -5,9 +5,22 @@
</BootstrapAlert> </BootstrapAlert>
<CardElement :text="$t('configadmin.BackupHeader')" textVariant="text-bg-primary" center-content> <CardElement :text="$t('configadmin.BackupHeader')" textVariant="text-bg-primary" center-content>
{{ $t('configadmin.BackupConfig') }} <div class="row g-3 align-items-center">
<button class="btn btn-primary" @click="downloadConfig">{{ $t('configadmin.Backup') }} <div class="col-sm">
</button> {{ $t('configadmin.BackupConfig') }}
</div>
<div class="col-sm">
<select class="form-select" v-model="backupFileSelect">
<option v-for="(file) in fileList.configs" :key="file.name" :value="file.name">
{{ file.name }}
</option>
</select>
</div>
<div class="col-sm">
<button class="btn btn-primary" @click="downloadConfig">{{ $t('configadmin.Backup') }}
</button>
</div>
</div>
</CardElement> </CardElement>
<CardElement :text="$t('configadmin.RestoreHeader')" textVariant="text-bg-primary" center-content add-space> <CardElement :text="$t('configadmin.RestoreHeader')" textVariant="text-bg-primary" center-content add-space>
@ -89,6 +102,7 @@
import BasePage from '@/components/BasePage.vue'; import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from "@/components/BootstrapAlert.vue"; import BootstrapAlert from "@/components/BootstrapAlert.vue";
import CardElement from '@/components/CardElement.vue'; import CardElement from '@/components/CardElement.vue';
import type { ConfigFileList } from '@/types/Config';
import { authHeader, handleResponse, isLoggedIn } from '@/utils/authentication'; import { authHeader, handleResponse, isLoggedIn } from '@/utils/authentication';
import * as bootstrap from 'bootstrap'; import * as bootstrap from 'bootstrap';
import { import {
@ -119,14 +133,15 @@ export default defineComponent({
UploadError: "", UploadError: "",
UploadSuccess: false, UploadSuccess: false,
file: {} as Blob, file: {} as Blob,
fileList: {} as ConfigFileList,
backupFileSelect: "",
}; };
}, },
mounted() { mounted() {
if (!isLoggedIn()) {
this.$router.push({ path: "/login", query: { returnUrl: this.$router.currentRoute.value.fullPath } });
}
this.modalFactoryReset = new bootstrap.Modal('#factoryReset'); this.modalFactoryReset = new bootstrap.Modal('#factoryReset');
this.loading = false; },
created() {
this.getFileList();
}, },
methods: { methods: {
onFactoryResetModal() { onFactoryResetModal() {
@ -154,8 +169,20 @@ export default defineComponent({
) )
this.modalFactoryReset.hide(); 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() { downloadConfig() {
fetch("/api/config/get", { headers: authHeader() }) fetch("/api/config/get?file=" + this.backupFileSelect, { headers: authHeader() })
.then(res => res.blob()) .then(res => res.blob())
.then(blob => { .then(blob => {
var file = window.URL.createObjectURL(blob); var file = window.URL.createObjectURL(blob);