webapp: Allow download of individual config files
This commit is contained in:
parent
7c33688167
commit
20ae646561
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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)) {
|
||||||
|
|||||||
7
webapp/src/types/Config.ts
Normal file
7
webapp/src/types/Config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface ConfigFileInfo {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigFileList {
|
||||||
|
configs: Array<ConfigFileInfo>;
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user