2 var current_model
= {};
5 return document
.getElementById(id
);
9 $(id
).style
.display
= 'block';
13 $(id
).style
.display
= 'none';
17 return str
.match(/[^\s,]+/g) || [];
20 function get_model_titles(titles
) {
21 return titles
.map(e
=> {
25 return ((e
.vendor
|| '') + ' ' + (e
.model
|| '') + ' ' + (e
.variant
|| '')).trim();
30 function get_version_url(version
) {
31 if (version
== 'SNAPSHOT') {
32 return config
.base_url
+ '/snapshots/targets/{target}'
34 return config
.base_url
+ '/releases/{version}/targets/{target}'.replace('{version}', version
)
38 function build_asa_request() {
39 if (!current_model
|| !current_model
.id
) {
44 function showStatus(text
) {
46 $('buildstatus').innerHTML
= text
;
50 function handleError(response
) {
55 var message
= mobj
['message'] || '<span class="tr-build-failed"></span>';
56 if (mobj
.buildlog
== true) {
57 var url
= config
.asu_url
+ '/store/' + mobj
.bin_dir
+ '/buildlog.txt';
58 showStatus('<a href="' + url
+ '">' + message
+ '</a>');
69 showStatus('<span class="tr-request-image"></span>');
72 'target': current_model
.target
,
73 'profile': current_model
.id
,
74 'packages': split($('packages').value
),
75 'version': $('versions').value
78 fetch(config
.asu_url
+ '/api/build', {
80 headers
: { 'Content-Type': 'application/json' },
81 body
: JSON
.stringify(request_data
)
84 switch (response
.status
) {
87 showStatus('<span class="tr-build-successful"></span>');
91 var download_url
= config
.asu_url
+ '/store/' + mobj
.bin_dir
;
96 get_model_titles(mobj
.titles
),
97 download_url
, mobj
, true
102 showStatus('<span class="tr-check-again"></span>');
103 setTimeout(_
=> { build_asa_request() }, 5000);
105 case 400: // bad request
106 case 422: // bad package
107 case 500: // build failed
108 handleError(response
);
113 hide('buildspinner');
118 function setupSelectList(select
, items
, onselection
) {
119 for (var i
= 0; i
< items
.length
; i
+= 1) {
120 var option
= document
.createElement('OPTION');
121 option
.innerHTML
= items
[i
];
122 select
.appendChild(option
);
125 select
.addEventListener('change', e
=> {
126 onselection(items
[select
.selectedIndex
]);
129 if (select
.selectedIndex
>= 0) {
130 onselection(items
[select
.selectedIndex
]);
134 // Change the translation of the entire document
135 function translate() {
136 var mapping
= translations
[config
.language
];
137 for (var tr
in mapping
) {
138 Array
.from(document
.getElementsByClassName(tr
))
139 .forEach(e
=> { e
.innerText
= mapping
[tr
]; })
143 function setupAutocompleteList(input
, items
, as_list
, onbegin
, onend
) {
144 var currentFocus
= -1;
146 // sort numbers and other characters separately
147 var collator
= new Intl
.Collator(undefined, {numeric
: true, sensitivity
: 'base'});
149 items
.sort(collator
.compare
);
151 input
.oninput = function(e
) {
155 var value
= this.value
;
159 // automcomplete last text item
160 offset
= this.value
.lastIndexOf(' ') + 1;
161 value
= this.value
.substr(offset
);
162 value_list
= split(this.value
.substr(0, offset
));
165 // close any already open lists of autocompleted values
172 // create a DIV element that will contain the items (values):
173 var list
= document
.createElement('DIV');
174 list
.setAttribute('id', this.id
+ '-autocomplete-list');
175 list
.setAttribute('class', 'autocomplete-items');
176 // append the DIV element as a child of the autocomplete container:
177 this.parentNode
.appendChild(list
);
180 for (var i
= 0; i
< items
.length
; i
+= 1) {
184 var j
= item
.toUpperCase().indexOf(value
.toUpperCase());
189 // do not offer a duplicate item
190 if (as_list
&& value_list
.indexOf(item
) != -1) {
196 var div
= document
.createElement('DIV');
197 div
.innerHTML
= '...';
198 list
.appendChild(div
);
201 var div
= document
.createElement('DIV');
202 // make the matching letters bold:
203 div
.innerHTML
= item
.substr(0, j
)
204 + '<strong>' + item
.substr(j
, value
.length
) + '</strong>'
205 + item
.substr(j
+ value
.length
)
206 + '<input type="hidden" value="' + item
+ '">';
208 div
.addEventListener('click', function(e
) {
209 // include selected value
210 var selected
= this.getElementsByTagName('input')[0].value
;
212 input
.value
= value_list
.join(' ') + ' ' + selected
;
214 input
.value
= selected
;
216 // close the list of autocompleted values,
221 list
.appendChild(div
);
226 input
.onkeydown = function(e
) {
227 var x
= document
.getElementById(this.id
+ '-autocomplete-list');
228 if (x
) x
= x
.getElementsByTagName('div');
229 if (e
.keyCode
== 40) {
232 // and and make the current item more visible:
234 } else if (e
.keyCode
== 38) {
237 // and and make the current item more visible:
239 } else if (e
.keyCode
== 13) {
240 // If the ENTER key is pressed, prevent the form from being submitted,
242 if (currentFocus
> -1) {
243 // and simulate a click on the 'active' item:
244 if (x
) x
[currentFocus
].click();
249 input
.onfocus = function() {
254 input
.onblur = function() {
258 function setActive(x
) {
259 // a function to classify an item as 'active':
260 if (!x
) return false;
261 // start by removing the 'active' class on all items:
262 for (var i
= 0; i
< x
.length
; i
++) {
263 x
[i
].classList
.remove('autocomplete-active');
265 if (currentFocus
>= x
.length
) currentFocus
= 0;
266 if (currentFocus
< 0) currentFocus
= (x
.length
- 1);
267 // add class 'autocomplete-active':
268 x
[currentFocus
].classList
.add('autocomplete-active');
271 function closeAllLists(elmnt
) {
272 // close all autocomplete lists in the document,
273 // except the one passed as an argument:
274 var x
= document
.getElementsByClassName('autocomplete-items');
275 for (var i
= 0; i
< x
.length
; i
++) {
276 if (elmnt
!= x
[i
] && elmnt
!= input
) {
277 x
[i
].parentNode
.removeChild(x
[i
]);
282 // execute a function when someone clicks in the document:
283 document
.addEventListener('click', e
=> {
284 closeAllLists(e
.target
);
288 // for attended sysupgrade
289 function updatePackageList(target
) {
290 // set available packages
291 fetch(config
.asu_url
+ '/' + target
+ '/packages.json')
292 .then(response
=> response
.json())
293 .then(all_packages
=> {
294 setupAutocompleteList($('packages'), all_packages
, true, _
=> {}, textarea
=> {
295 textarea
.value
= split(textarea
.value
)
296 .filter((value
, index
, self
) => self
.indexOf(value
) === index
) // make list unique
297 //.filter((value, index) => all_packages.indexOf(value) !== -1) // limit to available packages
303 function updateImages(version
, code
, date
, model
, url
, mobj
, is_custom
) {
304 // add download button for image
305 function addLink(type
, file
) {
306 var a
= document
.createElement('A');
307 a
.classList
.add('download-link');
309 .replace('{target}', mobj
.target
)
310 .replace('{version}', version
)
312 var span
= document
.createElement('SPAN');
313 span
.appendChild(document
.createTextNode(''));
315 a
.appendChild(document
.createTextNode(type
.toUpperCase()));
317 if (config
.showHelp
) {
318 a
.onmouseover = function() {
319 // hide all help texts
320 Array
.from(document
.getElementsByClassName('download-help'))
321 .forEach(e
=> e
.style
.display
= 'none');
322 var lc
= type
.toLowerCase();
323 if (lc
.includes('sysupgrade')) {
324 show('sysupgrade-help');
325 } else if (lc
.includes('factory') || lc
== 'trx' || lc
== 'chk') {
326 show('factory-help');
327 } else if (lc
.includes('kernel') || lc
.includes('zimage') || lc
.includes('uimage')) {
329 } else if (lc
.includes('root')) {
331 } else if (lc
.includes('sdcard')) {
333 } else if (lc
.includes('tftp')) {
341 $('download-links').appendChild(a
);
344 function switchClass(id
, from_class
, to_class
) {
345 $(id
).classList
.remove(from_class
);
346 $(id
).classList
.add(to_class
);
349 // remove all download links
350 Array
.from(document
.getElementsByClassName('download-link'))
351 .forEach(e
=> e
.remove());
353 // hide all help texts
354 Array
.from(document
.getElementsByClassName('download-help'))
355 .forEach(e
=> e
.style
.display
= 'none');
357 if (version
&& code
&& date
&& model
&& url
&& mobj
) {
358 var target
= mobj
.target
;
359 var images
= mobj
.images
;
361 // change between "version" and "custom" title
363 switchClass('images-title', 'tr-version-build', 'tr-custom-build');
364 switchClass('downloads-title', 'tr-version-downloads', 'tr-custom-downloads');
366 switchClass('images-title', 'tr-custom-build', 'tr-version-build');
367 switchClass('downloads-title', 'tr-custom-downloads', 'tr-version-downloads');
369 // update title translation
372 // fill out build info
373 $('image-model').innerText
= model
;
374 $('image-target').innerText
= target
;
375 $('image-version').innerText
= version
;
376 $('image-code').innerText
= code
;
377 $('image-date').innerText
= date
;
379 images
.sort((a
, b
) => a
.name
.localeCompare(b
.name
));
381 for (var i
in images
) {
382 addLink(images
[i
].type
, images
[i
].name
);
385 if (config
.asu_url
) {
386 updatePackageList(target
);
396 setupSelectList($('versions'), Object
.keys(config
.versions
), version
=> {
397 fetch(config
.versions
[version
]).then(data
=> {
398 data
.json().then(obj
=> {
399 // handle native openwrt json format
400 if ('profiles' in obj
) {
401 obj
['url'] = get_version_url(version
)
403 for (const [key
, value
] of Object
.entries(obj
['profiles'])) {
404 obj
['models'][get_model_titles(value
.titles
)] = value
405 obj
['models'][get_model_titles(value
.titles
)]['id'] = key
410 setupAutocompleteList($('models'), Object
.keys(obj
['models']), false, updateImages
, models
=> {
411 var model
= models
.value
;
412 if (model
in obj
['models']) {
413 var url
= obj
.url
|| 'unknown';
414 var code
= obj
.version_code
|| 'unknown';
415 var date
= obj
.build_date
|| 'unknown';
416 var mobj
= obj
['models'][model
];
417 updateImages(version
, code
, date
, model
, url
, mobj
, false);
418 current_model
= mobj
;
425 // trigger model update when selected version changes
426 $('models').onfocus();
431 if (config
.asu_url
) {
438 var user_lang
= (navigator
.language
|| navigator
.userLanguage
).split('-')[0];
439 if (user_lang
in translations
) {
440 config
.language
= user_lang
;
441 $('language-selection').value
= user_lang
;
446 $('language-selection').onclick = function() {
447 config
.language
= this.children
[this.selectedIndex
].value
;