Files
ota_ws_update/source/ota_ws_update_http_new.html
ok-home 2aa272282f .
2025-09-12 12:28:09 +07:00

576 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<title>OTA UPDATE - Обновление прошивки</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<style>
/* Встроенные иконки Font Awesome */
.fa {
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
font-family: "Font Awesome 5 Free";
font-weight: 900;
}
.fa-sync-alt:before { content: "\f2f1"; }
.fa-exclamation-triangle:before { content: "\f071"; }
.fa-file-upload:before { content: "\f574"; }
.fa-folder-open:before { content: "\f07c"; }
.fa-file-code:before { content: "\f1c9"; }
.fa-play-circle:before { content: "\f144"; }
.fa-times-circle:before { content: "\f057"; }
.fa-check-circle:before { content: "\f058"; }
.fa-undo:before { content: "\f2ea"; }
.fa-power-off:before { content: "\f011"; }
.fa-home:before { content: "\f015"; }
/* Основные стили */
:root {
--primary: #4361ee;
--primary-dark: #3a56d4;
--secondary: #6c757d;
--success: #28a745;
--warning: #ffc107;
--danger: #dc3545;
--info: #17a2b8;
--light: #f8f9fa;
--dark: #343a40;
--background: #f5f7fa;
--card-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--transition: all 0.3s ease;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background-color: var(--background);
color: var(--dark);
line-height: 1.6;
padding: 0;
margin: 0;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.container {
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
color: white;
padding: 20px;
text-align: center;
border-radius: 10px;
margin-bottom: 25px;
box-shadow: var(--card-shadow);
}
.header h1 {
font-size: 1.8rem;
margin-bottom: 5px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.header p {
font-size: 1rem;
opacity: 0.9;
}
.card {
background-color: white;
border-radius: 10px;
padding: 25px;
margin-bottom: 20px;
box-shadow: var(--card-shadow);
transition: var(--transition);
}
.card:hover {
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 14px 20px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: var(--transition);
width: 100%;
margin-bottom: 12px;
}
.btn-primary {
background-color: var(--primary);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-dark);
transform: translateY(-2px);
}
.btn-success {
background-color: var(--success);
color: white;
}
.btn-success:hover {
background-color: #218838;
transform: translateY(-2px);
}
.btn-warning {
background-color: var(--warning);
color: var(--dark);
}
.btn-warning:hover {
background-color: #e0a800;
transform: translateY(-2px);
}
.btn-danger {
background-color: var(--danger);
color: white;
}
.btn-danger:hover {
background-color: #c82333;
transform: translateY(-2px);
}
.btn-secondary {
background-color: var(--secondary);
color: white;
}
.btn-secondary:hover {
background-color: #5a6268;
transform: translateY(-2px);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none !important;
}
.progress-container {
margin: 20px 0;
}
.progress-info {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 14px;
color: var(--secondary);
}
.progress-bar {
height: 20px;
background-color: #e9ecef;
border-radius: 10px;
overflow: hidden;
}
.progress {
height: 100%;
background: linear-gradient(90deg, var(--primary), var(--info));
border-radius: 10px;
transition: width 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
font-weight: bold;
}
.status-card {
background-color: #f8f9fa;
border-left: 4px solid var(--info);
padding: 15px;
border-radius: 8px;
margin: 15px 0;
font-size: 14px;
}
.status-success {
border-left-color: var(--success);
background-color: rgba(40, 167, 69, 0.1);
}
.status-error {
border-left-color: var(--danger);
background-color: rgba(220, 53, 69, 0.1);
}
.status-warning {
border-left-color: var(--warning);
background-color: rgba(255, 193, 7, 0.1);
}
.connection-status {
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
border-radius: 6px;
margin-bottom: 20px;
background-color: #f8f9fa;
font-weight: 500;
}
.connected {
color: var(--success);
background-color: rgba(40, 167, 69, 0.1);
}
.disconnected {
color: var(--danger);
background-color: rgba(220, 53, 69, 0.1);
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.connected .status-indicator {
background-color: var(--success);
}
.disconnected .status-indicator {
background-color: var(--danger);
}
.file-info {
background-color: #e9ecef;
padding: 12px;
border-radius: 8px;
margin: 15px 0;
font-size: 14px;
display: flex;
align-items: center;
gap: 10px;
}
.file-info .fa {
color: var(--primary);
}
.section-title {
font-size: 18px;
margin-bottom: 15px;
color: var(--dark);
display: flex;
align-items: center;
gap: 10px;
}
.hidden {
display: none;
}
.footer {
text-align: center;
margin-top: auto;
padding: 20px;
color: var(--secondary);
font-size: 0.9rem;
}
@media (min-width: 768px) {
.btn-container {
display: flex;
gap: 12px;
}
.btn-container .btn {
width: 50%;
margin-bottom: 0;
}
.header h1 {
font-size: 2.2rem;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fa fa-sync-alt"></i> OTA UPDATE</h1>
<p>Обновление прошивки устройства по воздуху</p>
</div>
<div class="connection-status disconnected">
<span class="status-indicator"></span>
<span id="status-text">Не подключено</span>
</div>
<div id="rollback" class="card hidden">
<h2 class="section-title"><i class="fa fa-exclamation-triangle"></i> Подтверждение обновления</h2>
<div class="status-card status-warning">
<p>Обновление было загружено, но не подтверждено. Пожалуйста, подтвердите установку новой прошивки или откатитесь к предыдущей версии.</p>
</div>
<button class="btn btn-success" id="otaVerifyApp">
<i class="fa fa-check-circle"></i> Подтвердить и установить обновление
</button>
<button class="btn btn-danger" id="otaRollback">
<i class="fa fa-undo"></i> Отменить обновление и откатиться
</button>
</div>
<div id="update" class="card">
<h2 class="section-title"><i class="fa fa-file-upload"></i> Загрузка прошивки</h2>
<input type="file" id="otaFile" class="hidden" accept=".bin" onchange="readOtaFile(this)">
<button class="btn btn-primary" id="otaFileSelect" onclick="document.getElementById('otaFile').click()">
<i class="fa fa-folder-open"></i> Выбрать файл прошивки
</button>
<div id="fileInfo" class="file-info hidden">
<i class="fa fa-file-code"></i>
<span id="fileName">Файл не выбран</span>
</div>
<div id="otaProgressVisible" class="hidden">
<div class="progress-container">
<div class="progress-info">
<span id="progressStatus">Загрузка...</span>
<span id="progressPercent">0%</span>
</div>
<div class="progress-bar">
<div class="progress" id="otaProgress" style="width: 0%">0%</div>
</div>
</div>
</div>
<div class="btn-container">
<button class="btn btn-warning" id="otaStartCancel">
<i class="fa fa-play-circle"></i> Начать обновление
</button>
<button class="btn btn-success hidden" id="otaReStart">
<i class="fa fa-power-off"></i> Перезагрузить с новой прошивкой
</button>
</div>
</div>
<button class="btn btn-secondary" id="goHome">
<i class="fa fa-home"></i> Вернуться на главную
</button>
</div>
<div class="footer">
<p>NVS Device Management &copy; 2023</p>
</div>
<script>
let otaData;
let otaSetChunkSize = 0;
let otaStartsegment = 0;
let otaStarted = 0;
function readOtaFile(input) {
if (!input.files.length) return;
let reader = new FileReader();
let file = input.files[0];
// Показываем информацию о файле
document.getElementById('fileInfo').classList.remove('hidden');
document.getElementById('fileName').textContent = `${file.name} (${formatFileSize(file.size)})`;
document.getElementById('otaFileSelect').innerHTML = `<i class="fa fa-check-circle"></i> Файл выбран: ${file.name}`;
reader.readAsArrayBuffer(file);
input.value = null;
reader.onload = function () {
otaData = new Uint8Array(reader.result);
document.getElementById("otaStartCancel").classList.remove('hidden');
document.getElementById("otaProgressVisible").classList.add('hidden');
document.getElementById("otaReStart").classList.add('hidden');
};
reader.onerror = function () {
console.log(reader.error);
showStatus("Ошибка чтения файла", "error");
};
}
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + " байт";
else if (bytes < 1048576) return (bytes / 1024).toFixed(2) + " КБ";
else return (bytes / 1048576).toFixed(2) + " МБ";
}
function updateProgress(value, max) {
const percent = Math.round((value / max) * 100);
const progressBar = document.getElementById("otaProgress");
progressBar.style.width = percent + "%";
progressBar.textContent = percent + "%";
document.getElementById("progressPercent").textContent = percent + "%";
document.getElementById("progressStatus").textContent = `Загружено: ${formatFileSize(value)} из ${formatFileSize(max)}`;
}
function showStatus(message, type = "info") {
// Здесь можно реализовать отображение статусных сообщений
console.log(`${type}: ${message}`);
}
// Обработчики событий
document.getElementById("otaStartCancel").addEventListener("click", function (e) {
if (otaData && otaData.length > 0 && otaStarted == 0) {
socket.send(JSON.stringify({ name: "otaSize", value: otaData.length }));
otaStarted = 1;
this.innerHTML = "<i class='fa fa-times-circle'></i> Отменить загрузку";
document.getElementById("otaFileSelect").disabled = true;
document.getElementById("otaProgressVisible").classList.remove("hidden");
updateProgress(0, otaData.length);
} else {
otaStarted = 0;
socket.send(JSON.stringify({ name: "otaCancel", value: "Cancel" }));
this.innerHTML = "<i class='fa fa-play-circle'></i> Начать обновление";
document.getElementById("otaFileSelect").disabled = false;
}
});
document.getElementById("goHome").addEventListener("click", function (e) {
socket.close();
window.location.href = '/';
});
document.getElementById("otaReStart").addEventListener("click", function (e) {
socket.send(JSON.stringify({ name: "otaRestartEsp", value: "restart" }));
});
// Rollback handlers
document.getElementById("otaVerifyApp").addEventListener("click", function (e) {
socket.send(JSON.stringify({ name: "otaProcessRollback", value: "false" }));
document.getElementById("rollback").classList.add("hidden");
document.getElementById("update").classList.remove("hidden");
});
document.getElementById("otaRollback").addEventListener("click", function (e) {
socket.send(JSON.stringify({ name: "otaProcessRollback", value: "true" }));
document.getElementById("rollback").classList.add("hidden");
document.getElementById("update").classList.remove("hidden");
});
function receiveWsData(data) {
try {
let obj = JSON.parse(data);
switch (obj.name) {
case "otaSetChunkSize":
otaSetChunkSize = obj.value;
break;
case "otaGetChunk":
let otaDataSend = otaData.subarray(obj.value, obj.value + otaSetChunkSize);
updateProgress(obj.value, otaData.length);
document.getElementById("otaStartCancel").innerHTML = `<i class='fa fa-times-circle'></i> Загрузка: ${formatFileSize(obj.value)} из ${formatFileSize(otaData.length)}`;
socket.send(otaDataSend);
break;
case "otaEnd":
otaStartsegment = 0;
otaStarted = 0;
document.getElementById("otaStartCancel").classList.add("hidden");
document.getElementById("otaStartCancel").innerHTML = "<i class='fa fa-play-circle'></i> Начать обновление";
updateProgress(otaData.length, otaData.length);
document.getElementById("otaFileSelect").disabled = false;
document.getElementById("otaReStart").classList.remove("hidden");
document.getElementById("otaReStart").innerHTML = "<i class='fa fa-power-off'></i> Прошивка загружена. Перезагрузить устройство";
document.getElementById("otaReStart").disabled = false;
showStatus("Прошивка успешно загружена", "success");
break;
case "otaError":
case "otaCancel":
otaStartsegment = 0;
otaStarted = 0;
document.getElementById("otaStartCancel").classList.remove("hidden");
document.getElementById("otaStartCancel").innerHTML = "<i class='fa fa-play-circle'></i> Начать обновление";
document.getElementById("otaFileSelect").disabled = false;
document.getElementById("otaReStart").classList.remove("hidden");
document.getElementById("otaReStart").innerHTML = "Загрузка отменена: " + obj.value;
document.getElementById("otaReStart").disabled = true;
showStatus("Загрузка отменена: " + obj.value, "error");
break;
case "otaCheckRollback":
document.getElementById("rollback").classList.remove("hidden");
document.getElementById("update").classList.add("hidden");
break;
}
} catch {
console.log(data + "Error msg");
}
}
// WebSocket connection
let protocol = "ws:";
if (document.location.protocol == "https:") protocol = "wss:";
let wsHostStr = protocol + "//" + document.location.host + document.location.pathname;
wsHostStr += (document.location.pathname == '/') ? "ws" : "/ws";
var socket = new WebSocket(wsHostStr);
socket.binaryType = "arraybuffer";
// WebSocket events
socket.onopen = function () {
console.log("connect ws");
document.getElementById("status-text").textContent = "Подключено";
document.querySelector(".connection-status").classList.remove("disconnected");
document.querySelector(".connection-status").classList.add("connected");
};
socket.onclose = function (event) {
console.log("close ws - reload");
document.getElementById("status-text").textContent = "Не подключено";
document.querySelector(".connection-status").classList.remove("connected");
document.querySelector(".connection-status").classList.add("disconnected");
setTimeout(() => document.location.reload(), 2000);
};
socket.onerror = function () {
console.log("error ws");
document.getElementById("status-text").textContent = "Ошибка подключения";
document.querySelector(".connection-status").classList.remove("connected");
document.querySelector(".connection-status").classList.add("disconnected");
setTimeout(() => document.location.reload(), 2000);
};
socket.onmessage = function (event) {
receiveWsData(event.data);
};
</script>
</body>
</html>