фича: бекап параметров и восстановление через вебку

This commit is contained in:
Vladislav Ostapov 2025-06-05 15:21:18 +03:00
parent 57ba61da41
commit 996e711436
5 changed files with 516 additions and 0 deletions

View File

@ -72,3 +72,130 @@
this.submitStatus.modemReboot = 30
fetch('/api/reboot', { method: 'POST' }).then((r) => {})
},
async restoreAllSettings() {
// Порядок применения настроек
const settingsApplyOrder = ['qos', 'tcpaccel', 'rxtx', 'buclnb', 'network'];
// 1. Чтение JSON-файла, выбранного пользователем
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
const filePromise = new Promise((resolve, reject) => {
fileInput.onchange = e => {
const file = e.target.files[0];
if (!file) {
reject(new Error('Файл не выбран'));
return;
}
const reader = new FileReader();
reader.onload = event => {
try {
const jsonData = JSON.parse(event.target.result);
resolve(jsonData);
} catch (error) {
reject(new Error('Ошибка парсинга JSON'));
}
};
reader.onerror = () => reject(new Error('Ошибка чтения файла'));
reader.readAsText(file);
};
fileInput.click();
});
try {
const settingsToApply = await filePromise;
const errors = [];
// 2. Перебор групп параметров в заданном порядке
for (const groupName of settingsApplyOrder) {
if (!settingsToApply.hasOwnProperty(groupName)) {
continue; // Пропускаем группы, которых нет в файле
}
const groupSettings = settingsToApply[groupName];
if (typeof groupSettings !== 'object' || groupSettings === null) {
continue;
}
try {
// 2.1. POST-запрос для применения группы параметров
const postResponse = await fetch(`/api/set/${groupName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(groupSettings)
});
if (!postResponse.ok) {
throw new Error(`HTTP error ${postResponse.status}`);
}
const postResult = await postResponse.json();
if (postResult.status !== 'ok') {
throw new Error(`API error: ${postResult.message || 'unknown error'}`);
}
// 2.2. Проверка примененных параметров
const getResponse = await fetch('/api/get/settings', { method: 'GET' });
if (!getResponse.ok) {
throw new Error(`HTTP error ${getResponse.status}`);
}
const fetchSettingsResult = await getResponse.json();
if (fetchSettingsResult.status !== 'ok') {
throw new Error('Не удалось получить текущие настройки');
}
// Проверка соответствия параметров
const appliedGroup = fetchSettingsResult.settings[groupName] || {};
const failedSettings = [];
for (const [key, value] of Object.entries(groupSettings)) {
if (appliedGroup[key] !== value) {
failedSettings.push(`${key} (ожидалось: ${value}, получено: ${appliedGroup[key]})`);
}
}
if (failedSettings.length > 0) {
throw new Error(`Не совпадают параметры: ${failedSettings.join(', ')}`);
}
} catch (groupError) {
errors.push(`Группа ${groupName}: ${groupError.message}`);
}
}
// 3. Показ ошибок, если они есть
if (errors.length > 0) {
const errorMessage = errors.join('\n\n') +
'\n\nНекоторые настройки могли примениться некорректно.\nСтраница будет перезагружена.';
alert(errorMessage);
}
} catch (error) {
alert(`Ошибка при восстановлении настроек: ${error.message}`);
return;
}
// 4. Перезагрузка страницы
location.reload();
},
async dumpAllSettings() {
function downloadAsFile(data, filename) {
let a = document.createElement("a");
let file = new Blob([data], {type: 'application/json'});
a.href = URL.createObjectURL(file);
a.download = filename;
a.click();
}
const response = await fetch('/api/get/settings', { method: 'GET' })
if (response.ok) {
const jres = await response.json()
if (jres["status"] === "ok") {
downloadAsFile(JSON.stringify(jres["settings"], null, 4), "backup-" + this.about.firmwareVersion + "-" + this.about.modemSn + ".json")
}
}
},

View File

@ -21,6 +21,8 @@
<div>
<button class="dangerous-button" onclick="fetch('/api/resetSettings', { method: 'POST' }).then((r) => { window.location.reload(); })">Сбросить модем до заводских настроек</button>
</div>
<button class="action-button" @click="dumpAllSettings()">Сохранить бекап конфигурации</button>
<button class="dangerous-button" @click="restoreAllSettings()">Восстановить бекап конфигурации</button>
</div>
<h2>Обновление ПО</h2>

View File

@ -533,6 +533,8 @@
<div>
<button class="dangerous-button" onclick="fetch('/api/resetSettings', { method: 'POST' }).then((r) => { window.location.reload(); })">Сбросить модем до заводских настроек</button>
</div>
<button class="action-button" @click="dumpAllSettings()">Сохранить бекап конфигурации</button>
<button class="dangerous-button" @click="restoreAllSettings()">Восстановить бекап конфигурации</button>
</div>
<h2>Обновление ПО</h2>
@ -1363,6 +1365,133 @@
}
this.submitStatus.modemReboot = 30
fetch('/api/reboot', { method: 'POST' }).then((r) => {})
},
async restoreAllSettings() {
// Порядок применения настроек
const settingsApplyOrder = ['qos', 'tcpaccel', 'rxtx', 'buclnb', 'network'];
// 1. Чтение JSON-файла, выбранного пользователем
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
const filePromise = new Promise((resolve, reject) => {
fileInput.onchange = e => {
const file = e.target.files[0];
if (!file) {
reject(new Error('Файл не выбран'));
return;
}
const reader = new FileReader();
reader.onload = event => {
try {
const jsonData = JSON.parse(event.target.result);
resolve(jsonData);
} catch (error) {
reject(new Error('Ошибка парсинга JSON'));
}
};
reader.onerror = () => reject(new Error('Ошибка чтения файла'));
reader.readAsText(file);
};
fileInput.click();
});
try {
const settingsToApply = await filePromise;
const errors = [];
// 2. Перебор групп параметров в заданном порядке
for (const groupName of settingsApplyOrder) {
if (!settingsToApply.hasOwnProperty(groupName)) {
continue; // Пропускаем группы, которых нет в файле
}
const groupSettings = settingsToApply[groupName];
if (typeof groupSettings !== 'object' || groupSettings === null) {
continue;
}
try {
// 2.1. POST-запрос для применения группы параметров
const postResponse = await fetch(`/api/set/${groupName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(groupSettings)
});
if (!postResponse.ok) {
throw new Error(`HTTP error ${postResponse.status}`);
}
const postResult = await postResponse.json();
if (postResult.status !== 'ok') {
throw new Error(`API error: ${postResult.message || 'unknown error'}`);
}
// 2.2. Проверка примененных параметров
const getResponse = await fetch('/api/get/settings', { method: 'GET' });
if (!getResponse.ok) {
throw new Error(`HTTP error ${getResponse.status}`);
}
const fetchSettingsResult = await getResponse.json();
if (fetchSettingsResult.status !== 'ok') {
throw new Error('Не удалось получить текущие настройки');
}
// Проверка соответствия параметров
const appliedGroup = fetchSettingsResult.settings[groupName] || {};
const failedSettings = [];
for (const [key, value] of Object.entries(groupSettings)) {
if (appliedGroup[key] !== value) {
failedSettings.push(`${key} (ожидалось: ${value}, получено: ${appliedGroup[key]})`);
}
}
if (failedSettings.length > 0) {
throw new Error(`Не совпадают параметры: ${failedSettings.join(', ')}`);
}
} catch (groupError) {
errors.push(`Группа ${groupName}: ${groupError.message}`);
}
}
// 3. Показ ошибок, если они есть
if (errors.length > 0) {
const errorMessage = errors.join('\n\n') +
'\n\nНекоторые настройки могли примениться некорректно.\nСтраница будет перезагружена.';
alert(errorMessage);
}
} catch (error) {
alert(`Ошибка при восстановлении настроек: ${error.message}`);
return;
}
// 4. Перезагрузка страницы
location.reload();
},
async dumpAllSettings() {
function downloadAsFile(data, filename) {
let a = document.createElement("a");
let file = new Blob([data], {type: 'application/json'});
a.href = URL.createObjectURL(file);
a.download = filename;
a.click();
}
const response = await fetch('/api/get/settings', { method: 'GET' })
if (response.ok) {
const jres = await response.json()
if (jres["status"] === "ok") {
downloadAsFile(JSON.stringify(jres["settings"], null, 4), "backup-" + this.about.firmwareVersion + "-" + this.about.modemSn + ".json")
}
}
}, // ========== include end from 'common/admin-methods.js.j2'

View File

@ -285,6 +285,8 @@
<div>
<button class="dangerous-button" onclick="fetch('/api/resetSettings', { method: 'POST' }).then((r) => { window.location.reload(); })">Сбросить модем до заводских настроек</button>
</div>
<button class="action-button" @click="dumpAllSettings()">Сохранить бекап конфигурации</button>
<button class="dangerous-button" @click="restoreAllSettings()">Восстановить бекап конфигурации</button>
</div>
<h2>Обновление ПО</h2>
@ -756,6 +758,133 @@
}
this.submitStatus.modemReboot = 30
fetch('/api/reboot', { method: 'POST' }).then((r) => {})
},
async restoreAllSettings() {
// Порядок применения настроек
const settingsApplyOrder = ['qos', 'tcpaccel', 'rxtx', 'buclnb', 'network'];
// 1. Чтение JSON-файла, выбранного пользователем
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
const filePromise = new Promise((resolve, reject) => {
fileInput.onchange = e => {
const file = e.target.files[0];
if (!file) {
reject(new Error('Файл не выбран'));
return;
}
const reader = new FileReader();
reader.onload = event => {
try {
const jsonData = JSON.parse(event.target.result);
resolve(jsonData);
} catch (error) {
reject(new Error('Ошибка парсинга JSON'));
}
};
reader.onerror = () => reject(new Error('Ошибка чтения файла'));
reader.readAsText(file);
};
fileInput.click();
});
try {
const settingsToApply = await filePromise;
const errors = [];
// 2. Перебор групп параметров в заданном порядке
for (const groupName of settingsApplyOrder) {
if (!settingsToApply.hasOwnProperty(groupName)) {
continue; // Пропускаем группы, которых нет в файле
}
const groupSettings = settingsToApply[groupName];
if (typeof groupSettings !== 'object' || groupSettings === null) {
continue;
}
try {
// 2.1. POST-запрос для применения группы параметров
const postResponse = await fetch(`/api/set/${groupName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(groupSettings)
});
if (!postResponse.ok) {
throw new Error(`HTTP error ${postResponse.status}`);
}
const postResult = await postResponse.json();
if (postResult.status !== 'ok') {
throw new Error(`API error: ${postResult.message || 'unknown error'}`);
}
// 2.2. Проверка примененных параметров
const getResponse = await fetch('/api/get/settings', { method: 'GET' });
if (!getResponse.ok) {
throw new Error(`HTTP error ${getResponse.status}`);
}
const fetchSettingsResult = await getResponse.json();
if (fetchSettingsResult.status !== 'ok') {
throw new Error('Не удалось получить текущие настройки');
}
// Проверка соответствия параметров
const appliedGroup = fetchSettingsResult.settings[groupName] || {};
const failedSettings = [];
for (const [key, value] of Object.entries(groupSettings)) {
if (appliedGroup[key] !== value) {
failedSettings.push(`${key} (ожидалось: ${value}, получено: ${appliedGroup[key]})`);
}
}
if (failedSettings.length > 0) {
throw new Error(`Не совпадают параметры: ${failedSettings.join(', ')}`);
}
} catch (groupError) {
errors.push(`Группа ${groupName}: ${groupError.message}`);
}
}
// 3. Показ ошибок, если они есть
if (errors.length > 0) {
const errorMessage = errors.join('\n\n') +
'\n\nНекоторые настройки могли примениться некорректно.\nСтраница будет перезагружена.';
alert(errorMessage);
}
} catch (error) {
alert(`Ошибка при восстановлении настроек: ${error.message}`);
return;
}
// 4. Перезагрузка страницы
location.reload();
},
async dumpAllSettings() {
function downloadAsFile(data, filename) {
let a = document.createElement("a");
let file = new Blob([data], {type: 'application/json'});
a.href = URL.createObjectURL(file);
a.download = filename;
a.click();
}
const response = await fetch('/api/get/settings', { method: 'GET' })
if (response.ok) {
const jres = await response.json()
if (jres["status"] === "ok") {
downloadAsFile(JSON.stringify(jres["settings"], null, 4), "backup-" + this.about.firmwareVersion + "-" + this.about.modemSn + ".json")
}
}
}, // ========== include end from 'common/admin-methods.js.j2'

View File

@ -266,6 +266,8 @@
<div>
<button class="dangerous-button" onclick="fetch('/api/resetSettings', { method: 'POST' }).then((r) => { window.location.reload(); })">Сбросить модем до заводских настроек</button>
</div>
<button class="action-button" @click="dumpAllSettings()">Сохранить бекап конфигурации</button>
<button class="dangerous-button" @click="restoreAllSettings()">Восстановить бекап конфигурации</button>
</div>
<h2>Обновление ПО</h2>
@ -752,6 +754,133 @@
}
this.submitStatus.modemReboot = 30
fetch('/api/reboot', { method: 'POST' }).then((r) => {})
},
async restoreAllSettings() {
// Порядок применения настроек
const settingsApplyOrder = ['qos', 'tcpaccel', 'rxtx', 'buclnb', 'network'];
// 1. Чтение JSON-файла, выбранного пользователем
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json';
const filePromise = new Promise((resolve, reject) => {
fileInput.onchange = e => {
const file = e.target.files[0];
if (!file) {
reject(new Error('Файл не выбран'));
return;
}
const reader = new FileReader();
reader.onload = event => {
try {
const jsonData = JSON.parse(event.target.result);
resolve(jsonData);
} catch (error) {
reject(new Error('Ошибка парсинга JSON'));
}
};
reader.onerror = () => reject(new Error('Ошибка чтения файла'));
reader.readAsText(file);
};
fileInput.click();
});
try {
const settingsToApply = await filePromise;
const errors = [];
// 2. Перебор групп параметров в заданном порядке
for (const groupName of settingsApplyOrder) {
if (!settingsToApply.hasOwnProperty(groupName)) {
continue; // Пропускаем группы, которых нет в файле
}
const groupSettings = settingsToApply[groupName];
if (typeof groupSettings !== 'object' || groupSettings === null) {
continue;
}
try {
// 2.1. POST-запрос для применения группы параметров
const postResponse = await fetch(`/api/set/${groupName}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(groupSettings)
});
if (!postResponse.ok) {
throw new Error(`HTTP error ${postResponse.status}`);
}
const postResult = await postResponse.json();
if (postResult.status !== 'ok') {
throw new Error(`API error: ${postResult.message || 'unknown error'}`);
}
// 2.2. Проверка примененных параметров
const getResponse = await fetch('/api/get/settings', { method: 'GET' });
if (!getResponse.ok) {
throw new Error(`HTTP error ${getResponse.status}`);
}
const fetchSettingsResult = await getResponse.json();
if (fetchSettingsResult.status !== 'ok') {
throw new Error('Не удалось получить текущие настройки');
}
// Проверка соответствия параметров
const appliedGroup = fetchSettingsResult.settings[groupName] || {};
const failedSettings = [];
for (const [key, value] of Object.entries(groupSettings)) {
if (appliedGroup[key] !== value) {
failedSettings.push(`${key} (ожидалось: ${value}, получено: ${appliedGroup[key]})`);
}
}
if (failedSettings.length > 0) {
throw new Error(`Не совпадают параметры: ${failedSettings.join(', ')}`);
}
} catch (groupError) {
errors.push(`Группа ${groupName}: ${groupError.message}`);
}
}
// 3. Показ ошибок, если они есть
if (errors.length > 0) {
const errorMessage = errors.join('\n\n') +
'\n\nНекоторые настройки могли примениться некорректно.\nСтраница будет перезагружена.';
alert(errorMessage);
}
} catch (error) {
alert(`Ошибка при восстановлении настроек: ${error.message}`);
return;
}
// 4. Перезагрузка страницы
location.reload();
},
async dumpAllSettings() {
function downloadAsFile(data, filename) {
let a = document.createElement("a");
let file = new Blob([data], {type: 'application/json'});
a.href = URL.createObjectURL(file);
a.download = filename;
a.click();
}
const response = await fetch('/api/get/settings', { method: 'GET' })
if (response.ok) {
const jres = await response.json()
if (jres["status"] === "ok") {
downloadAsFile(JSON.stringify(jres["settings"], null, 4), "backup-" + this.about.firmwareVersion + "-" + this.about.modemSn + ".json")
}
}
}, // ========== include end from 'common/admin-methods.js.j2'