Select Git revision
app.js 39.96 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/>.
*/
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",
'bootloader': 'Bootloader-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,'":"') + '"}',
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|bootloader)[-.]/.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');
// 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+".jpg",
'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.jpg';
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 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)) {
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;
}();