346 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			346 lines
		
	
	
		
			9.3 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();
 | 
						|
  function alertRestart(){
 | 
						|
    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();
 | 
						|
      })
 | 
						|
  }
 | 
						|
  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);
 | 
						|
            checkChange(el);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      });
 | 
						|
  }
 | 
						|
  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 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+=v.getAttribute('name')+"="+encodeURIComponent(v.value)+"&";
 | 
						|
    }
 | 
						|
    getJson(url)
 | 
						|
      .then(function(status){
 | 
						|
        if(status.status == 'OK'){
 | 
						|
          alertRestart();
 | 
						|
        }
 | 
						|
        else{
 | 
						|
          alert("unable to set config: "+status.status);
 | 
						|
        }
 | 
						|
      })
 | 
						|
  }
 | 
						|
  function factoryReset(){
 | 
						|
    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 checkChange(el) {
 | 
						|
      let loaded = el.getAttribute('data-loaded');
 | 
						|
      if (loaded !== undefined) {
 | 
						|
        if (loaded != el.value) {
 | 
						|
          el.classList.add('changed');
 | 
						|
        }
 | 
						|
        else {
 | 
						|
          el.classList.remove("changed");
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  function createInput(configItem){
 | 
						|
    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);
 | 
						|
      })
 | 
						|
      return el;
 | 
						|
    }
 | 
						|
    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');
 | 
						|
    }
 | 
						|
    return el;
 | 
						|
  }
 | 
						|
  let configDefinitions;
 | 
						|
  function loadConfigDefinitions(){
 | 
						|
    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;
 | 
						|
          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 valueEl=createInput(item);
 | 
						|
          if (!valueEl) return;
 | 
						|
          valueEl.setAttribute('data-default',item.default);
 | 
						|
          valueEl.addEventListener('change',function(ev){
 | 
						|
            let el=ev.target;
 | 
						|
            checkChange(el);
 | 
						|
          })
 | 
						|
          if (item.check) valueEl.setAttribute('data-check',item.check);
 | 
						|
          row.appendChild(valueEl);
 | 
						|
          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');
 | 
						|
            checkChange(valueEl);
 | 
						|
          })
 | 
						|
          bt.textContent="X";
 | 
						|
          row.appendChild(bt);
 | 
						|
          bt=document.createElement('button');
 | 
						|
          bt.classList.add('infoButton');
 | 
						|
          bt.addEventListener('click',function(ev){
 | 
						|
            alert(item.description);
 | 
						|
          });
 | 
						|
          bt.textContent="?";
 | 
						|
          row.appendChild(bt);
 | 
						|
          frame.appendChild(row);
 | 
						|
        })
 | 
						|
        resetForm();
 | 
						|
      })
 | 
						|
      .catch(function(err){alert("unable to load config: "+err)})
 | 
						|
  }
 | 
						|
  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 {
 | 
						|
    color: green
 | 
						|
}
 | 
						|
span.label {
 | 
						|
    width: 10em;
 | 
						|
    display: inline-block;
 | 
						|
    opacity: 0.6;
 | 
						|
}
 | 
						|
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;
 | 
						|
}
 | 
						|
.buttons {
 | 
						|
    padding-left: 1em;
 | 
						|
}
 | 
						|
button.infoButton {
 | 
						|
    margin-left: 1em;
 | 
						|
    vertical-align: bottom;
 | 
						|
}
 | 
						|
#canDetails{
 | 
						|
  display:none;
 | 
						|
}
 | 
						|
#canDetails.visible{
 | 
						|
  display: block;
 | 
						|
}
 | 
						|
 | 
						|
</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>
 | 
						|
</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>
 | 
						|
</body>
 | 
						|
</html>
 | 
						|
 | 
						|
 |