1 function get_model_titles(titles
) {
3 for (var i
in titles
) {
5 title
.push(titles
[i
].title
)
7 var title_fragments
= []
8 if (titles
[i
].vendor
) { title_fragments
.push(titles
[i
].vendor
) }
9 title_fragments
.push(titles
[i
].model
)
10 if (titles
[i
].variant
) { title_fragments
.push(titles
[i
].variant
) }
11 title
.push(title_fragments
.join(" "))
14 return title
.join("/")
17 function build_request(request_data
) {
19 fetch(config
.asu_url
, {
21 headers
: { 'Content-Type': 'application/json' },
22 body
: JSON
.stringify(request_data
)
24 .then(function(response
) {
25 switch (response
.status
) {
27 console
.log("image found");
29 .then(function(mobj
) {
34 get_model_titles(mobj
.titles
),
40 // show some spinning animation
41 console
.log("check again in 5 seconds");
42 setTimeout(function() { build_request(request_data
) }, 5000);
45 console
.log("bad request"); // see message
48 console
.log("bad package"); // see message
51 console
.log("build failed");
57 function example_request() {
59 "version": "SNAPSHOT",
60 "packages": ["bmon", "vim", "ip-full"],
61 "profile": "tplink_tl-wdr4300-v1"
65 function loadFile(url
, callback
) {
66 var xmlhttp
= new XMLHttpRequest();
67 xmlhttp
.onreadystatechange = function() {
68 if (xmlhttp
.readyState
== 4 && xmlhttp
.status
== 200) {
69 callback(JSON
.parse(xmlhttp
.responseText
), url
);
72 xmlhttp
.open('GET', url
, true);
76 function setupSelectList(select
, items
, onselection
) {
77 for (var i
= 0; i
< items
.length
; i
+= 1) {
78 var option
= document
.createElement("OPTION");
79 option
.innerHTML
= items
[i
];
80 select
.appendChild(option
);
83 select
.addEventListener("change", function(e
) {
84 onselection(items
[select
.selectedIndex
]);
87 if (select
.selectedIndex
>= 0) {
88 onselection(items
[select
.selectedIndex
]);
92 // Change the translation of the entire document
93 function changeLanguage(language
) {
94 var mapping
= translations
[language
];
96 for (var tr
in mapping
) {
97 Array
.from(document
.getElementsByClassName(tr
))
98 .forEach(function(e
) { e
.innerText
= mapping
[tr
]; })
103 function setupAutocompleteList(input
, items
, onselection
) {
104 // the setupAutocompleteList function takes two arguments,
105 // the text field element and an array of possible autocompleted values:
106 var currentFocus
= -1;
108 // sort numbers and other characters separately
109 var collator
= new Intl
.Collator(undefined, {numeric
: true, sensitivity
: 'base'});
111 items
.sort(collator
.compare
);
113 // execute a function when someone writes in the text field:
114 input
.oninput = function(e
) {
118 var value
= this.value
;
119 // close any already open lists of autocompleted values
126 // create a DIV element that will contain the items (values):
127 var list
= document
.createElement("DIV");
128 list
.setAttribute("id", this.id
+ "-autocomplete-list");
129 list
.setAttribute("class", "autocomplete-items");
130 // append the DIV element as a child of the autocomplete container:
131 this.parentNode
.appendChild(list
);
133 // for each item in the array...
135 for (var i
= 0; i
< items
.length
; i
+= 1) {
139 var j
= item
.toUpperCase().indexOf(value
.toUpperCase());
146 var div
= document
.createElement("DIV");
147 div
.innerHTML
= "...";
148 list
.appendChild(div
);
151 var div
= document
.createElement("DIV");
152 // make the matching letters bold:
153 div
.innerHTML
= item
.substr(0, j
)
154 + "<strong>" + item
.substr(j
, value
.length
) + "</strong>"
155 + item
.substr(j
+ value
.length
)
156 + "<input type='hidden' value='" + item
+ "'>";
158 div
.addEventListener("click", function(e
) {
159 // set text field to selected value
160 input
.value
= this.getElementsByTagName("input")[0].value
;
161 // close the list of autocompleted values,
162 // (or any other open lists of autocompleted values:
165 onselection(input
.value
);
168 list
.appendChild(div
);
173 input
.onkeydown = function(e
) {
174 var x
= document
.getElementById(this.id
+ "-autocomplete-list");
175 if (x
) x
= x
.getElementsByTagName("div");
176 if (e
.keyCode
== 40) {
179 // and and make the current item more visible:
181 } else if (e
.keyCode
== 38) {
184 // and and make the current item more visible:
186 } else if (e
.keyCode
== 13) {
187 // If the ENTER key is pressed, prevent the form from being submitted,
189 if (currentFocus
> -1) {
190 // and simulate a click on the "active" item:
191 if (x
) x
[currentFocus
].click();
196 input
.onfocus = function() {
197 onselection(input
.value
);
200 function setActive(x
) {
201 // a function to classify an item as "active":
202 if (!x
) return false;
203 // start by removing the "active" class on all items:
204 for (var i
= 0; i
< x
.length
; i
++) {
205 x
[i
].classList
.remove("autocomplete-active");
207 if (currentFocus
>= x
.length
) currentFocus
= 0;
208 if (currentFocus
< 0) currentFocus
= (x
.length
- 1);
209 // add class "autocomplete-active":
210 x
[currentFocus
].classList
.add("autocomplete-active");
213 function closeAllLists(elmnt
) {
214 // close all autocomplete lists in the document,
215 // except the one passed as an argument:
216 var x
= document
.getElementsByClassName("autocomplete-items");
217 for (var i
= 0; i
< x
.length
; i
++) {
218 if (elmnt
!= x
[i
] && elmnt
!= input
) {
219 x
[i
].parentNode
.removeChild(x
[i
]);
224 // execute a function when someone clicks in the document:
225 document
.addEventListener("click", function (e
) {
226 closeAllLists(e
.target
);
231 return document
.getElementById(id
);
234 function findCommonPrefix(images
) {
235 var files
= images
.map(image
=> image
.name
)
236 var A
= files
.concat().sort();
238 var last
= A
[A
.length
- 1];
239 var L
= first
.length
;
241 while (i
< L
&& first
.charAt(i
) === last
.charAt(i
)) {
244 return first
.substring(0, i
);
247 function updateImages(version
, commit
, model
, url
, mobj
) {
248 // add download button for image
249 function addLink(label
, tags
, file
, help_id
) {
250 var a
= document
.createElement('A');
251 a
.classList
.add('download-link');
253 .replace('{target}', mobj
.target
)
254 .replace('{release}', version
)
256 var span
= document
.createElement('SPAN');
257 span
.appendChild(document
.createTextNode(''));
261 if (tags
.length
> 0) {
262 a
.appendChild(document
.createTextNode(label
+ ' (' + tags
.join(', ') + ')'));
264 a
.appendChild(document
.createTextNode(label
));
267 if (config
.showHelp
) {
268 a
.onmouseover = function() {
269 // hide all help texts
270 Array
.from(document
.getElementsByClassName('download-help'))
271 .forEach(function(e
) { e
.style
.display
= 'none'; });
272 $(help_id
).style
.display
= 'block';
276 $('download-links').appendChild(a
);
279 // remove all download links
280 Array
.from(document
.getElementsByClassName('download-link'))
281 .forEach(function(e
) { e
.remove(); });
283 // hide all help texts
284 Array
.from(document
.getElementsByClassName('download-help'))
285 .forEach(function(e
) { e
.style
.display
= 'none'; });
287 if (version
&& commit
&& model
&& url
&& mobj
) {
288 var target
= mobj
.target
;
289 var images
= mobj
.images
;
291 // fill out build info
292 $('image-model').innerText
= model
;
293 $('image-target').innerText
= target
;
294 $('image-release').innerText
= version
;
295 $('image-commit').innerText
= commit
;
297 var prefix
= findCommonPrefix(images
);
310 for (var i
in images
) {
311 var image
= images
[i
].name
;
312 var lc
= image
.toLowerCase()
313 if (lc
.includes('factory')) {
314 entries
['FACTORY'].push(image
);
315 } else if (lc
.includes('sysupgrade')) {
316 entries
['SYSUPGRADE'].push(image
);
317 } else if (lc
.includes('kernel') || lc
.includes('zimage') || lc
.includes('uimage')) {
318 entries
['KERNEL'].push(image
);
319 } else if (lc
.includes('rootfs')) {
320 entries
['ROOTFS'].push(image
);
321 } else if (lc
.includes('sdcard')) {
322 entries
['SDCARD'].push(image
);
323 } else if (lc
.includes('tftp')) {
324 entries
['TFTP'].push(image
);
326 entries
['OTHER'].push(image
);
330 function extractTags(prefix
, image
) {
331 var all
= image
.substring(prefix
.length
).split('.')[0].split('-');
332 var ignore
= ['', 'kernel', 'zImage', 'uImage', 'factory', 'sysupgrade', 'rootfs', 'sdcard'];
333 return all
.filter(function (el
) { return !ignore
.includes(el
); });
336 for (var category
in entries
) {
337 var images
= entries
[category
];
338 for (var i
in images
) {
339 var image
= images
[i
];
340 var tags
= (images
.length
> 1) ? extractTags(prefix
, image
) : [];
341 addLink(category
, tags
, image
, category
.toLowerCase() + '-help');
345 $('images').style
.display
= 'block';
347 $('images').style
.display
= 'none';
353 changeLanguage(config
.language
);
355 setupSelectList($("releases"), Object
.keys(config
.versions
), function(version
) {
356 loadFile(config
.versions
[version
], function(obj
) {
357 setupAutocompleteList($("models"), Object
.keys(obj
['models']), function(model
) {
358 if (model
in obj
['models']) {
360 var commit
= obj
.version_commit
;
361 var mobj
= obj
['models'][model
];
362 updateImages(version
, commit
, model
, url
, mobj
);
368 // trigger model update when selected version changes
369 $("models").onfocus();