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:
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);

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/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)) {

View File

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

View File

@ -5,9 +5,22 @@
</BootstrapAlert>
<CardElement :text="$t('configadmin.BackupHeader')" textVariant="text-bg-primary" center-content>
<div class="row g-3 align-items-center">
<div class="col-sm">
{{ $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 :text="$t('configadmin.RestoreHeader')" textVariant="text-bg-primary" center-content add-space>
@ -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);