b057cd219ba5c1b037c4458ee647e82c85d3e1ac
1 /* global translations, config */
2 /* exported build_asu_request, init */
4 let current_model
= {};
7 if (typeof query
=== "string") {
8 return document
.querySelector(query
);
14 function show(query
) {
15 $(query
).style
.display
= "block";
18 function hide(query
) {
19 $(query
).style
.display
= "none";
23 return str
.match(/[^\s,]+/g) || [];
26 function get_model_titles(titles
) {
44 function build_asu_request() {
45 if (!current_model
|| !current_model
.id
) {
50 function showStatus(message
, url
) {
52 const tr
= message
.startsWith("tr-") ? message
: "";
54 $("#buildstatus").innerHTML
=
55 '<a href="' + url
+ '" class="' + tr
+ '">' + message
+ "</a>";
57 $("#buildstatus").innerHTML
= '<span class="' + tr
+ '"></span>';
65 show("#buildspinner");
66 showStatus("tr-request-image");
68 const request_data
= {
69 target
: current_model
.target
,
70 profile
: current_model
.id
,
71 packages
: split($("#packages").value
),
72 version
: $("#versions").value
,
75 fetch(config
.asu_url
+ "/api/build", {
77 headers
: { "Content-Type": "application/json" },
78 body
: JSON
.stringify(request_data
),
81 switch (response
.status
) {
83 hide("#buildspinner");
84 showStatus("tr-build-successful");
86 response
.json().then((mobj
) => {
87 const download_url
= config
.asu_url
+ "/store/" + mobj
.bin_dir
;
88 showStatus("tr-build-successful", download_url
+ "/buildlog.txt");
93 get_model_titles(mobj
.titles
),
101 showStatus("tr-check-again");
106 case 400: // bad request
107 case 422: // bad package
108 case 500: // build failed
109 hide("#buildspinner");
110 response
.json().then((mobj
) => {
111 const message
= mobj
["message"] || "tr-build-failed";
112 const url
= mobj
.buildlog
113 ? config
.asu_url
+ "/store/" + mobj
.bin_dir
+ "/buildlog.txt"
115 showStatus(message
, url
);
121 hide("#buildspinner");
126 function setupSelectList(select
, items
, onselection
) {
127 for (const item
of items
) {
128 const option
= document
.createElement("OPTION");
129 option
.innerHTML
= item
;
130 select
.appendChild(option
);
133 select
.addEventListener("change", () => {
134 onselection(items
[select
.selectedIndex
]);
137 if (select
.selectedIndex
>= 0) {
138 onselection(items
[select
.selectedIndex
]);
142 // Change the translation of the entire document
143 function translate() {
144 const mapping
= translations
[config
.language
];
145 for (const tr
in mapping
) {
146 Array
.from(document
.getElementsByClassName(tr
)).forEach((e
) => {
147 e
.innerText
= mapping
[tr
];
152 function setupAutocompleteList(input
, items
, as_list
, onbegin
, onend
) {
153 let currentFocus
= -1;
155 // sort numbers and other characters separately
156 const collator
= new Intl
.Collator(undefined, {
161 items
.sort(collator
.compare
);
163 input
.oninput = function () {
167 let value
= this.value
;
171 // automcomplete last text item
172 offset
= this.value
.lastIndexOf(" ") + 1;
173 value
= this.value
.substr(offset
);
174 value_list
= split(this.value
.substr(0, offset
));
177 // close any already open lists of autocompleted values
184 // create a DIV element that will contain the items (values):
185 const list
= document
.createElement("DIV");
186 list
.setAttribute("id", this.id
+ "-autocomplete-list");
187 list
.setAttribute("class", "autocomplete-items");
188 // append the DIV element as a child of the autocomplete container:
189 this.parentNode
.appendChild(list
);
191 function normalize(s
) {
192 return s
.toUpperCase().replace(/[-_.]/g, " ");
195 const match
= normalize(value
);
197 for (const item
of items
) {
199 let j
= normalize(item
).indexOf(match
);
204 // do not offer a duplicate item
205 if (as_list
&& value_list
.indexOf(item
) != -1) {
211 let div
= document
.createElement("DIV");
212 div
.innerHTML
= "...";
213 list
.appendChild(div
);
216 let div
= document
.createElement("DIV");
217 // make the matching letters bold:
221 item
.substr(j
, value
.length
) +
223 item
.substr(j
+ value
.length
) +
224 '<input type="hidden" value="' +
228 div
.addEventListener("click", function () {
229 // include selected value
230 const selected
= this.getElementsByTagName("input")[0].value
;
232 input
.value
= value_list
.join(" ") + " " + selected
;
234 input
.value
= selected
;
236 // close the list of autocompleted values,
241 list
.appendChild(div
);
246 input
.onkeydown = function (e
) {
247 let x
= document
.getElementById(this.id
+ "-autocomplete-list");
248 if (x
) x
= x
.getElementsByTagName("div");
249 if (e
.keyCode
== 40) {
252 // and and make the current item more visible:
254 } else if (e
.keyCode
== 38) {
257 // and and make the current item more visible:
259 } else if (e
.keyCode
== 13) {
260 // If the ENTER key is pressed, prevent the form from being submitted,
262 if (currentFocus
> -1) {
263 // and simulate a click on the 'active' item:
264 if (x
) x
[currentFocus
].click();
269 input
.onfocus = function () {
274 input
.onblur = function () {
278 function setActive(xs
) {
279 // a function to classify an item as 'active':
280 if (!xs
) return false;
281 // start by removing the 'active' class on all items:
282 for (const x
of xs
) {
283 x
.classList
.remove("autocomplete-active");
285 if (currentFocus
>= xs
.length
) currentFocus
= 0;
286 if (currentFocus
< 0) currentFocus
= xs
.length
- 1;
287 // add class 'autocomplete-active':
288 xs
[currentFocus
].classList
.add("autocomplete-active");
291 function closeAllLists(elmnt
) {
292 // close all autocomplete lists in the document,
293 // except the one passed as an argument:
294 const xs
= document
.getElementsByClassName("autocomplete-items");
295 for (const x
of xs
) {
296 if (elmnt
!= x
&& elmnt
!= input
) {
297 x
.parentNode
.removeChild(x
);
302 // execute a function when someone clicks in the document:
303 document
.addEventListener("click", (e
) => {
304 closeAllLists(e
.target
);
308 // for attended sysupgrade
309 function updatePackageList(version
, target
) {
310 // set available packages
314 config
.versions
[version
] +
319 .then((response
) => response
.json())
320 .then((all_packages
) => {
321 setupAutocompleteList(
327 textarea
.value
= split(textarea
.value
)
328 // make list unique, ignore minus
329 .filter((value
, index
, self
) => {
330 const i
= self
.indexOf(value
.replace(/^-/, ""));
331 return i
=== index
|| i
< 0;
333 // limit to available packages, ignore minus
335 (value
) => all_packages
.indexOf(value
.replace(/^-/, "")) !== -1
343 function updateImages(version
, code
, date
, model
, url
, mobj
, is_custom
) {
344 // add download button for image
345 function addLink(type
, file
) {
346 const a
= document
.createElement("A");
347 a
.classList
.add("download-link");
349 url
.replace("{target}", mobj
.target
).replace("{version}", version
) +
352 const span
= document
.createElement("SPAN");
353 span
.appendChild(document
.createTextNode(""));
355 a
.appendChild(document
.createTextNode(type
.toUpperCase()));
357 if (config
.showHelp
) {
358 a
.onmouseover = function () {
359 // hide all help texts
360 Array
.from(document
.getElementsByClassName("download-help")).forEach(
361 (e
) => (e
.style
.display
= "none")
363 const lc
= type
.toLowerCase();
364 if (lc
.includes("sysupgrade")) {
365 show("#sysupgrade-help");
366 } else if (lc
.includes("factory") || lc
== "trx" || lc
== "chk") {
367 show("#factory-help");
369 lc
.includes("kernel") ||
370 lc
.includes("zimage") ||
371 lc
.includes("uimage")
373 show("#kernel-help");
374 } else if (lc
.includes("root")) {
375 show("#rootfs-help");
376 } else if (lc
.includes("sdcard")) {
377 show("#sdcard-help");
378 } else if (lc
.includes("tftp")) {
386 $("#download-links").appendChild(a
);
389 function switchClass(query
, from_class
, to_class
) {
390 $(query
).classList
.remove(from_class
);
391 $(query
).classList
.add(to_class
);
394 // remove all download links
395 Array
.from(document
.getElementsByClassName("download-link")).forEach((e
) =>
399 // hide all help texts
400 Array
.from(document
.getElementsByClassName("download-help")).forEach(
401 (e
) => (e
.style
.display
= "none")
404 if (model
&& url
&& mobj
) {
405 const target
= mobj
.target
;
406 const images
= mobj
.images
;
408 // change between "version" and "custom" title
410 switchClass("#build-title", "tr-version-build", "tr-custom-build");
413 "tr-version-downloads",
414 "tr-custom-downloads"
417 switchClass("#build-title", "tr-custom-build", "tr-version-build");
420 "tr-custom-downloads",
421 "tr-version-downloads"
425 // update title translation
428 // fill out build info
429 $("#image-model").innerText
= model
;
430 $("#image-target").innerText
= target
;
431 $("#image-version").innerText
= version
;
432 $("#image-code").innerText
= mobj
["code"] || code
;
433 $("#image-date").innerText
= date
;
435 images
.sort((a
, b
) => a
.name
.localeCompare(b
.name
));
437 for (const i
in images
) {
438 addLink(images
[i
].type
, images
[i
].name
);
441 if (config
.asu_url
) {
442 updatePackageList(version
, target
);
452 let build_date
= "unknown";
453 setupSelectList($("#versions"), Object
.keys(config
.versions
), (version
) => {
454 let url
= config
.versions
[version
];
455 if (config
.asu_url
) {
456 url
= config
.asu_url
+ "/" + url
+ "/profiles.json";
460 build_date
= obj
.headers
.get("last-modified");
464 // handle native openwrt json format
465 if ("profiles" in obj
) {
467 for (const [key
, value
] of Object
.entries(obj
["profiles"])) {
469 obj
["models"][get_model_titles(value
.titles
)] = value
;
475 setupAutocompleteList(
477 Object
.keys(obj
["models"]),
481 const model
= models
.value
;
482 if (model
in obj
["models"]) {
483 const url
= obj
.download_url
|| "unknown";
484 const code
= obj
.version_code
|| "unknown";
485 const mobj
= obj
["models"][model
];
486 updateImages(version
, code
, build_date
, model
, url
, mobj
, false);
487 current_model
= mobj
;
495 // trigger model update when selected version changes
496 $("#models").onfocus();
500 if (config
.asu_url
) {
507 // default to browser language
508 const user_lang
= (navigator
.language
|| navigator
.userLanguage
).split(
511 if (user_lang
in translations
) {
512 config
.language
= user_lang
;
513 $("#language-selection").value
= user_lang
;
518 $("#language-selection").onclick = function () {
519 config
.language
= this.children
[this.selectedIndex
].value
;