@@ -612,6 +627,9 @@ function boot(){
$("#h-sub").textContent=s.role+" · "+s.fw+" · "+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
+ // also on screen before login, where no shortcut into it belongs.
+ $("#h-console").classList.toggle("hide",s.mode!=="setup");
return api("/api/presets").catch(function(){return{presets:[]}});
}).then(function(p){
st.presets=p.presets||[];
@@ -623,7 +641,9 @@ function boot(){
});
}
-function show(v){["#v-login","#v-wizard","#v-app"].forEach(function(id){$(id).classList.add("hide")});$(v).classList.remove("hide");updateSaveBar()}
+function show(v){["#v-login","#v-wizard","#v-app"].forEach(function(id){$(id).classList.add("hide")});$(v).classList.remove("hide");
+ document.body.classList.remove("tab-cli"); // re-armed by the tab click when the CLI is the one shown
+ updateSaveBar()}
function showLogin(){show("#v-login")}
/* ---------- login ---------- */
@@ -1066,13 +1086,17 @@ function loadConfigSoft(){ // re-sync accepted values without clobbering chips/r
/* ---------- tabs / app ---------- */
function enterApp(){
show("#v-app");
- // Setup mode authenticates by proximity — there is no admin password yet. The
- // form batch is allowlisted (WebConfigKeys.h) so that is safe there, but the
- // CLI is the entire command surface, `erase` and `set prv.key` included, so
- // it stays behind a real login.
- $('#tabs button[data-t="cli"]').classList.toggle("hide",st.mode==="setup");
loadConfig().catch(function(e){if(e.message!=="auth")toast("Failed to load config")});
}
+// Setup-mode shortcut: straight past the guided steps into the terminal, for
+// operators who already have a config to paste. Setup mode authenticates by
+// proximity to the AP, so this hands the whole command surface to anyone in
+// range — the same trust the wizard already extends, since it can set the admin
+// password and reflash the node's identity too.
+function enterConsole(){
+ enterApp();
+ $('#tabs button[data-t="cli"]').click();
+}
$("#tabs").addEventListener("click",function(ev){
var b=ev.target.closest("button");if(!b)return;
$$("#tabs button").forEach(function(x){x.classList.toggle("on",x===b)});
@@ -1542,7 +1566,7 @@ var CLI_SLOT=[
];
var CLI_TYPES="req,response,txt_msg,ack,advert,grp_txt,grp_data,anon_req,path,trace,multipart,control,raw_custom".split(",");
-var cli={built:0,tbl:[],hist:[],hix:-1,draft:"",sug:[],sel:-1,busy:false,shown:false};
+var cli={built:0,tbl:[],hist:[],hix:-1,draft:"",sug:[],sel:-1,busy:false,shown:false,pwd:false};
// Rebuilt when the slot count changes: `active_slots` decides how many
// mqttN.* keys actually exist on this board.
@@ -1596,6 +1620,13 @@ function cliGap(){var d=cliEcho("gap","");return d}
function cliClear(){$("#term-out").innerHTML="";cliBanner();$("#term-in").focus()}
function cliBanner(){
cliEcho("sys","MeshCore · "+($("#h-sub").textContent||""));
+ 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
+ // rather than only warning at the point of reboot.
+ cliEcho("sys","Setup mode — paste a prepared config, then finish with "+
+ "\"password \" and \"reboot\". \"← setup\" returns to the guided steps.");
+ }
cliEcho("sys","Type help for a summary, or a prefix and Tab to complete.");
}
function cliHelp(){
@@ -1855,6 +1886,14 @@ function cliConfirm(cmds){
r.textContent="⚠ This "+risks.join("; and ")+".";
box.appendChild(r);
}
+ // Onboarding by paste skips the wizard's mandatory admin-password step, and
+ // rebooting is what commits the node to normal operation.
+ if(st.mode==="setup"&&!cli.pwd&&cmds.some(function(c){return /^(reboot|clkreboot)$/.test(c)})){
+ var p=document.createElement("div");p.className="w";
+ p.textContent="⚠ No password command has run here. Set the admin password before rebooting, "+
+ "or the node keeps the factory one.";
+ box.appendChild(p);
+ }
var btns=document.createElement("div");btns.className="btns";
var go=document.createElement("button");go.className="go";
go.textContent=one?"Run":("Run "+cmds.length+" commands");
@@ -1949,6 +1988,7 @@ function cliPoll(reqid,cmds,from,status,errs,idles){
function cliAfter(cmds){
var touched=false,restart=null;
cmds.forEach(function(c){
+ if(/^password\s/.test(c))cli.pwd=true;
if(/^(set|password)\s/.test(c))touched=true;
if(/^(reboot|clkreboot)$/.test(c))restart=restart||"reboot";
if(/^erase$/.test(c))restart=restart||"erase";
@@ -1965,7 +2005,7 @@ function cliAfter(cmds){
actually left, so size the terminal from that while the CLI tab is open. */
function cliFit(){
var vv=window.visualViewport,t=$("#term");
- if(!t||$("#t-cli").classList.contains("hide"))return;
+ if(!t||$("#t-cli").classList.contains("hide")||$("#v-app").classList.contains("hide"))return;
t.style.height=""; // back to the stylesheet's height
if(!vv)return;
var base=t.offsetHeight;
@@ -1987,6 +2027,7 @@ if(window.visualViewport){
}
function enterCli(){
$("#term-host").textContent="meshcore@"+($("#h-name").textContent||"node");
+ $("#term-setup").classList.toggle("hide",st.mode!=="setup");
if(!cli.shown){cli.shown=true;cliBanner()}
window.scrollTo(0,0); // the terminal is the whole tab; show all of it
cliFit();