-
Notifications
You must be signed in to change notification settings - Fork 11
/
main.js
580 lines (552 loc) · 22.5 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
/**
* ZTP GUI Web App
* Author: Tim Dorssers
* Version: 1.2
*/
function createDropdown(value, id, callback) {
// Create block for editable drop-down list
var div = document.createElement('DIV');
div.className = 'dropdown';
// First element is a text box
var ele = document.createElement('INPUT');
ele.id = id;
ele.type = 'text';
if (value) ele.value = value;
ele.addEventListener('change', callback, false);
div.appendChild(ele);
// Second element is a drop-down list
var ele = document.createElement('SELECT');
ele.addEventListener('change', function() {
this.previousElementSibling.value = this.value;
this.previousElementSibling.focus();
if (callback) callback();
}, false);
ele.className = 'picker';
addOptionsFromTable(ele);
div.appendChild(ele);
return div;
}
function addOptionsFromTable(select) {
// First option is blank
select.appendChild(document.createElement('OPTION'));
var table = document.getElementById('table_file');
if (table === null) return;
for (var row = 1; row < table.rows.length; row++) {
var ele = document.createElement('OPTION');
// Value of element in first cell of each table row will be the option text
ele.text = table.rows.item(row).cells[0].childNodes[0].data;
select.appendChild(ele);
}
}
function updateOptionsFromTable() {
// All drop-down lists
var pickers = document.getElementsByClassName('picker');
for (var i = 0; i < pickers.length; i++) {
// Remove all options
while (pickers[i].options.length) {
pickers[i].remove(0);
}
addOptionsFromTable(pickers[i]);
}
}
function createTableRow(table, object, key) {
var id = table.id + '_' + key;
var row = table.insertRow(-1);
// First cell is the object key name
var cell = row.insertCell(-1);
cell.className = 'min';
cell.innerHTML = key;
// Second cell is the object value
switch (key) {
case 'save':
var ele = document.createElement('INPUT');
ele.type = 'checkbox';
if (object[key]) ele.checked = true;
break;
case 'cli':
case 'template':
var ele = document.createElement('TEXTAREA');
ele.addEventListener('input', function() {
// Auto resize text area
var breaks = this.value.match(/\n/g);
var lines = breaks ? breaks.length + 2 : 2;
this.rows = (lines < 10) ? lines : 10;
}, false);
if (object[key]) {
ele.innerHTML = object[key];
// Set initial text area height
var breaks = object[key].match(/\n/g);
var lines = breaks ? breaks.length + 2 : 2;
ele.rows = (lines < 10) ? lines : 10;
}
break;
case 'install':
var ele = createDropdown(object[key], id, function() {
// Auto fill version input if version can be extracted from file name
var version = document.getElementById(table.id + '_version');
var match = /\.(\d+.\d+.\d+)\.?(\w?)\./g.exec(document.getElementById(id).value);
if (match) version.value = match[1].replace(/\b0+(\d)/g, '$1') + match[2];
});
break;
case 'config':
var ele = createDropdown(object[key], id, null);
break;
default:
var ele = document.createElement('INPUT');
ele.id = id;
ele.type = 'text';
if (object[key]) ele.value = object[key];
}
row.insertCell(-1).appendChild(ele);
}
function createCellWithButton(row, name, callback, id) {
var cell = row.insertCell(-1);
cell.className = 'min';
var ele = document.createElement('INPUT');
ele.type = 'button';
ele.value = name;
if (id) ele.id = id;
ele.addEventListener('click', callback, false);
cell.appendChild(ele);
}
function createCellWithText(row, value) {
var ele = document.createElement('INPUT');
ele.type = 'text';
ele.value = value;
row.insertCell(-1).appendChild(ele);
}
function createNestedTableRow(table, index, object, key) {
var row = table.insertRow(index);
createCellWithText(row, key);
createCellWithText(row, object[key]);
createCellWithButton(row, 'Insert Below', function() {addRow(this)}, null);
createCellWithButton(row, 'Remove', function() {removeRow(this)}, null);
}
function removeRow(button) {
var tr = button.parentNode.parentNode;
var table = tr.parentNode.parentNode;
// Prevent deletion of first row
if (table.rows.length > 1) table.deleteRow(tr.rowIndex);
}
function addRow(button) {
var tr = button.parentNode.parentNode;
var table = tr.parentNode.parentNode;
// Insert empty row below current row
createNestedTableRow(table, tr.rowIndex + 1, {'':''}, '');
}
function createNestedTable(table, object, key) {
var row = table.insertRow(-1);
// First cell is the object key name
row.insertCell(-1).innerHTML = key;
// Second cell is the nested table
var ele = document.createElement('TABLE');
ele.id = table.id + '_' + key;
if (typeof object[key] !== 'undefined' && object[key] !== null) {
// Create rows if object is not empty
for (var i in object[key]) {
createNestedTableRow(ele, -1, object[key], i);
}
} else {
// Create empty row in case of no or null object
createNestedTableRow(ele, -1, {'':''}, '');
}
row.insertCell(-1).appendChild(ele);
}
function removeContent(ele) {
// Remove all children from DOM element
while (ele.firstChild) {
ele.removeChild(ele.firstChild);
}
}
function createContent(data) {
for (var index = 0; index < data.length; index++) {
var table = document.createElement('TABLE');
// Table ID is used later to recontruct the array of objects
table.id = 'table_' + createContent.lastIndex;
if ('stack' in data[index]) {
createNestedTable(table, data[index], 'stack');
} else {
createTableRow(table, data[index], 'base_url');
}
createTableRow(table, data[index], 'version');
createTableRow(table, data[index], 'install');
createTableRow(table, data[index], 'config');
createNestedTable(table, data[index], 'subst');
createTableRow(table, data[index], 'save');
createTableRow(table, data[index], 'cli');
createTableRow(table, data[index], 'template');
if ('stack' in data[index]) {
// Create paragraph for table
var box = document.createElement('P');
box.className = 'box';
box.appendChild(table);
// Create remove stack button
var ele = document.createElement('INPUT');
ele.type = 'button';
ele.value = 'Remove Stack';
ele.addEventListener('click', removeTable('table_' + createContent.lastIndex), false);
box.appendChild(ele);
// Put the paragraph in the stacks block
document.getElementById('stacks').appendChild(box);
} else {
// Put the table in the defaults block
document.getElementById('defaults').appendChild(table);
}
createContent.lastIndex++;
}
}
function removeTable(id) {
return function() {
var table = document.getElementById(id);
// Remove paragraph containing table
table.parentNode.parentNode.removeChild(table.parentNode);
}
}
function addTable() {
createContent([{"stack": {"1": null}}]);
// Scroll down to added stack table
document.getElementById('table_' + (createContent.lastIndex - 1)).scrollIntoView();
}
function displayError(xhttp) {
if (xhttp.getResponseHeader('Content-type') == 'application/json') {
alert(JSON.parse(xhttp.responseText));
} else if (xhttp.status) {
alert(xhttp.statusText);
} else {
alert('Error occurred requesting data from server');
}
}
function submitData(exportCsv) {
var data = [];
var tables = document.getElementsByTagName('TABLE');
for (var i = 0; i < tables.length; i++) {
// Split the table ID into array index and key name
var refName = tables[i].id.split('_');
if (isNaN(refName[1])) continue;
if (refName.length > 2) {
// Nested table with two text boxes in a row
var object = {}, len = 0;
for (var row = 0; row < tables[i].rows.length; row++) {
var key = tables[i].rows.item(row).cells[0].childNodes[0].value;
var value = tables[i].rows.item(row).cells[1].childNodes[0].value;
if (key) {
object[key] = value;
len++;
}
}
if (len) {
data[refName[1]][refName[2]] = object;
} else if (refName[2] == 'stack') {
alert("Stack cannot be empty. Data not saved.");
return;
}
} else {
// Regular table with a key name and a text or check box in a row
var object = {};
for (var row = 0; row < tables[i].rows.length; row++) {
var key = tables[i].rows.item(row).cells[0].innerHTML;
var ele = tables[i].rows.item(row).cells[1].childNodes[0];
if (ele.type == 'checkbox') {
if (ele.checked) object[key] = true;
} else if (ele.type == 'text' || ele.tagName == 'TEXTAREA') {
if (ele.value) object[key] = ele.value;
} else if (ele.tagName == 'DIV') {
if (ele.childNodes[0].value) object[key] = ele.childNodes[0].value;
}
}
data[refName[1]] = object;
}
}
// Remove undefined array elements
for (var i = data.length - 1; i >= 0; i--) {
if (typeof data[i] === 'undefined') data.splice(i, 1);
}
// Upload JSON data and display status when finished
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
// Reload data for 412 status or refresh lastModified except when server error occurred
if (this.status < 500) loadData(this.status != 412);
// Open URL if exporting to CSV, otherwise display HTTP status
(exportCsv && this.status == 200) ? window.open('/csv', '_blank') : displayError(this);
}
};
xhttp.open("POST", "/data", true);
xhttp.setRequestHeader("Content-type", "application/json");
xhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
if (submitData.lastModified) xhttp.setRequestHeader('If-Unmodified-Since', submitData.lastModified);
xhttp.send(JSON.stringify(data));
}
function loadData(ignoreBody) {
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
submitData.lastModified = this.getResponseHeader('Last-Modified');
if (ignoreBody) return;
removeContent(document.getElementById('stacks'));
removeContent(document.getElementById('defaults'));
createContent.lastIndex = 0;
if (this.status == 200) {
// Parse retrieved JSON data
var data = JSON.parse(this.responseText);
// Look for a defaults object
var gotDefaults = false;
for (var index = 0; index < data.length; index++) {
if (!('stack' in data[index])) gotDefaults = true;
}
// Put an empty defaults object at head of the array
if (!gotDefaults) data.unshift({});
createContent(data);
} else {
// Just display an empty defaults object in case of error
createContent([{}]);
displayError(this);
}
}
};
xhttp.open(ignoreBody ? "HEAD" : "GET", "/data", true);
xhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhttp.send();
}
function createLink(txt, url, callback) {
var a = document.createElement('A');
a.href = url ? url : '#';
if (callback === null) a.target = '_blank';
a.addEventListener('click', callback, false);
a.innerHTML = txt;
return a;
}
function loadList() {
var table = document.getElementById('table_file');
if (table !== null) table.parentNode.removeChild(table);
document.getElementById('form_upload').reset();
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
// Parse retrieved JSON data
var files = JSON.parse(this.responseText);
if (files.length == 0) return;
// Create table with file list
var table = document.createElement('TABLE');
table.id = 'table_file';
var row = table.insertRow(-1);
['path', 'time', 'size', 'action'].forEach(function(key) {
var cell = document.createElement('TH');
cell.innerHTML = key;
row.appendChild(cell);
});
for (var index = 0; index < files.length; index++) {
var row = table.insertRow(-1);
row.insertCell(-1).innerHTML = files[index].file;
row.insertCell(-1).innerHTML = files[index].time;
row.insertCell(-1).innerHTML = files[index].size;
var cell = row.insertCell(-1);
cell.appendChild(createLink('Download', '/file/' + files[index].file, null));
cell.appendChild(document.createTextNode(' '));
cell.appendChild(createLink('Remove', null, function() {deleteFile(this)}));
}
document.getElementById('files').appendChild(table);
updateOptionsFromTable();
}
};
xhttp.open("GET", "/list", true);
xhttp.setRequestHeader('X-Requested-With','XMLHttpRequest');
xhttp.send();
}
function deleteFile(button) {
event.preventDefault() // Prevent default action for anchor tag
var tr = button.parentNode.parentNode;
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200) {
var table = tr.parentNode.parentNode;
if (table.rows.length > 2) {
// Remove row from table
table.deleteRow(tr.rowIndex);
updateOptionsFromTable();
} else {
table.parentNode.removeChild(table);
}
} else {
displayError(this);
}
}
};
// First cell in row is the file name
xhttp.open("DELETE", "/file/" + tr.cells[0].childNodes[0].data, true);
xhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhttp.send();
}
function upload() {
var table = document.getElementById('table_progress');
var browse = document.getElementById('browse');
for (var i = 0; i < browse.files.length; i++) {
var row = table.insertRow(-1);
// First cell is the filename
var cell = row.insertCell(-1);
cell.className = 'min';
var ele = document.createElement('SPAN');
ele.innerHTML = browse.files[i].name;
cell.appendChild(ele);
// Second cell is the progress bar
var progress = document.createElement('DIV');
progress.className = 'progress';
progress.id = 'progress_' + i;
var bar = document.createElement('DIV');
bar.className = 'bar';
bar.id = 'bar_' + i;
bar.innerHTML = '0%';
progress.appendChild(bar);
row.insertCell(-1).appendChild(progress);
// Third cell is the cancel button
createCellWithButton(row, 'Cancel', null, 'cancel_' + i);
// Resize file list
var height = 265 + table.scrollHeight;
document.getElementById('files').style.maxHeight = 'calc(100vh - ' + height + 'px)';
uploadFile(browse.files[i], i);
}
}
function abortUpload(xhttp) {
return function() {xhttp.abort()};
}
function uploadFile(file, i) {
var bar = document.getElementById('bar_' + i);
var tr = bar.parentNode.parentNode.parentNode;
// Prepare form fields to be sent by xhttp
var data = new FormData();
data.append('folder', document.getElementById('folder').value);
data.append('upload', file);
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status != 200) displayError(this);
// Remove progress bar
tr.parentNode.removeChild(tr);
// Resize file list
var table = document.getElementById('table_progress');
var height = 265 + table.scrollHeight;
document.getElementById('files').style.maxHeight = 'calc(100vh - ' + height + 'px)';
// Reload list if this was the progress bar
if (table.rows.length == 0) loadList();
}
};
xhttp.upload.addEventListener('progress', function(e) {
bar.innerHTML = bar.style.width = Math.round((e.loaded * 100) / e.total) + '%';
}, false);
xhttp.open('POST', '/file');
xhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhttp.send(data);
var cancel = document.getElementById('cancel_' + i);
cancel.addEventListener('click', abortUpload(xhttp), false);
}
function importCsv() {
var data = new FormData();
data.append('upload', document.getElementById('hiddenfile').files[0]);
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
// Reload data when import was successful, otherwise display error
(this.status == 200) ? loadData(false) : displayError(this);
}
};
xhttp.open("POST", "/csv", true);
xhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhttp.send(data);
document.getElementById('form_import').reset();
}
function loadLog() {
var log = document.getElementById('log');
removeContent(log);
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
if (this.status == 200) {
// Parse retrieved JSON data
var entries = JSON.parse(this.responseText);
if (entries.length == 0) {
var p = document.createElement('P');
p.innerHTML = 'No data';
log.appendChild(p);
return;
}
// Create table with log entries
var table = document.createElement('TABLE');
table.id = 'table_log';
var row = table.insertRow(-1);
['ip', 'time', 'serial', 'version', 'status', 'view'].forEach(function(key) {
var cell = document.createElement('TH');
cell.innerHTML = key;
row.appendChild(cell);
});
for (var index = 0; index < entries.length; index++) {
var row = table.insertRow(-1);
row.insertCell(-1).innerHTML = entries[index]['ip'];
row.insertCell(-1).innerHTML = entries[index]['time'];
row.insertCell(-1).innerHTML = entries[index]['serial'];
row.insertCell(-1).innerHTML = entries[index]['version'];
row.insertCell(-1).innerHTML = entries[index]['status'];
var cell = row.insertCell(-1);
['logbuf', 'cli'].forEach(function(key) {
if (typeof entries[index][key] !== 'undefined') {
cell.appendChild(createLink(key, null, openModal(key, entries[index][key])));
cell.appendChild(document.createTextNode(' '));
}
});
}
log.appendChild(table);
} else {
var p = document.createElement('P');
p.innerHTML = 'No data';
log.appendChild(p);
displayError(this);
}
}
};
xhttp.open("GET", "/log", true);
xhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhttp.send();
}
function clearLog() {
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4) {
// Reload log entries when clear was successful, otherwise display error
(this.status == 200) ? loadLog() : displayError(this);
}
};
xhttp.open("DELETE", "/log", true);
xhttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhttp.send();
}
function openPage(evt, name) {
var tabcontent = document.getElementsByClassName("tabcontent");
for (var i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
var tablinks = document.getElementsByClassName("tablinks");
for (var i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(name).style.display = "block";
evt.currentTarget.className += " active";
}
function openModal(head, txt) {
return function() {
event.preventDefault(); // Prevent default action for anchor tag
document.getElementById('modalhead').innerHTML = head;
var modalcontent = document.getElementById('modalcontent');
removeContent(modalcontent);
// Add content
var pre = document.createElement('PRE');
pre.innerHTML = txt;
modalcontent.appendChild(pre);
// Display modal frame
var modal = document.getElementById('modal');
modal.style.display = 'block';
// Close the modal by clicking outside of the modal frame
window.onclick = function(event) {
if (event.target == modal) modal.style.display = "none";
};
}
}