515 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			515 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			HTML
		
	
	
	
<!DOCTYPE html>
 | 
						|
<html><head>
 | 
						|
<meta name="viewport" content="width=device-width, initial-scale=1">
 | 
						|
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
 | 
						|
<title>NMEA 2000 Gateway</title>
 | 
						|
 | 
						|
<script type="text/javascript">
 | 
						|
  let self=this;
 | 
						|
  let lastUpdate=(new Date()).getTime();
 | 
						|
  let reloadConfig=false;
 | 
						|
  function alertRestart(){
 | 
						|
    reloadConfig=true;
 | 
						|
    alert("Board reset triggered, reconnect WLAN if necessary");
 | 
						|
  }
 | 
						|
  function getJson(url){
 | 
						|
    return fetch(url)
 | 
						|
      .then(function(r){return r.json()});
 | 
						|
  }
 | 
						|
  function reset(){
 | 
						|
    fetch('/api/reset');
 | 
						|
    alertRestart();
 | 
						|
  }
 | 
						|
  function update(){
 | 
						|
    let now=(new Date()).getTime();
 | 
						|
    let ce=document.getElementById('connected');
 | 
						|
    if (ce){
 | 
						|
      if ((lastUpdate+3000) > now){
 | 
						|
        ce.classList.add('ok');
 | 
						|
      }
 | 
						|
      else{
 | 
						|
        ce.classList.remove('ok');
 | 
						|
      }
 | 
						|
    }
 | 
						|
    getJson('/api/status')
 | 
						|
      .then(function(jsonData){
 | 
						|
        for (let k in jsonData){
 | 
						|
          if (k == 'cnv'){
 | 
						|
            updateCanDetails(jsonData[k]);
 | 
						|
          }
 | 
						|
          else{
 | 
						|
            let el=document.getElementById(k);
 | 
						|
            if (el) el.textContent=jsonData[k];
 | 
						|
          }
 | 
						|
        }
 | 
						|
        lastUpdate=(new Date()).getTime();
 | 
						|
        if (reloadConfig){
 | 
						|
          reloadConfig=false;
 | 
						|
          resetForm();
 | 
						|
        }
 | 
						|
      })
 | 
						|
  }
 | 
						|
  function resetForm(ev){
 | 
						|
    getJson("/api/config")
 | 
						|
      .then(function(jsonData){
 | 
						|
        for (let k in jsonData){
 | 
						|
          let el=document.querySelector("[name='"+k+"']");
 | 
						|
          if (el){
 | 
						|
            let v=jsonData[k];
 | 
						|
            el.value=v;
 | 
						|
            el.setAttribute('data-loaded',v);
 | 
						|
            let changeEvent=new Event('change');
 | 
						|
            el.dispatchEvent(changeEvent);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      });
 | 
						|
  }
 | 
						|
  function checkMaxClients(v){
 | 
						|
      let parsed=parseInt(v);
 | 
						|
      if (isNaN(parsed)) return "not a valid number";
 | 
						|
      if (parsed < 0) return "must be >= 0";
 | 
						|
      if (parsed > 10) return "max is 10";
 | 
						|
  }
 | 
						|
  function checkSystemName(v){
 | 
						|
    //2...32 characters for ssid
 | 
						|
    let allowed=v.replace(/[^a-zA-Z0-9]*/g,'');
 | 
						|
    if (allowed != v) return "contains invalid characters, only a-z, A-Z, 0-9";
 | 
						|
    if (v.length < 2 || v.length > 32) return "invalid length (2...32)";
 | 
						|
  }
 | 
						|
  function changeConfig(){
 | 
						|
    let url="/api/setConfig?";
 | 
						|
    let values=document.querySelectorAll('.configForm select , .configForm input');
 | 
						|
    for (let i=0;i<values.length;i++){
 | 
						|
      let v=values[i];
 | 
						|
      let name=v.getAttribute('name');
 | 
						|
      if (! name) continue;
 | 
						|
      if (name.indexOf("_") >= 0) continue;
 | 
						|
      let check=v.getAttribute('data-check');
 | 
						|
      if (check){
 | 
						|
        if (typeof(self[check]) === 'function'){
 | 
						|
          let res=self[check](v.value);
 | 
						|
          if (res){
 | 
						|
            alert("invalid config for "+v.getAttribute('name')+"("+v.value+"):\n"+res);
 | 
						|
            return;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
      url+=name+"="+encodeURIComponent(v.value)+"&";
 | 
						|
    }
 | 
						|
    getJson(url)
 | 
						|
      .then(function(status){
 | 
						|
        if(status.status == 'OK'){
 | 
						|
          alertRestart();
 | 
						|
        }
 | 
						|
        else{
 | 
						|
          alert("unable to set config: "+status.status);
 | 
						|
        }
 | 
						|
      })
 | 
						|
  }
 | 
						|
  function factoryReset(){
 | 
						|
    if (! confirm("Really delete all configuration?\n"+
 | 
						|
       "This will reset all your Wifi settings and disconnect you.")){
 | 
						|
         return;
 | 
						|
       }
 | 
						|
    getJson("/api/resetConfig")
 | 
						|
      .then(function(status){
 | 
						|
        alertRestart(); 
 | 
						|
      })
 | 
						|
  }
 | 
						|
  function showCanDetails(on){
 | 
						|
    let el=document.getElementById('canDetails');
 | 
						|
    if (!el) return;
 | 
						|
    if (on) el.classList.add('visible');
 | 
						|
    else(el.classList).remove('visible');
 | 
						|
  }
 | 
						|
  function updateCanDetails(details){
 | 
						|
    let frame=document.getElementById('canDetails');
 | 
						|
    if (! frame) return;
 | 
						|
    for (let k in details){
 | 
						|
      let el=frame.querySelector("[data-id=\""+k+"\"] ");
 | 
						|
      if (!el){
 | 
						|
        el=document.createElement('div');
 | 
						|
        el.classList.add('row');
 | 
						|
        let cv=document.createElement('span');
 | 
						|
        cv.classList.add('label');
 | 
						|
        cv.textContent="PGN"+k;
 | 
						|
        el.appendChild(cv);
 | 
						|
        cv=document.createElement('span');
 | 
						|
        cv.classList.add('value');
 | 
						|
        cv.setAttribute('data-id',k);
 | 
						|
        cv.textContent=details[k];
 | 
						|
        el.appendChild(cv);
 | 
						|
        frame.appendChild(el);
 | 
						|
      }
 | 
						|
      else{
 | 
						|
        el.textContent=details[k];
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
  function showOverlay(text,isHtml){
 | 
						|
    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');
 | 
						|
  }
 | 
						|
  function hideOverlay(){
 | 
						|
    let container=document.getElementById('overlayContainer');
 | 
						|
    container.classList.add('hidden');
 | 
						|
  }
 | 
						|
  function checkChange(el,row) {
 | 
						|
      let loaded = el.getAttribute('data-loaded');
 | 
						|
      if (loaded !== undefined) {
 | 
						|
        if (loaded != el.value) {
 | 
						|
          row.classList.add('changed');
 | 
						|
        }
 | 
						|
        else {
 | 
						|
          row.classList.remove("changed");
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  function createInput(configItem,frame){
 | 
						|
    let el;
 | 
						|
    if (configItem.type === 'boolean' || configItem.type === 'list'){
 | 
						|
      el=document.createElement('select')
 | 
						|
      el.setAttribute('name',configItem.name)
 | 
						|
      let slist=[];
 | 
						|
      if (configItem.list){
 | 
						|
        configItem.list.forEach(function(v){
 | 
						|
          slist.push({l:v,v:v});
 | 
						|
        })
 | 
						|
      }
 | 
						|
      else{
 | 
						|
        slist.push({l:'on',v:'true'})
 | 
						|
        slist.push({l:'off',v:'false'})
 | 
						|
      }
 | 
						|
      slist.forEach(function(sitem){
 | 
						|
        let sitemEl=document.createElement('option');
 | 
						|
        sitemEl.setAttribute('value',sitem.v);
 | 
						|
        sitemEl.textContent=sitem.l;
 | 
						|
        el.appendChild(sitemEl);
 | 
						|
      })
 | 
						|
      frame.appendChild(el);
 | 
						|
      return el;
 | 
						|
    }
 | 
						|
    if (configItem.type === 'filter'){
 | 
						|
      el=document.createElement('div');
 | 
						|
      el.classList.add('filter');
 | 
						|
      let ais=createInput({
 | 
						|
        type:'list',
 | 
						|
        name:configItem.name+"_ais",
 | 
						|
        list:['aison','aisoff']
 | 
						|
      },el);
 | 
						|
      let mode=createInput({
 | 
						|
        type:'list',
 | 
						|
        name:configItem.name+"_mode",
 | 
						|
        list: ['whitelist','blacklist']
 | 
						|
      },el);
 | 
						|
      let sentences=createInput({
 | 
						|
        type:'text',
 | 
						|
        name:configItem.name+"_sentences",
 | 
						|
      },el);
 | 
						|
      let data=document.createElement('input');
 | 
						|
      data.setAttribute('type','hidden');
 | 
						|
      el.appendChild(data);
 | 
						|
      let changeFunction=function(){
 | 
						|
        let cv=data.value||"";
 | 
						|
        let parts=cv.split(":");
 | 
						|
        ais.value=(parts[0]=='0')?"aisoff":"aison";
 | 
						|
        mode.value=(parts[1]=='0')?"whitelist":"blacklist";
 | 
						|
        sentences.value=parts[2]||"";
 | 
						|
      }
 | 
						|
      let updateFunction=function(){
 | 
						|
        let nv=(ais.value == 'aison')?"1":"0";
 | 
						|
        nv+=":";
 | 
						|
        nv+=(mode.value == 'blacklist')?"1":"0";
 | 
						|
        nv+=":";
 | 
						|
        nv+=sentences.value;
 | 
						|
        data.value=nv;
 | 
						|
        let chev=new Event('change');
 | 
						|
        data.dispatchEvent(chev);
 | 
						|
      }
 | 
						|
      mode.addEventListener('change',updateFunction);
 | 
						|
      ais.addEventListener("change",updateFunction);
 | 
						|
      sentences.addEventListener("change",updateFunction);
 | 
						|
      data.addEventListener('change',function(ev){
 | 
						|
        changeFunction();
 | 
						|
      });
 | 
						|
      data.setAttribute('name',configItem.name);
 | 
						|
      frame.appendChild(el);
 | 
						|
      return data;  
 | 
						|
    }
 | 
						|
    el=document.createElement('input');
 | 
						|
    el.setAttribute('name',configItem.name)
 | 
						|
    if (configItem.type === 'password'){
 | 
						|
      el.setAttribute('type','password');
 | 
						|
    }
 | 
						|
    else if (configItem.type === 'number'){
 | 
						|
      el.setAttribute('type','number');
 | 
						|
    }
 | 
						|
    else{
 | 
						|
      el.setAttribute('type','text');
 | 
						|
    }
 | 
						|
    frame.appendChild(el);
 | 
						|
    return el;
 | 
						|
  }
 | 
						|
  let configDefinitions;
 | 
						|
  function loadConfigDefinitions() {
 | 
						|
      getJson("api/capabilities")
 | 
						|
        .then(function (capabilities) {
 | 
						|
          getJson("config.json")
 | 
						|
            .then(function (defs) {
 | 
						|
              let frame = document.querySelector('.configFormRows');
 | 
						|
              if (!frame) throw Error("no config form");
 | 
						|
              frame.innerHTML = '';
 | 
						|
              configDefinitions = defs;
 | 
						|
              defs.forEach(function (item) {
 | 
						|
                if (!item.type) return;
 | 
						|
                if (item.capabilities !== undefined){
 | 
						|
                  for (let capability in item.capabilities){
 | 
						|
                    let values=item.capabilities[capability];
 | 
						|
                    if ( !capabilities[capability]) return;
 | 
						|
                    let found =false;
 | 
						|
                    values.forEach(function(v){
 | 
						|
                      if (capabilities[capability] == v) found=true;
 | 
						|
                    });
 | 
						|
                    if (! found) return;
 | 
						|
                  }
 | 
						|
                }
 | 
						|
                let row = document.createElement('div');
 | 
						|
                row.classList.add('row');
 | 
						|
                let label = item.label || item.name;
 | 
						|
                let labelEl = document.createElement('span');
 | 
						|
                labelEl.classList.add('label');
 | 
						|
                labelEl.textContent = label;
 | 
						|
                row.appendChild(labelEl);
 | 
						|
                let valueFrame=document.createElement("div");
 | 
						|
                valueFrame.classList.add("value");
 | 
						|
                row.appendChild(valueFrame);
 | 
						|
                let valueEl = createInput(item,valueFrame);
 | 
						|
                if (!valueEl) return;
 | 
						|
                valueEl.setAttribute('data-default', item.default);
 | 
						|
                valueEl.addEventListener('change', function (ev) {
 | 
						|
                  let el = ev.target;
 | 
						|
                  checkChange(el,row);
 | 
						|
                })
 | 
						|
                if (item.check) valueEl.setAttribute('data-check', item.check);
 | 
						|
                let btContainer=document.createElement("div");
 | 
						|
                btContainer.classList.add("buttonContainer");
 | 
						|
                row.appendChild(btContainer);
 | 
						|
                let bt = document.createElement('button');
 | 
						|
                bt.classList.add('defaultButton');
 | 
						|
                bt.setAttribute('data-default', item.default);
 | 
						|
                bt.addEventListener('click', function (ev) {
 | 
						|
                  valueEl.value = valueEl.getAttribute('data-default');
 | 
						|
                  let changeEvent=new Event('change');
 | 
						|
                  valueEl.dispatchEvent(changeEvent);
 | 
						|
                })
 | 
						|
                bt.textContent = "X";
 | 
						|
                btContainer.appendChild(bt);
 | 
						|
                bt = document.createElement('button');
 | 
						|
                bt.classList.add('infoButton');
 | 
						|
                bt.addEventListener('click', function (ev) {
 | 
						|
                  showOverlay(item.description);
 | 
						|
                });
 | 
						|
                bt.textContent = "?";
 | 
						|
                btContainer.appendChild(bt);
 | 
						|
                frame.appendChild(row);
 | 
						|
              })
 | 
						|
              resetForm();
 | 
						|
            })
 | 
						|
        })
 | 
						|
        .catch(function (err) { alert("unable to load config: " + err) })
 | 
						|
    }
 | 
						|
  function converterInfo(){
 | 
						|
    getJson("api/converterInfo").then(function(json){
 | 
						|
      let text="<h3>Converted entities</h3>";
 | 
						|
      text+="<p><b>NMEA0183 to NMEA2000:</b><br/>";
 | 
						|
      text+="   "+(json.nmea0183||"").replace(/,/g,", ");
 | 
						|
      text+="</p>";
 | 
						|
      text+="<p><b>NMEA2000 to NMEA0183:</b><br/>";
 | 
						|
      text+="   "+(json.nmea2000||"").replace(/,/g,", ");
 | 
						|
      text+="</p>";
 | 
						|
      showOverlay(text,true);
 | 
						|
    });
 | 
						|
  }  
 | 
						|
  window.setInterval(update,1000);
 | 
						|
  window.addEventListener('load',function(){
 | 
						|
  let buttons=document.querySelectorAll('button');
 | 
						|
    for (let i=0;i<buttons.length;i++){
 | 
						|
      let be=buttons[i];
 | 
						|
      be.onclick=window[be.id]; //assume a function with the button id
 | 
						|
    }
 | 
						|
    let cd=document.getElementById("showCanDetails");
 | 
						|
    cd.addEventListener('change',function(ev){
 | 
						|
      showCanDetails(ev.target.checked);
 | 
						|
    });
 | 
						|
    loadConfigDefinitions();
 | 
						|
  });
 | 
						|
</script>  
 | 
						|
<style type="text/css">
 | 
						|
.configForm {
 | 
						|
    margin-top: 1em;
 | 
						|
    margin-bottom: 1em;
 | 
						|
    padding-top: 0.5em;
 | 
						|
    padding-bottom: 0.5em;
 | 
						|
    border: 1px solid grey;
 | 
						|
    max-width: 40em;
 | 
						|
}
 | 
						|
.changed input{
 | 
						|
    color: green
 | 
						|
}
 | 
						|
.changed select{
 | 
						|
    color: green
 | 
						|
}
 | 
						|
span.label {
 | 
						|
    width: 10em;
 | 
						|
    display: inline-block;
 | 
						|
    opacity: 0.6;
 | 
						|
}
 | 
						|
.value {
 | 
						|
    width: 19em;
 | 
						|
    display: flex;
 | 
						|
    flex-direction: row;
 | 
						|
}
 | 
						|
span#connected {
 | 
						|
    display: inline-block;
 | 
						|
    background-color: red;
 | 
						|
    width: 1em;
 | 
						|
    height: 1em;
 | 
						|
    border-radius: 50%;
 | 
						|
}
 | 
						|
span#connected.ok{
 | 
						|
  background-color: #13ac13;
 | 
						|
}
 | 
						|
.row {
 | 
						|
    margin: 0.5em;
 | 
						|
    display: flex;
 | 
						|
    flex-direction: row;
 | 
						|
    flex-wrap: wrap;
 | 
						|
}
 | 
						|
.filter {
 | 
						|
    display: inline-block;
 | 
						|
}
 | 
						|
.buttons {
 | 
						|
    padding-left: 1em;
 | 
						|
}
 | 
						|
button.infoButton {
 | 
						|
    margin-left: 1em;
 | 
						|
    vertical-align: bottom;
 | 
						|
}
 | 
						|
#canDetails{
 | 
						|
  display:none;
 | 
						|
}
 | 
						|
#canDetails.visible{
 | 
						|
  display: block;
 | 
						|
}
 | 
						|
 | 
						|
.overlayContainer {
 | 
						|
    position: fixed;
 | 
						|
    top: 0;
 | 
						|
    bottom: 0;
 | 
						|
    left: 0;
 | 
						|
    right: 0;
 | 
						|
    background-color: #80808070;
 | 
						|
    display: flex;
 | 
						|
}
 | 
						|
.overlayContainer.hidden{
 | 
						|
  display: none;
 | 
						|
}
 | 
						|
div#overlay {
 | 
						|
    margin: auto;
 | 
						|
    background-color: white;
 | 
						|
    padding: 0.5em;
 | 
						|
    max-width: 100%;
 | 
						|
    box-sizing: border-box;
 | 
						|
}
 | 
						|
div#overlayContent {
 | 
						|
    padding: 0.5em;
 | 
						|
}
 | 
						|
div#overlayContent.text{
 | 
						|
  white-space: pre;
 | 
						|
}
 | 
						|
.overlayButtons {
 | 
						|
    border-top: 1px solid grey;
 | 
						|
    padding-top: 0.5em;
 | 
						|
    display: flex;
 | 
						|
    flex-direction: row;
 | 
						|
    justify-content: end;
 | 
						|
}
 | 
						|
</style>
 | 
						|
</head>
 | 
						|
<body>
 | 
						|
<div class="main">
 | 
						|
<h1>NMEA 2000 Gateway </h1>
 | 
						|
<div class="row">
 | 
						|
  <span class="label">VERSION</span>
 | 
						|
  <span class="value" id="version">---</span>
 | 
						|
  <button class="infoButton" id="converterInfo">?</button> 
 | 
						|
</div>
 | 
						|
<div class="row">
 | 
						|
  <span class="label">connected</span>
 | 
						|
  <span class="value" id="connected"></span>
 | 
						|
</div>
 | 
						|
<div class="row">
 | 
						|
  <span class="label">Access Point IP</span>
 | 
						|
  <span class="value" id="apIp">---</span>
 | 
						|
</div>
 | 
						|
<div class="row">
 | 
						|
  <span class="label"># NMEA2000 messages</span>
 | 
						|
  <span class="value" id="numcan">---</span>
 | 
						|
</div>
 | 
						|
<div class="row">
 | 
						|
  <span class="label">NMEA2000 details</span>
 | 
						|
  <input type="checkbox" id="showCanDetails"></span>
 | 
						|
</div>
 | 
						|
 | 
						|
<div class="row" id="canDetails">
 | 
						|
 | 
						|
</div>
 | 
						|
<div class="row">
 | 
						|
  <span class="label"># TCP clients</span>
 | 
						|
  <span class="value" id="numClients">---</span>
 | 
						|
</div>
 | 
						|
<div class="row">
 | 
						|
  <span class="label">wifi client connected</span>
 | 
						|
  <span class="value" id="wifiConnected">---</span>
 | 
						|
</div>
 | 
						|
<div class="row">
 | 
						|
  <span class="label">wifi client IP</span>
 | 
						|
  <span class="value" id="clientIP">---</span>
 | 
						|
</div>
 | 
						|
<button id="reset" >Reset</button>
 | 
						|
<div class="configForm">
 | 
						|
  <div class="configFormRows">
 | 
						|
 | 
						|
  </div>
 | 
						|
  <div class="buttons">
 | 
						|
    <button id="resetForm">ReloadConfig</button>
 | 
						|
    <button id="changeConfig">Save&Restart</button>
 | 
						|
    <button id="factoryReset">FactoryReset</button>
 | 
						|
  </div>
 | 
						|
</div>
 | 
						|
 | 
						|
</div>
 | 
						|
<div class="overlayContainer hidden" id="overlayContainer">
 | 
						|
  <div id="overlay">
 | 
						|
    <div id="overlayContent">
 | 
						|
      AHA
 | 
						|
    </div>
 | 
						|
    <div class="overlayButtons">
 | 
						|
      <button id="hideOverlay">Close</button>
 | 
						|
    </div>
 | 
						|
  </div>
 | 
						|
</div>
 | 
						|
</body>
 | 
						|
</html>
 | 
						|
 | 
						|
 |