Compare commits

..

2 Commits

4 changed files with 143 additions and 60 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ cmake-build-*
cert.pem
key.pem
dh.pem
/do-terminal-update.sh

View File

@ -80,6 +80,19 @@ class ServerResources {
std::unique_ptr<api_driver::ApiDriver> api;
http::auth::AuthProvider auth{};
void doTerminaFwUpdate(const http::server::Request& req) {
std::ofstream f("/tmp/firmware.zip", std::ios::binary);
if (f.is_open()) {
f.write(req.payload.data(), static_cast<long>(req.payload.size()));
f.close();
system("do-terminal-update.sh");
} else {
throw std::runtime_error("File is not open");
}
}
public:
static constexpr const char* INDEX_HTML = "static/main.html";
static constexpr const char* LOGIN_HTML = "static/login.html";
@ -322,6 +335,21 @@ public:
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}
}));
s.resources.emplace_back(std::make_unique<http::auth::AuthRequiredResource>("/api/firmwareUpdate", this->auth, http::auth::User::UPDATE_FIRMWARE, [this](const auto& req, auto& rep) {
if (req.method != "PUT") {
http::server::stockReply(http::server::bad_request, rep);
}
this->doTerminaFwUpdate(req);
rep.status = http::server::ok;
rep.headers.clear();
rep.headers.push_back({.name = "Content-Type", .value = toString(mime_types::json)});
std::string result = R"({"status":"ok","fwsize":)";
result += std::to_string(req.payload.size());
result += "}";
rep.content.insert(rep.content.end(), result.c_str(), result.c_str() + result.size());
}));
}
~ServerResources() = default;

View File

@ -1,6 +1,5 @@
#include "request_parser.hpp"
#include <iostream>
#include <sstream>
#include "request.hpp"
@ -14,16 +13,16 @@ namespace http::server {
*/
static bool requestBodySizeResolver(Request& req, size_t reqSize) {
// разрешаем тело только для POST запросов
if (req.method != "POST") {
return false;
if (req.method == "POST") {
return reqSize < 0x4000; // 16кб на все POST-запросы к API будет более чем достаточно
}
// для обновления прошивки разрешаем большое тело
if (req.url->path == "/api/firmwareUpdate") {
// это для обновления прошивки
if (req.method == "PUT" && req.url->path == "/api/firmwareUpdate") {
return reqSize <= HTTP_MAX_PAYLOAD;
}
return reqSize < 0x4000; // 16кб на все POST-запросы к API будет более чем достаточно
return false;
}
static void parseParams(Url& u, const std::string& query) {
@ -277,7 +276,7 @@ namespace http::server {
if (content_len.empty()) {
return good;
}
contentLenghtHeader = std::stol(content_len);
contentLenghtHeader = std::stoul(content_len);
if (contentLenghtHeader == 0) {
return good;
}

View File

@ -426,14 +426,14 @@
<div class="settings-set-container">
<label>
<span>Активировать QoS</span>
<span class="toggle-input"><input type="checkbox" v-model="qos.en" /><span class="slider"></span></span>
<span class="toggle-input"><input type="checkbox" v-model="param.qos.en" /><span class="slider"></span></span>
</label>
</div>
<template>
<div v-for="classesGroup in ['rt1', 'rt2', 'rt3', 'cd']">
<h3>Классы {{ classesGroup.toUpperCase() }}</h3>
<button class="action-button" @click="qosAddClass(classesGroup)">Добавить класс {{ classesGroup.toUpperCase() }}</button>
<details v-for="(qosClass, index) in qos[classesGroup]" :key="index" class="settings-set-container">
<details v-for="(qosClass, index) in param.qos[classesGroup]" :key="index" class="settings-set-container">
<summary>
<span v-if="classesGroup === 'cd'">#{{ index }} CIR={{ qosClass.cir }}кбит, PIR={{ qosClass.pir }}кбит {{ qosClass.description }}</span>
<span v-if="classesGroup !== 'cd'">#{{ index }} CBR={{ qosClass.cir }}кбит {{ qosClass.description }}</span>
@ -531,11 +531,11 @@
<div class="settings-set-container">
<label>
<span>Активировать акселерацию</span>
<span class="toggle-input"><input type="checkbox" v-model="tcpAccel.en" /><span class="slider"></span></span>
<span class="toggle-input"><input type="checkbox" v-model="param.tcpAccel.en" /><span class="slider"></span></span>
</label>
<label>
<span>Максимальное количество соединений</span>
<input type="number" v-model="tcpAccel.maxConnections" min="1" max="10000" />
<input type="number" v-model="param.tcpAccel.maxConnections" min="1" max="10000" />
</label>
</div>
<button class="action-button" @click="settingsSubmitTcpAccel()">Применить <span class="submit-spinner" v-show="submitStatus.tcpAccel"></span></button>
@ -617,10 +617,10 @@
<h3>Обновление ПО</h3>
<label>
<span>Порт для CinC</span>
<input type="file" accept="application/zip">
<span>Файл {{ this.uploadFw.progress !== null ? `(${this.uploadFw.progress}%)` : '' }}</span>
<input type="file" accept="application/zip" @change="(e) => { this.uploadFw.filename = e.target.files[0] }">
</label>
<button class="dangerous-button" @click="settingsUploadUpdate()">Обновить встроенное ПО</button>
<button class="dangerous-button" @click="settingsUploadUpdate()">Обновить встроенное ПО <span class="submit-spinner" v-show="submitStatus.firmwareUpload"></span></button>
</div>
<div hidden>
@ -798,6 +798,7 @@
network: false,
debugSend: false,
tcpAccel: false,
firmwareUpload: false,
},
stat_rx: {
@ -935,7 +936,6 @@
modemSn: '?',
macManagement: '?',
macData: '?',
}
},
qos: {
@ -950,6 +950,12 @@
en: false,
maxConnections: 128
},
},
uploadFw: {
progress: null,
filename: null
},
testState: false,
initState: '',
@ -1177,16 +1183,16 @@
return res
}
let query = {
"en": this.qos.en,
"en": this.param.qos.en,
"rt1": [],
"rt2": [],
"rt3": [],
"cd": []
}
for (let i = 0; i < this.qos.rt1.length; i++) { query.rt1.push(_translateQosClass('rt', this.qos.rt1[i])) }
for (let i = 0; i < this.qos.rt2.length; i++) { query.rt2.push(_translateQosClass('rt', this.qos.rt2[i])) }
for (let i = 0; i < this.qos.rt3.length; i++) { query.rt3.push(_translateQosClass('rt', this.qos.rt3[i])) }
for (let i = 0; i < this.qos.cd.length; i++) { query.cd.push(_translateQosClass('rt', this.qos.cd[i])) }
for (let i = 0; i < this.param.qos.rt1.length; i++) { query.rt1.push(_translateQosClass('rt', this.param.qos.rt1[i])) }
for (let i = 0; i < this.param.qos.rt2.length; i++) { query.rt2.push(_translateQosClass('rt', this.param.qos.rt2[i])) }
for (let i = 0; i < this.param.qos.rt3.length; i++) { query.rt3.push(_translateQosClass('rt', this.param.qos.rt3[i])) }
for (let i = 0; i < this.param.qos.cd.length; i++) { query.cd.push(_translateQosClass('rt', this.param.qos.cd[i])) }
console.log(query)
fetch('/api/set/qos', {
@ -1277,6 +1283,59 @@
})
},
async settingsUploadUpdate() {
if (!this.uploadFw.filename) {
alert('Выберите файл для загрузки');
return;
}
async function readFileAsArrayBuffer(fileName) {
return new Promise((resolve, reject) => {
if (!fileName) { reject(`Файл не выбран`); return }
const reader = new FileReader();
reader.onload = (e) => { resolve(reader.result) }
reader.onerror = (e) => { reject(e) }
reader.readAsArrayBuffer(fileName)
})
}
try {
this.submitStatus.firmwareUpload = true
const blob = await readFileAsArrayBuffer(this.uploadFw.filename)
const xhr = new XMLHttpRequest();
const success = await new Promise((resolve) => {
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
this.uploadFw.progress = Math.round((event.loaded / event.total) * 1000) / 10;
console.log("upload progress:", this.uploadFw.progress);
}
});
xhr.addEventListener("loadend", () => {
this.uploadFw.progress = 100
resolve(xhr.readyState === 4 && xhr.status === 200);
});
xhr.open("PUT", "/api/firmwareUpdate", true);
xhr.setRequestHeader("Content-Type", "application/octet-stream");
xhr.send(blob);
});
console.log("success:", success);
// const result = await fetch('', {
// method: 'POST',
// body: await readFileAsArrayBuffer(this.uploadFw.filename),
// headers: {
// 'Content-Type': 'application/zip',
// },
// onuploadprogress: (progressEvent) => {
// this.uploadFw.progress = Math.round((progressEvent.loaded / progressEvent.total) * 100);
// },
// })
} catch (e) {
alert(`Ошибка загрузки файла: ${e}`);
}
this.submitStatus.firmwareUpload = false
},
performUpdateSettings(reloadParts) {
const doFetchSettings = async () => {
let d = await fetch("/api/get/settings")
@ -1360,14 +1419,14 @@
updateQosSettings(vals) {
this.submitStatus.qos = false
this.qos.en = vals["settings"]["qos.enabled"]
this.param.qos.en = vals["settings"]["qos.enabled"]
const qosProfile = vals["settings"]["qos.profile"]
if (qosProfile !== null && qosProfile !== undefined) {
this.qos.rt1 = [] // .splice(0, this.qos.rt1.length)
this.qos.rt2 = [] // .splice(0, this.qos.rt2.length)
this.qos.rt3 = [] // .splice(0, this.qos.rt3.length)
this.qos.cd = [] // .splice(0, this.qos.cd.length)
this.param.qos.rt1 = [] // .splice(0, this.param.qos.rt1.length)
this.param.qos.rt2 = [] // .splice(0, this.param.qos.rt2.length)
this.param.qos.rt3 = [] // .splice(0, this.param.qos.rt3.length)
this.param.qos.cd = [] // .splice(0, this.param.qos.cd.length)
for (let trafficClass in qosProfile) {
if (['rt1', 'rt2', 'rt3', 'cd'].indexOf(trafficClass) < 0) {
@ -1402,10 +1461,10 @@
})
}
switch (trafficClass) {
case 'rt1': this.qos.rt1.push(result); break
case 'rt2': this.qos.rt2.push(result); break
case 'rt3': this.qos.rt3.push(result); break
case 'cd': this.qos.cd.push(result); break
case 'rt1': this.param.qos.rt1.push(result); break
case 'rt2': this.param.qos.rt2.push(result); break
case 'rt3': this.param.qos.rt3.push(result); break
case 'cd': this.param.qos.cd.push(result); break
}
}
}
@ -1448,7 +1507,6 @@
this.param.firmware.macData = vals["settings"]["firmware.macData"]
},
// addQosClass()
qosAddClass(name) {
let res = {
isEnabled: true,
@ -1458,10 +1516,10 @@
filters: []
}
switch (name) {
case 'rt1': this.qos.rt1.push(res); break
case 'rt2': this.qos.rt2.push(res); break
case 'rt3': this.qos.rt3.push(res); break
case 'cd': this.qos.cd.push(res); break
case 'rt1': this.param.qos.rt1.push(res); break
case 'rt2': this.param.qos.rt2.push(res); break
case 'rt3': this.param.qos.rt3.push(res); break
case 'cd': this.param.qos.cd.push(res); break
}
},
@ -1477,28 +1535,28 @@
dscp: ""
}
switch (name) {
case 'rt1': this.qos.rt1[index].filters.push(rule); break
case 'rt2': this.qos.rt2[index].filters.push(rule); break
case 'rt3': this.qos.rt3[index].filters.push(rule); break
case 'cd': this.qos.cd[index].filters.push(rule); break
case 'rt1': this.param.qos.rt1[index].filters.push(rule); break
case 'rt2': this.param.qos.rt2[index].filters.push(rule); break
case 'rt3': this.param.qos.rt3[index].filters.push(rule); break
case 'cd': this.param.qos.cd[index].filters.push(rule); break
}
},
qosDelClass(name, index) {
switch (name) {
case 'rt1': this.qos.rt1.splice(index, 1); break
case 'rt2': this.qos.rt2.splice(index, 1); break
case 'rt3': this.qos.rt3.splice(index, 1); break
case 'cd': this.qos.cd.splice(index, 1); break
case 'rt1': this.param.qos.rt1.splice(index, 1); break
case 'rt2': this.param.qos.rt2.splice(index, 1); break
case 'rt3': this.param.qos.rt3.splice(index, 1); break
case 'cd': this.param.qos.cd.splice(index, 1); break
}
},
qosDelFilter(name, index, filterIndex) {
switch (name) {
case 'rt1': this.qos.rt1[index].filters.splice(filterIndex, 1); break
case 'rt2': this.qos.rt2[index].filters.splice(filterIndex, 1); break
case 'rt3': this.qos.rt3[index].filters.splice(filterIndex, 1); break
case 'cd': this.qos.cd[index].filters.splice(filterIndex, 1); break
case 'rt1': this.param.qos.rt1[index].filters.splice(filterIndex, 1); break
case 'rt2': this.param.qos.rt2[index].filters.splice(filterIndex, 1); break
case 'rt3': this.param.qos.rt3[index].filters.splice(filterIndex, 1); break
case 'cd': this.param.qos.cd[index].filters.splice(filterIndex, 1); break
}
},
@ -1564,9 +1622,6 @@
document.getElementById("app").removeAttribute("hidden")
}
})
// import MyComponent from './modules/header'
// const sh = new Vue(MyComponent)
</script>
</body>
</html>