intermediate: prepare custom install in webinstaller

This commit is contained in:
andreas 2023-09-06 12:22:33 +02:00
parent 0e0be14415
commit a9396798ce
7 changed files with 283 additions and 47 deletions

View File

@ -1,3 +1,96 @@
.hidden{ .hidden{
display: none; display: none;
} }
/* reused stuff from configui */
.configui.container{
max-width: 35em;
margin-left: auto;
margin-right: auto;
position: relative;
}
.configui .info{
margin-bottom: 1em;
opacity: 0.6;
white-space: pre-line;
}
.configui .parameters {
border-bottom: 1px solid grey;
margin-bottom: 1em;
}
.configui .row input[type="checkbox"] {
flex-grow: 1;
appearance: auto;
}
.configui .row {
display: flex;
flex-direction: row;
margin: 0.5em;
flex-wrap: unset;
padding: 0;
}
.configui .row .label {
width: 10em;
opacity: 0.6;
padding: 0;
}
.configui .since {
display: block;
font-size: 0.8em;
}
.configui input[type=checkbox] {
width: 1.5em;
height: 1.5em;
opacity: 1;
z-index: unset;
}
.configui .buttons {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.configui button {
padding: 0.5em;
}
.configui .footer {
text-align: right;
margin-top: 1em;
font-size: 0.8em;
}
.configui .visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
}
.configui .dialogBack {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 10;
background-color: #8a8c8ec7;
display: flex;
}
.configui .hidden{
display: none;
}
.configui .dialog{
max-width: 35em;
margin: auto;
background-color: white;
padding: 2em;
}
.configui #warn{
display: none;
color: red;
}
.configui #warn.warn{
display: block;
}

View File

@ -7,32 +7,40 @@
</head> </head>
<body> <body>
<div class="configui container">
<h1>Build your own ESP32-NMEA2000</h1> <h1>Build your own ESP32-NMEA2000</h1>
<div class="xrow"> <div class="row">
<label>Board type</label> <span class="label">Board type</span>
<input type="text" id="environment" value="m5stack-atom-generic"> <input type="text" id="environment" value="m5stack-atom-generic">
</div> </div>
<div class="xrow"> <div class="row">
<label>Build Flags</label> <span class="label">Build Flags</span>
<input type="text" id="buildflags" value=""> <input type="text" id="buildflags" value="">
</div> </div>
<div class="xrow"> <div class="row">
<button id="start">Start</button> <button id="start">Start</button>
</div> </div>
<div class="xrow"> <div class="row">
<label>Pipeline Id</label> <span class="label">Job Id</span>
<div id="pipeline">---</div> <div id="pipeline">---</div>
</div> </div>
<div class="xrow"> <div class="row">
<label>Status</label> <span class="label">Status</span>
<div id="status">---</div> <div id="status">---</div>
</div> </div>
<div class="xrow hidden"> <div class="row hidden error">
<a target="_" id="link">WebStatus</a> <span class="label">Error</span>
<div class="value" id="error"></div>
</div> </div>
<div class="xrow hidden"> <div class="row hidden">
<span class="label">Web Status</span>
<a target="_" id="status_url">Link</a>
</div>
<div class="row hidden">
<button id="download">Download</button> <button id="download">Download</button>
<button id="webinstall">Install</button>
</div> </div>
<iframe id="dlframe" width="1" height="1"></iframe> <iframe id="dlframe" width="1" height="1"></iframe>
</div>
</body> </body>
</html> </html>

View File

@ -1,4 +1,4 @@
import { setButtons,fillValues, setValue, buildUrl, fetchJson, setVisible, enableEl } from "./helper"; import { setButtons,fillValues, setValue, buildUrl, fetchJson, setVisible, enableEl, setValues } from "./helper";
(function(){ (function(){
const STATUS_INTERVAL=2000; const STATUS_INTERVAL=2000;
const CURRENT_PIPELINE='pipeline'; const CURRENT_PIPELINE='pipeline';
@ -6,37 +6,56 @@ import { setButtons,fillValues, setValue, buildUrl, fetchJson, setVisible, enabl
let currentPipeline=undefined; let currentPipeline=undefined;
let downloadUrl=undefined; let downloadUrl=undefined;
let timer=undefined; let timer=undefined;
const fetchStatus=()=>{ const showError=(text)=>{
if (text === undefined){
setVisible('buildError',false,true);
return;
}
setValue('buildError',text);
setVisible('buildError',true,true);
}
const setRunning=(active)=>{
if (active){
downloadUrl=undefined;
showError();
setVisible('download',false,true);
setVisible('status_url',false,true);
}
enableEl('start',!active);
}
const fetchStatus=(initial)=>{
if (currentPipeline === undefined) return; if (currentPipeline === undefined) return;
fetchJson(API,{api:'status',pipeline:currentPipeline}) fetchJson(API,{api:'status',pipeline:currentPipeline})
.then((st)=>{ .then((st)=>{
setValue('status',st.status); setValues(st);
let l=document.getElementById('link'); setVisible('status_url',st.status_url !== undefined,true);
if (l){ setVisible('error',st.error !== undefined,true);
if (st.status_url){ if (st.status === 'error'){
l.setAttribute('href',st.status_url); setRunning(false);
setVisible(l.parentElement,true); setVisible('download',false,true);
} return;
else{
setVisible(l.parentElement,false);
}
} }
if (st.status === 'success'){ if (st.status === 'success'){
enableEl('start',true); setRunning(false);
fetchJson(API,{api:'artifacts',pipeline:currentPipeline}) fetchJson(API,{api:'artifacts',pipeline:currentPipeline})
.then((ar)=>{ .then((ar)=>{
if (! ar.items || ar.items.length < 1){ if (! ar.items || ar.items.length < 1){
throw new Error("no download link"); throw new Error("no download link");
} }
downloadUrl=ar.items[0].url; downloadUrl=buildUrl(API,{
setVisible(document.getElementById('download'),true,true); download: currentPipeline
});
setVisible('download',true,true);
}) })
.catch((err)=>alert("Unable to get build result: "+err)); .catch((err)=>{
showError("Unable to get build result: "+err);
setVisible('download',false,true);
});
return; return;
} }
else{ else{
setVisible(document.getElementById('download'),false,true); setVisible('download',false,true);
} }
timer=window.setTimeout(fetchStatus,STATUS_INTERVAL) timer=window.setTimeout(fetchStatus,STATUS_INTERVAL)
}) })
@ -55,6 +74,7 @@ import { setButtons,fillValues, setValue, buildUrl, fetchJson, setVisible, enabl
timer=undefined; timer=undefined;
fillValues(param,['environment','buildflags']); fillValues(param,['environment','buildflags']);
setValue('status','requested'); setValue('status','requested');
setRunning(true);
fetchJson(API,Object.assign({ fetchJson(API,Object.assign({
api:'start'},param)) api:'start'},param))
.then((json)=>{ .then((json)=>{
@ -65,13 +85,12 @@ import { setButtons,fillValues, setValue, buildUrl, fetchJson, setVisible, enabl
setCurrentPipeline(json.id); setCurrentPipeline(json.id);
setValue('pipeline',currentPipeline); setValue('pipeline',currentPipeline);
setValue('status',json.status); setValue('status',json.status);
enableEl('start',false);
timer=window.setTimeout(fetchStatus,STATUS_INTERVAL); timer=window.setTimeout(fetchStatus,STATUS_INTERVAL);
}) })
.catch((err)=>{ .catch((err)=>{
setRunning(false);
setValue('status','error'); setValue('status','error');
enableEl('start',true); showError(err);
alert(err);
}); });
} }
const runDownload=()=>{ const runDownload=()=>{
@ -82,17 +101,23 @@ import { setButtons,fillValues, setValue, buildUrl, fetchJson, setVisible, enabl
df.setAttribute('src',downloadUrl); df.setAttribute('src',downloadUrl);
} }
} }
const webInstall=()=>{
if (! downloadUrl) return;
let url=buildUrl("install.html",{custom:downloadUrl});
window.location.href=url;
}
const btConfig={ const btConfig={
start:startBuild, start:startBuild,
download:runDownload download:runDownload,
webinstall:webInstall
}; };
window.onload=()=>{ window.onload=()=>{
setButtons(btConfig); setButtons(btConfig);
currentPipeline=window.localStorage.getItem(CURRENT_PIPELINE); currentPipeline=window.localStorage.getItem(CURRENT_PIPELINE);
if (currentPipeline){ if (currentPipeline){
setValue('pipeline',currentPipeline); setValue('pipeline',currentPipeline);
enableEl('start',false); setRunning(true);
fetchStatus(); fetchStatus(true);
} }
} }
})(); })();

View File

@ -192,6 +192,7 @@ try {
} }
$dlurl = $astat['items'][0]['url']; $dlurl = $astat['items'][0]['url'];
#echo("DL: $dlurl\n"); #echo("DL: $dlurl\n");
header('Content-Disposition: attachment; filename="'.$astat['items'][0]['path'].'"');
proxy($dlurl); proxy($dlurl);
exit(0); exit(0);
} }

View File

@ -62,6 +62,21 @@ const setValue=(id,value)=>{
} }
if (el.tagName == 'INPUT'){ if (el.tagName == 'INPUT'){
el.value=value; el.value=value;
return;
}
if (el.tagName == 'A'){
el.setAttribute('href',value);
return;
}
}
const setValues=(data,translations)=>{
for (let k in data){
let id=k;
if (translations){
let t=translations[k];
if (t !== undefined) id=t;
}
setValue(id,data[k]);
} }
} }
const buildUrl=(url,pars)=>{ const buildUrl=(url,pars)=>{
@ -80,6 +95,7 @@ const fetchJson=(url,pars)=>{
return fetch(furl).then((rs)=>rs.json()); return fetch(furl).then((rs)=>rs.json());
} }
const setVisible=(el,vis,useParent)=>{ const setVisible=(el,vis,useParent)=>{
if (typeof(el) !== 'object') el=document.getElementById(el);
if (! el) return; if (! el) return;
if (useParent) el=el.parentElement; if (useParent) el=el.parentElement;
if (! el) return; if (! el) return;
@ -93,4 +109,4 @@ const enableEl=(id,en)=>{
else el.disabled=true; else el.disabled=true;
} }
export { getParam, addEl, forEachEl,setButtons,fillValues, setValue,buildUrl,fetchJson,setVisible, enableEl } export { getParam, addEl, forEachEl,setButtons,fillValues, setValue,setValues,buildUrl,fetchJson,setVisible, enableEl }

View File

@ -1,6 +1,7 @@
import {XtermOutputHandler} from "./installUtil.js"; import {XtermOutputHandler} from "./installUtil.js";
import ESPInstaller from "./installUtil.js"; import ESPInstaller from "./installUtil.js";
import { addEl, getParam } from "./helper.js"; import { addEl, getParam } from "./helper.js";
import * as zip from "https://cdn.jsdelivr.net/npm/@zip.js/zip.js@2.7.29/+esm";
(function(){ (function(){
let espLoaderTerminal; let espLoaderTerminal;
let espInstaller; let espInstaller;
@ -21,11 +22,11 @@ import { addEl, getParam } from "./helper.js";
alert(txt); alert(txt);
} }
} }
const buildHeading=(user,repo,element)=>{ const buildHeading=(info,element)=>{
let hFrame=document.querySelector(element||'.heading'); let hFrame=document.querySelector(element||'.heading');
if (! hFrame) return; if (! hFrame) return;
hFrame.textContent=''; hFrame.textContent='';
let h=addEl('h2',undefined,hFrame,`ESP32 Install ${user}:${repo}`) let h=addEl('h2',undefined,hFrame,`ESP32 Install ${info}`)
} }
const checkChip=(chipFamily,assetName)=>{ const checkChip=(chipFamily,assetName)=>{
//for now only ESP32 //for now only ESP32
@ -100,7 +101,7 @@ import { addEl, getParam } from "./helper.js";
let line=addEl('div','item',bFrame); let line=addEl('div','item',bFrame);
addEl('div','itemTitle',line,item.label); addEl('div','itemTitle',line,item.label);
let btLine=addEl('div','buttons',line); let btLine=addEl('div','buttons',line);
let tb=addEl('button','installButton',line,'Initial'); let tb=addEl('button','installButton',btLine,'Initial');
tb.addEventListener('click',async ()=>{ tb.addEventListener('click',async ()=>{
enableConsole(false,true); enableConsole(false,true);
await espInstaller.installClicked( await espInstaller.installClicked(
@ -113,7 +114,7 @@ import { addEl, getParam } from "./helper.js";
) )
enableConsole(true); enableConsole(true);
}); });
tb=addEl('button','installButton',line,'Update'); tb=addEl('button','installButton',btLine,'Update');
tb.addEventListener('click',async ()=>{ tb.addEventListener('click',async ()=>{
enableConsole(false,true); enableConsole(false,true);
await espInstaller.installClicked( await espInstaller.installClicked(
@ -129,23 +130,85 @@ import { addEl, getParam } from "./helper.js";
} }
} }
const buildCustomButtons = (name, updateData, fullData,version,element) => {
let bFrame = document.querySelector(element || '.content');
if (!bFrame) return;
bFrame.textContent = '';
addEl('div', 'version', bFrame, "Custom Installation");
let btLine = addEl('div', 'buttons', bFrame);
let tb = addEl('button', 'installButton', btLine, 'Initial');
tb.addEventListener('click', async () => {
enableConsole(false, true);
await espInstaller.runFlash(
true,
fullData,
4096,
version,
(ch) => checkChip(ch, name)
)
enableConsole(true);
});
tb = addEl('button', 'installButton', btLine, 'Update');
tb.addEventListener('click', async () => {
enableConsole(false, true);
await espInstaller.runFlash(
false,
updateData,
65536,
version,
(ch) => checkChip(ch, name)
)
enableConsole(true);
});
}
const showLoading=(on)=>{
};
window.onload = async () => { window.onload = async () => {
if (! ESPInstaller.checkAvailable()){ if (! ESPInstaller.checkAvailable()){
showError("your browser does not support the ESP flashing (no serial)"); showError("your browser does not support the ESP flashing (no serial)");
return; return;
} }
let user = window.gitHubUser||getParam('user'); let custom=getParam('custom');
let repo = window.gitHubRepo || getParam('repo'); let user;
if (!user || !repo) { let repo;
alert("missing parameter user or repo"); if (! custom){
user = window.gitHubUser||getParam('user');
repo = window.gitHubRepo || getParam('repo');
if (!user || !repo) {
alert("missing parameter user or repo");
}
} }
try { try {
espLoaderTerminal = new XtermOutputHandler('terminal'); espLoaderTerminal = new XtermOutputHandler('terminal');
espInstaller = new ESPInstaller(espLoaderTerminal); espInstaller = new ESPInstaller(espLoaderTerminal);
buildHeading(user, repo);
buildConsoleButtons(); buildConsoleButtons();
releaseData = await espInstaller.getReleaseInfo(user, repo); if (! custom){
buildButtons(user, repo); buildHeading(`${user}:${repo}`);
releaseData = await espInstaller.getReleaseInfo(user, repo);
buildButtons(user, repo);
}
else{
showLoading(true);
let reader= new zip.HttpReader(custom);
let zipReader= new zip.ZipReader(reader);
const entries=(await zipReader.getEntries());
let fullData;
let updateData;
let base="";
for (let i=0;i<entries.length;i++){
if (entries[i].filename.match(/-all.bin$/)){
fullData=await(entries[i].getData(new zip.BlobWriter()))
base=entries[i].filename.replace("-all.bin","");
}
if (entries[i].filename.match(/-update.bin$/)){
updateData=await(entries[i].getData(new zip.BlobWriter()))
base=entries[i].filename.replace("-update.bin","");
}
}
buildCustomButtons("dummy",updateData,fullData,base);
showLoading(false);
}
} catch(error){alert("unable to query release info for user "+user+", repo "+repo+": "+error)}; } catch(error){alert("unable to query release info for user "+user+", repo "+repo+": "+error)};
} }
})(); })();

View File

@ -268,6 +268,36 @@ class ESPInstaller{
alert(`Error: ${e}`); alert(`Error: ${e}`);
} }
} }
/**
* directly run the flash
* @param {*} isFull
* @param {*} address
* @param {*} imageData the data to be flashed
* @param {*} version the info shown in the dialog
* @returns
*/
async runFlash(isFull,address,imageData,version,assetName){
try {
await this.connect();
if (typeof (assetName) === 'function') {
assetName(this.getChipFamily()); //just check
}
let fileList = [
{ data: imageData, address: address }
];
let txt = isFull ? "baseImage (all data will be erased)" : "update";
if (!confirm(`ready to install ${version}\n${txt}`)) {
this.espLoaderTerminal.writeLine("aborted by user...");
await this.disconnect();
return;
}
await this.writeFlash(fileList);
await this.disconnect();
} catch (e) {
this.espLoaderTerminal.writeLine(`Error: ${e}`);
alert(`Error: ${e}`);
}
}
/** /**
* fetch the release info from the github API * fetch the release info from the github API
* @param {*} user * @param {*} user