feat(webconfig): trim the displayed version to base, build and channel

The full embedded string is v1.16.0.5-observer-beta-dev-a1b2c3d. The -observer
tag is on every observer build and the commit is for machines, so neither tells
a person anything at a glance. Both the page header and the console banner now
show base + published build number + channel, paired with the build date the
way `ver` pairs them:

  v1.16.0.5-dev (6 Jun 2026)
  v1.16.0.5 (6 Jun 2026)

`ver` still prints the whole string, commit included, for when that is what you
need. The channel suffix follows the release filenames rather than the embedded
tag — build.sh writes FILENAME_CHANNEL_TAG "-dev" for the same builds it tags
"-observer-beta-dev" internally, so "-dev" is the name these already carry.

Carrying the build date meant /api/status had to report it; WebConfigServer now
takes FIRMWARE_BUILD_DATE alongside FIRMWARE_VERSION, from the same defines
`ver` reads.

A local build has neither build number nor channel to show, so the fact worth
knowing about it moves to the second line: "local build, OTA not configured".
build.sh deliberately leaves OTA_MANIFEST_BASE undefined there, and a bare
version number gives no hint that the node cannot update itself.
This commit is contained in:
agessaman committed 2026-08-08 09:01:29 -07:00
1 parent cac91c91d8
commit 20826dcccf
5 files changed
+27 -22

No files matched your search

+1 -1
View File
@@ -1438,7 +1438,7 @@ bool MyMesh::startWebConfig(bool force_ap, char* reply) {
}
if (!_webconfig) {
_webconfig = new WebConfigServer(&_prefs, _cli.getObserverPrefs(), this,
self_id.pub_key, getFirmwareVer(), getRole(),
self_id.pub_key, getFirmwareVer(), getBuildDate(), getRole(),
_cli.getBoard()->getManufacturerName());
}
if (force_ap) {
+2 -1
View File
@@ -187,7 +187,8 @@ class State:
# Shaped like build.sh's EMBEDDED_VERSION_STRING
# (base[.build][-observer][-channel]-hash) so the console's channel
# labelling is exercised against a real version, not "v1.x-mock".
"fw": FW_VERSION, "role": "Repeater", "board": "Heltec V3 (mock)",
"fw": FW_VERSION, "build_date": "6 Jun 2026",
"role": "Repeater", "board": "Heltec V3 (mock)",
"uptime_s": int(time.time() - self.start),
"runtime_slots": 6, "max_slots": 6, "active_slots": self.active_slots,
"max_cmds": CLI_MAX_CMDS,
+5 -1
View File
@@ -112,9 +112,10 @@ static portMUX_TYPE s_wc_route_mux = portMUX_INITIALIZER_UNLOCKED;
WebConfigServer::WebConfigServer(NodePrefs* prefs, MQTTPrefs* obs, Callbacks* callbacks,
const uint8_t* pub_key, const char* fw_ver,
const char* build_date,
const char* role, const char* board_name)
: _prefs(prefs), _obs(obs), _cb(callbacks), _pub_key(pub_key),
_fw_ver(fw_ver), _role(role), _board_name(board_name) {
_fw_ver(fw_ver), _build_date(build_date), _role(role), _board_name(board_name) {
_mux = xSemaphoreCreateMutex();
}
@@ -571,6 +572,9 @@ void WebConfigServer::handleStatus(AsyncWebServerRequest* req) {
for (int i = 0; i < 8; i++) sprintf(&node_id[i * 2], "%02x", _pub_key[i]);
doc["node_id"] = node_id;
doc["fw"] = _fw_ver;
// The page shows a trimmed version — base + build number + channel — and
// pairs it with this, the way `ver` does. Both come from the same defines.
doc["build_date"] = _build_date;
doc["role"] = _role;
doc["board"] = _board_name;
doc["uptime_s"] = millis() / 1000;
+2 -1
View File
@@ -64,7 +64,7 @@ public:
};
WebConfigServer(NodePrefs* prefs, MQTTPrefs* obs, Callbacks* callbacks,
const uint8_t* pub_key, const char* fw_ver,
const uint8_t* pub_key, const char* fw_ver, const char* build_date,
const char* role, const char* board_name);
~WebConfigServer();
@@ -125,6 +125,7 @@ private:
Callbacks* _cb;
const uint8_t* _pub_key;
const char* _fw_ver;
const char* _build_date;
const char* _role;
const char* _board_name;
+17 -18
View File
@@ -582,6 +582,18 @@ function $(s){return document.querySelector(s)}
function $$(s){return Array.prototype.slice.call(document.querySelectorAll(s))}
function toast(m){var t=$("#toast");t.textContent=m;t.classList.add("show");clearTimeout(t._h);t._h=setTimeout(function(){t.classList.remove("show")},2600)}
function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c]})}
// build.sh embeds base[.build][-observer][-channel]-hash, e.g.
// v1.16.0.5-observer-beta-dev-a1b2c3d. What identifies a build to a person is
// the base, the published build number and the channel — the -observer variant
// tag is the same on every observer build and the commit is for machines. `ver`
// still prints the whole string when you want it.
function shortVer(){
var fw=st.fw||"",m=/^(v?\d+\.\d+\.\d+(?:\.\d+)?)/.exec(fw);
var s=m?m[1]:(fw||"unknown version");
if(/-dev(-|$)/.test(fw))s+="-dev"; // OTA_CHANNEL_TAG=beta-dev
else if(/-beta(-|$)/.test(fw))s+="-beta"; // a beta-only channel, if ever
return st.build?s+" ("+st.build+")":s;
}
function api(path,opts){
opts=opts||{};
@@ -629,9 +641,9 @@ function boot(){
// size for older firmware that doesn't report it.
st.mode=s.mode;st.authed=s.auth;st.needsSetup=!!s.needs_setup;st.nslots=s.active_slots||s.runtime_slots||6;
if(s.max_cmds>0)CLI_MAX=s.max_cmds;
st.fw=s.fw||"";st.role=s.role||"";st.board=s.board||"";
st.fw=s.fw||"";st.build=s.build_date||"";st.role=s.role||"";st.board=s.board||"";
$("#h-name").textContent=s.name||"MeshCore";
$("#h-sub").textContent=s.role+" · "+s.fw+" · "+s.board;
$("#h-sub").textContent=s.role+" · "+shortVer()+" · "+s.board;
var b=$("#h-badge");b.classList.remove("hide");
if(s.mode==="setup"){b.textContent="SETUP";b.classList.add("setup")}else{b.textContent="LAN"}
// Only in setup mode: in LAN mode the console is a tab, and the header is
@@ -1637,23 +1649,10 @@ function cliEcho(cls,text){
}
function cliGap(){var d=cliEcho("gap","");return d}
function cliClear(){$("#term-out").innerHTML="";cliBanner();$("#term-in").focus()}
// build.sh composes FIRMWARE_VERSION as base[.build][-observer][-channel]-hash,
// e.g. v1.16.0.5-observer-beta-dev-a1b2c3d. A plain `pio run` never goes through
// build.sh, so it keeps the header default (v1.16.0) with no build number, no
// hash, and — deliberately — no OTA_MANIFEST_BASE, so such a node cannot update
// itself. Worth saying out loud: the version alone does not make that obvious.
function cliChannel(fw){
if(/-dev(-|$)/.test(fw))return"dev channel"; // OTA_CHANNEL_TAG=beta-dev
if(/-beta(-|$)/.test(fw))return"beta channel";
if(/^v?\d+\.\d+\.\d+$/.test(fw))return"local build — not from CI, OTA not configured";
return"release channel";
}
function cliBanner(){
// Full string, unabbreviated: the 4th component is the published build number
// and the trailing token is the commit, which is what identifies a build.
var ch=cliChannel(st.fw||"");
cliEcho("sys","MeshCore "+(st.fw||"unknown version")+(ch?" ("+ch+")":""));
cliEcho("sys",(st.role||"")+" · "+(st.board||""));
cliEcho("sys","MeshCore "+shortVer());
cliEcho("sys",(st.role||"")+" · "+(st.board||"")+
(/^v?\d+\.\d+\.\d+$/.test(st.fw||"")?" · local build, OTA not configured":""));
if(st.mode==="setup"){
// The wizard refuses to finish without an admin password; nothing stops a
// console-driven setup from rebooting on the factory one, so say so here