Add OTA UI

This commit is contained in:
cpq
2026-05-03 10:29:16 +01:00
parent 5a2e42ae25
commit 01aeaa207c
3 changed files with 87 additions and 10 deletions
+1 -5
View File
@@ -100,10 +100,7 @@
.then(resp => {
status.username = resp?.user || '';
status.userlevel = resp?.level || 0;
//status.authed = !!status.username;
//if (settings.auth && status.authed) {
rpc_connect();
//rescan();
return resp;
});
};
@@ -116,7 +113,6 @@
status.username = '';
status.userlevel = 0;
rpc_disconnect();
rescan();
return true;
});
}
@@ -125,7 +121,7 @@
const isMock = location.protocol === 'file:' || !!window.frameElement || (window !== window.top);
const settings = {data: {}, edits: {}, debug: true};
const userhandlers = {}; // User event handlers
const status = { online: true, username: '', userlevel: 0, lastseen: '', ready: false };
const status = { online: false, username: '', userlevel: 0, ready: false };
let rpc = null;
const fromPath = (path, value) => path.split('.').reverse().reduce((acc, k) => ({ [k]: acc }), value);
@@ -69,6 +69,10 @@ static struct mg_field_set set_metrics = {
static double s_volume = 17.2;
static int s_log_level = MG_LL_DEBUG;
static char s_name[20] = "Dublin";
static int s_ota_interval = 30;
static char s_ota_version[] = "1.1.2";
static char s_ota_status[40] = "No scans yet";
static char s_ota_url[100] = "https://my-product.com/ota.json";
static void write_settings(void) {
mg_log_level = s_log_level;
@@ -82,6 +86,10 @@ static struct mg_field fields_settings[] = {
{"volume", MG_VAL_DBL, &s_volume, sizeof(s_volume)},
{"name", MG_VAL_STR, &s_name, sizeof(s_name)},
{"log_level", MG_VAL_INT, &s_log_level, sizeof(s_log_level)},
{"ota_version", MG_VAL_STR, &s_ota_version, sizeof(s_ota_version)},
{"ota_status", MG_VAL_STR, &s_ota_status, sizeof(s_ota_status)},
{"ota_url", MG_VAL_STR, &s_ota_url, sizeof(s_ota_url)},
{"ota_interval", MG_VAL_INT, &s_ota_interval, sizeof(s_ota_interval)},
{NULL, MG_VAL_INT, NULL, 0},
};
+78 -5
View File
@@ -14,8 +14,8 @@
graph2: {data: [], report: true},
actions: { action1: false, action2: false },
leds: { led1: true, led2: false, led3: false },
settings: { volume: 12, log_level: 3, name: 'Galway' },
metrics: { temperature: 1.2, cpu: 4, ram: 5 },
settings: { volume: 12, log_level: 3, name: 'Galway', ota_version: '1.2.3', ota_status: 'No checks yet', ota_url: 'http://my-product.com/ota.json', ota_interval: 30 },
metrics: { temperature: 1.2, cpu: 4, ram: 5, version: '1.2.3' },
files: {data: [{name: 'device-log-2026-04-25.txt', size: 8625563}]},
},
});
@@ -222,7 +222,7 @@ a, a:visited, a:link { text-decoration: none; }
.panel-body { display: flex; flex-direction: column; gap: 0.5rem; padding: 1rem 1.25rem; flex: 1; }
.panel-footer { display: flex; align-items: center; justify-content: flex-end; gap: 0.75rem; padding: 0.75rem 1.25rem; border-top: 1px solid #e2e8f0; }
.field { display: flex; flex-direction: row; justify-content: space-between; align-items: center; gap: 0.75rem; }
.field-label { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: #94a3b8; flex-shrink: 0; white-space: nowrap; min-width: 6rem; }
.field-label { font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: #94a3b8; flex-shrink: 0; white-space: nowrap; min-width: 3rem; }
.field-input { flex: 1; min-width: 0; padding: 0.45rem 0.75rem; font-size: 0.88rem; font-family: inherit; color: #334155; border: 1px solid #e2e8f0; border-radius: 0.55rem; outline: none; transition: border-color 0.15s ease; }
.field-input.edited { border-color: #00500040 !important; background: #00ff0020; }
.field-input.error { border-color: #50000040; background: #ff000020; }
@@ -253,6 +253,7 @@ a, a:visited, a:link { text-decoration: none; }
.file-icon { display: inline-flex; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem; color: #0369a1; background: #e0f2fe; border: 1px solid #bae6fd; border-radius: 0.65rem; flex-shrink: 0; }
.file-icon svg { width: 1.15rem; height: 1.15rem; }
.file-name { min-width: 0; color: #334155; font-size: 0.9rem; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.field-value { min-width: 0; color: #334155; font-size: 0.9rem; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-size { color: #94a3b8; font-size: 0.8rem; font-weight: 700; text-align: right; white-space: nowrap; }
.file-actions { display: flex; align-items: center; justify-content: flex-end; gap: 0.45rem; }
#graph1, #graph2 { width: 100%; height: 100%; min-height: 10rem; }
@@ -269,7 +270,7 @@ a, a:visited, a:link { text-decoration: none; }
.toggle.edited { border-color: #0fe; }
.u-under { top: 0 !important; }
.hidden { display: none; }
@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } #files .row { flex-direction: column; flex-wrap: nowrap; } #files .panel, #files .note { flex: 1 1 auto; width: 100%; } }
@media (max-width: 900px) { .grid { grid-template-columns: 1fr !important; } #files .row { flex-direction: column; flex-wrap: nowrap; } #files .panel, #files .note { flex: 1 1 auto; width: 100%; } }
@media (max-width: 900px) { .metrics-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .metrics-grid { grid-template-columns: 1fr; } .file-row { grid-template-columns: 1fr; } .file-actions { justify-content: flex-start; } .file-row { gap: 0.65rem; } .file-size { text-align: left; } }
</style>
@@ -602,7 +603,79 @@ a, a:visited, a:link { text-decoration: none; }
</div>
</section>
<section id="firmware" class="tab-panel" role="tabpanel" aria-labelledby="tab-firmware" hidden></section>
<section id="firmware" class="tab-panel" role="tabpanel" aria-labelledby="tab-firmware" hidden>
<div class="grid" style="grid-template-columns: 28rem 1fr;">
<div class="panel">
<div class="panel-title">Firmware update via file upload</div>
<div class="panel-body">
<div class="field">
<div class="field-label">Current firmware version</div>
<span class="field-value">${settings.ota_version}</span>
</div>
<div class="field">
<div class="field-label">Upload new firmware</div>
<label class="btn btn-primary">
Select .bin file
<input type="file" accept=".bin" hidden>
</label>
</div>
</div>
</div>
<div class="note" style="min-width: 40rem;">
<div>
This firmware update method is the simplest. Just select your
`firmware.bin` file and upload it.
Mongoose includes built-in firmware update support for many
architectures, such as STM32, RP2040, and ESP32. See
<a target="_blank" href="https://github.com/cesanta/mongoose/blob/master/src/ota.h">ota.h</a>
for the full list of supported MCUs.
If you are using STM32, it works out of the box with no additional setup.
For other architectures, add `#define MG_OTA MG_OTA_XXX` to your
mongoose_config.h.
</div>
</div>
<div class="panel">
<div class="panel-title">Firmware update via periodic URL check</div>
<div class="panel-body">
<div class="field">
<div class="field-label">Current firmware version</div>
<span class="field-value">${settings.ota_version}</span>
</div>
<div class="field">
<div class="field-label">Current status</div>
<span class="field-value">${settings.ota_status}</span>
</div>
<div class="field">
<div class="field-label">Check interval, seconds</div>
<input class="field-input" style="max-width: 6rem;" data-bind="settings.ota_interval" data-autosave="1" />
</div>
<div class="field">
<div class="field-label">URL</div>
<input class="field-input" data-bind="settings.ota_url" data-autosave="1" />
</div>
</div>
</div>
<div class="note" style="min-width: 20rem;">
<div>
With this mechanism, you can configure an entire device fleet to update
automatically. Upload your firmware .bin to a web server and create a
JSON file that describes the firmware, including its URL, checksum, and
size. Mongoose periodically checks this JSON file, and if the version
differs from the current one, it downloads the firmware and performs the
update. The JSON file format is described in the
<a target="_blank" href="https://mongoose.ws/docs/api/ota/#mg_ota_url_check">docs</a>:
<pre>
{
"version": "1.2.3",
"url": "http://my-product.com/firmwares/firmware-1.2.3.bin",
"size": 324645
}
</pre>
</div>
</div>
</section>
<section id="files" class="tab-panel" role="tabpanel" aria-labelledby="tab-files" hidden>
<div class="row">
<div class="panel">