893 lines
32 KiB
JavaScript
893 lines
32 KiB
JavaScript
import { addEl, setButtons,fillValues, setValue, buildUrl, fetchJson, setVisible, enableEl, setValues, getParam, fillSelect, forEachEl, readFile } from "./helper.js";
|
|
import {load as yamlLoad} from "https://cdn.skypack.dev/js-yaml@4.1.0";
|
|
import fileDownload from "https://cdn.skypack.dev/js-file-download@0.4.12"
|
|
class PipelineInfo{
|
|
constructor(id){
|
|
this.STFIELDS=['status','error','status_url'];
|
|
this.reset(id);
|
|
this.lastUpdate=0;
|
|
}
|
|
update(state){
|
|
this.lastUpdate=(new Date()).getTime();
|
|
if (state.pipeline_id !== undefined && state.pipeline_id !== this.id){
|
|
return false;
|
|
}
|
|
this.STFIELDS.forEach((i)=>{
|
|
let v=state[i];
|
|
if (v !== undefined)this[i]=v;
|
|
});
|
|
}
|
|
reset(id,opt_state){
|
|
this.id=id;
|
|
this.STFIELDS.forEach((i)=>this[i]=undefined);
|
|
this.downloadUrl=undefined;
|
|
if (opt_state) {
|
|
this.update(opt_state);
|
|
}
|
|
else{
|
|
if (id !== undefined) this.status='fetching';
|
|
}
|
|
}
|
|
valid(){
|
|
return this.id !== undefined;
|
|
}
|
|
isRunning(){
|
|
if (! this.valid()) return false;
|
|
if (this.status === undefined) return false;
|
|
return ['error','success','canceled','failed','errored'].indexOf(this.status) < 0;
|
|
}
|
|
|
|
}
|
|
(function(){
|
|
const STATUS_INTERVAL=2000;
|
|
const CURRENT_PIPELINE='pipeline';
|
|
const API="cibuild.php";
|
|
const GITAPI="install.php";
|
|
const GITUSER="wellenvogel";
|
|
const GITREPO="esp32-nmea2000";
|
|
let currentPipeline=new PipelineInfo();
|
|
let timer=undefined;
|
|
let structure=undefined;
|
|
let config={}; //values as read and stored
|
|
let configStruct={}; //complete struct merged of config and struct
|
|
let displayMode='last';
|
|
let delayedSearch=undefined;
|
|
let gitSha=undefined;
|
|
let buildVersion=undefined;
|
|
let configName="buildconfig";
|
|
let isModified=false;
|
|
const modeStrings={
|
|
last: 'Last Build',
|
|
existing: 'Existing Build',
|
|
current: 'Current Build'
|
|
};
|
|
const setDisplayMode=(mode)=>{
|
|
let old=displayMode;
|
|
let ms=modeStrings[mode];
|
|
if (ms === undefined){
|
|
return false;
|
|
}
|
|
displayMode=mode;
|
|
setValue('resultTitle',ms);
|
|
return mode !== old;
|
|
}
|
|
const updateStatus=()=>{
|
|
setValues(currentPipeline,{
|
|
id: 'pipeline'
|
|
});
|
|
setVisible('download',currentPipeline.valid() && currentPipeline.downloadUrl!==undefined,true);
|
|
setVisible('status_url',currentPipeline.valid() && currentPipeline.status_url!==undefined,true);
|
|
setVisible('error',currentPipeline.error!==undefined,true);
|
|
let values={};
|
|
fillValues(values,['configError','environment']);
|
|
setVisible('buildCommand',values.environment !== "" && ! values.configError);
|
|
if (values.configError) {
|
|
enableEl('start',false);
|
|
return;
|
|
}
|
|
if (!values.environment){
|
|
enableEl('start',false);
|
|
return;
|
|
}
|
|
if (displayMode != 'existing'){
|
|
if (currentPipeline.valid()){
|
|
//check pipeline state
|
|
if (['error','success','canceled','failed'].indexOf(currentPipeline.status) >= 0){
|
|
enableEl('start',true);
|
|
return;
|
|
}
|
|
enableEl('start',false);
|
|
return;
|
|
}
|
|
enableEl('start',true);
|
|
return;
|
|
}
|
|
//display mode existing
|
|
//allow start if either no pipeline or not running and status != success
|
|
enableEl('start',!currentPipeline.valid() || (!currentPipeline.isRunning() && currentPipeline.status != "success"));
|
|
}
|
|
const isRunning=()=>{
|
|
return currentPipeline.isRunning();
|
|
}
|
|
const fetchStatus=(initial)=>{
|
|
if (! currentPipeline.valid()){
|
|
updateStatus();
|
|
return;
|
|
}
|
|
let queryPipeline=currentPipeline.id;
|
|
fetchJson(API,{api:'status',pipeline:currentPipeline.id})
|
|
.then((st)=>{
|
|
if (queryPipeline !== currentPipeline.id) return;
|
|
let stid=st.pipeline_id||st.id;
|
|
if (stid !== undefined && currentPipeline.id !== stid) return;
|
|
if (st.status === undefined) st.status=st.state;
|
|
currentPipeline.update(st);
|
|
updateStatus();
|
|
if (st.status === 'error' || st.status === 'errored' || st.status === 'canceled'){
|
|
return;
|
|
}
|
|
if (st.status === 'success'){
|
|
fetchJson(API,{api:'artifacts',pipeline:currentPipeline.id})
|
|
.then((ar)=>{
|
|
if (! ar.items || ar.items.length < 1){
|
|
throw new Error("no download link");
|
|
}
|
|
currentPipeline.downloadUrl=buildUrl(API,{
|
|
download: currentPipeline.id
|
|
});
|
|
updateStatus();
|
|
|
|
})
|
|
.catch((err)=>{
|
|
currentPipeline.update({
|
|
status:'error',
|
|
error:"Unable to get build result: "+err
|
|
});
|
|
updateStatus();
|
|
});
|
|
return;
|
|
}
|
|
timer=window.setTimeout(fetchStatus,STATUS_INTERVAL)
|
|
})
|
|
.catch((e)=>{
|
|
timer=window.setTimeout(fetchStatus,STATUS_INTERVAL);
|
|
})
|
|
}
|
|
const setCurrentPipeline=(pipeline,doStore)=>{
|
|
currentPipeline.reset(pipeline);
|
|
if (doStore) window.localStorage.setItem(CURRENT_PIPELINE,pipeline);
|
|
};
|
|
const startBuild=()=>{
|
|
let param={};
|
|
currentPipeline.reset(undefined,{status:'requested'});
|
|
if (timer) window.clearTimeout(timer);
|
|
timer=undefined;
|
|
fillValues(param,['environment','buildflags']);
|
|
setDisplayMode('current');
|
|
updateStatus();
|
|
if (gitSha !== undefined) param.tag=gitSha;
|
|
param.config=JSON.stringify(config);
|
|
if (buildVersion !== undefined){
|
|
param.suffix="-"+buildVersion;
|
|
}
|
|
fetchJson(API,Object.assign({
|
|
api:'start'},param))
|
|
.then((json)=>{
|
|
let status=json.status || json.state|| 'error';
|
|
if (status === 'error'){
|
|
currentPipeline.update({status:status,error:json.error})
|
|
updateStatus();
|
|
throw new Error("unable to create job "+(json.error||''));
|
|
}
|
|
if (!json.id) {
|
|
let error="unable to create job, no id"
|
|
currentPipeline.update({status:'error',error:error});
|
|
updateStatus();
|
|
throw new Error(error);
|
|
}
|
|
setCurrentPipeline(json.id,true);
|
|
updateStatus();
|
|
timer=window.setTimeout(fetchStatus,STATUS_INTERVAL);
|
|
})
|
|
.catch((err)=>{
|
|
currentPipeline.update({status:'error',error:err});
|
|
updateStatus();
|
|
});
|
|
}
|
|
const runDownload=()=>{
|
|
if (! currentPipeline.downloadUrl) return;
|
|
let df=document.getElementById('dlframe');
|
|
if (df){
|
|
df.setAttribute('src',null);
|
|
df.setAttribute('src',currentPipeline.downloadUrl);
|
|
}
|
|
}
|
|
const webInstall=()=>{
|
|
if (! currentPipeline.downloadUrl) return;
|
|
let url=buildUrl("install.html",{custom:currentPipeline.downloadUrl});
|
|
window.location.href=url;
|
|
}
|
|
const uploadConfig=()=>{
|
|
let form=document.getElementById("upload");
|
|
form.reset();
|
|
let fsel=document.getElementById("fileSelect");
|
|
fsel.onchange=async ()=>{
|
|
if (fsel.files.length < 1) return;
|
|
let file=fsel.files[0];
|
|
if (! file.name.match(/json$/)){
|
|
alert("only json files");
|
|
return;
|
|
}
|
|
try{
|
|
let content=await readFile(file,true);
|
|
let newConfig=JSON.parse(content);
|
|
removeSelectors(ROOT_PATH,true);
|
|
config=newConfig;
|
|
buildSelectors(ROOT_PATH,structure.config.children,true);
|
|
findPipeline();
|
|
} catch (e){
|
|
alert("upload "+fsel.files[0].name+" failed: "+e);
|
|
}
|
|
|
|
}
|
|
fsel.click();
|
|
}
|
|
const downloadConfig=()=>{
|
|
let name=configName;
|
|
if (isModified) name=name.replace(/[0-9]*$/,'')+formatDate(undefined,true);
|
|
name+=".json";
|
|
fileDownload(JSON.stringify(config),name);
|
|
}
|
|
const showOverlay=(text, isHtml, secondButton)=>{
|
|
let el = document.getElementById('overlayContent');
|
|
if (isHtml) {
|
|
el.innerHTML = text;
|
|
el.classList.remove("text");
|
|
}
|
|
else {
|
|
el.textContent = text;
|
|
el.classList.add("text");
|
|
}
|
|
let container = document.getElementById('overlayContainer');
|
|
container.classList.remove('hidden');
|
|
let db=document.getElementById("secondDialogButton");
|
|
if (db) {
|
|
if (secondButton && secondButton.callback) {
|
|
db.classList.remove("hidden");
|
|
if (secondButton.title){db.textContent=secondButton.title}
|
|
db.onclick=secondButton.callback;
|
|
}
|
|
else {
|
|
db.classList.add("hidden");
|
|
}
|
|
}
|
|
}
|
|
const hideOverlay=()=> {
|
|
let container = document.getElementById('overlayContainer');
|
|
container.classList.add('hidden');
|
|
}
|
|
const loadConfig=async (url)=>{
|
|
let config=await fetch(url).then((r)=>{
|
|
if (!r.ok) throw new Error("unable to fetch: "+r.statusText);
|
|
return r.text()
|
|
});
|
|
let parsed=yamlLoad(config);
|
|
return parsed;
|
|
}
|
|
const showBuildCommand= async ()=>{
|
|
let v={};
|
|
fillValues(v,['environment','buildflags']);
|
|
if (v.environment !== ""){
|
|
let help="Run the build from a command line:\n";
|
|
let cmd="PLATFORMIO_BUILD_FLAGS=\"";
|
|
cmd+=v.buildflags;
|
|
cmd+="\" pio run -e "+v.environment;
|
|
help+=cmd;
|
|
showOverlay(help,false,{title:"Copy",callback:(ev)=>{
|
|
try{
|
|
navigator.clipboard.writeText(cmd);
|
|
//alert("copied:"+cmd);
|
|
}
|
|
catch (e){
|
|
alert("Unable to copy:"+e);
|
|
}
|
|
}});
|
|
}
|
|
}
|
|
const btConfig={
|
|
start:startBuild,
|
|
download:runDownload,
|
|
webinstall:webInstall,
|
|
uploadConfig: uploadConfig,
|
|
downloadConfig: downloadConfig,
|
|
hideOverlay: hideOverlay,
|
|
buildCommand: showBuildCommand
|
|
};
|
|
|
|
|
|
const PATH_ATTR='data-path';
|
|
const SEPARATOR=':';
|
|
const expandObject=(obj,parent)=>{
|
|
if (typeof(obj) !== 'object'){
|
|
obj={value:obj}
|
|
}
|
|
let rt=Object.assign({},obj);
|
|
if (rt.value === undefined && rt.key !== undefined) rt.value=rt.key;
|
|
if (rt.key === undefined) rt.key=rt.value;
|
|
if (rt.value === null) rt.value=undefined;
|
|
if (rt.label === undefined){
|
|
if (rt.value !== undefined) rt.label=rt.value;
|
|
else rt.label=rt.key;
|
|
}
|
|
if (rt.resource === undefined && typeof(parent) === 'object'){
|
|
if (parent.resource !== undefined){
|
|
if (parent.resource.match(/:$/)){
|
|
if(rt.value !== undefined && rt.value !== null){
|
|
rt.resource=parent.resource+rt.value;
|
|
}
|
|
}
|
|
else{
|
|
rt.resource=parent.resource;
|
|
}
|
|
}
|
|
}
|
|
if (rt.target === undefined && typeof(parent) === 'object'){
|
|
rt.target=parent.target;
|
|
}
|
|
if (rt.mandatory === undefined && typeof(parent) === 'object'){
|
|
rt.mandatory=parent.mandatory;
|
|
}
|
|
return rt;
|
|
}
|
|
const expandList=(lst,parent)=>{
|
|
let rt=[];
|
|
if (! lst) return rt;
|
|
lst.forEach((e)=>rt.push(expandObject(e,parent)));
|
|
return rt;
|
|
}
|
|
|
|
const addDescription=(v,frame)=>{
|
|
if (frame === undefined) return;
|
|
if (v.description){
|
|
if(v.url) {
|
|
let lnk = addEl('a', 'description', frame, v.description);
|
|
lnk.setAttribute('href', v.url);
|
|
lnk.setAttribute('target', '_');
|
|
}
|
|
else{
|
|
let de=addEl('div','description',frame,v.description);
|
|
}
|
|
}
|
|
if (v.help){
|
|
let bt=addEl('button','help',frame,'?');
|
|
bt.addEventListener('click',()=>showOverlay(v.help));
|
|
}
|
|
else if (v.helpHtml){
|
|
let bt=addEl('button','help',frame,'?');
|
|
bt.addEventListener('click',()=>showOverlay(v.helpHtml,true));
|
|
}
|
|
}
|
|
/**
|
|
*
|
|
* @param {build a selector} parent
|
|
* @param {*} config
|
|
* @param {*} name
|
|
* @param {*} current
|
|
* @param {*} callback will be called with: children,key,value,initial
|
|
* @returns
|
|
*/
|
|
const buildSelector=(parent,cfgBase,name,current,callback)=>{
|
|
let config=expandObject(cfgBase);
|
|
if (current === undefined && config.default !== undefined){
|
|
current=config.default;
|
|
}
|
|
let rep=new RegExp("[^"+SEPARATOR+"]*","g");
|
|
let level=name.replace(rep,'');
|
|
let frame=addEl('div','selector level'+level.length+' t'+config.type,parent);
|
|
frame.setAttribute(PATH_ATTR,name);
|
|
let inputFrame=addEl('div','inputFrame',frame);
|
|
let titleFrame=undefined;
|
|
if (config.label !== undefined){
|
|
titleFrame=addEl('div','titleFrame t'+config.type,inputFrame);
|
|
addEl('div','title t'+config.type,titleFrame,config.label);
|
|
}
|
|
let initialConfig=undefined
|
|
if (config.type === 'frame' || config.type === undefined){
|
|
initialConfig=config;
|
|
}
|
|
let expandedValues=expandList(config.values,config);
|
|
expandedValues.forEach((v)=>{
|
|
if (v.type !== undefined && v.type !== "frame"){
|
|
let err="value element with wrong type "+v.type+" at "+name;
|
|
alert(err);
|
|
throw new Error(err);
|
|
}
|
|
})
|
|
if (config.type === 'select') {
|
|
addDescription(config,titleFrame);
|
|
for (let idx=0;idx<expandedValues.length;idx++){
|
|
let v=expandedValues[idx];
|
|
if (v.key === undefined) continue;
|
|
let ef = addEl('div', 'radioFrame', inputFrame);
|
|
addEl('div', 'label', ef, v.label);
|
|
let re = addEl('input', 'radioCi', ef);
|
|
re.setAttribute('type', 'radio');
|
|
re.setAttribute('name', name);
|
|
re.addEventListener('change', (ev) => callback(v,false));
|
|
addDescription(v,ef);
|
|
if (v.key == current) {
|
|
re.setAttribute('checked','checked');
|
|
initialConfig=v;
|
|
}
|
|
};
|
|
}
|
|
if (expandedValues.length > 0 && config.type === 'dropdown'){
|
|
let sel=addEl('select','t'+config.type,inputFrame);
|
|
for (let idx=0;idx<expandedValues.length;idx++){
|
|
let v=expandedValues[idx];
|
|
if (v.key === undefined) continue;
|
|
let opt=addEl('option','',sel,v.label);
|
|
opt.setAttribute('value',idx);
|
|
if (v.key == current){
|
|
opt.setAttribute('selected',true);
|
|
initialConfig=v;
|
|
}
|
|
};
|
|
addDescription(config,inputFrame);
|
|
sel.addEventListener('change',(ev)=>{
|
|
let v=expandedValues[ev.target.value];
|
|
if (! v) return;
|
|
callback(v,false);
|
|
});
|
|
}
|
|
if (config.type === 'range'){
|
|
if (config.min !== undefined && config.max !== undefined) {
|
|
let min=config.min+0;
|
|
let step=1;
|
|
if (config.step !== undefined) step=config.step+0;
|
|
let max=config.max+0;
|
|
let valid=false;
|
|
if (step > 0){
|
|
if (min < max) valid=true;
|
|
}
|
|
else{
|
|
if (min > max) {
|
|
let tmp=max;
|
|
max=min;
|
|
min=tmp;
|
|
valid=true;
|
|
}
|
|
}
|
|
if (! valid){
|
|
console.log("invalid range config",config);
|
|
}
|
|
else {
|
|
let sel = addEl('select', 'tdropdown', inputFrame);
|
|
for (let idx=min;idx <=max;idx+=step){
|
|
let opt=addEl('option','',sel,idx);
|
|
opt.setAttribute('value',idx);
|
|
if (idx == current){
|
|
opt.setAttribute('selected',true);
|
|
initialConfig=expandObject({key:idx,value:idx},config);
|
|
}
|
|
}
|
|
if (! initialConfig){
|
|
initialConfig=expandObject({key:min,value:min},config);
|
|
}
|
|
addDescription(config, inputFrame);
|
|
sel.addEventListener('change', (ev) => {
|
|
let v = expandObject({ key: ev.target.value, value: ev.target.value },config);
|
|
callback(v, false);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (expandedValues.length > 0 && config.type === 'checkbox'){
|
|
let act=undefined;
|
|
let inact=undefined;
|
|
expandedValues.forEach((ev)=>{
|
|
if (ev.key === true || ev.key === undefined){
|
|
act=ev;
|
|
if (act.key === undefined) act.key=true;
|
|
return;
|
|
}
|
|
inact=ev;
|
|
});
|
|
if (act !== undefined){
|
|
if (inact === undefined) inact={key:false};
|
|
let cb=addEl('input','t'+config.type,inputFrame);
|
|
cb.setAttribute('type','checkbox');
|
|
if (current) {
|
|
cb.setAttribute('checked',true);
|
|
initialConfig=act;
|
|
}
|
|
else{
|
|
initialConfig=inact;
|
|
}
|
|
addDescription(config,inputFrame);
|
|
cb.addEventListener('change',(ev)=>{
|
|
if (ev.target.checked){
|
|
callback(act,false);
|
|
}
|
|
else
|
|
{
|
|
callback(inact,false);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
if (expandedValues.length > 0 && config.type === 'display'){
|
|
let cb=addEl('div','t'+config.type,inputFrame);
|
|
addDescription(config,inputFrame);
|
|
initialConfig=expandedValues[0];
|
|
}
|
|
let childFrame=addEl('div','childFrame',frame);
|
|
if (initialConfig !== undefined){
|
|
callback(initialConfig,true,childFrame);
|
|
}
|
|
return childFrame;
|
|
}
|
|
const removeSelectors=(prefix,removeValues)=>{
|
|
forEachEl('.selectorFrame',(el)=>{
|
|
let path=el.getAttribute(PATH_ATTR);
|
|
if (! path) return;
|
|
if (path.indexOf(prefix) == 0){
|
|
el.remove();
|
|
}
|
|
})
|
|
if (removeValues){
|
|
let removeKeys=[];
|
|
for (let k in configStruct){
|
|
if (k.indexOf(prefix) == 0) removeKeys.push(k);
|
|
}
|
|
for (let k in config){
|
|
if (k.indexOf(prefix) == 0) removeKeys.push(k);
|
|
}
|
|
removeKeys.forEach((k)=>{
|
|
delete config[k];
|
|
delete configStruct[k];
|
|
});
|
|
}
|
|
}
|
|
const buildSelectors=(prefix,configList,initial,base,parent)=>{
|
|
if (!parent) parent=document.getElementById("selectors");;
|
|
if (!configList) return;
|
|
let frame=addEl('div','selectorFrame',parent);
|
|
frame.setAttribute(PATH_ATTR,prefix);
|
|
let expandedList=expandList(configList);
|
|
expandedList.forEach((cfg)=>{
|
|
let currentBase=Object.assign({},base,cfg.base);
|
|
cfg=replaceValues(cfg,currentBase);
|
|
if (cfg.key === undefined){
|
|
if (cfg.type !== undefined && cfg.type !== 'frame'){
|
|
console.log("config without key",cfg);
|
|
return;
|
|
}
|
|
}
|
|
let name=prefix;
|
|
if (name !== undefined){
|
|
if (cfg.key !== undefined) {
|
|
name=prefix+SEPARATOR+cfg.key;
|
|
}
|
|
}
|
|
else{
|
|
name=cfg.key;
|
|
}
|
|
let current=config[name];
|
|
let childFrame=buildSelector(frame,cfg,name,current,
|
|
(child,initial,opt_frame)=>{
|
|
if(cfg.key !== undefined) removeSelectors(name,!initial);
|
|
if (! initial) isModified=true;
|
|
buildSelectors(name,child.children,initial,Object.assign({},currentBase,child.base),opt_frame||childFrame);
|
|
if (cfg.key !== undefined) configStruct[name]={cfg:child,base:currentBase};
|
|
buildValues(initial);
|
|
})
|
|
})
|
|
}
|
|
const replaceValues=(str,base)=>{
|
|
if (! base) return str;
|
|
if (typeof(str) === 'string'){
|
|
for (let k in base){
|
|
if (typeof(base[k]) !== 'string'){
|
|
//special replacement
|
|
//for complete parts
|
|
if (str === '#'+k+'#'){
|
|
return base[k];
|
|
}
|
|
}
|
|
else{
|
|
let r=new RegExp("#"+k+"#","g");
|
|
str=str.replace(r,base[k]);
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
if (str instanceof Array){
|
|
let rt=[];
|
|
str.forEach((el)=>{
|
|
rt.push(replaceValues(el,base));
|
|
})
|
|
return rt;
|
|
}
|
|
if (str instanceof Object){
|
|
let rt={};
|
|
for (let k in str){
|
|
if (k == 'children') rt[k]=str[k];
|
|
else rt[k]=replaceValues(str[k],base);
|
|
}
|
|
return rt;
|
|
}
|
|
return str;
|
|
}
|
|
const ROOT_PATH='root';
|
|
const buildValues=(initial)=>{
|
|
let environment;
|
|
let flags="";
|
|
if (! initial){
|
|
config={};
|
|
}
|
|
let allowedResources={};
|
|
let currentResources={};
|
|
let errors="";
|
|
for (let round = 0; round <= 1; round++) {
|
|
//round1: find allowed resources
|
|
//round2: really collect values
|
|
for (let k in configStruct) {
|
|
let container = configStruct[k];
|
|
if (! container.cfg) continue;
|
|
let struct=container.cfg;
|
|
if (round > 0) config[k] = struct.key;
|
|
if (struct.target !== undefined ) {
|
|
if (struct.value === undefined){
|
|
if (struct.mandatory && round > 0){
|
|
errors+=" missing value for "+k+"\n";
|
|
}
|
|
continue;
|
|
}
|
|
let target=replaceValues(struct.target,container.base);
|
|
if (target === 'environment' ) {
|
|
if (round > 0 && struct.key !== undefined) environment = struct.value;
|
|
else allowedResources=struct.resource;
|
|
continue;
|
|
}
|
|
if (round < 1) continue;
|
|
if (struct.resource){
|
|
let splitted=struct.resource.split(",");
|
|
splitted.forEach((resource) => {
|
|
let resList = currentResources[resource];
|
|
if (!resList) {
|
|
resList = [];
|
|
currentResources[resource] = resList;
|
|
}
|
|
resList.push(struct);
|
|
});
|
|
}
|
|
if (target === 'define') {
|
|
flags += " -D" + struct.value;
|
|
continue;
|
|
}
|
|
const DEFPRFX = "define:";
|
|
if (target.indexOf(DEFPRFX) == 0) {
|
|
let def = target.substring(DEFPRFX.length);
|
|
flags += " -D" + def + "=" + struct.value;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (buildVersion !== undefined){
|
|
flags+=" -DGWRELEASEVERSION="+buildVersion;
|
|
}
|
|
setValues({environment:environment,buildflags:flags});
|
|
//check resources
|
|
for (let k in currentResources){
|
|
let ak=k.replace(/:.*/,'');
|
|
let resList=currentResources[k];
|
|
let allowed=allowedResources[ak];
|
|
if (allowed === undefined) allowed=1;
|
|
if (resList.length > allowed){
|
|
errors+=" more than "+allowed+" device(s) of type "+k+" used";
|
|
}
|
|
|
|
}
|
|
if (errors){
|
|
setValue('configError',errors);
|
|
setVisible('configError',true,true);
|
|
}
|
|
else{
|
|
setValue('configError','');
|
|
setVisible('configError',false,true);
|
|
}
|
|
if (! initial) findPipeline();
|
|
updateStatus();
|
|
}
|
|
let findIdx=0;
|
|
const findPipeline=()=>{
|
|
if (delayedSearch !== undefined){
|
|
window.clearTimeout(delayedSearch);
|
|
delayedSearch=undefined;
|
|
}
|
|
if (isRunning()) {
|
|
delayedSearch=window.setTimeout(findPipeline,500);
|
|
return;
|
|
}
|
|
findIdx++;
|
|
let queryIdx=findIdx;
|
|
let param={find:1};
|
|
fillValues(param,['environment','buildflags']);
|
|
if (gitSha !== undefined) param.tag=gitSha;
|
|
fetchJson(API,param)
|
|
.then((res)=>{
|
|
if (queryIdx != findIdx) return;
|
|
setCurrentPipeline(res.pipeline);
|
|
if (res.pipeline) currentPipeline.status="found";
|
|
setDisplayMode('existing');
|
|
updateStatus();
|
|
fetchStatus(true);
|
|
})
|
|
.catch((e)=>{
|
|
console.log("findPipeline error ",e)
|
|
if (displayMode == 'existing'){
|
|
setCurrentPipeline();
|
|
updateStatus();
|
|
}
|
|
});
|
|
|
|
}
|
|
const formatDate=(opt_date,opt_includeMs)=>{
|
|
const fmt=(v)=>{
|
|
return ((v<10)?"0":"")+v;
|
|
}
|
|
let now=opt_date|| new Date();
|
|
let rt=now.getFullYear()+fmt(now.getMonth()+1)+fmt(now.getDate());
|
|
if (opt_includeMs){
|
|
rt+=fmt(now.getHours())+fmt(now.getMinutes())+fmt(now.getSeconds());
|
|
}
|
|
return rt;
|
|
}
|
|
window.onload=async ()=>{
|
|
setButtons(btConfig);
|
|
let pipeline=window.localStorage.getItem(CURRENT_PIPELINE);
|
|
setDisplayMode('last');
|
|
if (pipeline){
|
|
setCurrentPipeline(pipeline);
|
|
updateStatus();
|
|
fetchStatus(true);
|
|
}
|
|
let gitParam={user:GITUSER,repo:GITREPO};
|
|
let branch=getParam('branch');
|
|
if (branch){
|
|
try{
|
|
let info=await fetchJson(GITAPI,Object.assign({},gitParam,{branch:branch}));
|
|
if (info.object){
|
|
gitSha=info.object.sha;
|
|
setValue('branchOrTag','branch');
|
|
setValue('branchOrTagValue',branch);
|
|
}
|
|
}catch (e){
|
|
console.log("branch query error",e);
|
|
}
|
|
}
|
|
if (gitSha === undefined) {
|
|
let tag = getParam('tag');
|
|
let type="tag";
|
|
if (!tag) {
|
|
try {
|
|
let relinfo = await fetchJson(GITAPI, Object.assign({}, gitParam, { api: 1 }));
|
|
if (relinfo.tag_name) {
|
|
tag = relinfo.tag_name;
|
|
type="release";
|
|
}
|
|
else {
|
|
alert("unable to query latest release");
|
|
}
|
|
} catch (e) {
|
|
alert("unable to query release info " + e);
|
|
}
|
|
}
|
|
if (tag){
|
|
try{
|
|
let info=await fetchJson(GITAPI,Object.assign({},gitParam,{tag:tag}));
|
|
if (info.object){
|
|
gitSha=info.object.sha;
|
|
setValue('branchOrTag',type);
|
|
setValue('branchOrTagValue',tag);
|
|
}
|
|
}catch(e){
|
|
alert("cannot get sha for tag "+tag+": "+e);
|
|
}
|
|
}
|
|
}
|
|
if (gitSha === undefined){
|
|
//last resort: no sha, let the CI pick up latest
|
|
setValue('gitSha','unknown');
|
|
setValue('branchOrTag','branch');
|
|
setValue('branchOrTagValue','master');
|
|
}
|
|
else{
|
|
setValue('gitSha',gitSha);
|
|
}
|
|
let bot=document.getElementById('branchOrTag');
|
|
let botv=document.getElementById('branchOrTagValue');
|
|
if (bot && botv){
|
|
let type=bot.textContent;
|
|
let val=botv.textContent;
|
|
if (type && val){
|
|
if (type != 'release' && type != 'tag' && type != 'branch'){
|
|
val=type+val;
|
|
}
|
|
if (type == 'branch'){
|
|
val=val+formatDate();
|
|
}
|
|
val=val.replace(/[:.]/g,'_');
|
|
val=val.replace(/[^a-zA-Z0-9_]*/g,'');
|
|
if (val.length > 32){
|
|
val=val.substring(val.length-32)
|
|
}
|
|
if (val.length > 0){
|
|
buildVersion=val;
|
|
setValue('buildVersion',buildVersion);
|
|
}
|
|
}
|
|
}
|
|
if (gitSha !== undefined){
|
|
let url=buildUrl(GITAPI,Object.assign({},gitParam,{sha:gitSha,proxy:'webinstall/build.yaml'}));
|
|
try{
|
|
structure=await loadConfig(url);
|
|
}catch (e){
|
|
alert("unable to load config for selected release:\n "+e+"\n falling back to default");
|
|
}
|
|
}
|
|
if (! structure){
|
|
structure=await loadConfig("build.yaml");
|
|
}
|
|
let ucfg=getParam('config');
|
|
let loadedCfg=undefined;
|
|
if (ucfg){
|
|
ucfg=ucfg.replace(/[^.a-zA-Z_0-9-]/g,'');
|
|
if (gitSha !== undefined){
|
|
try{
|
|
loadedCfg=await fetchJson(GITAPI,Object.assign({},gitParam,{sha:gitSha,proxy:'webinstall/config/'+ucfg+".json"}));
|
|
}catch(e){
|
|
alert("unable to load config "+ucfg+" for selected release, trying latest");
|
|
}
|
|
}
|
|
if (loadedCfg === undefined){
|
|
try{
|
|
loadedCfg=await fetchJson('config/'+ucfg+".json");
|
|
}catch(e){
|
|
alert("unable to load config "+ucfg+": "+e);
|
|
}
|
|
}
|
|
if (loadedCfg !== undefined){
|
|
configName=ucfg;
|
|
config=loadedCfg;
|
|
}
|
|
}
|
|
buildSelectors(ROOT_PATH,structure.config.children,true);
|
|
if (! isRunning()) findPipeline();
|
|
updateStatus();
|
|
const translationCheck=()=>{
|
|
const lang = document.documentElement.lang;
|
|
if (lang != "en"){
|
|
alert(
|
|
"This page will not work correctly with translation enabled"
|
|
);
|
|
}
|
|
}
|
|
// Works at least for Chrome, Firefox, Safari and probably more. Not Microsoft
|
|
// Edge though. They're special.
|
|
// Yell at clouds if a translator doesn't change it
|
|
const observer = new MutationObserver(() => {
|
|
translationCheck();
|
|
});
|
|
observer.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ['lang'],
|
|
childList: false,
|
|
characterData: false,
|
|
});
|
|
translationCheck();
|
|
|
|
}
|
|
})(); |