616 lines
18 KiB
JavaScript
616 lines
18 KiB
JavaScript
// Copyright 2017, 2018, 2019, 2020, 2021 Max H. Parke KA1RBI
|
|
//
|
|
// This file is part of OP25
|
|
//
|
|
// OP25 is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 3, or (at your option)
|
|
// any later version.
|
|
//
|
|
// OP25 is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
|
// License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with OP25; see the file COPYING. If not, write to the Free
|
|
// Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
|
// 02110-1301, USA.
|
|
|
|
|
|
// Functions related to Online Config
|
|
|
|
function find_parent(ele, tagname) {
|
|
while (ele) {
|
|
if (ele.nodeName == tagname)
|
|
return ele;
|
|
else if (ele.nodeName == 'HTML')
|
|
return null;
|
|
ele = ele.parentNode;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function f_command(ele, command) {
|
|
var myrow = find_parent(ele, 'TR');
|
|
var mytbl = find_parent(ele, 'TABLE');
|
|
amend_d(myrow, mytbl, command);
|
|
}
|
|
|
|
function edit_freq(freq, to_ui) {
|
|
var MHZ = 1000000;
|
|
if (to_ui) {
|
|
var f = freq / MHZ + '';
|
|
if (f.indexOf('.') == -1)
|
|
f += '.0';
|
|
return f;
|
|
} else {
|
|
var f = parseFloat(freq);
|
|
if (freq.indexOf('.'))
|
|
f *= MHZ;
|
|
return Math.round(f);
|
|
}
|
|
}
|
|
|
|
|
|
function edit_d(d, to_ui) {
|
|
var new_d = {};
|
|
var hexints = {
|
|
'nac': 1
|
|
};
|
|
var ints = {
|
|
'if_rate': 1,
|
|
'ppm': 1,
|
|
'rate': 1,
|
|
'offset': 1,
|
|
'nac': 1,
|
|
'logfile-workers': 1,
|
|
'decim-amt': 1,
|
|
'seek': 1,
|
|
'hamlib-model': 1
|
|
};
|
|
var bools = {
|
|
'active': 1,
|
|
'trunked': 1,
|
|
'rate': 1,
|
|
'offset': 1,
|
|
'phase2_tdma': 1,
|
|
'phase2-tdma': 1,
|
|
'wireshark': 1,
|
|
'udp-player': 1,
|
|
'audio-if': 1,
|
|
'tone-detect': 1,
|
|
'vocoder': 1,
|
|
'audio': 1,
|
|
'pause': 1
|
|
};
|
|
var floats = {
|
|
'costas-alpha': 1,
|
|
'gain-mu': 1,
|
|
'calibration': 1,
|
|
'fine-tune': 1,
|
|
'gain': 1,
|
|
'excess-bw': 1,
|
|
'offset': 1,
|
|
'excess_bw': 1
|
|
};
|
|
var lists = {
|
|
'blacklist': 1,
|
|
'whitelist': 1,
|
|
'cclist': 1
|
|
};
|
|
var freqs = {
|
|
'frequency': 1,
|
|
'cclist': 1
|
|
};
|
|
for (var k in d) {
|
|
if (!to_ui) {
|
|
if (d[k] == 'None')
|
|
new_d[k] = '';
|
|
else
|
|
new_d[k] = d[k];
|
|
if (k == 'plot' && !d[k].length)
|
|
new_d[k] = null;
|
|
if (k in ints) {
|
|
new_d[k] = parseInt(new_d[k]);
|
|
} else if (k in floats) {
|
|
new_d[k] = parseFloat(new_d[k]);
|
|
} else if (k in lists) {
|
|
var l = [];
|
|
if (new_d[k].length)
|
|
l = new_d[k].split(',');
|
|
if (k in freqs && new_d[k].length) {
|
|
var new_l = [];
|
|
for (var i in l)
|
|
new_l.push(edit_freq(l[i], to_ui));
|
|
new_d[k] = new_l;
|
|
} else {
|
|
new_d[k] = l;
|
|
}
|
|
} else if (k in freqs) {
|
|
new_d[k] = edit_freq(new_d[k], to_ui);
|
|
}
|
|
} else {
|
|
if (k in hexints) {
|
|
if (d[k] == null)
|
|
new_d[k] = '0x0';
|
|
else
|
|
new_d[k] = '0x' + d[k].toString(16);
|
|
} else if (k in ints) {
|
|
if (d[k] == null)
|
|
new_d[k] = '';
|
|
else
|
|
new_d[k] = d[k].toString(10);
|
|
} else if (k in lists) {
|
|
if (k in freqs) {
|
|
var new_l = [];
|
|
for (var i in d[k]) {
|
|
new_l.push(edit_freq(d[k][i], to_ui));
|
|
}
|
|
new_d[k] = new_l.join(',');
|
|
} else {
|
|
if (!d[k] || !d[k].length)
|
|
new_d[k] = [];
|
|
else
|
|
new_d[k] = d[k].join(',');
|
|
}
|
|
} else if (k in freqs) {
|
|
new_d[k] = edit_freq(d[k], to_ui);
|
|
} else {
|
|
new_d[k] = d[k];
|
|
}
|
|
}
|
|
}
|
|
return new_d;
|
|
}
|
|
|
|
|
|
function edit_l(cfg, to_ui) {
|
|
var new_d = {
|
|
'devices': [],
|
|
'channels': []
|
|
};
|
|
for (var device in cfg['devices'])
|
|
new_d['devices'].push(edit_d(cfg['devices'][device], to_ui));
|
|
for (var channel in cfg['channels'])
|
|
new_d['channels'].push(edit_d(cfg['channels'][channel], to_ui));
|
|
new_d['backend-rx'] = edit_d(cfg['backend-rx'], to_ui);
|
|
return new_d;
|
|
}
|
|
|
|
function amend_d(myrow, mytbl, command) {
|
|
var trunk_row = null;
|
|
if (mytbl.id == 'chtable')
|
|
trunk_row = find_next(myrow, 'TR');
|
|
if (command == 'delete') {
|
|
var ok = confirm('Confirm delete');
|
|
if (ok) {
|
|
myrow.parentNode.removeChild(myrow);
|
|
if (mytbl.id == 'chtable')
|
|
trunk_row.parentNode.removeChild(trunk_row);
|
|
}
|
|
} else if (command == 'clone') {
|
|
var newrow = myrow.cloneNode(true);
|
|
newrow.id = find_free_id('id_');
|
|
if (mytbl.id == 'chtable') {
|
|
var newrow2 = trunk_row.cloneNode(true);
|
|
newrow2.id = 'tr_' + newrow.id.substring(3);
|
|
if (trunk_row.nextSibling) {
|
|
myrow.parentNode.insertBefore(newrow2, trunk_row.nextSibling);
|
|
myrow.parentNode.insertBefore(newrow, trunk_row.nextSibling);
|
|
} else {
|
|
myrow.parentNode.appendChild(newrow);
|
|
myrow.parentNode.appendChild(newrow2);
|
|
}
|
|
} else {
|
|
if (myrow.nextSibling)
|
|
myrow.parentNode.insertBefore(newrow, myrow.nextSibling);
|
|
else
|
|
myrow.parentNode.appendChild(newrow);
|
|
}
|
|
} else if (command == 'new') {
|
|
var newrow = null;
|
|
var parent = null;
|
|
var pfx = 'id_';
|
|
if (mytbl.id == 'chtable') {
|
|
newrow = document.getElementById('chrow').cloneNode(true);
|
|
parent = document.getElementById('chrow').parentNode;
|
|
} else if (mytbl.id == 'devtable') {
|
|
newrow = document.getElementById('devrow').cloneNode(true);
|
|
parent = document.getElementById('devrow').parentNode;
|
|
} else if (mytbl.className == 'tgtable') {
|
|
newrow = mytbl.querySelector('.tgrow').cloneNode(true);
|
|
parent = mytbl.querySelector('.tgrow').parentNode;
|
|
pfx = 'tg_';
|
|
} else {
|
|
return null;
|
|
}
|
|
newrow.style['display'] = '';
|
|
newrow.id = find_free_id(pfx);
|
|
parent.appendChild(newrow);
|
|
if (mytbl.id == 'chtable') {
|
|
var newrow2 = document.getElementById('trrow').cloneNode(true);
|
|
newrow2.id = 'tr_' + newrow.id.substring(3);
|
|
parent.appendChild(newrow2);
|
|
}
|
|
return newrow;
|
|
}
|
|
}
|
|
|
|
function f_save_list(ele) {
|
|
var flist = [];
|
|
for (var nac in enable_status) {
|
|
if (enable_status[nac])
|
|
flist.push(nac.toString(10));
|
|
}
|
|
|
|
if (flist.length == 0) {
|
|
jAlert('Cannot disable all NACs - request ignored.', 'Warning');
|
|
}
|
|
document.getElementById('save_list_row').style['display'] = 'none';
|
|
enable_changed = false;
|
|
send_command('settings-enable', flist.join(','));
|
|
}
|
|
|
|
function f_enable_changed(ele, nac) {
|
|
enable_status[nac] = ele.checked;
|
|
enable_changed = true;
|
|
document.getElementById('save_list_row').style['display'] = '';
|
|
}
|
|
|
|
|
|
function config_list(d) {
|
|
var html = '';
|
|
html += '<select id="config_select" name="cfg-list">';
|
|
for (var file in d['data']) {
|
|
html += '<option value="' + d['data'][file] + '">' + d['data'][file] + '</option>';
|
|
}
|
|
html += '<option value="New Configuration">New Configuration</option>';
|
|
html += '</select>';
|
|
document.getElementById('cfg_list_area').innerHTML = html;
|
|
}
|
|
|
|
function config_data(d) {
|
|
var cfg = edit_l(d['data'], true);
|
|
open_editor();
|
|
var chtable = document.getElementById('chtable');
|
|
var devtable = document.getElementById('devtable');
|
|
var chrow = document.getElementById('chrow');
|
|
var devrow = document.getElementById('devrow');
|
|
for (var device in cfg['devices'])
|
|
rollup_row('dev', amend_d(devrow, devtable, 'new'), cfg['devices'][device]);
|
|
for (var channel in cfg['channels'])
|
|
rollup_row('ch', amend_d(chrow, chtable, 'new'), cfg['channels'][channel]);
|
|
rollup_rx_rows(cfg['backend-rx']);
|
|
}
|
|
|
|
|
|
function open_editor() {
|
|
document.getElementById('edit_settings').style['display'] = '';
|
|
var rows = document.querySelectorAll('.dynrow');
|
|
var ct = 0;
|
|
for (var r in rows) {
|
|
var row = rows[r];
|
|
ct += 1;
|
|
if (row.id && (row.id.substring(0, 3) == 'id_' || row.id.substring(0, 3) == 'tr_')) {
|
|
row.parentNode.removeChild(row);
|
|
}
|
|
}
|
|
var oldtbl = document.getElementById('rt_1');
|
|
if (oldtbl)
|
|
oldtbl.parentNode.removeChild(oldtbl);
|
|
var tbl = document.getElementById('rxopt-table');
|
|
var newtbl = tbl.cloneNode(true);
|
|
newtbl.id = 'rt_1';
|
|
newtbl.style['display'] = '';
|
|
var rxrow = newtbl.querySelector('.rxrow');
|
|
var advrow = newtbl.querySelector('.advrow');
|
|
rxrow.id = 'rx_1';
|
|
advrow.id = 'rx_2';
|
|
if (tbl.nextSibling)
|
|
tbl.parentNode.insertBefore(newtbl, tbl.nextSibling);
|
|
else
|
|
tbl.parentNode.appendChild(newtbl);
|
|
}
|
|
|
|
function find_next(e, tag) {
|
|
var n = e.nextSibling;
|
|
for (var i = 0; i < 25; i++) {
|
|
if (n == null)
|
|
return null;
|
|
if (n.nodeName == tag)
|
|
return n;
|
|
n = n.nextSibling;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function find_free_id(pfx) {
|
|
for (var seq = 1; seq < 5000; seq++) {
|
|
var test_id = pfx + seq;
|
|
var ele = document.getElementById(test_id);
|
|
if (!ele)
|
|
return test_id;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function f_trunked(e) {
|
|
var row = find_parent(e, 'TR');
|
|
var trrow = document.getElementById('tr_' + row.id.substring(3));
|
|
trrow['style']['display'] = e.checked ? '' : 'none';
|
|
}
|
|
|
|
function read_write_sel(sel_node, def) {
|
|
var result = [];
|
|
var elist = sel_node.querySelectorAll('option');
|
|
for (var e in elist) {
|
|
var ele = elist[e];
|
|
if (def) {
|
|
if (!def[sel_node.name])
|
|
return;
|
|
var options = def[sel_node.name].split(',');
|
|
var opts = {};
|
|
for (var o in options)
|
|
opts[options[o]] = 1;
|
|
if (ele.value in opts)
|
|
ele.selected = true;
|
|
else
|
|
ele.selected = false;
|
|
} else {
|
|
if (ele.selected)
|
|
result.push(ele.value);
|
|
}
|
|
}
|
|
if (!def)
|
|
return result.join();
|
|
}
|
|
|
|
function read_write(elist, def) {
|
|
var result = {};
|
|
for (var e in elist) {
|
|
var ele = elist[e];
|
|
if (ele.nodeName == 'INPUT') {
|
|
if (ele.type == 'text')
|
|
if (def)
|
|
ele.value = def[ele.name];
|
|
else
|
|
result[ele.name] = ele.value;
|
|
else if (ele.type == 'checkbox')
|
|
if (def)
|
|
ele.checked = def[ele.name];
|
|
else
|
|
result[ele.name] = ele.checked;
|
|
} else if (ele.nodeName == 'SELECT') {
|
|
if (def)
|
|
read_write_sel(ele, def);
|
|
else
|
|
result[ele.name] = read_write_sel(ele, def);
|
|
}
|
|
}
|
|
if (!def)
|
|
return result;
|
|
}
|
|
|
|
function rollup_row(which, row, def) {
|
|
var elements = Array.from(row.querySelectorAll('input,select'));
|
|
if (which == 'ch') {
|
|
var trrow = document.getElementById('tr_' + row.id.substring(3));
|
|
var trtable = trrow.querySelector('table.trtable');
|
|
elements = elements.concat(Array.from(trtable.querySelectorAll('input,select')));
|
|
if (def)
|
|
trrow.style['display'] = def['trunked'] ? '' : 'none';
|
|
} else if (which == 'rx') {
|
|
var advrow = document.getElementById('rx_2');
|
|
elements = elements.concat(Array.from(advrow.querySelectorAll('input,select')));
|
|
}
|
|
var result = read_write(elements, def);
|
|
if (which == 'ch') {
|
|
var tgtable = trrow.querySelector('table.tgtable');
|
|
var tgrow = trrow.querySelector('tr.tgrow');
|
|
if (def) {
|
|
for (var k in def['tgids']) {
|
|
var val = def['tgids'][k];
|
|
var newrow = amend_d(tgrow, tgtable, 'new');
|
|
var inputs = newrow.querySelectorAll('input');
|
|
read_write(inputs, {
|
|
'tg_id': k,
|
|
'tg_tag': val
|
|
});
|
|
}
|
|
} else {
|
|
var tgids = {};
|
|
var rows = tgtable.querySelectorAll('tr.tgrow');
|
|
for (var i = 0; i < rows.length; i++) {
|
|
if (rows[i].id == null || rows[i].id.substring(0, 3) != 'tg_')
|
|
continue;
|
|
var inputs = rows[i].querySelectorAll('input');
|
|
var vals = read_write(inputs, null);
|
|
tgids[vals['tg_id']] = vals['tg_tag'];
|
|
}
|
|
result['tgids'] = tgids;
|
|
}
|
|
}
|
|
if (!def)
|
|
return result;
|
|
}
|
|
|
|
function rollup(which, def) {
|
|
var result = [];
|
|
var mytbl = document.getElementById(which + 'table');
|
|
var elements = mytbl.querySelectorAll('.dynrow');
|
|
for (var e in elements) {
|
|
var row = elements[e];
|
|
if (row.id != null && row.id.substring(0, 3) == 'id_')
|
|
result.push(rollup_row(which, row, def));
|
|
}
|
|
if (!def)
|
|
return result;
|
|
}
|
|
|
|
function rollup_rx_rows(def) {
|
|
return rollup_row('rx', document.getElementById('rx_1'), def);
|
|
}
|
|
|
|
function f_save() {
|
|
var name = document.getElementById('config_name');
|
|
if (!name.value) {
|
|
alert('Name is required');
|
|
name.focus();
|
|
return;
|
|
}
|
|
if (name.value == 'New Configuration') {
|
|
alert("'" + name.value + "' is a reserved name, please retry");
|
|
name.value = '';
|
|
name.focus();
|
|
return;
|
|
}
|
|
var cfg = {
|
|
'devices': rollup('dev', null),
|
|
'channels': rollup('ch', null),
|
|
'backend-rx': rollup_rx_rows(null)
|
|
};
|
|
cfg = edit_l(cfg, false);
|
|
var request = {
|
|
'name': name.value,
|
|
'value': cfg
|
|
};
|
|
send_command('config-save', request);
|
|
f_list();
|
|
}
|
|
|
|
function f_list() {
|
|
var inp = document.getElementById('include_tsv');
|
|
send_command('config-list', inp.checked ? 'tsv' : '');
|
|
}
|
|
|
|
function f_start() {
|
|
var sel = document.getElementById('config_select');
|
|
if (!sel)
|
|
return;
|
|
var val = read_write_sel(sel, null);
|
|
if (!val || val == 'New Configuration') {
|
|
alert('You must select a valid configuration to start');
|
|
return;
|
|
}
|
|
if (val.indexOf('[TSV]') >= 0) {
|
|
alert('TSV files not supported. First, invoke "Edit"; inspect the resulting configuration; then click "Save".');
|
|
return;
|
|
}
|
|
send_command('rx-start', val);
|
|
}
|
|
|
|
|
|
function f_stop() {
|
|
send_command('rx-stop', '');
|
|
}
|
|
|
|
function f_load() {
|
|
var sel = document.getElementById('config_select');
|
|
if (!sel)
|
|
return;
|
|
var val = read_write_sel(sel, null);
|
|
if (!val) {
|
|
alert('You must select a configuration to edit');
|
|
return;
|
|
}
|
|
if (val == 'New Configuration') {
|
|
open_editor();
|
|
} else {
|
|
send_command('config-load', val);
|
|
var ele = document.getElementById('config_name');
|
|
ele.value = val;
|
|
}
|
|
}
|
|
|
|
function show_advanced(o) {
|
|
var tbl = find_parent(o, 'TABLE');
|
|
var row = tbl.querySelector('.advrow');
|
|
toggle_show_hide(o, row);
|
|
}
|
|
|
|
function toggle_show_hide(o, ele) {
|
|
if (o.value == 'Show') {
|
|
o.value = 'Hide';
|
|
ele.style['display'] = '';
|
|
} else {
|
|
o.value = 'Show';
|
|
ele.style['display'] = 'none';
|
|
}
|
|
}
|
|
|
|
function f_tags(o) {
|
|
var mydiv = find_parent(o, 'DIV');
|
|
var tbl = mydiv.querySelector('.tgtable');
|
|
toggle_show_hide(o, tbl);
|
|
}
|
|
|
|
// communicate w/ HTTP server functions
|
|
|
|
function http_req_cb() {
|
|
req_cb_count += 1;
|
|
s = http_req.readyState;
|
|
if (s != 4) {
|
|
nfinal_count += 1;
|
|
return;
|
|
}
|
|
if (http_req.status != 200) {
|
|
n200_count += 1;
|
|
return;
|
|
}
|
|
r200_count += 1;
|
|
dispatch_commands(http_req.responseText);
|
|
}
|
|
|
|
function send_command(command, data) {
|
|
var d = {
|
|
'command': command,
|
|
'data': data
|
|
};
|
|
send(d);
|
|
}
|
|
|
|
function send(d) {
|
|
request_count += 1;
|
|
if (send_queue.length >= SEND_QLIMIT) {
|
|
send_qfull += 1;
|
|
send_queue.unshift();
|
|
}
|
|
send_queue.push(d);
|
|
send_process();
|
|
}
|
|
|
|
function send_process() {
|
|
s = http_req.readyState;
|
|
if (s != 0 && s != 4) {
|
|
send_busy += 1;
|
|
return;
|
|
}
|
|
http_req.open('POST', '/');
|
|
http_req.onreadystatechange = http_req_cb;
|
|
http_req.setRequestHeader('Content-type', 'application/json');
|
|
cmd = JSON.stringify(send_queue);
|
|
send_queue = [];
|
|
http_req.send(cmd);
|
|
}
|
|
|
|
|
|
function save_tsv(command, data, file) {
|
|
var d = {
|
|
'command': command,
|
|
'data': data,
|
|
'file': file
|
|
};
|
|
send(d);
|
|
}
|
|
// command, data
|
|
|
|
function reload_tsv(data) { // commands OP25 to reload the tsv talkgroup/source tag data
|
|
console.log('sending reload command...');
|
|
var command = "reload_tags";
|
|
var d = {
|
|
'command': command,
|
|
'data': data
|
|
};
|
|
send(d);
|
|
} |