var current_model = {}; function $(query) { if (typeof query === 'string') { return document.querySelector(query); } else { return query; } } function show(query) { $(query).style.display = 'block'; } function hide(query) { $(query).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(); } }).join(' / '); } function build_asu_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 = '' + message + ''; } else { $('#buildstatus').innerHTML = ''; } 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_asu_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'); option.innerHTML = items[i]; select.appendChild(option); } select.addEventListener('change', e => { onselection(items[select.selectedIndex]); }); if (select.selectedIndex >= 0) { onselection(items[select.selectedIndex]); } } // Change the translation of the entire document 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, as_list, onbegin, onend) { var currentFocus = -1; // sort numbers and other characters separately var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}); items.sort(collator.compare); input.oninput = function(e) { 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(); if (!value) { return false; } // 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'); // append the DIV element as a child of the autocomplete container: this.parentNode.appendChild(list); function normalize(s) { return s.toUpperCase().replace(/[-_.]/g, ' '); } var match = normalize(value); var c = 0; for (var i = 0; i < items.length; i += 1) { var item = items[i]; // match var j = normalize(item).indexOf(match); if (j < 0) { 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 = '...'; list.appendChild(div); break; } else { var div = document.createElement('DIV'); // make the matching letters bold: div.innerHTML = item.substr(0, j) + '' + item.substr(j, value.length) + '' + item.substr(j + value.length) + ''; 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, closeAllLists(); onend(input); }); list.appendChild(div); } } }; input.onkeydown = function(e) { var x = document.getElementById(this.id + '-autocomplete-list'); if (x) x = x.getElementsByTagName('div'); if (e.keyCode == 40) { // key down currentFocus += 1; // and and make the current item more visible: setActive(x); } else if (e.keyCode == 38) { // key up currentFocus -= 1; // and and make the current item more visible: setActive(x); } else if (e.keyCode == 13) { // 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: if (x) x[currentFocus].click(); } } }; input.onfocus = function() { onend(input); } // focus lost input.onblur = function() { onend(input); } function setActive(x) { // a function to classify an item as 'active': if (!x) return false; // start by removing the 'active' class on all items: for (var i = 0; i < x.length; i++) { 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'); } function closeAllLists(elmnt) { // close all autocomplete lists in the document, // except the one passed as an argument: 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', e => { closeAllLists(e.target); }); } // 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(version, code, date, model, url, mobj, is_custom) { // add download button for image function addLink(type, file) { var a = document.createElement('A'); a.classList.add('download-link'); a.href = url .replace('{target}', mobj.target) .replace('{version}', version) + '/' + file; var span = document.createElement('SPAN'); span.appendChild(document.createTextNode('')); a.appendChild(span); a.appendChild(document.createTextNode(type.toUpperCase())); if (config.showHelp) { a.onmouseover = function() { // hide all help texts Array.from(document.getElementsByClassName('download-help')) .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(query, from_class, to_class) { $(query).classList.remove(from_class); $(query).classList.add(to_class); } // remove all download links Array.from(document.getElementsByClassName('download-link')) .forEach(e => e.remove()); // hide all help texts Array.from(document.getElementsByClassName('download-help')) .forEach(e => e.style.display = 'none'); if (model && url && mobj) { var target = mobj.target; var images = mobj.images; // change between "version" and "custom" title if (is_custom) { switchClass('#build-title', 'tr-version-build', 'tr-custom-build'); switchClass('#downloads-title', 'tr-version-downloads', 'tr-custom-downloads'); } else { switchClass('#build-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-version').innerText = version; $('#image-code').innerText = code; $('#image-date').innerText = date; images.sort((a, b) => a.name.localeCompare(b.name)); for (var i in images) { addLink(images[i].type, images[i].name); } if (config.asu_url) { updatePackageList(version, target); } show('#images'); } else { hide('#images'); } } 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.download_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 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(); } }