Skip to content
Snippets Groups Projects
Select Git revision
  • 3260f16f60bdbe88485f08ab75ec6655f0e97dc2
  • master default
  • nrb/rebase-20200624
  • nrb/rebase-recommended
  • nrb/old-deprectated-devices
  • next
6 results

app.js

Blame
  • app.js 22.86 KiB
    /*
     * This program is free software: you can redistribute it and/or modify
     * it under the terms of the GNU Affero General Public License as published by
     * the Free Software Foundation, either version 3 of the License, or
     * (at your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU Affero General Public License for more details.
     *
     * You should have received a copy of the GNU Affero General Public License
     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     */
    
    function $(s) {
      return document.querySelector(s);
    }
    
    function toggleClass(s, cssClass) {
      $(s).classList.toggle(cssClass);
    }
    
    function show_inline(s) {
      $(s).style.display = 'inline-block';
    }
    
    function show_block(s) {
      $(s).style.display = 'block';
    }
    
    function hide(s) {
      $(s).style.display = 'none';
    }
    
    // Object.values() replacement
    function ObjectValues(obj) {
      return Object.keys(obj).map(function(key) { return obj[key]; });
    }
    
    function isEmptyObject(obj) {
        for (var name in obj) {
            return false;
        }
        return true;
    }
    
    function scrollDown() {
      window.scrollBy({
        top: 512,
        left: 0,
        behavior: 'smooth'
      });
    }
    
    var firmwarewizard = function() {
      var app = {};
    
      var IGNORED_ELEMENTS = [
        './', '../', 'experimental.manifest', 'beta.manifest', 'stable.manifest',
        '-tftp', '-fat', '-loader', '-NA', '-x2-', '-hsv2', '-p1020'
      ];
      var PANE = {'MODEL': 0, 'IMAGETYPE': 1, 'BRANCH': 2};
    
      var wizard = parseWizardObject();
      app.currentVersions = {};
      var images = {};
    
      function buildVendorModelsReverse() {
        var vendormodels_reverse = {};
    
        // create a map of {match : [{vendor, model, default-revision}, ... ], ...}
        for (var vendor in config.vendormodels) {
          var models = config.vendormodels[vendor];
          for (var model in models) {
            var match = models[model];
            if (typeof match == 'string') {
              addArray(vendormodels_reverse, match, {'vendor': vendor, 'model': model, 'revision': ''});
            } else for (var m in match) {
              addArray(vendormodels_reverse, m, {'vendor': vendor, 'model': model, 'revision': match[m]});
            }
          }
        }
    
        return vendormodels_reverse;
      }
    
      function createHistoryState(wizard) {
        if (!window.history || !history.pushState) return;
    
        var parameters = '';
        for (var key in wizard) {
          parameters += '&' + key + '=' + encodeURIComponent(wizard[key]);
        }
    
        // replace first occurence of "&" by "?"
        parameters = parameters.replace('&', '?');
        history.pushState(wizard, '', parameters);
      }
    
      function parseWizardObject(wizard) {
        if (wizard === undefined || wizard === null) wizard = {};
        wizard.vendor            = wizard.vendor || -1;
        wizard.model             = wizard.model || -1;
        wizard.revision          = wizard.revision || -1;
        wizard.imageType         = wizard.imageType || -1;
        wizard.showFirmwareTable = (wizard.showFirmwareTable == 'true');
        return wizard;
      }
    
      window.onpopstate = function(event) {
        if (event.state === null) return;
        wizard = parseWizardObject(event.state);
        updateHTML(wizard);
      };
    
      window.onload = function() {
        function parseURLasJSON() {
          var search = location.search.substring(1);
          return search ? JSON.parse(
            '{"' + search.replace(/&/g, '","').replace(/=/g,'":"') + '"}',
            function(key, value) {
              return (key=== '') ? value:decodeURIComponent(value);
            }):{};
        }
        var parsedURL = parseURLasJSON();
        wizard = parseWizardObject(parsedURL);
        updateHTML(wizard);
      };
    
      app.genericError = function() {
        alert('Da ist was schiefgelaufen. Frage doch bitte einmal im Chat nach.');
      };
    
      // methods to set options
    
      app.setVendor = function(vendor) {
        wizard.vendor = vendor;
        wizard.model = -1;
        wizard.revision = -1;
        wizard.imageType = -1;
        createHistoryState(wizard);
        updateHTML(wizard);
      };
    
      app.setModel = function(model) {
        wizard.model = model;
        wizard.revision = -1;
        wizard.imageType = -1;
    
        if (wizard.model != -1) {
          // skip revision selection if there is only one option left
          var revisions = getRevisions();
          if (revisions.length == 1) {
            app.setRevision(revisions[0], true);
          }
        }
    
        createHistoryState(wizard);
        updateHTML(wizard);
      };
    
      app.setRevision = function(revision, silentUpdate) {
        if (silentUpdate === undefined) {
          silentUpdate = false;
        }
        wizard.revision = revision;
        wizard.imageType = -1;
        if (!silentUpdate) {
          createHistoryState(wizard);
          updateHTML(wizard);
        }
      };
    
      app.setImageType = function(type) {
        wizard.imageType = type;
        createHistoryState(wizard);
        updateHTML(wizard);
      };
    
      app.showFirmwareTable = function() {
        wizard.showFirmwareTable = true;
        createHistoryState(wizard);
        updateHTML(wizard);
      };
    
      app.hideFirmwareTable = function() {
        wizard.showFirmwareTable = false;
        createHistoryState(wizard);
        updateHTML(wizard);
      };
    
      // exclude file names containing a string
      function ignoreFileName(name) {
        for (var i in IGNORED_ELEMENTS) {
          if (name.indexOf(IGNORED_ELEMENTS[i]) != -1) {
            return true;
          }
        }
        return false;
      }
    
      // simplified version string sort
      function sortByRevision(revisions) {
        revisions.sort(function(a, b) {
            a = a.revision; b = b.revision;
            if (a.length > b.length) return 1;
            if (a.length < b.length) return -1;
            if (a > b) return 1;
            if (a < b) return -1;
            return 0;
          });
        return revisions;
      }
    
      function findType(name) {
        var m = /-(sysupgrade|factory|rootfs|kernel)[-.]/.exec(name);
        return m ? m[1] : 'factory';
      }
    
      function findVersion(name) {
        // version with optional date in it (e.g. 0.8.0~20160502)
        var m = /-([0-9]+.[0-9]+.[0-9]+(~[0-9]+)?)[.-]/.exec(name);
        return m ? m[1] : '';
      }
    
      function findRevision(name) {
        // reversion identifier like a1, v2
        var m = /-([a-z][0-9]+(.[0-9]+)?)[.-]/.exec(name);
        return m ? m[1] : 'alle';
      }
    
      function findRegion(name) {
        var m = /-(cn|de|en|eu|il|jp|us)[.-]/.exec(name);
        return m ? m[1] : '';
      }
    
      function findSize(name) {
        var m = /-(4M|8M|16M|32M|64M)[.-]/.exec(name);
        return m ? m[1] : '';
      }
    
      function addArray(obj, key, value) {
        if (key in obj) {
          obj[key].push(value);
        } else {
          obj[key] = [value];
        }
      }
    
      function parseFilePath(match, basePath, filename, branch) {
        var location = basePath + filename;
    
        var devices = vendormodels_reverse[match];
        if (!(devices instanceof Array) || devices.length != 1) {
          console.log("Error: vendormodels_reverse did not contain", match);
          return;
        }
        var device = devices[0];
    
        if (device.model == '--ignore--' || device.revision == '--ignore--') {
          return;
        }
    
        var strippedFilename = filename;
        strippedFilename = strippedFilename.replace(config.community_prefix, '-');
    
        var version = findVersion(strippedFilename);
        strippedFilename = strippedFilename.replace(version, '');
    
        strippedFilename = strippedFilename.replace(match, '');
    
        var revision = device.revision || findRevision(strippedFilename);
        strippedFilename = strippedFilename.replace(revision, '');
    
        var region = findRegion(strippedFilename);
        strippedFilename = strippedFilename.replace(region, '');
    
        if (region.length) {
          revision += '-' + region;
        }
    
        var size = findSize(strippedFilename);
        strippedFilename = strippedFilename.replace(size, '');
    
        var type = findType(strippedFilename);
        strippedFilename = strippedFilename.replace(type, '');
    
        strippedFilename = strippedFilename.replace(/.(bin|img.gz|img|tar|vdi|vmdk)/, '');
        strippedFilename = strippedFilename.replace(/-/g, '');
    
        if (strippedFilename !== '') {
          console.log("Match for file", filename, "was not exhaustive.");
        }
    
        // collect branch versions
        app.currentVersions[branch] = version;
    
        if (!(device.vendor in images)) {
          images[device.vendor] = {};
        }
    
        addArray(images[device.vendor], device.model, {
          'revision': revision,
          'branch': branch,
          'type': type,
          'version': version,
          'location': location,
          'size': size
        });
      }
    
      function createOption(value, title, selectedOption) {
        var o = document.createElement('option');
        o.value = value;
        o.innerHTML = title;
        o.selected = (value === selectedOption);
        return o;
      }
    
      function getRevisions() {
        return sortByRevision(images[wizard.vendor][wizard.model])
          .map(function(e) { return e.revision; })
          .filter(function(value, index, self) { return self.indexOf(value) === index; });
      }
    
      function getImageTypes() {
        return images[wizard.vendor][wizard.model]
          .map(function(e) { return e.type; })
          .filter(function(value, index, self) { return self.indexOf(value) === index; })
          .sort();
      }
    
      // format string for searching
      function searchable(q) {
        return q.toLowerCase().replace(/[-/ ]/g, '');
      }
    
      // create a list with all models optimized for searching
      function createSearchableModellist() {
        var modelList = [];
        var vendors = Object.keys(images);
        for(var i in vendors) {
          var models = Object.keys(images[vendors[i]]);
          for (var j in models) {
            modelList.push([searchable(vendors[i]+models[j]), vendors[i], models[j]]);
          }
        }
        return modelList;
      }
    
      function createPicturePreview(vendor, model, searchstring) {
        if (!(vendor in images)) return '';
        if (!(model in images[vendor])) return '';
    
        // determine revision index (use image for latest revision)
        var latestRevisionIndex = 0;
        var highesRevision = 1;
        for (var r in images[vendor][model]) {
          var rev = images[vendor][model][r].revision.substr(1);
          if (parseInt(rev) > highesRevision){// && rev.toString() == parseInt(rev).toString()) {
            latestRevisionIndex = r;
            highesRevision = parseInt(rev);
          }
        }
    
        var location = images[vendor][model][latestRevisionIndex].location;
        var startIndex = location.lastIndexOf(vendor.toLowerCase().replace(/ /g, '-'));
        var src = location.substr(startIndex);
        src = src.replace(/(.bin|.img)$/, '.jpg');
        src = src.replace('-sysupgrade', '');
        src = src.replace('alfa-network', 'alfa');
    
        var image = document.createElement('img');
        image.src = 'pictures/'+src;
        image.alt = name;
        image.addEventListener('error', firmwarewizard.setDefaultImg);
    
        var caption = document.createElement('span');
        caption.innerText = vendor+' '+model;
    
        var wrapper = document.createElement('div');
        wrapper.className = 'preview';
        wrapper.style.display = 'none';
        wrapper.setAttribute('data-searchstring', searchstring);
        wrapper.setAttribute('data-vendor', vendor);
        wrapper.setAttribute('data-model', model);
        wrapper.addEventListener('click', previewClickEventHandler);
        wrapper.appendChild(image);
        wrapper.appendChild(caption);
        return wrapper;
      }
    
      function previewClickEventHandler(e) {
        var vendor = e.currentTarget.getAttribute('data-vendor');
        var model = e.currentTarget.getAttribute('data-model');
        app.setVendor(vendor);
        app.setModel(model);
        scrollDown();
      }
    
      function setDefaultImg(e) {
        fallbackImg = 'pictures/no_picture_available.jpg';
        if (e.target.src.search(fallbackImg) == -1) {
          e.target.src = fallbackImg;
        }
      }
      app.setDefaultImg = setDefaultImg;
    
      // search for models (and vendors)
      function searchModel(query) {
        function filterModelList(modelList, query) {
          var filteredModelList = [];
          query = searchable(query);
          for (var m in modelList) {
            if (modelList[m][0].search(query) != -1) {
              filteredModelList.push(modelList[m]);
            }
          }
          return filteredModelList;
        }
    
        var modelList = createSearchableModellist();
    
        // search for vendors and models
        var queryparts = query.split(' ');
        for(var q in queryparts) {
          modelList = filterModelList(modelList, queryparts[q]);
        }
    
        updatePreviewList(modelList);
      }
      app.searchModel = searchModel;
    
      function updatePreviewList(modelList) {
        var previews = document.querySelectorAll('.imagePreview .preview');
        for(var p = 0; p < previews.length; p++) {
          previews[p].style.display = 'none';
        }
    
        for (var f in modelList) {
          var searchstring = modelList[f][0];
          var vendor = modelList[f][1];
          var model = modelList[f][2];
    
          for(p = 0; p < previews.length; p++) {
            if (previews[p].getAttribute('data-searchstring') == searchstring) {
              previews[p].style.display = 'inline-block';
            }
          }
        }
      }
    
      // update all elements of the page according to the wizard object.
      function updateHTML(wizard) {
        if (wizard.showFirmwareTable) {
          show_block('#firmwareTable');
          hide('#wizard');
        } else {
          show_block('#wizard');
          hide('#firmwareTable');
        }
    
        // show vendor dropdown menu.
        function showVendors() {
          var select = $('#vendorselect');
          select.innerHTML = '';
          select.appendChild(
            createOption(-1, '-- Bitte Hersteller wählen --')
          );
    
          var vendors = Object.keys(images).sort();
          for (var i in vendors) {
            select.appendChild(
              createOption(vendors[i], vendors[i], wizard.vendor)
            );
          }
    
          if (wizard.vendor != -1) {
            $('.modelSearch').value = wizard.vendor;
            searchModel(wizard.vendor);
          }
        }
        showVendors();
    
        // show model dropdown menu
        function showModels() {
          var select = $('#modelselect');
    
          select.innerHTML = '';
          select.appendChild(
            createOption(-1, '-- Bitte Modell wählen --')
          );
    
          if (wizard.vendor == -1 || isEmptyObject(images)) {
            return;
          }
    
          var models = Object.keys(images[wizard.vendor]).sort();
          for (var i in models) {
            select.appendChild(
              createOption(models[i], models[i], wizard.model)
            );
          }
    
          if (wizard.model != -1) {
            $('.modelSearch').value = wizard.vendor+' '+wizard.model;
    
            updatePreviewList([[
              searchable(wizard.vendor+wizard.model), wizard.vendor, wizard.model]]);
          }
    
        }
        showModels();
    
        // show revision dropdown menu
        function showRevisions() {
          var select = $('#revisionselect');
    
          select.innerHTML = '';
    
          select.appendChild(
            createOption(-1, '-- Bitte Hardwarerevision wählen --', wizard.revision)
          );
    
          if (wizard.vendor == -1 || wizard.model == -1 || isEmptyObject(images)) {
            return;
          }
    
          var revisions = getRevisions();
          for (var i in revisions) {
            select.appendChild(
              createOption(revisions[i], revisions[i], wizard.revision)
            );
          }
        }
        showRevisions();
    
        // show image type selection
        function showImageTypes() {
          if (wizard.model == -1 || isEmptyObject(images)) {
            return;
          }
    
          var content = '';
          var types = getImageTypes();
          var typeNames = {
            'factory': 'Erstinstallation',
            'sysupgrade': 'Upgrade',
            'rootfs': "Root-Image",
            'kernel': "Kernel-Image"
          };
    
          for (var i in types) {
            var type = types[i];
            if (type === '') continue;
    
            var displayType = typeNames[type] || type;
            content += '<input type="radio" id="radiogroup-typeselect-' +
              type + '" ' + ((type == wizard.imageType) ? 'checked ' : '') +
              'name="firmwareType" onclick="firmwarewizard.setImageType(\'' + type + '\'); scrollDown();">' +
              '<label for="radiogroup-typeselect-' + type + '">' + displayType + '</label>';
          }
          $('#typeselect').innerHTML = content;
        }
        showImageTypes();
    
        // show branch selection
        function showBranches() {
          if (wizard.model == -1 || wizard.revision == -1 || wizard.imageType == -1 || isEmptyObject(images)) {
            return;
          }
    
          var revisions = images[wizard.vendor][wizard.model]
            .filter(function(e) { return e.revision == wizard.revision && e.type == wizard.imageType; });
    
          $('#branchselect').innerHTML = '';
          $('#branch-experimental-dl').innerHTML = '';
    
          for (var i in revisions) {
            var rev = revisions[i];
            if (rev.branch == 'experimental') {
              $('#branchselect').innerHTML += '<button class="btn dl-expermental" onclick="toggleClass(\'#branch-pane\', \'show-experimental-warning\'); scrollDown();">'+rev.branch+' (' +rev.version+')</button>';
              $('#branch-experimental-dl').innerHTML = '<a href="'+rev.location+'" class="btn">Download für Experimentierfreudige</a>';
            } else {
              $('#branchselect').innerHTML += '<a href="'+rev.location+'" class="btn">'+rev.branch+' (' +rev.version+')</a>';
            }
          }
        }
        showBranches();
    
        function updateHardwareSelection() {
          if (wizard.vendor == -1) {
            hide('#modelselect');
            hide('.revisionselectWrapper');
          } else {
            show_inline('#modelselect');
            if (wizard.model == -1) {
              hide('.revisionselectWrapper');
            } else {
              show_inline('.revisionselectWrapper');
            }
          }
        }
        updateHardwareSelection();
    
        // add pictures for router models
        if ($('.imagePreview').innerHTML === '') {
          var previews = $('.imagePreview');
          var modelList = createSearchableModellist();
          for (var m in modelList) {
            var searchstring = modelList[m][0];
            var vendor = modelList[m][1];
            var model = modelList[m][2];
            previews.append(createPicturePreview(vendor, model, searchstring));
          }
        }
    
        function updatePanes() {
          var pane = PANE.MODEL;
          if (wizard.vendor != -1 && wizard.model != -1 && wizard.revision != -1) {
            pane = PANE.IMAGETYPE;
            if (wizard.imageType != -1) {
              pane = PANE.BRANCH;
            }
          }
    
          $('#model-pane').style.display = (pane >= PANE.MODEL) ? 'block' : 'none';
          $('#type-pane').style.display = (pane >= PANE.IMAGETYPE) ? 'block' : 'none';
          $('#branch-pane').style.display = (pane >= PANE.BRANCH) ? 'block' : 'none';
        }
        updatePanes();
    
        function updateCurrentVersions() {
          var branches = ObjectValues(config.directories)
            .filter(function(value, index, self) { return self.indexOf(value) === index; });
    
          $('#currentVersions').innerHTML = branches.reduce(function(ret, branch, i) {
            ret += ((i === 0) ? '' : ' // ') + branch;
            ret += (branch in app.currentVersions) ?  (': '  + app.currentVersions[branch]) : '';
            return ret;
          }, '');
        }
        updateCurrentVersions();
    
        function updateFirmwareTable() {
          $('#firmwareTableBody').innerHTML = '';
    
          var initializeRevHTML = function(rev) {
            upgradeHTML[rev.branch] = '';
            factoryHTML[rev.branch] = '';
          };
    
          var addToRevHTML = function(rev) {
            var html = '[<a href="' + rev.location + '" title="' + rev.version + '">' + rev.revision + '</a>] ';
            if (rev.type == 'sysupgrade') {
              upgradeHTML[rev.branch] += html;
              show = true;
            } else if (rev.type == 'factory') {
              factoryHTML[rev.branch] += html;
              show = true;
            }
          };
    
          var vendors = Object.keys(images).sort();
          for (var v in vendors) {
            var vendor = vendors[v];
            var models = Object.keys(images[vendor]).sort();
            for (var m in models) {
              var model = models[m];
              var revisions = sortByRevision(images[vendor][model]);
              var upgradeHTML = {};
              var factoryHTML = {};
              var show = false;
    
              revisions.forEach(initializeRevHTML);
              revisions.forEach(addToRevHTML);
    
              if (!show) {
                continue;
              }
    
              var tr = document.createElement('tr');
    
              var tdVendor = document.createElement('td');
              tdVendor.innerText = vendor;
              tr.append(tdVendor);
    
              var tdModel = document.createElement('td');
              tdModel.innerText = model;
              tr.append(tdModel);
    
              var tdFactoryHTML = '';
              for(var branch in factoryHTML) {
                tdFactoryHTML += branch + ': ' + (factoryHTML[branch] || '-')+ '<br>';
              }
              var tdFactory = document.createElement('td');
              tdFactory.innerHTML = tdFactoryHTML;
              tr.append(tdFactory);
    
              var tdUpgradeHTML = '';
              for(branch in upgradeHTML) {
                tdUpgradeHTML += branch + ': ' + (upgradeHTML[branch] || '-') + '<br>';
              }
              var tdUpgrade = document.createElement('td');
              tdUpgrade.innerHTML = tdUpgradeHTML;
              tr.append(tdUpgrade);
    
              $('#firmwareTableBody').append(tr);
            }
          }
        }
        updateFirmwareTable();
      }
    
      function loadSite(url, callback) {
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.onreadystatechange = function() {
          if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
            callback(xmlhttp.responseText, url);
          }
        };
        xmlhttp.open('GET', url, true);
        xmlhttp.send();
      }
    
      // parse the contents of the given directories
      function loadDirectories() {
        // sort by length to get the longest match
        var matches = Object.keys(vendormodels_reverse).sort(function(a, b) {
          if (a.length < b.length) return 1;
          if (a.length > b.length) return -1;
          return 0;
        });
    
        var parseSite = function(data, indexPath) {
          var basePath = indexPath.substring(0, indexPath.lastIndexOf('/') + 1);
          var branch = config.directories[indexPath];
          reLink.lastIndex = 0;
    
          var m;
          do {
            m = reLink.exec(data);
            if (m) {
              var href = m[1];
              if (ignoreFileName(href)) {
                continue;
              }
              var match = reMatch.exec(href);
              if (match) {
                parseFilePath(match[1], basePath, href, branch);
              } else if (config.listMissingImages) {
                console.log("No rule for firmware image:", href);
              }
            }
          } while (m);
    
          sitesLoadedSuccessfully++;
          if (sitesLoadedSuccessfully == Object.keys(config.directories).length) {
            updateHTML(wizard);
          }
        };
    
        // match all links
        var reLink = new RegExp('href="([^"]*)"', 'g');
    
        // match image files
        var reMatch = new RegExp('('+matches.join('|')+')[.-]');
    
        // check if image regexes contain regular expressions themself
        var reCheckRegex = new RegExp(/[^\\]+[+?*]/);
    
        if (reCheckRegex.exec(matches.join('|')) !== null) {
          console.log("Warning! Some regular expressions for firmware images, contain unescaped characters.");
        }
    
        var sitesLoadedSuccessfully = 0;
        for (var indexPath in config.directories) {
          // retrieve the contents of the directory
          loadSite(indexPath, parseSite);
        }
      }
    
      var vendormodels_reverse = buildVendorModelsReverse();
      loadDirectories();
    
      // set link to firmware source directory
      for(var path in config.directories) {
        $('#firmware-source-dir').href = path.replace(/\/[^\/]*$/, '');
        break;
      }
    
      return app;
    }();