/* * 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/>. */ var firmwarewizard = function() { var app = {}; // helper functions function $(s) { return document.querySelector(s); } function toggleClass(e, cssClass) { setClass(e, cssClass, !hasClass(e, cssClass)); } function hasClass(e, cssClass) { var searchstring = (' ' + e.className + ' ').replace(/[\n\t]/g, " "); return (searchstring.indexOf(' ' + cssClass+ ' ') !== -1); } function setClass(e, cssClass, active) { if (active && !hasClass(e, cssClass)) { e.className = (e.className+' '+cssClass).trim(); } else if (!active && hasClass(e, cssClass)) { e.className = e.className.replace(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'; } function sortCaseInsensitive(a, b) { return a.localeCompare(b, 'en', {'sensitivity': 'base'}); } // 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() { try { window.scrollBy({ top: 512, left: 0, behavior: 'smooth' }); } catch (e) { var start = document.body.scrollTop; var lastTop = start - 1; var interval = window.setInterval(function() { if (document.body.scrollTop > start + 512 || lastTop - document.body.scrollTop === 0) { window.clearInterval(interval); console.log('stop'); } lastTop = document.body.scrollTop; document.body.scrollTop += 4; }, 1); } } // constants var IGNORED_ELEMENTS = [ './', '../', '.manifest', '-tftp', '-fat', '-loader', '-NA', '-x2-', '-hsv2', '-p1020' ]; var PANE = {'MODEL': 0, 'IMAGETYPE': 1, 'BRANCH': 2}; // the invisible separator is used to delimit searchstring parts, e.g. // "NanoStation[SEPARATOR]" vs. "NanoStation Loco[SEPARATOR]" var INVISIBLE_SEPARATOR = '\u2063'; var NON_BREAKING_SPACE = '\u00A0'; //var INVISIBLE_SEPARATOR = '#'; // debug //var NON_BREAKING_SPACE = '?'; // debug var MODEL_SEARCHSTRING = 0; var MODEL_STRIPPED_SEARCHSTRING = 1; var MODEL_VENDOR = 2; var MODEL_MODEL = 3; var MODEL_MATCHED_REVISION = 4; var wizard = parseWizardObject(); app.currentVersions = {}; var availableImages = {}; var images = availableImages; var vendormodels_reverse; var typeNames = { 'factory': 'Erstinstallation', 'sysupgrade': 'Upgrade', 'rootfs': "Root-Image", 'kernel': "Kernel-Image", 'eva-filesystem': 'Bootloader-Root-Image', 'eva-kernel': 'Bootloader-Kernel-Image', 'bootloader': 'Bootloader-Image', 'recovery': 'Recovery-Image' }; var branches = ObjectValues(config.directories).filter(function(e, index, self) { return index === self.indexOf(e); }); var reFileExtension = new RegExp(/\.(bin|chk|img\.gz|img|tar|ubi)$/); var reRemoveDashes = new RegExp(/-/g); var reSearchable = new RegExp('[-/ '+NON_BREAKING_SPACE+']', 'g'); var reRemoveSpaces = new RegExp(/ /g); var reStripDashes = new RegExp(/^\-+|\-+$/g); if (config.version_regex === undefined) { console.log("config.version_regex missing in config.js"); return; } var reVersionRegex = new RegExp(config.version_regex); var rePrettyPrintVersionRegex; if(config.prettyPrintVersionRegex !== undefined) { rePrettyPrintVersionRegex = new RegExp(config.prettyPrintVersionRegex); } var PREVIEW_PICTURES_DIR = 'pictures/'; if(config.preview_pictures !== undefined) { PREVIEW_PICTURES_DIR = config.preview_pictures; } var enabled_device_categories = ['recommended']; if ("enabled_device_categories" in config) { enabled_device_categories = config.enabled_device_categories; } if ("recommended_toggle" in config && config.recommended_toggle) { enabled_device_categories = ['recommended']; show_inline('.notRecommendedLink'); if (config.recommended_info_link) { $('#notrecommendedinfo').innerHTML = '<p><a href="' + config.recommended_info_link + '" target="_new">Mehr Informationen</a>'; } } function buildVendorModelsReverse() { var vendormodels_reverse = {}; for (var device_category in config.vendormodels) { for (var vendor in config.vendormodels[device_category]) { var models = config.vendormodels[device_category][vendor]; for (var model in models) { var match = models[model]; if (typeof match == 'string') { addArray(vendormodels_reverse, match, {'vendor': vendor, 'model': model, 'revision': '', category: device_category}); } else for (var m in match) { addArray(vendormodels_reverse, m, {'vendor': vendor, 'model': model, 'revision': match[m], category: device_category}); } } } } return vendormodels_reverse; } function createHistoryState(wizard) { if (!window.history || !history.pushState) return; var parameters = ''; for (var key in wizard) { if (wizard[key] != -1 && wizard[key] !== false) { 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.q = wizard.q || ''; wizard.showFirmwareTable = (wizard.showFirmwareTable == 'true'); return wizard; } function setFilteredImages() { images = {}; for (var vendor in availableImages) { images[vendor] = {}; for (var model in availableImages[vendor]) { for (var device in availableImages[vendor][model]) { if (enabled_device_categories.indexOf(availableImages[vendor][model][device].category) > -1) { addArray(images[vendor], model, availableImages[vendor][model][device]); } } } } } window.onpopstate = function(event) { if (event.state === null) return; wizard = parseWizardObject(event.state); $('.modelSearch').value = wizard.q; updateHTML(wizard); }; window.onload = function() { if (config.title !== undefined) { document.title = config.title; } $('#notrecommendedselect').checked = false; function parseURLasJSON() { var search = location.search.substring(1); return search ? JSON.parse( '{"' + search.replace(/&/g, '","').replace(/=/g,'":"').replace(/\+/g,'%20') + '"}', function(key, value) { return (key=== '') ? value:decodeURIComponent(value); }):{}; } var parsedURL = parseURLasJSON(); wizard = parseWizardObject(parsedURL); $('.modelSearch').value = wizard.q; $('.modelSearch').addEventListener('keyup', function(e) { firmwarewizard.updateSearchQuery($('.modelSearch').value); }); $('#vendorselect').addEventListener('change', function(e) { firmwarewizard.setSearchQuery($('#vendorselect').value); scrollDown(); }); $('#modelselect').addEventListener('change', function(e) { firmwarewizard.setSearchQuery($('#modelselect').value); scrollDown(); }); $('#revisionselect').addEventListener('change', function(e) { firmwarewizard.setSearchQuery($('#revisionselect').value); scrollDown(); }); $('#wizard .notRecommendedLink').addEventListener('click', function(e) { toggleClass($('#model-pane'), 'show-notrecommended-warning'); }); $('#wizard .firmwareTableLink').addEventListener('click', function(e) { firmwarewizard.showFirmwareTable(); }); $('#firmwareTable .firmwareTableLink').addEventListener('click', function(e) { firmwarewizard.hideFirmwareTable(); }); $('#notrecommendedselect').addEventListener('change', function(e) { if (this.checked) { enabled_device_categories = config.enabled_device_categories; } else if ("enabled_device_categories" in config) { enabled_device_categories = ['recommended']; } setFilteredImages(); updateHTML(wizard); updateFirmwareTable(); }); vendormodels_reverse = buildVendorModelsReverse(); loadDirectories(function() { // initialize pictures for router models var previews = $('.imagePreview'); var fullModelList = createSearchableModellist(); for (var m in fullModelList) { var searchstring = fullModelList[m][MODEL_SEARCHSTRING]; var vendor = fullModelList[m][MODEL_VENDOR]; var model = fullModelList[m][MODEL_MODEL]; previews.appendChild(createPicturePreview(vendor, model, searchstring)); } setFilteredImages(); updateHTML(wizard); show_block('.manualSelection'); updateFirmwareTable(); }); // set link to firmware source directory for(var path in config.directories) { $('#firmware-source-dir').href = path.replace(/\/[^\/]*$/, ''); break; } }; function updateSearchQuery(query) { wizard.q = query; createHistoryState(wizard); updateHTML(wizard); } app.updateSearchQuery = updateSearchQuery; function setSearchQuery(query) { $('.modelSearch').value = query; updateSearchQuery(query); } app.setSearchQuery = setSearchQuery; 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|eva-filesystem|eva-kernel|bootloader|recovery)[-.]/.exec(name); return m ? m[1] : 'factory'; } function findVersion(name) { var m = reVersionRegex.exec(name); return m ? m[1] : ''; } // regex that is applied to the version to print a prettier version function prettyPrintVersion(version) { if (rePrettyPrintVersionRegex !== undefined) { var v = rePrettyPrintVersionRegex.exec(version); return v ? v[1] : version; } return version; } 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 + encodeURIComponent(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(reFileExtension, ''); strippedFilename = strippedFilename.replace(reRemoveDashes, ''); if (strippedFilename !== '') { console.log("Match for file", filename, "was not exhaustive. Missing filename parts:", strippedFilename); return; } // derive preview file name var preview = filename; preview = preview.replace(config.community_prefix, ''); preview = preview.replace(version, ''); preview = preview.replace(revision, ''); preview = preview.replace(region, ''); preview = preview.replace(size, ''); preview = preview.replace(type, ''); preview = preview.replace(reFileExtension, ''); preview = preview.replace(reStripDashes, ''); // vendor and model specific fine tuning preview = preview.replace('alfa-network', 'alfa'); preview = preview.replace('buffalo-wzr-hp-ag300h', 'buffalo-wzr-hp-ag300h-wzr-600dhp'); preview = preview.replace('buffalo-wzr-600dhp', 'buffalo-wzr-hp-ag300h-wzr-600dhp'); preview = preview.replace('buffalo-wzr-hp-g300nh2', 'buffalo-wzr-hp-g300nh'); preview = preview.replace('d-link-dir-505-rev', 'd-link-dir-505-rev-a1'); preview = preview.replace('d-link-dir-825-rev', 'd-link-dir-825-rev-b1'); preview = preview.replace('gl-inet-6408a', 'gl-inet-6408a-v1'); preview = preview.replace('gl-inet-6416a', 'gl-inet-6416a-v1'); preview = preview.replace('netgear-wndrmac', 'netgear-wndrmacv2'); preview = preview.replace('openmesh-mr600', 'openmesh-mr600-v1'); preview = preview.replace('openmesh-mr900', 'openmesh-mr900-v1'); if (preview.indexOf('tp-link') != -1) preview += '-' + revision; preview = preview.replace('tp-link-archer-c5-v1', 'tp-link-archer-c7-v2'); // Archer C5 v1 and Archer C7 v2 are identical preview = preview.replace('tp-link-tl-wa801n-nd-v3', 'tp-link-tl-wa801n-nd-v2'); // no preview picture for v3 yet preview = preview.replace('tp-link-tl-wr940n-v3', 'tp-link-tl-wr940n-v2'); // no preview picture for v3 yet preview = preview.replace('ubiquiti-unifi-ap', 'ubiquiti-unifi'); preview = preview.replace('ubiquiti-unifi-lr', 'ubiquiti-unifi'); preview = preview.replace('ubiquiti-unifi-pro', 'ubiquiti-unifi-ap-pro'); preview = preview.replace('x86-virtualbox', 'x86-virtualbox.vdi'); preview = preview.replace('x86-64-virtualbox', 'x86-virtualbox.vdi'); preview = preview.replace('x86-vmware', 'x86-vmware.vmdk'); preview = preview.replace('x86-64-vmware', 'x86-vmware.vmdk'); preview = preview.replace('x86-generic', 'x86-generic.img'); preview = preview.replace('x86-64', 'x86-generic.img'); preview = preview.replace('x86-kvm', 'x86-kvm.img'); preview = preview.replace('x86-generic.img.vdi', 'x86-virtualbox.vdi'); preview = preview.replace('x86-generic.img.vmdk', 'x86-vmware.vmdk'); preview = preview.replace('x86-legacy', 'x86-legacy.img'); // collect branch versions app.currentVersions[branch] = version; if (!(device.vendor in availableImages)) { availableImages[device.vendor] = {}; } addArray(availableImages[device.vendor], device.model, { 'revision': revision, 'branch': branch, 'type': type, 'version': version, 'location': location, 'size': size, 'preview': preview+".svg", 'category': device.category }); } function createOption(value, title, selectedOption) { var o = document.createElement('option'); o.value = value; o.innerText = title; o.selected = (value === selectedOption); return o; } function getRevisions(currentVendor, currentModel) { var models = images[currentVendor]; if (models === undefined) { return []; } var revisions = models[currentModel]; if (revisions === undefined) { return []; } return sortByRevision(revisions) .map(function(e) { return e.revision; }) .filter(function(value, index, self) { return self.indexOf(value) === index; }); } function getImageTypes(modelList) { if (modelList.length == 1) { var vendor = modelList[0][MODEL_VENDOR]; var model = modelList[0][MODEL_MODEL]; var revision = modelList[0][MODEL_MATCHED_REVISION]; return images[vendor][model] .filter(function(value, index, self) { return value['revision'] == revision; }) .map(function(e) { return e.type; }) .filter(function(value, index, self) { return self.indexOf(value) === index; }) .sort(); } else { return []; } } // format string for searching function searchable(q) { return q.toLowerCase().replace(reSearchable, ''); } // make string atomic (atomic strings won't get split when searching for models // and have a well-defined ending) function atomic(q) { return q.replace(/ /g, NON_BREAKING_SPACE) + INVISIBLE_SEPARATOR; } // 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) { var revisions = getRevisions(vendors[i], models[j]); var searchingstring = searchable( vendors[i]+INVISIBLE_SEPARATOR+models[j]+INVISIBLE_SEPARATOR+ revisions.map(atomic).join('')); // The 2nd searchingstring entry is used to strip already matched query // parts. This is needed when vendor and model are the same (e.g. // "Vocore Vocore") and there are more models from the same vendor. // The last entry is used to store the revision matched by the searchstring. modelList.push([searchingstring, searchingstring, vendors[i], models[j], '']); } } // Sort the reulting list alphabetically modelList = modelList.sort(function(a, b) { return a[0] > b[1] ? 1 : -1; }); return modelList; } // search for models (and vendors) function searchModel(query) { var foundImageType = ''; var modelList = createSearchableModellist(); // TODO: maybe generate once + deepcopy // search for vendors and models var queryparts = query.split(' '); for(var i in queryparts) { var q = searchable(queryparts[i]); var atomicQ = q; if (q.length === 0 || q[q.length-1] !== INVISIBLE_SEPARATOR) { atomicQ += INVISIBLE_SEPARATOR; } var filteredModelList = []; for (var m in modelList) { if (modelList[m][MODEL_STRIPPED_SEARCHSTRING].indexOf(q) != -1) { modelList[m][MODEL_STRIPPED_SEARCHSTRING] = modelList[m][MODEL_STRIPPED_SEARCHSTRING].replace(q, ''); // add revision to modelList entry (if unique) var revisions = getRevisions(modelList[m][MODEL_VENDOR], modelList[m][MODEL_MODEL]); if (revisions.length == 1 && revisions[0] == 'alle') { modelList[m][MODEL_MATCHED_REVISION] = revisions[0]; } else { var r = revisions.map(atomic).map(searchable).indexOf(atomicQ); if (r != -1) { modelList[m][MODEL_MATCHED_REVISION] = revisions[r]; // add revision to modelList entry } } filteredModelList.push(modelList[m]); } else { var typeKeys = Object.keys(typeNames); var typeValues = ObjectValues(typeNames); for (var k in typeKeys) { if (searchable(typeKeys[k]).substr(0, q.length) == q || searchable(typeValues[k]).substr(0, q.length) == q) { foundImageType = Object.keys(typeNames)[k]; filteredModelList.push(modelList[m]); } } } } modelList = filteredModelList; } var searchResult = {}; searchResult.vendor = getVendorFromModelList(modelList); searchResult.model = ''; searchResult.revision = ''; searchResult.imageType = foundImageType; searchResult.imageTypes = []; searchResult.modelList = modelList; if (modelList.length == 1) { searchResult.model = modelList[0][MODEL_MODEL]; searchResult.revision = modelList[0][MODEL_MATCHED_REVISION]; searchResult.imageTypes = getImageTypes(modelList); } return searchResult; } // check if vendor is unique and return the vendor name function getVendorFromModelList(modelList) { var vendor = ''; for (var i in modelList) { if (vendor === '') vendor = modelList[i][MODEL_VENDOR]; if (vendor != modelList[i][MODEL_VENDOR]) return ''; } return vendor; } 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 highestRevision = 1; for (var r in images[vendor][model]) { var rev = images[vendor][model][r].revision.substr(1); if (parseInt(rev) > highestRevision){ latestRevisionIndex = r; highestRevision = parseInt(rev); } } var image = document.createElement('img'); image.src = PREVIEW_PICTURES_DIR+images[vendor][model][latestRevisionIndex].preview; 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.setSearchQuery(atomic(vendor)+' '+atomic(model)); setClass(e.currentTarget, 'selected', true); scrollDown(); } function setDefaultImg(e) { fallbackImg = PREVIEW_PICTURES_DIR+'no_picture_available.svg'; if (e.target.src.indexOf(fallbackImg) == -1) { e.target.src = fallbackImg; } } app.setDefaultImg = setDefaultImg; function updatePreviewList(modelList) { var previews = document.querySelectorAll('.imagePreview .preview'); for(var p = 0; p < previews.length; p++) { previews[p].style.display = 'none'; setClass(previews[p], 'selected', false); } for (var f in modelList) { var searchstring = modelList[f][MODEL_SEARCHSTRING]; var vendor = modelList[f][MODEL_VENDOR]; var model = modelList[f][MODEL_MODEL]; for(p = 0; p < previews.length; p++) { if (previews[p].getAttribute('data-model') == model) { previews[p].style.display = 'inline-block'; if (modelList.length == 1) { setClass(previews[p], 'selected', true); } } } } } function hasVendorDevicesForEnabledDeviceCategories(vendor) { if (images[vendor]) { for (let [key, value] of Object.entries(images[vendor])) { if (enabled_device_categories.includes(value[0].category)) { return true; } } } return false; } function getVendors() { var vendorlist = []; for (var device_category_idx in enabled_device_categories) { var device_category = enabled_device_categories[device_category_idx]; var category_vendors = Object.keys(config.vendormodels[device_category]); category_vendors.forEach(function (val, idx) { if (!vendorlist.includes(val) && hasVendorDevicesForEnabledDeviceCategories(val)) { vendorlist.push(val); } }); } return vendorlist; } // update all elements of the page according to the wizard object. function updateHTML(wizard) { // parse searchstring to retrieve current vendor and model var s = searchModel(wizard.q); if (wizard.showFirmwareTable) { show_block('#firmwareTable'); hide('#wizard'); } else { show_block('#wizard'); hide('#firmwareTable'); } // show vendor dropdown menu. function showVendors(currentVendor) { var select = $('#vendorselect'); select.innerHTML = ''; select.appendChild( createOption('', '-- Bitte Hersteller wählen --') ); var vendors = getVendors().sort(sortCaseInsensitive); for (var i in vendors) { select.appendChild( createOption(atomic(vendors[i]), vendors[i], atomic(currentVendor)) ); } } showVendors(s.vendor); // show model dropdown menu function showModels(currentVendor, currentModel) { var select = $('#modelselect'); setClass(select, 'invalid', currentModel === ''); select.innerHTML = ''; select.appendChild(createOption( atomic(currentVendor), '-- Bitte Modell wählen --', atomic(currentVendor)) ); if (currentVendor === '' || isEmptyObject(images)) { return; } var prefix = atomic(currentVendor) + ' '; var models = Object.keys(images[currentVendor]).sort(sortCaseInsensitive); for (var i in models) { select.appendChild(createOption( prefix + atomic(models[i]), models[i], prefix + atomic(currentModel))); } } showModels(s.vendor, s.model); // show revision dropdown menu function showRevisions(currentVendor, currentModel, currentRevision) { var select = $('#revisionselect'); setClass(select, 'invalid', currentRevision === ''); select.innerHTML = ''; select.appendChild(createOption( atomic(currentVendor) + ' ' + atomic(currentModel), '-- Bitte Hardwarerevision wählen --', atomic(currentVendor) + ' ' + atomic(currentModel)) ); if (currentVendor === '' || currentModel === '' || isEmptyObject(images)) { return; } var prefix = atomic(currentVendor) + ' ' + atomic(currentModel) + ' '; var revisions = getRevisions(currentVendor, currentModel); for (var i in revisions) { select.appendChild(createOption( prefix + atomic(revisions[i]), revisions[i], prefix + atomic(currentRevision)) ); } } showRevisions(s.vendor, s.model, s.revision); updatePreviewList(s.modelList); // show image type selection function showImageTypes(currentVendor, currentModel, currentRevision, currentImageTypes, currentImageType) { if (currentModel === '' || isEmptyObject(images)) { return; } function imageTypeChangedEventHandler(e) { setSearchQuery(e.target.getAttribute('data-query')); scrollDown(); } // find device info link function findDeviceInfo(vendor, model, revision, links) { if (links[vendor] !== undefined && links[vendor][model] !== undefined) { revisions = links[vendor][model]; } else { return ''; } if (typeof revisions == 'object' && revisions[revision] !== undefined) { return revisions[revision]; } else if (typeof revisions == 'string') { return revisions; } else { return ''; } } var url = ''; var custom_url = ''; var deviceinfo = $('#deviceinfo'); deviceinfo.innerHTML = ''; url = findDeviceInfo(currentVendor, currentModel, currentRevision, devices_info); if ("devices_info" in config){ custom_url = findDeviceInfo(currentVendor, currentModel, currentRevision, config.devices_info); } if (custom_url !== '') { url = custom_url; } if (url !== '') { setClass($('#type-pane'), 'show-deviceinfo-warning', true); var a = document.createElement('a'); a.href = url; a.className = 'btn'; a.target = '_blank'; a.innerText = 'Anleitung'; deviceinfo.appendChild(a); } else { setClass($('#type-pane'), 'show-deviceinfo-warning', false); } var typeselect = $('#typeselect'); typeselect.innerHTML = ''; var prefix = atomic(currentVendor) + ' ' + atomic(currentModel) + ' ' + atomic(currentRevision) + ' '; for (var i in currentImageTypes) { var type = currentImageTypes[i]; if (type === '') continue; var displayType = typeNames[type] || type; var query = prefix + displayType; var input = document.createElement('input'); input.id = 'radiogroup-typeselect-' + type; input.type = 'radio'; input.name = 'firmwareType'; input.checked = (type == currentImageType); input.setAttribute('data-query', query); input.addEventListener('click', imageTypeChangedEventHandler); var label = document.createElement('label'); label.for = 'radiogroup-typeselect-' + type; label.innerText = displayType; label.setAttribute('data-query', query); label.addEventListener('click', imageTypeChangedEventHandler); typeselect.appendChild(input); typeselect.appendChild(label); } } showImageTypes(s.vendor, s.model, s.revision, s.imageTypes, s.imageType); // show branch selection function showBranches(currentVendor, currentModel, currentRevision, currentImageType) { if (currentVendor === '' || currentModel === '' || currentRevision === '' || currentImageType === '' || isEmptyObject(images)) { return; } var revisions = images[currentVendor][currentModel].filter(function(e) { return e.revision == currentRevision && e.type == currentImageType; }).sort(function(a, b) { // non-experimental branches should appear first var a_experimental = config.experimental_branches.indexOf(a.branch) != -1; var b_experimental = config.experimental_branches.indexOf(b.branch) != -1; if (a_experimental && !b_experimental) return 1; if (!a_experimental && b_experimental) return -1; return branches.indexOf(a.branch) > branches.indexOf(b.branch); }); $('#branchdescs').innerHTML = ''; $('#branchselect').innerHTML = ''; $('#branch-experimental-dl').innerHTML = ''; var toggleExperimentalWarning = function() { toggleClass($('#branch-pane'), 'show-experimental-warning'); scrollDown(); }; for (var i in revisions) { var rev = revisions[i]; var a = document.createElement('a'); a.href = rev.location; a.className = 'btn'; a.innerText = rev.branch + (rev.size!==''?' ['+rev.size+']':'') + ' (' +prettyPrintVersion(rev.version)+')'; if (rev.branch in config.branch_descriptions) { var li = document.createElement('li'); var name = document.createElement('span'); name.innerText = rev.branch; name.id = 'branchName'; var desc = document.createElement('span'); desc.id = 'branchDesc' desc.innerText = ' ' + config.branch_descriptions[rev.branch]; li.appendChild(name); if (rev.branch == config.recommended_branch) { var recommended = document.createElement('sup'); recommended.innerText = ' Empfehlung'; name.appendChild(recommended); } br = document.createElement('br'); li.appendChild(br); li.appendChild(desc); $('#branchdescs').appendChild(li); } if (config.experimental_branches.indexOf(rev.branch) != -1) { if($('#branchselect .dl-experimental') === null) { var button = document.createElement('button'); button.className = 'btn dl-experimental'; button.addEventListener('click', toggleExperimentalWarning); button.innerText = 'Experimentelle Firmware anzeigen'; $('#branchselect').appendChild(button); } $('#branch-experimental-dl').appendChild(a); } else { $('#branchselect').appendChild(a); } } } showBranches(s.vendor, s.model, s.revision, s.imageType); // update hardware dropdown menu if (s.vendor === '') { hide('#modelselect'); hide('#revisionselect'); } else { show_inline('#modelselect'); if (s.model === '') { hide('#revisionselect'); } else { show_inline('#revisionselect'); } } function updatePanes(currentVendor, currentModel, currentRevision, currentImageType) { var pane = PANE.MODEL; if (currentVendor !== '' && currentModel !== '' && currentRevision !== '') { pane = PANE.IMAGETYPE; if (currentImageType !== '') { 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(s.vendor, s.model, s.revision, s.imageType); function updateCurrentVersions() { $('#currentVersions').innerText = ''; if (config.changelog !== undefined) { var a = document.createElement('a'); a.href = config.changelog; a.innerText = 'CHANGELOG'; $('#currentVersions').appendChild(a); var text = document.createTextNode(' // '); $('#currentVersions').appendChild(text); } var versionStr = branches.reduce(function(ret, branch, i) { ret += ((i === 0) ? '' : ' // ') + branch; ret += (branch in app.currentVersions) ? (': ' + prettyPrintVersion(app.currentVersions[branch])) : ''; return ret; }, ''); $('#currentVersions').appendChild(document.createTextNode(versionStr)); } updateCurrentVersions(); } function updateFirmwareTable() { $('#firmwareTableBody').innerHTML = ''; var initializeRevHTML = function(rev) { if (bootloaderRevBranchDict[rev.branch] === undefined) { bootloaderRevBranchDict[rev.branch] = document.createElement('span'); } if (upgradeRevBranchDict[rev.branch] === undefined) { upgradeRevBranchDict[rev.branch] = document.createElement('span'); } if (factoryRevBranchDict[rev.branch] === undefined) { factoryRevBranchDict[rev.branch] = document.createElement('span'); } }; var addToRevHTML = function(rev) { var a = document.createElement('a'); a.href = rev.location; a.title = prettyPrintVersion(rev.version); a.innerText = rev.revision; var textNodeStart = document.createTextNode('['); var textNodeEnd = document.createTextNode('] '); if (rev.type == 'sysupgrade') { upgradeRevBranchDict[rev.branch].appendChild(textNodeStart); upgradeRevBranchDict[rev.branch].appendChild(a); upgradeRevBranchDict[rev.branch].appendChild(textNodeEnd); show = true; } else if (rev.type == 'factory') { factoryRevBranchDict[rev.branch].appendChild(textNodeStart); factoryRevBranchDict[rev.branch].appendChild(a); factoryRevBranchDict[rev.branch].appendChild(textNodeEnd); show = true; } else if (rev.type == 'bootloader') { bootloaderRevBranchDict[rev.branch].appendChild(textNodeStart); bootloaderRevBranchDict[rev.branch].appendChild(a); bootloaderRevBranchDict[rev.branch].appendChild(textNodeEnd); show = true; } }; function createRevTd(revBranchDict) { var td = document.createElement('td'); for(var branch in revBranchDict) { var textNode; if (revBranchDict[branch].children.length === 0) { textNode = document.createTextNode(branch + ': -'); td.appendChild(textNode); } else { textNode = document.createTextNode(branch + ': '); td.appendChild(textNode); td.appendChild(revBranchDict[branch]); } var br = document.createElement("br"); td.appendChild(br); } return td; } var vendors = Object.keys(images).sort(sortCaseInsensitive); for (var v in vendors) { var vendor = vendors[v]; var models = Object.keys(images[vendor]).sort(sortCaseInsensitive); for (var m in models) { var model = models[m]; var revisions = sortByRevision(images[vendor][model]); var upgradeRevBranchDict = {}; var factoryRevBranchDict = {}; var bootloaderRevBranchDict = {}; 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.appendChild(tdVendor); var tdModel = document.createElement('td'); tdModel.innerText = model; tr.appendChild(tdModel); tr.appendChild(createRevTd(bootloaderRevBranchDict)); tr.appendChild(createRevTd(factoryRevBranchDict)); tr.appendChild(createRevTd(upgradeRevBranchDict)); $('#firmwareTableBody').appendChild(tr); } } } function loadSite(url, callback) { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { callback(xmlhttp.responseText, url); } else if (xmlhttp.readyState == 4) { console.log("Could not load " + url); callback(null, url); } }; xmlhttp.open('GET', url, true); xmlhttp.send(); } // parse the contents of the given directories function loadDirectories(callback) { var parseSite = function(data, indexPath) { var basePath = indexPath.substring(0, indexPath.lastIndexOf('/') + 1); var branch = config.directories[indexPath]; reLink.lastIndex = 0; var hrefMatch; do { hrefMatch = reLink.exec(data); if (hrefMatch) { var href = decodeURIComponent(hrefMatch[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 (hrefMatch); // check if we loaded all directories directoryLoadCount++; if (directoryLoadCount == Object.keys(config.directories).length) { callback(); } }; // match all links var reLink = new RegExp('href="([^"]*)"', 'g'); // 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; }); // prepare the matches for use in regex (join by pipe and escape regex special characters) // according to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions var matchString = matches.map(x => x.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'); // match image files. The match either ends with // - a dash or dot (if a file extension will follow) // - the end of the expression (if the file extension is part of the regex) var reMatch = new RegExp('('+matchString+')([.-]|$)'); var directoryLoadCount = 0; for (var indexPath in config.directories) { // retrieve the contents of the directory loadSite(indexPath, parseSite); } } return app; }();