-function loadFile(url, callback) {
- var xmlhttp = new XMLHttpRequest();
- xmlhttp.onreadystatechange = function() {
- if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
- callback(xmlhttp.responseText, url);
+var current_model = {};
+
+function $(id) {
+ return document.getElementById(id);
+}
+
+function show(id) {
+ $(id).style.display = 'block';
+}
+
+function hide(id) {
+ $(id).style.display = 'none';
+}
+
+function split(str) {
+ return str.match(/[^\s,]+/g) || [];
+}
+
+function get_model_titles(titles) {
+ return titles.map(e => {
+ if (e.title) {
+ return e.title;
+ } else {
+ return ((e.vendor || '') + ' ' + (e.model || '') + ' ' + (e.variant || '')).trim();
}
- };
- xmlhttp.open('GET', url, true);
- xmlhttp.send();
+ }).join(' / ');
+}
+
+function build_asa_request() {
+ if (!current_model || !current_model.id) {
+ alert('bad profile');
+ return;
+ }
+
+ function showStatus(message, url) {
+ show('buildstatus');
+ var tr = message.startsWith('tr-') ? message : '';
+ if (url) {
+ $('buildstatus').innerHTML = '<a href="' + url + '" class="' + tr + '">' + message + '</a>';
+ } else {
+ $('buildstatus').innerHTML = '<span class="' + tr + '"></span>';
+ }
+ translate();
+ }
+
+ // hide image view
+ updateImages();
+
+ show('buildspinner');
+ showStatus('tr-request-image');
+
+ var request_data = {
+ 'target': current_model.target,
+ 'profile': current_model.id,
+ 'packages': split($('packages').value),
+ 'version': $('versions').value
+ }
+
+ fetch(config.asu_url + '/api/build', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(request_data)
+ })
+ .then(response => {
+ switch (response.status) {
+ case 200:
+ hide('buildspinner');
+ showStatus('tr-build-successful');
+
+ response.json()
+ .then(mobj => {
+ var download_url = config.asu_url + '/store/' + mobj.bin_dir;
+ showStatus('tr-build-successful', download_url + '/buildlog.txt');
+ updateImages(
+ mobj.version_number,
+ mobj.version_code,
+ mobj.build_at,
+ get_model_titles(mobj.titles),
+ download_url, mobj, true
+ );
+ });
+ break;
+ case 202:
+ showStatus('tr-check-again');
+ setTimeout(_ => { build_asa_request() }, 5000);
+ break;
+ case 400: // bad request
+ case 422: // bad package
+ case 500: // build failed
+ hide('buildspinner');
+ response.json()
+ .then(mobj => {
+ var message = mobj['message'] || 'tr-build-failed';
+ var url = mobj.buildlog ? (config.asu_url + '/store/' + mobj.bin_dir + '/buildlog.txt') : undefined;
+ showStatus(message, url);
+ })
+ break;
+ }
+ })
+ .catch(err => {
+ hide('buildspinner');
+ showStatus(err);
+ })
}
function setupSelectList(select, items, onselection) {
for (var i = 0; i < items.length; i += 1) {
- var option = document.createElement("OPTION");
+ var option = document.createElement('OPTION');
option.innerHTML = items[i];
select.appendChild(option);
}
- select.addEventListener("change", function(e) {
+ select.addEventListener('change', e => {
onselection(items[select.selectedIndex]);
});
}
// Change the translation of the entire document
-function changeLanguage(language) {
- var mapping = translations[language];
- if (mapping) {
- for (var tr in mapping) {
- Array.from(document.getElementsByClassName(tr))
- .forEach(function(e) { e.innerText = mapping[tr]; })
- }
+function translate() {
+ var mapping = translations[config.language];
+ for (var tr in mapping) {
+ Array.from(document.getElementsByClassName(tr))
+ .forEach(e => { e.innerText = mapping[tr]; })
}
}
-function setupAutocompleteList(input, items, onselection) {
- // the setupAutocompleteList function takes two arguments,
- // the text field element and an array of possible autocompleted values:
+function setupAutocompleteList(input, items, as_list, onbegin, onend) {
var currentFocus = -1;
// sort numbers and other characters separately
items.sort(collator.compare);
- // execute a function when someone writes in the text field:
input.oninput = function(e) {
- // clear images
- updateImages();
+ onbegin();
+ var offset = 0;
var value = this.value;
+ var value_list = [];
+
+ if (as_list) {
+ // automcomplete last text item
+ offset = this.value.lastIndexOf(' ') + 1;
+ value = this.value.substr(offset);
+ value_list = split(this.value.substr(0, offset));
+ }
+
// close any already open lists of autocompleted values
closeAllLists();
}
// create a DIV element that will contain the items (values):
- var list = document.createElement("DIV");
- list.setAttribute("id", this.id + "-autocomplete-list");
- list.setAttribute("class", "autocomplete-items");
+ var list = document.createElement('DIV');
+ list.setAttribute('id', this.id + '-autocomplete-list');
+ list.setAttribute('class', 'autocomplete-items');
// append the DIV element as a child of the autocomplete container:
this.parentNode.appendChild(list);
- // for each item in the array...
var c = 0;
for (var i = 0; i < items.length; i += 1) {
var item = items[i];
continue;
}
+ // do not offer a duplicate item
+ if (as_list && value_list.indexOf(item) != -1) {
+ continue;
+ }
+
c += 1;
if (c >= 15) {
- var div = document.createElement("DIV");
- div.innerHTML = "...";
+ var div = document.createElement('DIV');
+ div.innerHTML = '...';
list.appendChild(div);
break;
} else {
- var div = document.createElement("DIV");
+ var div = document.createElement('DIV');
// make the matching letters bold:
div.innerHTML = item.substr(0, j)
- + "<strong>" + item.substr(j, value.length) + "</strong>"
+ + '<strong>' + item.substr(j, value.length) + '</strong>'
+ item.substr(j + value.length)
- + "<input type='hidden' value='" + item + "'>";
-
- div.addEventListener("click", function(e) {
- // set text field to selected value
- input.value = this.getElementsByTagName("input")[0].value;
+ + '<input type="hidden" value="' + item + '">';
+
+ div.addEventListener('click', function(e) {
+ // include selected value
+ var selected = this.getElementsByTagName('input')[0].value;
+ if (as_list) {
+ input.value = value_list.join(' ') + ' ' + selected;
+ } else {
+ input.value = selected;
+ }
// close the list of autocompleted values,
- // (or any other open lists of autocompleted values:
closeAllLists();
- // callback
- onselection(input.value);
+ onend(input);
});
list.appendChild(div);
};
input.onkeydown = function(e) {
- var x = document.getElementById(this.id + "-autocomplete-list");
- if (x) x = x.getElementsByTagName("div");
+ var x = document.getElementById(this.id + '-autocomplete-list');
+ if (x) x = x.getElementsByTagName('div');
if (e.keyCode == 40) {
// key down
currentFocus += 1;
// If the ENTER key is pressed, prevent the form from being submitted,
e.preventDefault();
if (currentFocus > -1) {
- // and simulate a click on the "active" item:
+ // and simulate a click on the 'active' item:
if (x) x[currentFocus].click();
}
}
};
input.onfocus = function() {
- onselection(input.value);
+ onend(input);
+ }
+
+ // focus lost
+ input.onblur = function() {
+ onend(input);
}
function setActive(x) {
- // a function to classify an item as "active":
+ // a function to classify an item as 'active':
if (!x) return false;
- // start by removing the "active" class on all items:
+ // start by removing the 'active' class on all items:
for (var i = 0; i < x.length; i++) {
- x[i].classList.remove("autocomplete-active");
+ x[i].classList.remove('autocomplete-active');
}
if (currentFocus >= x.length) currentFocus = 0;
if (currentFocus < 0) currentFocus = (x.length - 1);
- // add class "autocomplete-active":
- x[currentFocus].classList.add("autocomplete-active");
+ // add class 'autocomplete-active':
+ x[currentFocus].classList.add('autocomplete-active');
}
function closeAllLists(elmnt) {
// close all autocomplete lists in the document,
// except the one passed as an argument:
- var x = document.getElementsByClassName("autocomplete-items");
+ var x = document.getElementsByClassName('autocomplete-items');
for (var i = 0; i < x.length; i++) {
if (elmnt != x[i] && elmnt != input) {
x[i].parentNode.removeChild(x[i]);
}
// execute a function when someone clicks in the document:
- document.addEventListener("click", function (e) {
+ document.addEventListener('click', e => {
closeAllLists(e.target);
});
}
-function $(id) {
- return document.getElementById(id);
-}
-
-function findCommonPrefix(files){
- var A = files.concat().sort();
- var first = A[0];
- var last = A[A.length - 1];
- var L = first.length;
- var i = 0;
- while (i < L && first.charAt(i) === last.charAt(i)) {
- i += 1;
- }
- return first.substring(0, i);
+// for attended sysupgrade
+function updatePackageList(version, target) {
+ // set available packages
+ fetch(config.asu_url + '/' + config.versions[version] + '/' + target + '/index.json')
+ .then(response => response.json())
+ .then(all_packages => {
+ setupAutocompleteList($('packages'), all_packages, true, _ => {}, textarea => {
+ textarea.value = split(textarea.value)
+ // make list unique, ignore minus
+ .filter((value, index, self) => {
+ var i = self.indexOf(value.replace(/^\-/, ''));
+ return (i === index) || (i < 0);
+ })
+ // limit to available packages, ignore minus
+ .filter((value, index) => all_packages.indexOf(value.replace(/^\-/, '')) !== -1)
+ .join(' ');
+ });
+ });
}
-function updateImages(release, commit, model, image_link, mobj) {
+function updateImages(version, code, date, model, url, mobj, is_custom) {
// add download button for image
- function addLink(label, tags, file, help_id) {
+ function addLink(type, file) {
var a = document.createElement('A');
a.classList.add('download-link');
- a.href = image_link
- .replace('%target', mobj[0])
- .replace('%release', release)
- .replace('%file', file);
+ a.href = url
+ .replace('{target}', mobj.target)
+ .replace('{version}', version)
+ + '/' + file;
var span = document.createElement('SPAN');
span.appendChild(document.createTextNode(''));
a.appendChild(span);
-
- // add sub label
- if (tags.length > 0) {
- a.appendChild(document.createTextNode(label + ' (' + tags.join(', ') + ')'));
- } else {
- a.appendChild(document.createTextNode(label));
- }
+ a.appendChild(document.createTextNode(type.toUpperCase()));
if (config.showHelp) {
a.onmouseover = function() {
// hide all help texts
Array.from(document.getElementsByClassName('download-help'))
- .forEach(function(e) { e.style.display = 'none'; });
- $(help_id).style.display = 'block';
+ .forEach(e => e.style.display = 'none');
+ var lc = type.toLowerCase();
+ if (lc.includes('sysupgrade')) {
+ show('sysupgrade-help');
+ } else if (lc.includes('factory') || lc == 'trx' || lc == 'chk') {
+ show('factory-help');
+ } else if (lc.includes('kernel') || lc.includes('zimage') || lc.includes('uimage')) {
+ show('kernel-help');
+ } else if (lc.includes('root')) {
+ show('rootfs-help');
+ } else if (lc.includes('sdcard')) {
+ show('sdcard-help');
+ } else if (lc.includes('tftp')) {
+ show('tftp-help');
+ } else {
+ show('other-help');
+ }
};
}
$('download-links').appendChild(a);
}
+ function switchClass(id, from_class, to_class) {
+ $(id).classList.remove(from_class);
+ $(id).classList.add(to_class);
+ }
+
// remove all download links
Array.from(document.getElementsByClassName('download-link'))
- .forEach(function(e) { e.remove(); });
+ .forEach(e => e.remove());
// hide all help texts
Array.from(document.getElementsByClassName('download-help'))
- .forEach(function(e) { e.style.display = 'none'; });
+ .forEach(e => e.style.display = 'none');
+
+ if (version && code && date && model && url && mobj) {
+ var target = mobj.target;
+ var images = mobj.images;
- if (release && commit && model && image_link && mobj) {
- var target = mobj[0];
- var files = mobj[1];
+ // change between "version" and "custom" title
+ if (is_custom) {
+ switchClass('images-title', 'tr-version-build', 'tr-custom-build');
+ switchClass('downloads-title', 'tr-version-downloads', 'tr-custom-downloads');
+ } else {
+ switchClass('images-title', 'tr-custom-build', 'tr-version-build');
+ switchClass('downloads-title', 'tr-custom-downloads', 'tr-version-downloads');
+ }
+ // update title translation
+ translate();
// fill out build info
$('image-model').innerText = model;
$('image-target').innerText = target;
- $('image-release').innerText = release;
- $('image-commit').innerText = commit;
-
- var prefix = findCommonPrefix(files);
- var entries = {
- 'FACTORY': [],
- 'SYSUPGRADE': [],
- 'KERNEL': [],
- 'ROOTFS': [],
- 'SDCARD': [],
- 'TFTP': [],
- 'OTHER': []
- };
-
- files.sort();
-
- for (var i in files) {
- var file = files[i];
- if (file.includes('-factory')) {
- entries['FACTORY'].push(file);
- } else if (file.includes('-sysupgrade')) {
- entries['SYSUPGRADE'].push(file);
- } else if (file.includes('-kernel') || file.includes('-zImage') || file.includes('-uImage')) {
- entries['KERNEL'].push(file);
- } else if (file.includes('-rootfs')) {
- entries['ROOTFS'].push(file);
- } else if (file.includes('-sdcard')) {
- entries['SDCARD'].push(file);
- } else if (file.includes('-tftp')) {
- entries['TFTP'].push(file);
- } else {
- entries['OTHER'].push(file);
- }
- }
+ $('image-version').innerText = version;
+ $('image-code').innerText = code;
+ $('image-date').innerText = date;
- function extractTags(prefix, file) {
- var all = file.substring(prefix.length).split('.')[0].split('-');
- var ignore = ['', 'kernel', 'zImage', 'uImage', 'factory', 'sysupgrade', 'rootfs', 'sdcard'];
- return all.filter(function (el) { return !ignore.includes(el); });
+ images.sort((a, b) => a.name.localeCompare(b.name));
+
+ for (var i in images) {
+ addLink(images[i].type, images[i].name);
}
- for (var category in entries) {
- var files = entries[category];
- for (var i in files) {
- var file = files[i];
- var tags = (files.length > 1) ? extractTags(prefix, file) : [];
- addLink(category, tags, file, category.toLowerCase() + '-help');
- }
+ if (config.asu_url) {
+ updatePackageList(version, target);
}
- $('images').style.display = 'block';
+ show('images');
} else {
- $('images').style.display = 'none';
+ hide('images');
}
}
-// hide fields
-updateImages();
-changeLanguage(config.language);
-
-loadFile(config.data, function(data) {
- var obj = JSON.parse(data);
- setupSelectList($("releases"), Object.keys(obj), function(release) {
- setupAutocompleteList($("models"), Object.keys(obj[release]['models']), function(model) {
- if (model in obj[release]['models']) {
- var link = obj[release].link;
- var commit = obj[release].commit;
- var mobj = obj[release]['models'][model];
- updateImages(release, commit, model, link, mobj);
+function init() {
+ var build_date = "unknown"
+ setupSelectList($('versions'), Object.keys(config.versions), version => {
+ var url = config.versions[version];
+ if (config.asu_url) {
+ url = config.asu_url + '/' + url + '/profiles.json';
+ }
+ fetch(url)
+ .then(obj => {
+ build_date = obj.headers.get('last-modified');
+ return obj.json();
+ })
+ .then(obj => {
+ // handle native openwrt json format
+ if ('profiles' in obj) {
+ obj['models'] = {}
+ for (const [key, value] of Object.entries(obj['profiles'])) {
+ obj['models'][get_model_titles(value.titles)] = value
+ obj['models'][get_model_titles(value.titles)]['id'] = key
+ }
+ }
+ return obj
+ })
+ .then(obj => {
+ setupAutocompleteList($('models'), Object.keys(obj['models']), false, updateImages, models => {
+ var model = models.value;
+ if (model in obj['models']) {
+ var url = obj.url || 'unknown';
+ var code = obj.version_code || 'unknown';
+ var mobj = obj['models'][model];
+ updateImages(version, code, build_date, model, url, mobj, false);
+ current_model = mobj;
} else {
updateImages();
+ current_model = {};
}
});
- // trigger model update when selected release changes
- $("models").onfocus();
+ // trigger model update when selected version changes
+ $('models').onfocus();
});
-})
+ });
+
+ if (config.asu_url) {
+ show('custom');
+ }
+
+ // hide fields
+ updateImages();
+
+ var user_lang = (navigator.language || navigator.userLanguage).split('-')[0];
+ if (user_lang in translations) {
+ config.language = user_lang;
+ $('language-selection').value = user_lang;
+ }
+
+ translate();
+
+ $('language-selection').onclick = function() {
+ config.language = this.children[this.selectedIndex].value;
+ translate();
+ }
+}