From b9752d54bf6c54b75a621c37539ce12122db8787 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 31 Jan 2022 22:43:34 -0500 Subject: [PATCH] New RX Two Pane Mode thx Trip --- .../www/www-static/index.html | 17 +- op25/gr-op25_repeater/www/www-static/main.css | 44 + op25/gr-op25_repeater/www/www-static/main.js | 5386 +++++++++-------- 3 files changed, 2756 insertions(+), 2691 deletions(-) diff --git a/op25/gr-op25_repeater/www/www-static/index.html b/op25/gr-op25_repeater/www/www-static/index.html index 2df85a8..94b139b 100644 --- a/op25/gr-op25_repeater/www/www-static/index.html +++ b/op25/gr-op25_repeater/www/www-static/index.html @@ -35,6 +35,8 @@ +
+
@@ -48,7 +50,8 @@ -
  •  
  • +
  • +
    @@ -339,7 +342,7 @@ -   + —   @@ -347,7 +350,7 @@ -   + —   @@ -735,8 +738,8 @@
    - - +
    +
    @@ -845,5 +848,7 @@
    + + - + \ No newline at end of file diff --git a/op25/gr-op25_repeater/www/www-static/main.css b/op25/gr-op25_repeater/www/www-static/main.css index 0dd909f..65a936a 100644 --- a/op25/gr-op25_repeater/www/www-static/main.css +++ b/op25/gr-op25_repeater/www/www-static/main.css @@ -165,6 +165,38 @@ body { background-color: var(--bg-color); } +/* BEGIN 2 COLUMN LAYOUT ELEMENTS */ + + * { + box-sizing: border-box; + } + + /* Create two equal columns that floats next to each other */ + .column { + float: left; + width: 50%; + padding: 5px; +/* height: 300px; /* Should be removed. Only for demonstration */ */ + } + + /* Clear floats after the columns */ + .row:after { + content: ""; + display: table; + clear: both; + } + + /* Responsive layout - makes the two columns stack on top of each other instead of next to each other */ + @media screen and (max-width: 740px) { + .column { + width: 100%; + } + } + + +/* END 2 COLUMN LAYOUT ELEMENTS */ + + .main { margin-top: 5px; /* make margin-top 60px for fixed nav-bar, see also .nav-bar below. */ @@ -678,6 +710,18 @@ div.adjacent { /* adjacent sites container that holds the table */ font-weight: bold; } +.nav-button2 { + background-color: var(--nav-background); + background: linear-gradient(var(--nav-grad-1), var(--nav-grad-2)); + color: var(--button-text-2); + width: 15px; + border: 0px; + padding: 20px; + display: block; + font-size: 14px; + font-weight: bold; +} + .nav-button-active { background-color: var(--button-grad-2); background: linear-gradient(var(--button-grad-2), var(--button-grad-1)); diff --git a/op25/gr-op25_repeater/www/www-static/main.js b/op25/gr-op25_repeater/www/www-static/main.js index 62577c1..f59ede4 100644 --- a/op25/gr-op25_repeater/www/www-static/main.js +++ b/op25/gr-op25_repeater/www/www-static/main.js @@ -1,2685 +1,2701 @@ -// Copyright 2017, 2018, 2019, 2020, 2021, 2022 Max H. Parke KA1RBI -// Copyright 2020, 2021, 2022 Michael Rose -// -// 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. - -var lastUpdated = "09-Jan-2022"; - -var d_debug = 0; -var http_req = new XMLHttpRequest(); -var counter1 = 0; -var error_val = null; -var current_tgid = null; -var active_tgid = null; -var active_nac = null; -var send_busy = 0; -var send_qfull = 0; -var send_queue = []; -var req_cb_count = 0; -var request_count = 0; -var nfinal_count = 0; -var n200_count = 0; -var r200_count = 0; -var SEND_QLIMIT = 5; -var summary_mode = true; -var enable_changed = false; -var enable_status = []; -var last_srcaddr = []; -var last_srctag = []; -var last_alg = []; -var last_algid = []; -var last_keyid = []; -var tgid_files = {}; -var srcid_files = {}; -var channel_id = {}; -var freqTable = true; -var event_source = null; // must be in global scope for Babysitter to work. - -window.g_change_freq = []; -window.g_cc_event = []; -window.src = []; - -var intvAlias = null; -var intvCss = null; -localStorage.AliasTableUpdated == true; -localStorage.ColorsTableUpdated == true; - -var encsym = "∅"; - -const zeroPad = (num, places) => String(num).padStart(places, '0'); // leading zeros for single digit time values - -function do_onload() { - $('#div_status').show(window.animateSpeed); - $('#babysitter').hide(); - $('#b1').addClass('nav-button-active'); - $('#uiupdated').html(lastUpdated); - document.documentElement.setAttribute('data-theme', 'dark'); - window.siteAlias = null; - - intvAlias = setInterval(getSiteAlias, 5000); - intvCss = setInterval(generateCSS, 4000); - resetFileReload = setInterval(rstFileReload, 12000) - - connect(); - generateCSS(); - getSiteAlias(); - beginJsonSettings(); - - accColorSel(); - for (i = 1; i < 100; i ++) { - $('#unk_default').append(new Option(i, i)); - } - window.animateSpeed = $('#ani_speed').val(); -} - -// vars set when saving things... -// localStorage.AliasTableUpdated == true; -// localStorage.ColorsTableUpdated == true; - -$(document).ready(function() { -// // populate url into oplog url field -// var x = window.location.origin.split( ':' ); -// var y = x[0] + ':' + x[1] + ':5000'; -// $('#oplogUrl').val(y); - loadHelp(); -}); - -function connect() { - event_source = new EventSource('/stream'); - event_source.addEventListener('message', eventsource_listener); - event_source.onerror = function() { - event_source.close(); - } - setReconnect(); -} - -function eventsource_listener(event) { - dispatch_commands(event.data); -} - -// Babysitter - watches /stream, reacts when lost/restored -function setReconnect() { - // readyState values: 0 = connecting, 1 = open, 2 = closed - var reconnecting = false; - - // do we need to setInterval with a var here since we call setReconnect again? - - setInterval(() => { - if (event_source.readyState == 2) { - reconnecting = true; - $('#babysitter').show(); - $('#estat').html(event_source.readyState); - connect(); - } else if (reconnecting) { - reconnecting = false - $('#estat').html(event_source.readyState); - if (!event_source.readyState == 0) - $('#estat').html('OK'); - $('#babysitter').hide(); - } - }, 3000); -} - - -function rstFileReload() { - if (event_source == null || event_source.readyState != 1) - return; - // forces the alias and colors info to reload in case user - // makes changes to them from another browser. - localStorage.AliasTableUpdated == true; - localStorage.ColorsTableUpdated == true; -} - - -function navOplog() { - window.open($('#oplogUrl').val()); -} - -function phaseType(nac) { - // reset the UI to a Phase 1 display (untested) - window[nac + 'flavor'] = null; -} - -function nav_update(command) { - var names = [ - 'b1', - 'b2', - 'b3', - 'b4', - 'b5', - 'b7' - ]; - var bmap = { - 'status': 'b1', - 'settings': 'b2', - 'rx': 'b3', - 'help': 'b4', - 'view': 'b5', - 'about': 'b7' - }; - var id = bmap[command]; - for (var id1 in names) { - b = document.getElementById(names[id1]); - if (names[id1] == id) { - b.className = 'nav-button-active'; - } else { - b.className = 'nav-button'; - } - } -} - -function f_select(command) { - var div_list = [ - 'status', - 'settings', - 'rx', - 'help', - 'about' - ]; - - var orig_command = command; - - if (command == 'rx') { - $('#div_logs').show(window.animateSpeed); - command = 'status'; - summary_mode = false; - } else { - summary_mode = true; - $('#div_logs').hide(window.animateSpeed); - } - - for (var i = 0; i < div_list.length; i++) { - (command == div_list[i]) ? $('#div_' + div_list[i]).show(window.animateSpeed) : $('#div_' + div_list[i]).hide(window.animateSpeed); - } - - (command == 'status' && summary_mode == true) ? $('#div_images').show(window.animateSpeed) : $('#div_images').hide(window.animateSpeed); - - (command == 'status') ? $('#controls').show() : $('#controls').hide(); - - nav_update(orig_command); - - if (command == 'settings') - f_list(); -} - -function rx_update(d) { - if (d['files'].length > 0) { - for (var i = 0; i < d['files'].length; i++) { - var img = document.getElementById('img' + i); - if (img['src'] != d['files'][i]) { - img['src'] = d['files'][i]; - img.style['display'] = ''; - } - } - } - error_val = d['error']; - fine_tune = d['fine_tune']; -} - -// frequency, system, and talkgroup display -function change_freq(d) { // d json_type = change_freq - t = 'TDMA'; - var t = 'FDMA'; - if ((d['tdma'] == 0) || (d['tdma'] == 1)) { - t = 'TDMA ' + d['tdma'] + ''; - } - - $('#stx').html(t); - - var displayTgid = '—'; - var displayTag = ' '; - var display_src = '—'; - var display_alg = '—'; - var display_keyid = '—'; - var display_srctag = '—'; - var e_class = 'value'; - var trunc = $('#valTruncate').val(); - - last_srcaddr[d['nac']] = d['srcaddr']; - last_alg[d['nac']] = d['alg']; - last_algid[d['nac']] = d['algid']; - last_keyid[d['nac']] = d['keyid']; - last_srctag[d['nac']] = d['srcaddr.tag']; - if (d['tgid'] != null) { - displayTgid = d['tgid']; - displayTag = d['tag'].substring(0, trunc); - - if (d['srcaddr'] != null && d['srcaddr'] > 0) { - display_src = d['srcaddr']; - display_srctag = d['srcaddr_tag']; - } - - display_alg = d['alg']; - - if (d['algid'] != 128) { - display_keyid = hex(d['keyid']); - e_class = 'red_value'; - } - } - - // main display - system, talkgroup, encryption, keyid, source addr display - - var d_sys = 'system' in d ? d['system'].substring(0, trunc) : 'Undefined'; - - var html = ''; - html += ''; - - html += ''; - html += ''; - - html += ''; - html += ''; - - html += ''; - html += ''; - - html += ''; - html += ''; - - html += ''; - html += ''; - html += ''; - - html += ''; - html += '
    ' + d_sys + ''; - html += 'Frequency
    ' + freqDisplay(d['freq'] / 1000000) + '
    ' + displayTag + ''; - html += 'Talkgroup ID
    ' + displayTgid + ''; - html += '
    '; - html += 'Encryption
    ' + display_alg + ''; - html += '
    '; - html += 'Key ID
    ' + display_keyid + ''; - html += '
    '; - html += 'Source Addr
    ' + display_src + ''; - html += '
    '; - - $('#div_s2').html(html).show(); - - active_nac = d['nac']; - active_tgid = d['tgid']; - if (d['tgid'] != null) { - current_tgid = d['tgid']; - } - - // color/style for main display - - var fontStyle = $('#valFontStyle').val(); - var tgSize = parseInt($('#valTagFont').val()); - var sysSize = parseInt($('#valSystemFont').val()); - var defColor = $('#sysColor').val(); - var sysColor = ""; - var tagColor = ""; - var clr = getProperty('.c' + d.tag_color, 'color', 'tgcolors'); - var ani = getProperty('.c' + d.tag_color, 'animation', 'tgcolors'); - var bg = getProperty('.c' + d.tag_color, 'backgroundColor', 'tgcolors'); - - sysColor = (cbState('color_main_sys') && d['tag_color']) ? clr : defColor; - tagColor = (cbState('color_main_tag') && d['tag_color']) ? clr : defColor; - $('#dSys').css({"color": sysColor, "font-size": sysSize, "font-weight": fontStyle}); - $('#dTag').css({"color": tagColor, "font-size": tgSize, "font-weight": fontStyle, "animation": ani, "background-color": bg}); - -} // end change_freq - -function trunk_summary(d) { // d json_type = trunk_update - var nacs = []; - for (var nac in d) { - if (!is_digit(nac.charAt(0))) - continue; - nacs[nac] = 1; - } - var html = ''; - html += '
    '; - html += ''; - html += ''; - html += ''; - for (nac in d) { - if (!is_digit(nac.charAt(0))) - continue; - last_srcaddr[nac] = d[nac]['srcaddr']; - last_srctag[nac] = d[nac]['srcaddr_tag']; - last_alg[nac] = d[nac]['alg']; - last_algid[nac] = d[nac]['algid']; - last_keyid[nac] = d[nac]['keyid']; - if (!(nac in enable_status)) - enable_status[nac] = true; - var times = []; - var last_tsbk = d[nac]['last_tsbk'] * 1000; - var display_last_tsbk = getTime(last_tsbk); - times.push(display_last_tsbk); - var min_t = 0; - if (times.length) { - for (var i = 0; i < times.length; i++) { - if (i == 0 || times[i] < min_t) - min_t = times[i]; - } - times = min_t; - } else { - times = ' '; - } - var ns = parseInt(nac).toString(16); - html += ''; - var tf = d[nac]['tgid_tags_file']; - var sf = d[nac]['unit_id_tags_file']; - var sysid = d[nac]['sysid']; - var checked = enable_status[nac] ? 'checked' : ''; - - html += ''; - // checkbox styling nonsense above - html += ''; - html += ''; - html += ''; - html += ''; - html += '' - html += ''; - } - var display = ''; - if (!enable_changed) - display = 'none'; - html += ''; - html += '
    EnabledNACSystemLast TSBKTSBK CountAlias TSV
    '; - html += '' + ns.toUpperCase() + '' + d[nac]['sysname'] + '' + times + '' + comma(d[nac]['tsbks']) + ' '; - if (tf) - html +='TG'; - if (sf) - html +='  SRC'; - html += '  SA'; - html += '
    '; - html += ''; - html += '
    '; - return html; -} // end trunk_summary() - -// additional system info: wacn, sysID, rfss, site id, freq table, adjc sites, secondary control channels, freq error -function trunk_detail(d) { // d json_type = trunk_update - var html = ''; - var alias, sysid, rfss, site; - var error_val = fine_tune = "—"; - for (var nac in d) { - if (!is_digit(nac.charAt(0))) - continue; - var p2 = window[nac + 'flavor']; // if phase 2 was detected, add phase 2 layouts - var flavor = "Phase 1"; - if (p2) - flavor = "Phase 2"; - last_srcaddr[nac] = d[nac]['srcaddr']; - last_srctag[nac] = d[nac]['srcaddr_tag']; - last_alg[nac] = d[nac]['alg']; - last_algid[nac] = d[nac]['algid']; - last_keyid[nac] = d[nac]['keyid']; - error_val = sessionStorage.errorVal; - fine_tune = sessionStorage.fineTune; - - sysid = d[nac]['sysid']; - rfss = d[nac]['rfid']; - site = d[nac]['stid']; - - // use the Site Alias if defined, otherwise use d/nac/sysname - if (window.siteAlias != null && window.siteAlias[sysid] && window.siteAlias[sysid][rfss] && window.siteAlias[sysid][rfss][site]) { - alias = window.siteAlias[sysid][rfss][site]['alias']; - } else { - alias = d[nac]['sysname']; - } - - html += '
    '; - html += ''; - - html += ''; // 1 - html += ''; // 2 - html += ''; // 3 - html += ''; // 4 - html += ''; // 5 - html += ''; // 6 - - html += ''; - - // 1 - html += ''; - - // 2 - html += ''; - - // 3 - html += ''; - - // 4 - html += ''; - - // 5 - html += ''; - - - // 6 - html += ''; - - html += ''; - - // row 2 - html += ''; - - // 1 - html += ''; - - // 2, 3, 4 - scc = ''; - for (i = 0; i < d[nac]['secondary'].length; i++) { - scc += freqDisplay(d[nac]['secondary'][i] / 1000000); - if ( (i+1) != d[nac]['secondary'].length) - scc += '   '; // space between freqs - } - - html += ''; - - // 5 - html += ''; - - // 6 - var last_tsbk = d[nac]['last_tsbk'] * 1000; - var display_last_tsbk = getTime(last_tsbk); - html += ''; - - html += ''; - - if (cbState('showBandPlan')) { - - var zsys = d[nac]['sysid']; - html += ''; - } - - html += '
    ' + alias + '
    '; - html += 'System ID
    ' + '0x' + parseInt(d[nac]['sysid']).toString(16).toUpperCase(); - html += '
    '; - html += 'NAC
    ' + '0x' + parseInt(nac).toString(16).toUpperCase(); - html += '
    '; - html += 'WACN
    ' + '0x' + parseInt(d[nac]['wacn']).toString(16).toUpperCase(); - html += '
    '; - html += 'RFSS ID
    ' + d[nac]['rfid'] - html += '
    '; - html += 'Site ID
    ' + d[nac]['stid'] - html += '
    '; - html += 'Type
    ' + flavor + '
    '; - html += '
    '; - html += 'Control Channel
    ' + freqDisplay(d[nac]['rxchan'] / 1000000); - html += '
    '; - - html += 'Secondary Control Channels
    '; - - if (d[nac]['secondary'].length) { - html += scc; - } else { - html += 'None'; - } - - html += '
    '; - html += 'TSBK
    ' + comma(d[nac]['tsbks']); - html += '
    '; - html += 'Last TSBK
    ' + display_last_tsbk + ''; - html += '
    '; - - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - for (p in channel_id[d[nac]['sysid']]) { - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - } - html += '
    IDTypeBase FrequencyTx OffsetSpacing (kHz)Slots
    ' + channel_id[zsys][p]['iden'] + '' + channel_id[zsys][p]['type'] + '' + freqDisplay(channel_id[zsys][p]['freq']) + '' + freqDisplay(channel_id[zsys][p]['offset']) + '' + (channel_id[zsys][p]['step'] * 100) + '' + channel_id[zsys][p]['slots'] + '
    '; - - html += '
    '; - - // system frequencies table // d json_type = trunk_update - html += '
    '; - - if (freqTable == true) { // show/hide table with F key - - html += ''; - - html += ''; // 1 Freq - if (cbState('showLast')) - html += ''; // 2 Last - html += ''; // 3 tgid - html += ''; // 4 tgtag - html += ''; // 5 enc - html += ''; // 6 srcaddr - html += ''; // 7 srctag - - html += ''; - html += ''; // 1 - if (cbState('showLast')) - html += ''; // 2 - html += ''; // 3 / 4 / 5 - html += ''; // 6 / 7 - html += ''; - - var c, src_c, sc_src, sf_freq, sf_last, sf_hits, sf_timeout; - - // save keystrokes! - var fd = 'frequency_data'; - var ft = 'frequency_tracking'; - var td = 'talkgroup_data'; - var ts = 'time_slot'; - var calls = 'calls'; - - sf_timeout = 0; // delay before clearing the tg info - - // #frequency# - - //calls - var sf_count = []; // "count" - var sf_lastactive = []; // "last_active" - var sf_protected = []; // "protected" - var sf_starttime = []; // "start_time" - var sf_endtime = []; // "end_time" - - //tgid - var sf_tgid = []; // "tg_id" - var sf_tgtag = []; // "tag" - var sf_tgcolor = []; // "color" - - //srcaddr - var sf_srcaddr = []; // "unit_id" - var sf_srctag = []; // "tag" - var sf_srccolor = []; // "color" - - var slot, tgid, x, y; - var sf_enc = []; - var sf_la = []; - - var sf_last = 0; - - sf_tgid[0] = " "; - sf_tgtag[0] = " "; - sf_srcaddr[0] = " "; - sf_srctag[0] = " "; - sf_enc[0] = " "; - sf_tgcolor[0] = 0; - sf_srccolor[0] = 0; - sf_la[0] = 0; - - sf_tgid[1] = " "; - sf_tgtag[1] = " "; - sf_srcaddr[1] = " "; - sf_srctag[1] = " "; - sf_enc[1] = " "; - sf_tgcolor[1] = 0; - sf_srccolor[1] = 0; - sf_la[1] = 0; - - for (var freq in d[nac][ft]) { - - // temp for testing -- semi-perm? - if ( d[nac][ft][freq]['tdma'] == true ) { - zt = "T"; - } else { - zt = " "; - } - // end temp - - slot = 0; - - for (var call of d[nac][ft][freq][calls]) { - - if (call == null) { - - sf_tgid[slot] = " "; - sf_tgtag[slot] = " "; - sf_srcaddr[slot] = " "; - sf_srctag[slot] = " "; - sf_enc[slot] = " "; - - slot++; - continue; - } - - if (call.end_time == 0) { // if end_time != 0 then the call is dead and should not be displayed in Active Freq table - - sf_tgid[slot] = call.tgid.tg_id; - - if (call.tgid.tag) { - sf_tgtag[slot] = call.tgid.tag; - } else { - sf_tgtag[slot] = "Talkgroup " + call.tgid.tg_id; - call.tgid.color = $('#unk_default').val(); - } - - sf_tgcolor[slot] = call.tgid.color; - - sf_srcaddr[slot] = call.srcaddr.unit_id ? call.srcaddr.unit_id : " "; - sf_srctag[slot] = call.srcaddr.tag ? call.srcaddr.tag : " "; - sf_srccolor[slot] = call.srcaddr.color; - - sf_protected[slot] = call['protected']; // protected is a reserved word in JS so can't use dot notation here - sf_enc[slot] = (sf_protected[slot] == true) ? encsym : " "; - - } // end if call.end_time - - sf_count[slot] = call.count; - sf_lastactive[slot] = call.last_active; - - sf_la[slot] = parseInt(d.time - sf_lastactive[slot], 10); - - slot++; - - } // end for var call in calls - - sf_freq = freqDisplay(parseInt(freq) / 1000000); - - sf_last = d[nac][ft][freq]['last_active']; - sf_last = parseFloat(sf_last).toFixed(0); - - if (sf_la[0] > sf_timeout) { - sf_tgid[0] = " "; - sf_tgtag[0] = " "; - sf_srcaddr[0] = " "; - sf_srctag[0] = " "; - sf_enc[0] = " "; - } - - if (sf_la[1] > sf_timeout) { - sf_tgid[1] = " "; - sf_tgtag[1] = " "; - sf_srcaddr[1] = " "; - sf_srctag[1] = " "; - sf_enc[1] = " "; - } - - for (slot = 0; slot < 2; slot++ ) { - - var c = 0; - var src_c = 0; - - if (sf_tgcolor[slot]) { - c = sf_tgcolor[slot]; - } else { - c = smartColor(sf_tgtag[slot]) - } - - if (sf_srccolor[slot]) { - src_c = sf_srccolor[slot]; - } else { - src_c = smartColor(sf_srctag[slot]) - } - - sc_src = ""; - sc = ""; - - var tfile = d[nac]['tgid_tags_file']; - var sfile = d[nac]['unit_id_tags_file']; - - // Active Frequencies Table - - html += ''; - - if (slot == 0) - html += ''; // 1 - if (slot == 1) - html += ''; // 1 - if (cbState('showLast')) - html += ''; // 2 - html += ''; // 3 - html += ''; // 4 - html += ''; // 5 - html += ''; // 6 - html += ''; // 7 - - html += ''; -// if (!p2 || zt == " " ) break; - if (!p2 || zt == " " || ( sf_la[slot] > 120 && cbState('colSlot2')) ) break; - } // end for slot - } // end for freq - - html += '
    Frequency Last TalkgroupSource
    ' + sf_freq + ' ' + zt + ' / 2     ' + sf_la[slot] + '' + sc + sf_tgid[slot] + '' + sc + sf_tgtag[slot] + '' + sf_enc[slot] + '' + sc_src + sf_srcaddr[slot] + '' + sc_src + sf_srctag[slot] + '
    '; - } // end (freqTable == true) - - if (cbState('show_adj')) - html += adjacent_data(d[nac]['adjacent_data']); - - } // end for nac in d - - return html; - -} // end trunk_detail() - -function editTsv(click, source, file, nac) { // dbl click the ui to access the TSV editor - if (!click) - return; - - // source - // 1 = Active Frequencies - // 2 = Call History - - window.getSelection().removeAllRanges(); // unselect the text that was double-clicked on - var cname = click.attributes.name; - cname = (cname.value); - - - if (source == 2 && (cname == "tgid" || cname == "tag")) { // Call History Talkground dbl clicked - var sid = file; - file = tgid_files[sid]; - } - - if (source == 2 && (cname == "srcid" || cname == "srctag")) { // Call History Source dbl clicked - var sid = file; - file = srcid_files[sid]; - } - - if ( (cname =="tgid" || cname == "tag") && (!file || file == "null")) { // null is being stored as a string in window.tgid_files - jAlert ("Talkgroup tag TSV file not specified in config file. Cannot edit.", "Error"); - return; - } - - if ( (cname =="srcid" || cname == "srctag") && (!file || file == "null")) { - jAlert ("Source tag TSV file not specified in config file. Cannot edit.", "Error"); - return; - } - - var id = 0; - var url, x, y, tsv; - - // Active Frequencies // Call History - // cellIndex[2] = Talkgroup ID // cellIndex[3] = Talkgroup ID - // cellIndex[3] = Talkgroup Tag // cellIndex[4] = Talkgroup Tag - // cellIndex[5] = Source ID // cellIndex[5] = Source ID - // cellIndex[6] = Source Tag // cellIndex[6] = Source Tag - - switch (source) { - case 1: // sysfreq Table TD - x = 2; // the cell index of tgid - y = 5; // the cell index of srcid - - switch (cname) { - case 'tgid': - id = click.innerText; - url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=tg&id=' + id + '&nac=' + nac; - tsv = window.open(url, 'tsv'); - break; - - case 'tag': - id = click.parentElement.cells[x].innerText; - url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=tg&id=' + id + '&nac=' + nac; - tsv = window.open(url, 'tsv'); - break; - - case 'srcid': - id = click.innerText; - url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=src&id=' + id + '&nac=' + nac; - tsv = window.open(url, 'tsv'); - break; - - case 'srctag': - id = click.parentElement.cells[y].innerText; - url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=src&id=' + id + '&&nac=' + nac; - tsv = window.open(url, 'tsv'); - break; - } - break; - - case 2: // Call History Table SPAN - x = 3; // the cell index of tgid - y = 5; // the cell index of srcid - - switch (cname) { - case 'tgid': - id = click.parentElement.parentElement.cells[x].innerText; - url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=tg&id=' + id + '&nac=' + nac; - tsv = window.open(url, 'tsv'); - break; - - case 'tag': - id = click.parentElement.parentElement.cells[x].innerText; - url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=tg&id=' + id + '&nac=' + nac;; - tsv = window.open(url, 'tsv'); - break; - - case 'srcid': - id = click.parentElement.parentElement.cells[y].innerText; - url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=src&id=' + id + '&nac=' + nac; - tsv = window.open(url, 'tsv'); - break; - - case 'srctag': - id = click.parentElement.parentElement.cells[y].innerText; - url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=src&id=' + id + '&nac=' + nac; - tsv = window.open(url, 'tsv'); - break; - } // end switch cname - break; - } // end switch source -} // end editTsv() - -// adjacent sites table -function adjacent_data(d) { - // d json_type = trunk_update - if (Object.keys(d).length < 1) - return ''; - // var html = "
    "; // open div-adjacent - var html = '
    '; - - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - - html += ''; - var ct = 0; - var alias, sysid, rfss, site; - for (var freq in d) { - sysid = d[freq]['sysid']; - rfss = d[freq]['rfid']; - site = d[freq]['stid']; - alias = "-"; - if (window.siteAlias != null && window.siteAlias[sysid] && window.siteAlias[sysid][rfss] && window.siteAlias[sysid][rfss][site]) { - alias = window.siteAlias[sysid][rfss][site]['alias']; - } else { - alias = 'Site ' + d[freq]['stid']; - } - - html += ''; - html += ''; - html += ''; - } - html += '
    Adjacent SitesFrequencySystem IDRFSS IDSite IDUplink
    ' + alias + ''+ freqDisplay(freq / 1000000) + '' + d[freq]['sysid'].toString(16).toUpperCase() + '' + d[freq]['rfid'] + '' + d[freq]['stid'] + '' + freqDisplay(d[freq]['uplink'] / 1000000) + '
    '; - return html; // end adjacent sites table -} // end adjacent_data() - -function trunk_update(d) { - - var html; - if (summary_mode) { - html = trunk_summary(d); // home screen - } else { - html = trunk_detail(d); // RX screen - - } - - $('#div_s1').html(html); - - if (!summary_mode) - sortTable('adjacent-sites', 4); - - // display hold indicator - if (d['data']['hold_mode']) { - $('#holdIndicator').show(); - } else { - $('#holdIndicator').hide(); - } - - // display last command unless it was more than 10 seconds ago - x2 = d['data']['last_command']; - if (x2 && d['data']['last_command_time'] > -10) { - $('#lastCommand').html('Last Command
    ' + x2.toUpperCase() + '
    ' + ' ' + d['data']['last_command_time'] * -1 + ' secs ago'); - } else { - $('#lastCommand').html(''); - } - - update_data(d); -} // end trunk_update() - -function update_data(d) { // d json type = trunk_update - if (active_nac == null || active_tgid == null) - return; - - var display_src = '—'; - var display_srctag = '—'; - var display_alg = '—'; - var display_keyid = '—'; - - var e_class = 'value'; - - if (last_srcaddr[active_nac] != null) { - display_src = last_srcaddr[active_nac]; - var ele = document.getElementById('dSrc'); - if (ele != null) - ele.innerHTML = display_src; - } - - if (last_srctag[active_nac] != null) { - display_srctag = last_srctag[active_nac]; - var ele = document.getElementById('dSrctag'); - if (ele != null) - ele.innerHTML = display_srctag; - } - - if (last_algid[active_nac] == null || last_alg[active_nac] == null || last_keyid[active_nac] == null) - return; - display_alg = last_alg[active_nac]; - if (last_algid[active_nac] != 128) { - display_keyid = last_keyid[active_nac]; - e_class = 'red_value'; - } - ele = document.getElementById('dAlg'); - if (ele != null) { - ele.innerHTML = display_alg; - ele.className = e_class; - } - ele = document.getElementById('dKey'); - if (ele != null) - ele.innerHTML = display_keyid; - -} // end update_data() - -function error_tracking() { - return; // empty right now, handled in dispatch_commands switch block -} // end error_tracking - -function dispatch_commands(txt) { - if (txt == '[]') { - return; - } - - var dl = JSON.parse(txt); - - var dispatch = { - 'trunk_update': trunk_update, - 'change_freq': change_freq, - 'rx_update': rx_update, - 'config_data': config_data, - 'config_list': config_list, - 'cc_event': cc_event, - 'freq_error_tracking': error_tracking - }; - - for (var i = 0; i < dl.length; i++) { - var d = dl[i]; - if (!('json_type' in d)) - continue; - if (!(d['json_type'] in dispatch)) - continue; - var j_type = d['json_type']; - var time = getTime(new Date()); - if (d.time) - time = getTime(d.time * 1000); - - switch (j_type) { - - case 'freq_error_tracking': - - var ele, bx, cx, dx, ex, fx, gx; - $('#error_tracking').show(); - bx = d.device; - cx = d.name; - dx = d.error_band; - ex = d.freq_correction; - fx = d.freq_error; - gx = d.tuning_error; - appendErrorTable (time, bx, cx, dx, ex, fx, gx, 'errors'); - break; - - case 'change_freq': - - cb = cbState('log_cf'); - time = getTime(d['effective_time'] * 1000); - if (d.tgid && !d.tag) { - d.tag = 'Talkgroup ' + d.tgid + ''; // talkgroup tag isn't in the tsv - d.tag_color = $('#unk_default').val(); - } - sysid = d.sysid ? hex(d.sysid).toUpperCase() : "—" - var freq = d['freq'] / 1000000; - var srctag = "—"; - var color, srccolor, srcaddr; - - - if (d['tag_color']) { - color = d['tag_color']; - } else { - color = smartColor(d.tag); - d['tag_color'] = color; - } - - if (d['srcaddr_color']) { - srccolor = d['srcaddr_color']; - } else { - srccolor = smartColor(d.tag); - } - - var tag = '' + d.tag + ''; - var tgid = '' + d.tgid + ''; - - // TODO - cb not defined keeps coming up a couple times in the console, right at start up. - if (cb && d.tgid) { - appendJsonTable(time, j_type, sysid, tag, tgid, 'OP25', freq, '--', 'history'); - } - - if (d.tgid) { - srctag = (window['srct' + d.srcaddr]) ? '' + window['srct' + d.srcaddr] : "—"; - srcaddr = '' + d.srcaddr; - } - window.g_change_freq = d; - break; - - case 'trunk_update': - - cb = cbState('log_tu'); - var tf, sf, sid, a, z; - var sysid, site, rfid, color; - var grpaddr, c_grpaddr, srcaddr, c_srcaddr, talkgroup, c_talkgroup, srctag, c_srctag; - for (nac in d) { - if (Number.isInteger(parseInt(nac))) { - sysid = d[nac]['sysid']; - sid = sysid; - sysid = hex(sysid).toUpperCase(); - site = d[nac]['stid']; - rfid = d[nac]['rfid']; - grpaddr = d[nac]['grpaddr']; - srcaddr = d[nac]['srcaddr']; - - tf = d[nac]['tgid_tags_file']; - tgid_files[sid] = tf; - - sf = d[nac]['unit_id_tags_file']; - srcid_files[sid] = sf; - - if (window['tgidt' + grpaddr]) - talkgroup = window['tgidt' + grpaddr]; - - srctag = (window['srct' + srcaddr]) ? (window['srct' + srcaddr]) : " "; - - color = smartColor(talkgroup); - srccolor = smartColor(talkgroup); - - if (window['tgidc' + grpaddr]) - color = window['tgidc' + grpaddr]; - - c_grpaddr = '' + grpaddr + ''; - c_talkgroup = '' + talkgroup + ''; - - color = (window['srcc' + srcaddr]) ? window['srcc' + srcaddr] : "0"; - c_srcaddr = '' + srcaddr + ''; - c_srctag = '' + srctag + ''; - - var sr = "Site: " + site + "     RFID: " + rfid; - - var col_f = "OP25"; // empty for now - - if (cb) { - if (grpaddr) { // do not append the table if no grpaddr is present - TU into Events table is pretty much useless anyway - appendJsonTable(time, j_type, sysid, sr, c_grpaddr, col_f, "Update", "--", "history"); - } - } //end if cb - } // end if nac is number - } // end for nac in d - sources(d); - window.g_trunk_update = d; - break; - - case 'rx_update': - cb = cbState('log_rx'); - if (d['files'][0]) { - var ps = "Plots: True"; - } else { - var ps = "Plots: False" - } - if (d['fine_tune']) { - var ft = d['fine_tune']; - } else { - var ft = "n/a"; - } - - if (cb) { - appendJsonTable(time, j_type, ps, 'Fine Tune: ' + ft, 'Tune Err: ' + d['error'], 'OP25', 'RX Update', '--', 'history'); - } // this Events table entry doesn't add much value either. - - sessionStorage.fineTune = d['fine_tune'] ? d['fine_tune'] : "—"; - sessionStorage.errorVal = d['error'] ? d['error'] : "—"; - break; - - case 'cc_event': - - time = getTime(d['time'] * 1000); - cb = cbState('log_cc'); - var opcode, n_opcode, sysid, tag, target, source, srctag, src_c, color; - var logCall = 0; - var noLog = 0; - var xp = 0; - - if (d.opcode !== null) { - opcode = d['opcode'].toString(16); - if (g_opcode[opcode]) { - n_opcode = g_opcode[opcode]; - } else { - n_opcode = 'Opcode Not Found: ' + opcode; - } - } - - tag = target = source = '—'; - srctag = " "; // used - - // [group] does not appear in every cc_event! - if (d.group) { - - if (d.group && d.group.tag) { - tag = d.group.tag; - } else { - d.group.tag = 'Talkgroup ' + d.group.tg_id; - d.group.color = $('#unk_default').val(); - } - - } // end if d.group - - if (d.reason) { - tag = getReason(hex(d.reason)); - } - - n_opcode = g_opcode[opcode] ? g_opcode[opcode] : opcode; - - if (d['srcaddr']) { - source = (d.srcaddr.unit_id) ? d.srcaddr.unit_id : "— No ID"; // This condition is reached when there is traffic - srctag = (d.srcaddr.tag) ? d.srcaddr.tag : " "; // but no source unit id is present. on ebrcs, this - // happens when an old u/vhf system is patched onto ebrcs. - } - - // handle manufacturer opcodes: - // 0x10 = Relm/BK - // 0x68 = Kenwood - // 144(10) 0x90 = Motorola - // 164(10) 0xA4 = Harris - // 0xD8 = Tait - // 0xF8 = Vertex Standard - // trunking.py sends the following opcodes: - // -1, 0x00, 01, 02, 03, 09, 20, 27, 28, 2a, 2b, 2c, 2d, 2f, 31, 33, 34, 3d - - switch (opcode) { - - case "-1": // end call (not a P25 standard) - tag = d.tgid.tag; - target = d.tgid.tg_id; - n_opcode = "End Call"; - noLog = 1; // Events - // TODO - maybe nothing? - break; - - case "0": - if (d.mfrid != null) { - switch (d.mfrid) { - case 0: - n_opcode = "Call"; // GRP_V_CH_GRANT - target = d['group']['tg_id']; - srctag = d['srcaddr']['tag']; - noLog = cbState('je_calls') ? 0 : 1; //events - logCall = 1; // callHistory - break; - case 144: // MOTOROLA - n_opcode = "XP Adds"; //mot_grg_add_cmd 0x00" - tag = d.sg.tag; - target = d.sg.tg_id; - noLog = 1; // chatty af - logCall = 0; // callHistory - break; - case "default": - n_opcode = "Call"; - target = d['group']['tg_id']; - srctag = d['srcaddr']['tag']; - noLog = cbState('je_calls') ? 0 : 1; - logCall = 1; // callHistory - break; - } // end switch - } // end if - break; - - case "1": // 0x01 - RESERVED - if (d.mfrid != null) { - switch (d.mfrid) { - case 0: - n_opcode = "Reserved 0x01"; - break; - case 144: // MOTOROLA - n_opcode = "XP Drops"; - tag = d.sg.tag; - target = d.sg.tg_id; - break; - } // end switch - } // end if - break; - - case "2": // 0x02 - grp_v_ch_grant_updt - if (d.mfrid != null) { - switch (d.mfrid) { - case 0: - n_opcode = "grp_v_ch_grant_updt 0x02"; - noLog = cbState('je_calls') ? 0 : 1; - break; - case 144: // MOTOROLA - d['group'] = d['sg']; - d['srcaddr'] = d['sa']; - srctag = d.sa.tag; - n_opcode = "XP Call"; // MOT XP Call mot_grg_cn_grant 0x02 - tag = d.sg.tag; - target = d.sg.tg_id; - source = d.sa.unit_id; - noLog = cbState('je_calls') ? 0 : 1; - logCall = 1; // callHistory - xp = 1; - break; - } // end switch - } // end if - break; - - case "3": // 0x03 - GRP_V_CH_GRANT_UPDT_EXP - if (d.mfrid != null) { - switch (d.mfrid) { - case 0: - n_opcode = "grp_v_ch_grant_updt_exp 0x03"; - break; - case 144: // MOTOROLA - n_opcode = "mot_grg_cn_grant_updt 0x03"; - tag = d.sg1.tag; - target = d.sg1.tg_id; - noLog = 1; // used for late entry, very chatty, probably should not log to JSON Events - break; - } // end switch - } // end if - break; - - case "9": // MOT System Load ? Could be "Motorola Scan Marker" - if (d.mfrid != null) { - switch (d.mfrid) { - case 0: - n_opcode = "Opcode 0x03"; - break; - case 144: // MOTOROLA - n_opcode = "System Load " + d.test1; - noLog = 1; - break; - } // end switch - } // end if - break; - - case "20": // 0x20 - ACK_RSP_FNE - noLog = 1; - break; - - case "24": // 0x24 - Extended Function Command (inhibit) TODO: get reason code, log it - var efclass = d.efclass; - var efoperand = d.efoperand; - var efargs = d.efargs; - var target = d.target; - n_opcode = "Ext Fnct Cmd: " + efoperand; - source = efargs; - noLog = 0; - break; - - case "27": // 0x27 - DENY_RSP - source = d.target.unit_id; - srctag = d.target.tag; - noLog = cbState('je_deny') ? 0 : 1; - break; - - case "28": // 0x28 - GRP_AFF_RSP - source = d.target.unit_id; - target = d.group.tg_id; - noLog = cbState('je_joins') ? 0 : 1; - break; - - case "2a": // 0x2A - GRP_AFF_Q - Group Affiliate Query - noLog = 1; - break; - - case "2b": // 0x2B - LOC_REG_RSP - Location Registration Response - if (d['rv'] != null) { - switch (d['rv']) { - case 0: - n_opcode = "Joins"; - target = d.group.tg_id; - source = d.target.unit_id; - srctag = d.target.tag; - noLog = cbState('je_joins') ? 0 : 1; - break; - case 1: - n_opcode = "Reg Fail"; - target = d.group.tg_id; - source = d.target.unit_id; - srctag = d.target.tag; - noLog = cbState('je_deny') ? 0 : 1; - break; - case 2: - n_opcode = "Reg Denied"; - target = d.group.tg_id; - source = d.target.unit_id; - srctag = d.target.tag; - noLog = cbState('je_deny') ? 0 : 1; - break; - case 3: - n_opcode = "Reg Refused"; - target = d.group.tg_id; - source = d.target.unit_id; - srctag = d.target.tag; - noLog = cbState('je_deny') ? 0 : 1; - break; - } // end rv switch - } // end if - break; - - case "2c": // 0x2C - U_REG_RSP - Login TODO: source and target are the same from the json - source = d.source.unit_id; - srctag = d.source.tag; - // target = d.target.unit_id; - // tag = d.target.tag; - noLog = cbState('je_log') ? 0 : 1; - break; - - case "2d": // 0x2D - U_REG_CMD - Force SU Registration - source = d.source.unit_id; - srctag = d.source.tag; - target = d.target.unit_id; - tag = d.target.tag; - noLog = cbState('je_log') ? 0 : 1; - break; - - case "2f": // 0x2F - U_DE_REG_ACK - Logout - source = d.source.unit_id; - srctag = d.source.tag; - noLog = cbState('je_log') ? 0 : 1; - break; - - case "31": // 0x31 - AUTH_DMD - Authentication Demand - source = d.target_id.unit_id; - srctag = d.target_id.tag; - target = d.target_address.unit_id; - tag = d.target_address.tag; - break; - - case "33": // 0x33 - iden up tdma - var sysid = d.sysid; - var type = 'TDMA'; - var iden = d.iden; - var freq = d.freq / 1000000; - var offset = d.offset / 1000000; - var step = d.step/100000; - var slots = d.slots; - channelId (sysid, iden, type, freq, offset, step, slots); - noLog = 1; - break; - - case "34": // 0x34 - iden up vhf/uhf - // TODO - test this - var sysid = d.sysid; - var type = 'FDMA'; - var iden = d.iden; - var freq = d.freq / 1000000; - var offset = d.offset / 1000000; - var step = d.step/100000; - var slots = 1; - channelId (sysid, iden, type, freq, offset, step, slots); - noLog = 1; - break; - - case "3d": // 0x3D - iden up (7/800) - var sysid = d.sysid; - var type = 'FDMA'; - var iden = d.iden; - var freq = d.freq / 1000000; - var offset = d.offset / 1000000; - var step = d.step/100000; - var slots = 1; - channelId (sysid, iden, type, freq, offset, step, slots); - noLog = 1; - break; - - } // end switch - end handle manf opcodes - - if (d.options) { - n_opcode = ( (d.options >> 6) & 1 ) ? n_opcode = n_opcode + "   ∅" : n_opcode; //encrypted - } - - c = smartColor(tag); - src_c = 0; - - if (d['group']) - c = (d['group']['color']) ? d['group']['color'] : c; - - if (d['sg']) - c = (d['sg']['color']) ? d['sg']['color'] : c; - - tag = "" + tag; - target = "" + target; - - - if (d['group']) { - src_c = d['group']['color']; - } - - if (d['source']) { - src_c = d['source']['color']; - } - - if (d['srcaddr']) { // present for voice calls, not present for other cc_events - src_c = (d['srcaddr']['color']) ? d['srcaddr']['color'] : src_c; - } - - - - source = "" + source; - srctag = "" + srctag; - - if (cb && noLog == 0) { - appendJsonTable(time, j_type, n_opcode, tag, target, source, srctag, opcode, 'history'); - } - - if (target != "—" && logCall) { - - target = "" + target; - tag = "" + tag; - - source = "" + source; - srctag = "" + srctag; - - var tdma_slot = d.tdma_slot; // s/b null if not running tdma - - appendCallHistory(time, "--", target, tag, source, srctag, 'callHistory', d.options, xp, d.sysid, d.nac, tdma_slot); - } - - window.g_cc_event = d; - break; - } // end switch - - dispatch[d['json_type']](d); // correct function is called based on json type in the dataset - } // end for - f_debug(d); -} // end dispatch_commands() - -function cc_event(d) { - // does nothing right now - return; -} // end cc_event() - -function do_update() { - f_debug(); -} // do_update() - -function f_scan_button(command) { - if (current_tgid == null) - send_command(command, -1); - else - send_command(command, current_tgid); -} - -function f_goto_button(command) { - var _tgid = 0; - if (command == 'goto') { - command = 'hold'; - if (current_tgid != null) - _tgid = current_tgid; - _tgid = parseInt(prompt('Enter TGID to hold.', _tgid)); - if (isNaN(_tgid) || _tgid < 0 || _tgid > 65535) { - return; // Cancel was pressed or invalid entry - } - - // it's necessary to release current hold before trying to hold on a new tg - if ($('#holdIndicator').is(':visible') ){ - send_command('skip', -1); - setTimeout(function() { - send_command(command, _tgid); - }, 500); - return; - } - send_command(command, _tgid); - } -} - -function f_debug(d) { - if (!d_debug) - return; - - var html = "
    Debug:
    "; -// html += 'window.g_nac = ' + window.g_nac; - html += '
    '; - html += 'json type: ' + d['json_type']; - html += '
    '; - html += "busy " + send_busy; - html += " qfull " + send_qfull; - html += " sendq size " + send_queue.length; - html += " requests " + request_count; - html += "
    callbacks: "; - html += " total=" + req_cb_count; - html += " incomplete=" + nfinal_count; - html += " error=" + n200_count; - html += " OK=" + r200_count; - html += "
    "; - - $('#div_debug').html(html); -} - -function popOut() { - var myWindow = window.open(window.location.href, '', 'width=760,height=400'); -} - -function toggleCSS() { - $(document.documentElement).attr('data-theme') == 'light' ? - $(document.documentElement).attr('data-theme', 'dark') : - $(document.documentElement).attr('data-theme', 'light'); - sdmode(); -} - -function comma(x) { - // add comma formatting to whatever you give it (xx,xxxx,xxxx) - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); -} - -document.onkeydown = function(evt) { - // keyboard shortcuts - evt = evt || window.event; - var x = document.activeElement.tagName; - if (x == "INPUT") - return; // don't do anything if user is typing in an input field - - switch (evt.keyCode) { - - case 70: - // 'f' key - show/hide frequency table - $('#lastCommand').html('F - Freq Tbl

    ').show(); - freqTable = !freqTable; - setTimeout(function() {$('#lastCommand').html('').hide();}, 2000); - break; - - case 71: - // 'g' key - GOTO - $('#lastCommand').html('G - GOTO

    ').show(); - f_goto_button('goto'); - break; - case 76: - // 'l' key - LOCKOUT - $('#lastCommand').html('L - LOCKOUT

    ').show(); - f_scan_button('lockout'); - break; - case 72: - // 'h' key - HOLD - $('#lastCommand').html('H - HOLD

    ').show(); - f_scan_button('hold'); - break; - case 80: - // 'p' show/hide plots - $('#lastCommand').html('P - Plots

    ').show(); - minify('div_images'); - break; - case 83: - // 's' key - SKIP - $('#lastCommand').html('S - Skip

    ').show(); - f_scan_button('skip'); - break; - case 86: - // 'v' key - VIEW (light/dark) - $('#lastCommand').html('V - View

    ').show(); - toggleCSS(); - break; - case 82: - // 'r' key - RX screen - f_select('rx'); - break; - case 74: - // 'j' key - HOME screen - f_select('status'); - break; - case 77: - // 'm' key - MINIFY - minify('nav-bar'); - minify('div_images'); - minify('div_s1'); - break; - case 48: // '0' key - show/hide Main Display - minify('controlsDisplay'); - break; - case 49: // '1' key - show/hide callHistory - $('#lastCommand').html('1 - Calls

    ').show(); - minify('log_container_1'); - break; - case 50: - $('#lastCommand').html('2 - Events

    ').show(); - // '2' key - show/hide history (json log) - minify('log_container_2'); - break; - case 51: - // '3' key - show/hide logs block - $('#lastCommand').html('3 - Logs

    ').show(); - if ( $('#div_logs').is(":hidden") ) { - $('#div_logs').show(window.animateSpeed); - } else { - $('#div_logs').hide(window.animateSpeed); - } - break; // showBandPlan - case 52: - // '4' key - show/hide Band Plan - $('#lastCommand').html('4 - Bandplan

    ').show(); - $('#showBandPlan').trigger('click'); - setTimeout(function() {$('#lastCommand').html('').hide();}, 2000); - break; - case 65: - $('#lastCommand').html('A - Neighbors

    ').show(); - $('#show_adj').trigger('click'); - break; - case 66: - // 'b' key - bold - $('#valFontStyle').val('bold'); - break; - case 78: - // 'n' key - normal - $('#valFontStyle').val('normal'); - break; - } // end switch -}; // end onkeydown - -function minify(div) { - $('#' + div).toggle(window.animateSpeed); -} - -function appendJsonTable(a, b, c, d, e, f, srctag, opcode, target) { - var numRows = document.getElementById(target).rows.length; - var size = parseInt($('#log_len').val()); - if (!Number.isInteger(size)) { - // entry in Config / Display Options must be a number - size = 1500; - } - - // shorter friendly view - var fv = { - 'cc_event': "CC", - 'rx_update': 'RX', - 'change_freq': 'CF', - 'trunk_update': 'TU' - }; - - if (numRows > size) - document.getElementById(target).deleteRow(-1); - var table = document.getElementById(target); - var lastRowIndex = table.rows.length - 1; - - $('#eh-count').html(' '); - if (d_debug == 1) - $('#eh-count').html('   ' + comma(table.rows.length)); - var skip = 0; - - var prevTime = nohtml(table.rows[1].cells[0].innerHTML); // time - - // do not duplicate history enteries - uses window object to store previous enteries and compares them with current data - // seems to work... - - if (target == 'history' && b == 'trunk_update' && window.g_trunk_update_c && window.g_trunk_update_d) { - if (c == window.g_trunk_update_c && d == window.g_trunk_update_d && a == prevTime) { - skip = 1; - } - } - if (target == 'history' && b.includes('cc_event') && window.g_cc_event_c && window.g_cc_event_e) { - if (c == window.g_cc_event_c && (e == window.g_cc_event_e || f == window.g_cc_event_f) && a == prevTime) { - skip = 1; - } - - } - if (target == 'history' && b.includes('change_freq') && window.g_change_freq_c && window.g_change_freq_e) { - if (c == window.g_change_freq_c && e == window.g_change_freq_e && a == prevTime) { - skip = 1; - } - } - - if (!skip) { - var row = table.insertRow(1); // 2nd row insert - - var cell0 = row.insertCell(0); - var cell1 = row.insertCell(1); - var cell2 = row.insertCell(2); - var cell3 = row.insertCell(3); - var cell4 = row.insertCell(4); - var cell5 = row.insertCell(5); - var cell6 = row.insertCell(6); - var cell7 = row.insertCell(7); - - opcode = opcode.toString(); - opcode = opcode.length == 1 ? "0" + opcode : opcode; - - cell0.innerHTML = a; //time - cell1.innerHTML = (target == 'history') ? '
    ' + fv[b] + '
    ' : '
    ' + b + '
    '; // type - cell2.innerHTML = f; // source id - cell3.innerHTML = srctag; - cell4.innerHTML = '
    ' + opcode.toUpperCase() + '
    '; // opcode - cell5.innerHTML = c; // n_coode (event) - cell6.innerHTML = e; // target - cell7.innerHTML = d; // tag - - // only update the window globals if we haven't skipped, so do this inside if !skip - if (target == 'history' && b == 'trunk_update') { - window.g_trunk_update_c = c; - window.g_trunk_update_d = d; - } - if (target == 'history' && b.includes('cc_event')) { - window.g_cc_event_c = c; - window.g_cc_event_e = e; - window.g_cc_event_f = f; - } - - if (target == 'history' && b.includes('change_freq')) { - window.g_change_freq_c = c; - window.g_change_freq_e = e; - } - - } // end if !skip -} // end appendJsonTable - -function appendCallHistory(a, b, c, d, e, f, target, options, xp, sysid, nac, tdma_slot) { - var numRows = document.getElementById(target).rows.length; -// var size = document.getElementById('log_len').value; - var size = parseInt($('#log_len').val()); - if (!Number.isInteger(size)) { - // entry in Config / Display Options must be a number - size = 1500; - } - if (numRows > size) - $('#' + target + ' tr:last').remove(); - var table = document.getElementById(target); - - $('#ch-count').html(' '); - if (d_debug == 1) - $('#ch-count').html('   ' + comma((table.rows.length))); - - var lastRowIndex = table.rows.length - 1; - var skip = 0; - var pri, enc, xpatch, x, y; - - - b = (options) ? options : " "; - enc = ((b >> 6) & 1 ) ? "∅ " : ""; - pri = ((b >> 2) & 1).toString() + ((b >> 1) & 1).toString() + ((b >> 0) & 1).toString(); - pri = parseInt(pri, 2); - pri = (xp) ? "XP " : pri; - - var prevTime, prevTg, prevSrc; - - // avoid duplicate channel grant enteries into Call History table. - // Search previous 9 rows, compares current data with existing tgid, srcaddr, and time - // if tgid and srcaddr are equal and time is within 2 seconds, skip the entry, - search: { - for (x = 1; x < 8; x++) { - if (!table.rows[x]) { - break search; - } - prevTime = nohtml(table.rows[x].cells[0].innerHTML); // time - prevTg = nohtml(table.rows[x].cells[3].innerHTML); // tgid - prevSrc = nohtml(table.rows[x].cells[5].innerHTML); // source addr - - var psec = prevTime.slice(-1); - var asec = a.slice(-1); - var diff = Math.abs(psec - asec); - if (nohtml(c) == prevTg && nohtml(e) == prevSrc && (a == prevTime || diff <= 2)) { - skip = 1; - } // end if - } // end for - } // end search - - if (((b >> 6) & 1) && cbState('hide_enc')) { // hide encrypted calls if selected in Config - skip = 1; - } - - // do not append the Call History table if not enabled on Home tab - if (enable_status[nac] == false) { - skip = 1; - } - - var tslot = ''; - if (cbState('showSlot') == true && tdma_slot != null) { - tslot = 'S' + ( tdma_slot +1 ) + ' '; - } - - if (!skip) { - - var row = table.insertRow(1); // 2nd row insert - - var cell0 = row.insertCell(0); - var cell1 = row.insertCell(1); - var cell2 = row.insertCell(2); - var cell3 = row.insertCell(3); - var cell4 = row.insertCell(4); - var cell5 = row.insertCell(5); - var cell6 = row.insertCell(6); - - cell0.innerHTML = a; - cell1.innerHTML = '
    ' + hex(sysid).toUpperCase() + '
    '; - cell2.innerHTML = '
    ' + tslot + enc + pri + '
    '; - cell3.innerHTML = c; - cell4.innerHTML = d; - cell5.innerHTML = e; - cell6.innerHTML = f; - - } // end if !skip -} // end appendCallHistory - -function appendErrorTable(ax, bx, cx, dx, ex, fx, gx, target) { - var numRows = document.getElementById(target).rows.length; - var size = parseInt($('#log_len').val()); - if (!Number.isInteger(size)) { - // entry in Config / Display Options must be a number - size = 1500; - } - if (numRows > size) - document.getElementById(target).deleteRow(-1); - var table = document.getElementById(target); - var lastRowIndex = table.rows.length - 1; - var skip = 0; - - if (!skip) { - var row = table.insertRow(1); // 2nd row insert - var cell0 = row.insertCell(0); - var cell1 = row.insertCell(1); - var cell2 = row.insertCell(2); - var cell3 = row.insertCell(3); - var cell4 = row.insertCell(4); - var cell5 = row.insertCell(5); - var cell6 = row.insertCell(6); - - cell0.innerHTML = ax; //time - cell1.innerHTML = bx; - cell2.innerHTML = cx; - cell3.innerHTML = dx; - cell4.innerHTML = ex; - cell5.innerHTML = fx; - cell6.innerHTML = gx; - - } // end if !skip -} // end appendErrorTable - -function update_freq(d) { - return; // not currently used -} - -function channelId (sysid, iden, type, freq, offset, step, slots) { - !(sysid in channel_id) && (channel_id[sysid] = {}); - !(iden in channel_id[sysid]) && (channel_id[sysid][iden] = {}); - channel_id[sysid][iden] = { - 'iden': iden, 'type': type, 'freq': freq, 'offset': offset, 'step': step, 'slots': slots - }; -} - -function smartColor(t) { - // searches string t for items in sc1 and sc2, returns a color if found. - - var z = 4; // number of smart colors - TOTO add the UI elements in index.html - - if (!t || !cbState('smartcolors')) - return 0; // do nothing if there is no talkgroup name passed, or if the box is not checked (cb default is false!) - var tag = t.toString().toUpperCase(); // throws an error if t is an int - var color = 0; - var ele, x, i, sc, alen; - - for (i = 1; i < (z+1); i++) { - ele = document.getElementById('sc'+i).value; - sc = ele.split(" "); - for (var x = 0; x < sc.length; x++) { - if (tag.includes(sc[x].toUpperCase())) { - color = i; - return color; - } - } - } - return color; -} // end smartColor() - -function divExpand(div) { - switch ($('#' + div).height()) { - case 1: - $('#' + div).height(301); - $('#' + div).show(); - break; - case 301: - $('#' + div).height(702); - break; - case 702: - $('#' + div).height(1); - $('#' + div).hide(); - break; - } -} - -function openTable(div, ref) { - // popout window for review/search of log tables - var divText = $('#' + div).prop('outerHTML'); - divText = divText.replace('table id="history"', 'table id="searchTable"'); - divText = divText.replace('table id="callHistory"', 'table id="searchTable"'); - divText = divText.replace('table id="errors"', 'table id="searchTable"'); - - var myWindow = window.open('', '', 'width=900,height=600'); - var view = document.documentElement.getAttribute('data-theme'); - var doc = myWindow.document; - - doc.open(); - doc.write(''); - doc.write(''); - doc.write(''); - doc.write(''); - doc.write(''); - doc.write(''); - // search icon 🔎 - doc.write('' + ref.id + '

    '); - doc.write(''); - doc.write('
    '); - doc.write(''); - doc.write(''); - if (view == 'dark') - doc.documentElement.setAttribute('data-theme', 'dark'); - doc.write(divText); - doc.close(); -} - -function searchTable() { - var input, filter, table, tr, i, x, s, cols; - var td = []; - cols = document.getElementById('searchTable').rows[1].cells.length; - input = document.getElementById('searchInput'); - filter = input.value.toUpperCase(); - table = document.getElementById('searchTable'); - tr = table.getElementsByTagName('tr'); - for (i = 1; i < tr.length; i++) { - var s = undefined; - for (x = 0; x < cols; x++) { - td[x] = tr[i].getElementsByTagName('td')[x]; - s = s + ' ' + nohtml(td[x].innerHTML).toUpperCase(); - } - if ( s.includes(filter)) { - tr[i].style.display = ''; - } else { - tr[i].style.display = 'none' - } - } // end for -} // end function - -function searchTsvTable() { - var input, filter, table, tr, i, x, s, y, cols; - var td = []; - table = document.getElementById('talkgroups'); - cols = table.rows[1].cells.length; - input = document.getElementById('searchInput'); - filter = input.value.toUpperCase(); - tr = table.getElementsByTagName('tr'); - for (i = 1; i < tr.length; i++) { - var s = undefined; - for (x = 1; x < cols; x++) { // don't search the first column [0] - td[x] = tr[i].getElementsByTagName('td')[x]; - s += ' ' + td[x].firstChild.value.toUpperCase(); - } - if ( s.includes(filter)) { - tr[i].style.display = ''; - } else { - tr[i].style.display = 'none' - } - } // end for -} // end function - -function hex(dec) { - if (!dec) return; - return dec.toString(16); -} - -function dec(hex) { - if (!hex) return; - return parseInt(hex, 16); -} - -function nohtml(str) { - if (typeof str != 'string') return str; - var x = str.replace(/(<([^>]+)>)/gi, ""); - return x; -} - -function freqDisplay(f) { - var freq; - if (!f) return; - if (cbState('trailing_zeros')) { - freq = f.toFixed(5); - } else { - if (Number.isInteger(f)) { - freq = f + ".0"; - } else { - freq = f; - } - } - return freq; -} - -function displayMode() { // just returns the display mode - var x,y; - x = document.documentElement.getAttribute('data-theme'); - y = (x == "dark") ? "dark" : "light"; - return y; -} - -function sdmode() { - $('#selDispMode').val(displayMode()); -} - -function sdmodeChange() { - var y = document.getElementById('selDispMode'); - document.documentElement.setAttribute('data-theme', y.value); -} - -function is_digit(s) { - return ((s >= '0' && s <= '9')); -} - -function getTime(x) { - //expects Unix timestamp, returns 24 hour time, hrs:min:sec - date = new Date(parseInt(x)); - var time = zeroPad(date.getHours(), 2) + ':' + zeroPad(date.getMinutes(), 2) + ':' + zeroPad(date.getSeconds(), 2); - return time; -} - -function cbState(x) { - // returns the state (true / false) of whatever checkbox you ask it to - return $('#' + x).is(':checked'); -} - -function csvTable(table_id, separator = ',') { // Quick and simple export target #table_id into a csv - // Select rows from table_id - var rows = document.querySelectorAll('table#' + table_id + ' tr'); - // Construct csv - var csv = []; - for (var i = 0; i < rows.length; i++) { - var row = [], - cols = rows[i].querySelectorAll('td, th'); - for (var j = 0; j < cols.length; j++) { - // Clean innertext to remove multiple spaces and jumpline (break csv) - var data = cols[j].innerText.replace(/(\r\n|\n|\r)/gm, '').replace(/(\s\s)/gm, ' '); - // Escape double-quote with double-double-quote - data = data.replace(/"/g, '""'); - // Push escaped string - row.push('"' + data + '"'); - } - csv.push(row.join(separator)); - } - var csv_string = csv.join('\n'); - // Download it - var filename = 'export_' + table_id + '_' + new Date().toLocaleDateString() + '.csv'; - var link = document.createElement('a'); - link.style.display = 'none'; - link.setAttribute('target', '_blank'); - link.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv_string)); - link.setAttribute('download', filename); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); -} - -function exportToCsv(filename, rows) { // another csv export tool, but this one accepts an array. - var processRow = function (row) { - var finalVal = ''; - for (var j = 0; j < row.length; j++) { - var innerValue = row[j] === null ? '' : row[j].toString(); - if (row[j] instanceof Date) { - innerValue = row[j].toLocaleString(); - }; - var result = innerValue.replace(/"/g, '""'); - if (result.search(/("|,|\n)/g) >= 0) - result = '"' + result + '"'; - if (j > 0) - finalVal += ','; - finalVal += result; - } - return finalVal + '\n'; - }; - - var csvFile = ''; - for (var i = 0; i < rows.length; i++) { - csvFile += processRow(rows[i]); - } - - var blob = new Blob([csvFile], { type: 'text/csv;charset=utf-8;' }); - if (navigator.msSaveBlob) { // IE 10+ - navigator.msSaveBlob(blob, filename); - } else { - var link = document.createElement("a"); - if (link.download !== undefined) { // feature detection - // Browsers that support HTML5 download attribute - var url = URL.createObjectURL(blob); - link.setAttribute("href", url); - link.setAttribute("download", filename); - link.style.visibility = 'hidden'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } - } -} - -function sortTable(t, c) { // table id, column index - if (!cbState('show_adj')) - return; - if (!t) return; - var table, rows, switching, i, x, y, shouldSwitch; - table = document.getElementById(t); - switching = true; - while (switching) { - switching = false; - rows = table.rows; - // (skip the first row (0), which contains table headers) - for (i = 1; i < (rows.length - 1); i++) { - shouldSwitch = false; - x = rows[i].getElementsByTagName("TD")[c]; - y = rows[i + 1].getElementsByTagName("TD")[c]; - if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) { - shouldSwitch = true; - break; - } - } - if (shouldSwitch) { - rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); - switching = true; - } - } -} // end sort table - -function sources(d) { // d json type = trunk_update - - // stores source IDs and tags and colors in a global object - - // USAGE: - - // window['tgidc' + tgid] = tgid color - // window['tgidt' + tgid] = tgid tag "talkgroup" - - // window['srct' + srcaddr] = srcaddr tag - // window['srcc' + srcaddr] = srcaddr color - - if (Object.keys(d).length < 1) - return; - var f, nac, srcaddr, srcaddr_tag; - var ft = 'frequency_tracking'; - var calls = 'calls'; - for (nac in d) { - if (Number.isInteger(parseInt(nac))) { - for (f in d[nac][ft]) { - - srcaddr_tag = d[nac][ft][f]['calls'][0]['srcaddr']['tag'] ? d[nac][ft][f]['calls'][0]['srcaddr'] : null; - srcaddr = d[nac][ft][f]['calls'][0]['srcaddr']['unit_id'] ? d[nac][ft][f]['calls'][0]['srcaddr'] : null; - - if (srcaddr && srcaddr_tag) { - window['tgidc' + d[nac][ft][f]['calls'][0]['tgid']['tg_id']] = d[nac][ft][f]['calls'][0]['tgid']['color']; - window['tgidt' + d[nac][ft][f]['calls'][0]['tgid']['tg_id']] = d[nac][ft][f]['calls'][0]['tgid']['tag']; - - window['srct' + d[nac][ft][f]['calls'][0]['srcaddr']['unit_id']] = d[nac][ft][f]['calls'][0]['srcaddr']['tag']; - window['srcc' + d[nac][ft][f]['calls'][0]['srcaddr']['unit_id']] = d[nac][ft][f]['calls'][0]['srcaddr']['color']; - } - - if (d[nac][ft][f]['tdma'] == true ) { - window[nac + 'flavor'] = "2"; // set it as p2 and leave it that way for the duration - } - } // end for f - } // end if number - } // end for x -} // end function - -function getReason(r) { - // denial reason lookup - if (g_reason[r]) { - result = '0x' + r + ' - ' + g_reason[r]; - } else { - result = '0x' + r; - } - return result; -} - -function saveDisplaySettings() { - var settings = {}; - $('#saveSettings').html('Saved'); - setTimeout(() => { $('#saveSettings').html('Save Display Settings');}, 2000); - - var s = [ - "valSystemFont", - "valTagFont", - "valTruncate", - "valFontStyle", - "sc1", - "sc2", - "sc3", - "sc4", - "log_len", - "color_main_tag", - "color_main_sys", - "smartcolors", - "log_cc", - "log_cf", - "log_tu", - "log_rx", - "show_adj", - "je_joins", - "je_calls", - "je_deny", - "je_log", - "hide_enc", - "trailing_zeros", - "selDispMode", - "acc1", - "acc2", - "sysColor", - "valColor", - "sysColor", - "btnColor", - "unk_default", - "ani_speed", - "showBandPlan", - "showSlot", - "oplogip", - "oplogport", - "showLast", - "colSlot2" ]; - - for (r in s) { - if ($('#' + s[r]).attr('type') == "checkbox") { - settings[s[r]] = $('#' + s[r]).is(':checked') ? true : false; - } else { - settings[s[r]] = $('#' + s[r]).val(); - } - } // end for - - var settingsJSON = JSON.stringify(settings, null, 2); - send_command('config-savesettings', settingsJSON); -} - - -function loadHelp(){ // this is also called from editor.js - f = 'help.html'; - $.ajax({ - url : f, - type : 'GET', - success : popHelp, - error : function(XMLHttpRequest, textStatus, errorThrown) {alert('Settings File Acces Error: \n\nFile:' + f + '\n\n' + errorThrown + '\n\n');} - }); -} - -function popHelp(h){ - $('#div_help').html(h) -} - - -function beginJsonSettings(){ // this is also called from editor.js - f = 'ui-settings.json'; - $.ajax({ - url : f, - type : 'GET', - success : loadJsonDisplaySettings, - error : function(XMLHttpRequest, textStatus, errorThrown) {alert('Settings File Acces Error: \n\nFile:' + f + '\n\n' + errorThrown + '\n\n');} - }); -} - -function loadJsonDisplaySettings(settings) { - $('#loadSettings').html('Loaded'); - setTimeout(() => { $('#loadSettings').html('Load Settings'); }, 2000); - - var ele, m; - for (item in settings) { - ele = document.getElementById(item); - if (ele) { - if (ele.type == "checkbox") { - ele.checked = settings[item] == true ? true : false; - continue; - } - - if (ele.type == "select") { - ele.value = settings[item]; - continue; - } - - ele.value = settings[item]; - } - - if (item == "selDispMode") { - m = settings[item]; - document.documentElement.setAttribute('data-theme', m); - sdmode(); - } - - // populate url into oplog url field - var x = window.location.origin.split( ':' ); -// var y = x[0] + ':' + x[1] + ':5000'; -// $('#oplogUrl').val(y); - $('#oplogip').val(x[1].substring(2)); - $('#oplogport').val('5000'); - - - if (item == "oplogip") { - $('#oplogip').val(settings[item]); - } - - if (item == "oplogport") { - $('#oplogport').val(settings[item]); - } - - $('#oplogUrl').val('http://' + $('#oplogip').val() + ':' + $('#oplogport').val()); - - } // end for item - - uiColorRefresh(); - -} - -function uiColorRefresh() { // this is also called from editor.js - $('#acc1').trigger('change'); - $('#valColor').trigger('change'); - $('#sysColor').trigger('change'); - $('#btnColor').trigger('change'); - $('#ani_speed').trigger('change'); -} - -function changeCss(className, classValue) { - - // we need invisible container to store additional css definitions - var cssMainContainer = $('#css-modifier-container'); - if (cssMainContainer.length == 0) { - cssMainContainer = $('
    '); - cssMainContainer.hide(); - cssMainContainer.appendTo($('body')); - } - - // and we need one div for each class - var classContainer = cssMainContainer.find('div[data-class="' + className + '"]'); - if (classContainer.length == 0) { - classContainer = $('
    '); - classContainer.appendTo(cssMainContainer); - } - - // append additional style - classContainer.html(''); -} - -function getSiteAlias() { - - if (localStorage.AliasTableUpdated == false) - return; - if (event_source == null || event_source.readyState != 1) // the server has gone out to lunch - return; - $.ajax({ - url : 'site-alias.json', - type : 'GET', - success : loadSiteAlias, - error : function(XMLHttpRequest, textStatus, errorThrown) {console.log('site-alias.json file not found. \n\nFile:' + url + '\n\n' + errorThrown + '\n\n');} - }); -} - -function loadSiteAlias(json) { - window.siteAlias = json; - localStorage.AliasTableUpdated == false; -} - -function accColorSel() { // this is also called from editor.js - - $('.accColor').on('change', function() { - this.blur(); - var c1 = $('#acc1').val(); - var c2 = $('#acc2').val(); - var z = 'linear-gradient(' + c1 + ', ' + c2 +')'; - $('.controlsDisplay').css('background', z); - }); - - $('#valColor').on('change', function() { - this.blur(); - var c1 = $('#valColor').val(); - changeCss('.value', 'color: ' + c1 + ';'); - changeCss('.nac', 'color: ' + c1 + ';'); - }); - - $('#sysColor').on('change', function() { - this.blur(); - var c1 = $('#sysColor').val(); - changeCss('.systgid', 'color: ' + c1 + ';'); - }); - - $('#btnColor').on('change', function() { - this.blur(); - var c1 = $('#btnColor').val(); - changeCss('button', 'color: ' + c1 + ';'); - changeCss('.nav-button-active', 'color: ' + c1 + ';'); - changeCss('.nav-button:hover', 'color: ' + c1 + ';'); - changeCss('.nav-button-active:hover', 'color: ' + c1 + ';'); - changeCss('.btn', 'color: ' + c1 + ';'); - changeCss('.control-button', 'color: ' + c1 + ';'); - changeCss('.control-button:hover', 'color: ' + c1 + ';'); - }); - - $('#ani_speed').on('change', function() { - this.blur(); - window.animateSpeed = parseInt(this.value); - }); - -} - -// color, animation, backgroundColor - used for main talkgroup/system display -// returns the property value of the selector supplied. sheet is document.styleSheets[index].title -function getProperty(selector, property, sheet) { - for (y in document.styleSheets) { - if (document.styleSheets[y].title == sheet) // this title is set in the json color map builder func - break; - } - rules = document.styleSheets[y].cssRules; - for(i in rules) { - if(rules[i].selectorText == selector) - return rules[i]['style'][property]; - } - return false; -} - -function getStyle(className) { //test, not used - var cssText = ""; - var classes = document.styleSheets[1].rules || document.styleSheets[0].cssRules; - for (var x = 0; x < classes.length; x++) { - if (classes[x].selectorText == className) { - cssText += classes[x].cssText || classes[x].style.cssText; - } - } - return cssText; -} - -var g_opcode = { // global opcodes - '0': 'Call', // Group Voice Grant grp_v_ch_grant dec=0 - '1': 'Reserved', // REserved 0x01 - '2': 'Update 0x02', - '3': 'Update 0x03', - '4': 'UnitVoiceGrant', - '5': 'UU_ANS_RSP', // unit to unit answer response - '6': 'UnitVoiceUpdate', - '8': 'PhoneGrant', - '9': 'TELE_INT_PSTN_AEQ', - '0a': 'Phone Alert', // tele interconnect - '0b': 'Reserved', - '10': 'UnitDataGrant', - '11': 'GroupDataGrant', - '12': 'GroupDataUpdate', - '13': 'GroupDataUpdateExplicit', - '18': 'UnitStatusUpdate', - '1a': 'UnitStatusQuery', - '1c': 'UnitShortMessage', - '1d': 'UnitMonitor', - '1f': 'UnitCallAlert', // Call Alert - '20': 'Ack Response', // AckResponse - ACK_RESP_U - This is the generic response supplied by a unit to acknowledge an action when there is no other expected response. Response from radio to system poll ("are you still there?") - '21': 'QueuedResponse', - '24': 'ExtFunctionCommand', - '27': 'Denied', // DenyResponse - '28': 'Joins', // GroupAffiliationResponse - GRP_AFF_RSP - This is the response to the request for group affiliation by a unit. This will present the necessary information to the requesting unit to allow it to perform group operations for the indicated group identity. - '29': 'Ack Response FNE', // This is the generic response supplied to a unit to acknowledge an action when there is no other expected response. This response is sent to a subscriber unit in response to an earlier action or service request. - '2a': 'Group Aff Q', // GRP_AFF_Q - This transaction is to be used to determine what a targeted subscriber unit maintains as the group affiliation data for the unit. The Query will usually originate in the system, but the standard enables other originators. - '2b': 'Joins', // LocRegResponse - This transaction is to be used to respond to a Location Registration Request. The response indicates that the subscriber is registered in the new location area. - '2c': 'Login', // UnitRegResponse - '2d': 'Force SU Reg', // UnitRegCommand - U_REG_CMD - This transaction is to be used to force an SU to initiate Unit Registration - '2e': 'UnitAuthCommand', - '2f': 'Logout', // UnitDeregAck - '31': 'Auth Demand', - '36': 'RoamingAddrCommand', - '37': 'RoamingAddrUpdate', - '38': 'SystemServiceBroadcast', // This broadcast will inform the subscriber units of the current system services supported and currently offered on the Primary control channel of this site. - '39': 'AltControlChannel', - '3a': 'RfssStatusBroadcast', - '3b': 'NetworkStatusBroadcast', - '3c': 'AdjacentSite', // Adjacent Site Broadcast - '3d': 'ChannelParamsUpdate', // IDEN_UP - '3e': 'ProtectionParamBroadcast', - '3f': 'ProtectionParamUpdate' -}; - -var g_reason = { // TIA-10.AABC-B Annex B Deny Response Reason Codes - '10': 'Unit not valid', - '11': 'Unit not authoirized', - '20': 'Target unit not valid', - '21': 'Target unit not authorized', - '2f': 'Target unit refused', - '30': 'Target group invalid', - '31': 'Target group not authoirzed', - '40': 'Invalid dialing', - '41': 'Telephone number not authroized', - '42': 'PSTN address invalid', - '50': 'Call time-out', - '51': 'Call terminated by landline', - '52': 'Call terminated by subscriber', - '5f': 'Call pre-empted', - '60': 'Site access denial', - '61': 'User/system def', // 0x61 - 0xEF per standard - '67': 'User/sys def', - '77': 'User/sys def', - 'c0': 'User/sys def', // seen on MOT system - 'f0': 'Call options invalid', - 'f1': 'Protection service option invalid', - 'f2': 'Duples service option invalid', - 'f3': 'circuit/packet mode service option invalid', - 'ff': 'Service not supported by system' -}; - -var g_moto_opcode = { // opcodes when mfrid is MOT (0x90, 144) - '0': 'MOT Add Patch Group', - '1': 'MOT Del Patch Group', - '3': 'MOT Patch Voice Channel Grant Update', - '4': 'MOT Unknown', - '5': 'MOT Traffic Chan Stn ID', - '6': 'MOT Unknown', - '7': 'MOT Unknown', - '8': 'MOT Unknown', - '9': 'MOT System Load', - '0a': 'MOT Unknown', - '0b': 'MOT Control Chan Base Stn ID', - '0c': 'MOT Unknown', - '0d': 'MOT Unknown', - '0e': 'MOT Planned Control Channel Shutdown', - // 0f through 3f = unknown -}; - -var g_serviceOption = { - // TODO - populate this, not used currently. - // bits 0-2 priority - '0': 'reserved', - '1': 'Lowest', - '2': 'User/system def', - '3': 'User/system def', - '4': 'Default', - '5': 'User/system def', - '6': 'User/system def', - '7': 'Highest', - - // bit 3 - reserved - // bit 4 - mode - -}; - - -// const value_string MFIDS[] = { -// { 0x00, "Standard MFID (pre-2001)" }, -// { 0x01, "Standard MFID (post-2001)" }, -// { 0x09, "Aselsan Inc." }, -// { 0x10, "Relm / BK Radio" }, -// { 0x18, "EADS Public Safety Inc." }, -// { 0x20, "Cycomm" }, -// { 0x28, "Efratom Time and Frequency Products, Inc" }, -// { 0x30, "Com-Net Ericsson" }, -// { 0x34, "Etherstack" }, -// { 0x38, "Datron" }, -// { 0x40, "Icom" }, -// { 0x48, "Garmin" }, -// { 0x50, "GTE" }, -// { 0x55, "IFR Systems" }, -// { 0x5A, "INIT Innovations in Transportation, Inc" }, -// { 0x60, "GEC-Marconi" }, -// { 0x64, "Harris Corp." }, -// { 0x68, "Kenwood Communications" }, -// { 0x70, "Glenayre Electronics" }, -// { 0x74, "Japan Radio Co." }, -// { 0x78, "Kokusai" }, -// { 0x7C, "Maxon" }, -// { 0x80, "Midland" }, -// { 0x86, "Daniels Electronics Ltd." }, -// { 0x90, "Motorola" }, -// { 0xA0, "Thales" }, -// { 0xA4, "M/A-COM" }, -// { 0xB0, "Raytheon" }, -// { 0xC0, "SEA" }, -// { 0xC8, "Securicor" }, -// { 0xD0, "ADI" }, -// { 0xD8, "Tait Electronics" }, -// { 0xE0, "Teletec" }, -// { 0xF0, "Transcrypt International" }, -// { 0xF8, "Vertex Standard" }, -// { 0xFC, "Zetron, Inc" }, -// }; - - -// const value_string ALGIDS[] = { -// /* Type I */ -// { 0x00, "ACCORDION 1.3" }, -// { 0x01, "BATON (Auto Even)" }, -// { 0x02, "FIREFLY Type 1" }, -// { 0x03, "MAYFLY Type 1" }, -// { 0x04, "SAVILLE" }, -// { 0x05, "Motorola Assigned - PADSTONE" }, -// { 0x41, "BATON (Auto Odd)" }, -// /* Type III */ -// { 0x80, "Unencrypted" }, -// { 0x81, "DES-OFB, 56 bit key" }, -// { 0x83, "3 key Triple DES, 168 bit key" }, -// { 0x84, "AES-256-OFB" }, -// { 0x85, "AES-128-ECB"}, -// { 0x88, "AES-CBC"}, -// { 0x89, "AES-128-OFB"}, -// /* Motorola proprietary - some of these have been observed over the air, -// some have been taken from firmware dumps on various devices, others -// have come from the TIA's FTP website while it was still public, -// from document "ALGID Guide 2015-04-15.pdf", and others have been -// have been worked out with a little bit of "guesswork" ;) */ -// { 0x9F, "Motorola DES-XL 56-bit key" }, -// { 0xA0, "Motorola DVI-XL" }, -// { 0xA1, "Motorola DVP-XL" }, -// { 0xA2, "Motorola DVI-SPFL"}, -// { 0xA3, "Motorola HAYSTACK" }, -// { 0xA4, "Motorola Assigned - Unknown" }, -// { 0xA5, "Motorola Assigned - Unknown" }, -// { 0xA6, "Motorola Assigned - Unknown" }, -// { 0xA7, "Motorola Assigned - Unknown" }, -// { 0xA8, "Motorola Assigned - Unknown" }, -// { 0xA9, "Motorola Assigned - Unknown" }, -// { 0xAA, "Motorola ADP (40 bit RC4)" }, -// { 0xAB, "Motorola CFX-256" }, -// { 0xAC, "Motorola Assigned - Unknown" }, -// { 0xAD, "Motorola Assigned - Unknown" }, -// { 0xAE, "Motorola Assigned - Unknown" }, -// { 0xAF, "Motorola AES-256-GCM (possibly)" }, -// { 0xB0, "Motorola DVP"}, -// }; - - - - - - +// Copyright 2017, 2018, 2019, 2020, 2021, 2022 Max H. Parke KA1RBI +// Copyright 2020, 2021, 2022 Michael Rose +// +// 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. + +var lastUpdated = "09-Jan-2022"; + +var d_debug = 0; +var http_req = new XMLHttpRequest(); +var counter1 = 0; +var error_val = null; +var current_tgid = null; +var active_tgid = null; +var active_nac = null; +var send_busy = 0; +var send_qfull = 0; +var send_queue = []; +var req_cb_count = 0; +var request_count = 0; +var nfinal_count = 0; +var n200_count = 0; +var r200_count = 0; +var SEND_QLIMIT = 5; +var summary_mode = true; +var enable_changed = false; +var enable_status = []; +var last_srcaddr = []; +var last_srctag = []; +var last_alg = []; +var last_algid = []; +var last_keyid = []; +var tgid_files = {}; +var srcid_files = {}; +var channel_id = {}; +var freqTable = true; +var chsize = 1; +var evsize = 1; +var ersize = 1; +var event_source = null; // must be in global scope for Babysitter to work. + +window.g_change_freq = []; +window.g_cc_event = []; +window.src = []; + +var intvAlias = null; +var intvCss = null; +localStorage.AliasTableUpdated == true; +localStorage.ColorsTableUpdated == true; + +var encsym = "∅"; + +const zeroPad = (num, places) => String(num).padStart(places, '0'); // leading zeros for single digit time values + +function do_onload() { + $('#div_status').show(window.animateSpeed); + $('#babysitter').hide(); + $('#b1').addClass('nav-button-active'); + $('#uiupdated').html(lastUpdated); + document.documentElement.setAttribute('data-theme', 'dark'); + window.siteAlias = null; + + intvAlias = setInterval(getSiteAlias, 5000); + intvCss = setInterval(generateCSS, 4000); + resetFileReload = setInterval(rstFileReload, 12000) + + connect(); + generateCSS(); + getSiteAlias(); + beginJsonSettings(); + + accColorSel(); + for (i = 1; i < 100; i ++) { + $('#unk_default').append(new Option(i, i)); + } + window.animateSpeed = $('#ani_speed').val(); +} + +// vars set when saving things... +// localStorage.AliasTableUpdated == true; +// localStorage.ColorsTableUpdated == true; + +$(document).ready(function() { +// // populate url into oplog url field +// var x = window.location.origin.split( ':' ); +// var y = x[0] + ':' + x[1] + ':5000'; +// $('#oplogUrl').val(y); + loadHelp(); +}); + +function connect() { + event_source = new EventSource('/stream'); + event_source.addEventListener('message', eventsource_listener); + event_source.onerror = function() { + event_source.close(); + } + setReconnect(); +} + +function eventsource_listener(event) { + dispatch_commands(event.data); +} + +// Babysitter - watches /stream, reacts when lost/restored +function setReconnect() { + // readyState values: 0 = connecting, 1 = open, 2 = closed + var reconnecting = false; + + // do we need to setInterval with a var here since we call setReconnect again? + + setInterval(() => { + if (event_source.readyState == 2) { + reconnecting = true; + $('#babysitter').show(); + $('#estat').html(event_source.readyState); + connect(); + } else if (reconnecting) { + reconnecting = false + $('#estat').html(event_source.readyState); + if (!event_source.readyState == 0) + $('#estat').html('OK'); + $('#babysitter').hide(); + } + }, 3000); +} + + +function rstFileReload() { + if (event_source == null || event_source.readyState != 1) + return; + // forces the alias and colors info to reload in case user + // makes changes to them from another browser. + localStorage.AliasTableUpdated == true; + localStorage.ColorsTableUpdated == true; +} + + +function navOplog() { + window.open($('#oplogUrl').val()); +} + +function phaseType(nac) { + // reset the UI to a Phase 1 display (untested) + window[nac + 'flavor'] = null; +} + +function nav_update(command) { + var names = [ + 'b1', + 'b2', + 'b3', + 'b4', + 'b5', + 'b7' + ]; + var bmap = { + 'status': 'b1', + 'settings': 'b2', + 'rx': 'b3', + 'help': 'b4', + 'view': 'b5', + 'about': 'b7' + }; + var id = bmap[command]; + for (var id1 in names) { + b = document.getElementById(names[id1]); + if (names[id1] == id) { + b.className = 'nav-button-active'; + } else { + b.className = 'nav-button'; + } + } +} + +function f_select(command) { + var div_list = [ + 'status', + 'settings', + 'rx', + 'help', + 'about' + ]; + + var orig_command = command; + + if (command == 'rx') { + $('#div_logs').show(window.animateSpeed); + command = 'status'; + summary_mode = false; + } else { + summary_mode = true; + $('#div_logs').hide(window.animateSpeed); + } + + for (var i = 0; i < div_list.length; i++) { + (command == div_list[i]) ? $('#div_' + div_list[i]).show(window.animateSpeed) : $('#div_' + div_list[i]).hide(window.animateSpeed); + } + + (command == 'status' && summary_mode == true) ? $('#div_images').show(window.animateSpeed) : $('#div_images').hide(window.animateSpeed); + + (command == 'status') ? $('#controls').show() : $('#controls').hide(); + + nav_update(orig_command); + + if (command == 'settings') + f_list(); +} + +function rx_update(d) { + if (d['files'].length > 0) { + for (var i = 0; i < d['files'].length; i++) { + var img = document.getElementById('img' + i); + if (img['src'] != d['files'][i]) { + img['src'] = d['files'][i]; + img.style['display'] = ''; + } + } + } + error_val = d['error']; + fine_tune = d['fine_tune']; +} + +// frequency, system, and talkgroup display +function change_freq(d) { // d json_type = change_freq + t = 'TDMA'; + var t = 'FDMA'; + if ((d['tdma'] == 0) || (d['tdma'] == 1)) { + t = 'TDMA ' + d['tdma'] + ''; + } + + $('#stx').html(t); + + var displayTgid = '—'; + var displayTag = ' '; + var display_src = '—'; + var display_alg = '—'; + var display_keyid = '—'; + var display_srctag = '—'; + var e_class = 'value'; + var trunc = $('#valTruncate').val(); + + last_srcaddr[d['nac']] = d['srcaddr']; + last_alg[d['nac']] = d['alg']; + last_algid[d['nac']] = d['algid']; + last_keyid[d['nac']] = d['keyid']; + last_srctag[d['nac']] = d['srcaddr.tag']; + if (d['tgid'] != null) { + displayTgid = d['tgid']; + displayTag = d['tag'].substring(0, trunc); + + if (d['srcaddr'] != null && d['srcaddr'] > 0) { + display_src = d['srcaddr']; + display_srctag = d['srcaddr_tag']; + } + + display_alg = d['alg']; + + if (d['algid'] != 128) { + display_keyid = hex(d['keyid']); + e_class = 'red_value'; + } + } + + // main display - system, talkgroup, encryption, keyid, source addr display + + var d_sys = 'system' in d ? d['system'].substring(0, trunc) : 'Undefined'; + + var html = ''; + html += ''; + + html += ''; + html += ''; + + html += ''; + html += ''; + + html += ''; + html += ''; + + html += ''; + html += ''; + + html += ''; + html += ''; + html += ''; + + html += ''; + html += '
    ' + d_sys + ''; + html += 'Frequency
    ' + freqDisplay(d['freq'] / 1000000) + '
    ' + displayTag + ''; + html += 'Talkgroup
    ' + displayTgid + ''; + html += '
    '; + html += 'Encryption
    ' + display_alg + ''; + html += '
    '; + html += 'Key ID
    ' + display_keyid + ''; + html += '
    '; + html += 'Source Addr
    ' + display_src + ''; + html += '
    '; + + $('#div_s2').html(html).show(); + + active_nac = d['nac']; + active_tgid = d['tgid']; + if (d['tgid'] != null) { + current_tgid = d['tgid']; + } + + // color/style for main display + + var fontStyle = $('#valFontStyle').val(); + var tgSize = parseInt($('#valTagFont').val()); + var sysSize = parseInt($('#valSystemFont').val()); + var defColor = $('#sysColor').val(); + var sysColor = ""; + var tagColor = ""; + var clr = getProperty('.c' + d.tag_color, 'color', 'tgcolors'); + var ani = getProperty('.c' + d.tag_color, 'animation', 'tgcolors'); + var bg = getProperty('.c' + d.tag_color, 'backgroundColor', 'tgcolors'); + + sysColor = (cbState('color_main_sys') && d['tag_color']) ? clr : defColor; + tagColor = (cbState('color_main_tag') && d['tag_color']) ? clr : defColor; + $('#dSys').css({"color": sysColor, "font-size": sysSize, "font-weight": fontStyle}); + $('#dTag').css({"color": tagColor, "font-size": tgSize, "font-weight": fontStyle, "animation": ani, "background-color": bg}); + +} // end change_freq + +function trunk_summary(d) { // d json_type = trunk_update + var nacs = []; + for (var nac in d) { + if (!is_digit(nac.charAt(0))) + continue; + nacs[nac] = 1; + } + var html = ''; + html += '
    '; + html += '
    '; + html += ''; + html += ''; + for (nac in d) { + if (!is_digit(nac.charAt(0))) + continue; + last_srcaddr[nac] = d[nac]['srcaddr']; + last_srctag[nac] = d[nac]['srcaddr_tag']; + last_alg[nac] = d[nac]['alg']; + last_algid[nac] = d[nac]['algid']; + last_keyid[nac] = d[nac]['keyid']; + if (!(nac in enable_status)) + enable_status[nac] = true; + var times = []; + var last_tsbk = d[nac]['last_tsbk'] * 1000; + var display_last_tsbk = getTime(last_tsbk); + times.push(display_last_tsbk); + var min_t = 0; + if (times.length) { + for (var i = 0; i < times.length; i++) { + if (i == 0 || times[i] < min_t) + min_t = times[i]; + } + times = min_t; + } else { + times = ' '; + } + var ns = parseInt(nac).toString(16); + html += ''; + var tf = d[nac]['tgid_tags_file']; + var sf = d[nac]['unit_id_tags_file']; + var sysid = d[nac]['sysid']; + var checked = enable_status[nac] ? 'checked' : ''; + + html += ''; + // checkbox styling nonsense above + html += ''; + html += ''; + html += ''; + html += ''; + html += '' + html += ''; + } + var display = ''; + if (!enable_changed) + display = 'none'; + html += ''; + html += '
    EnabledNACSystemLast TSBKTSBK CountAlias TSV
    '; + html += '' + ns.toUpperCase() + '' + d[nac]['sysname'] + '' + times + '' + comma(d[nac]['tsbks']) + ' '; + if (tf) + html +='TG'; + if (sf) + html +='  SRC'; + html += '  SA'; + html += '
    '; + html += ''; + html += '
    '; + return html; +} // end trunk_summary() + +// additional system info: wacn, sysID, rfss, site id, freq table, adjc sites, secondary control channels, freq error +function trunk_detail(d) { // d json_type = trunk_update + var html = ''; + var alias, sysid, rfss, site; + var error_val = fine_tune = "—"; + for (var nac in d) { + if (!is_digit(nac.charAt(0))) + continue; + var p2 = window[nac + 'flavor']; // if phase 2 was detected, add phase 2 layouts + var flavor = "Phase 1"; + if (p2) + flavor = "Phase 2"; + last_srcaddr[nac] = d[nac]['srcaddr']; + last_srctag[nac] = d[nac]['srcaddr_tag']; + last_alg[nac] = d[nac]['alg']; + last_algid[nac] = d[nac]['algid']; + last_keyid[nac] = d[nac]['keyid']; + error_val = sessionStorage.errorVal; + fine_tune = sessionStorage.fineTune; + + sysid = d[nac]['sysid']; + rfss = d[nac]['rfid']; + site = d[nac]['stid']; + + // use the Site Alias if defined, otherwise use d/nac/sysname + if (window.siteAlias != null && window.siteAlias[sysid] && window.siteAlias[sysid][rfss] && window.siteAlias[sysid][rfss][site]) { + alias = window.siteAlias[sysid][rfss][site]['alias']; + } else { + alias = d[nac]['sysname']; + } + + html += '
    '; + html += ''; + + html += ''; // 1 + html += ''; // 2 + html += ''; // 3 + html += ''; // 4 + html += ''; // 5 + html += ''; // 6 + + html += ''; + + // 1 + html += ''; + + // 2 + html += ''; + + // 3 + html += ''; + + // 4 + html += ''; + + // 5 + html += ''; + + + // 6 + html += ''; + + html += ''; + + // row 2 + html += ''; + + // 1 + html += ''; + + // 2, 3, 4 + scc = ''; + for (i = 0; i < d[nac]['secondary'].length; i++) { + scc += freqDisplay(d[nac]['secondary'][i] / 1000000); + if ( (i+1) != d[nac]['secondary'].length) + scc += '   '; // space between freqs + } + + html += ''; + + // 5 + html += ''; + + // 6 + var last_tsbk = d[nac]['last_tsbk'] * 1000; + var display_last_tsbk = getTime(last_tsbk); + html += ''; + + html += ''; + + if (cbState('showBandPlan')) { + + var zsys = d[nac]['sysid']; + html += ''; + } + + html += '
    ' + alias + '
    '; + html += 'System ID
    ' + '0x' + parseInt(d[nac]['sysid']).toString(16).toUpperCase(); + html += '
    '; + html += 'NAC
    ' + '0x' + parseInt(nac).toString(16).toUpperCase(); + html += '
    '; + html += 'WACN
    ' + '0x' + parseInt(d[nac]['wacn']).toString(16).toUpperCase(); + html += '
    '; + html += 'RFSS ID
    ' + d[nac]['rfid'] + html += '
    '; + html += 'Site ID
    ' + d[nac]['stid'] + html += '
    '; + html += 'Type
    ' + flavor + '
    '; + html += '
    '; + html += 'Control Channel
    ' + freqDisplay(d[nac]['rxchan'] / 1000000); + html += '
    '; + + html += 'Secondary Control Channels
    '; + + if (d[nac]['secondary'].length) { + html += scc; + } else { + html += 'None'; + } + + html += '
    '; + html += 'TSBK
    ' + comma(d[nac]['tsbks']); + html += '
    '; + html += 'Last TSBK
    ' + display_last_tsbk + ''; + html += '
    '; + + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + for (p in channel_id[d[nac]['sysid']]) { + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + } + html += '
    IDTypeBase FrequencyTx OffsetSpacing (kHz)Slots
    ' + channel_id[zsys][p]['iden'] + '' + channel_id[zsys][p]['type'] + '' + freqDisplay(channel_id[zsys][p]['freq']) + '' + freqDisplay(channel_id[zsys][p]['offset']) + '' + (channel_id[zsys][p]['step'] * 100) + '' + channel_id[zsys][p]['slots'] + '
    '; + + html += '
    '; + + // system frequencies table // d json_type = trunk_update + html += '
    '; + + if (freqTable == true) { // show/hide table with F key + + html += ''; + + html += ''; // 1 Freq + if (cbState('showLast')) + html += ''; // 2 Last + html += ''; // 3 tgid + html += ''; // 4 tgtag + html += ''; // 5 enc + html += ''; // 6 srcaddr + html += ''; // 7 srctag + + html += ''; + html += ''; // 1 + if (cbState('showLast')) + html += ''; // 2 + html += ''; // 3 / 4 / 5 + html += ''; // 6 / 7 + html += ''; + + var c, src_c, sc_src, sf_freq, sf_last, sf_hits, sf_timeout; + + // save keystrokes! + var fd = 'frequency_data'; + var ft = 'frequency_tracking'; + var td = 'talkgroup_data'; + var ts = 'time_slot'; + var calls = 'calls'; + + sf_timeout = 0; // delay before clearing the tg info + + // #frequency# + + //calls + var sf_count = []; // "count" + var sf_lastactive = []; // "last_active" + var sf_protected = []; // "protected" + var sf_starttime = []; // "start_time" + var sf_endtime = []; // "end_time" + + //tgid + var sf_tgid = []; // "tg_id" + var sf_tgtag = []; // "tag" + var sf_tgcolor = []; // "color" + + //srcaddr + var sf_srcaddr = []; // "unit_id" + var sf_srctag = []; // "tag" + var sf_srccolor = []; // "color" + + var slot, tgid, x, y; + var sf_enc = []; + var sf_la = []; + + var sf_last = 0; + + sf_tgid[0] = " "; + sf_tgtag[0] = " "; + sf_srcaddr[0] = " "; + sf_srctag[0] = " "; + sf_enc[0] = " "; + sf_tgcolor[0] = 0; + sf_srccolor[0] = 0; + sf_la[0] = 0; + + sf_tgid[1] = " "; + sf_tgtag[1] = " "; + sf_srcaddr[1] = " "; + sf_srctag[1] = " "; + sf_enc[1] = " "; + sf_tgcolor[1] = 0; + sf_srccolor[1] = 0; + sf_la[1] = 0; + + for (var freq in d[nac][ft]) { + + // temp for testing -- semi-perm? + if ( d[nac][ft][freq]['tdma'] == true ) { + zt = "T"; + } else { + zt = " "; + } + // end temp + + slot = 0; + + for (var call of d[nac][ft][freq][calls]) { + + if (call == null) { + + sf_tgid[slot] = " "; + sf_tgtag[slot] = " "; + sf_srcaddr[slot] = " "; + sf_srctag[slot] = " "; + sf_enc[slot] = " "; + + slot++; + continue; + } + + if (call.end_time == 0) { // if end_time != 0 then the call is dead and should not be displayed in Active Freq table + + sf_tgid[slot] = call.tgid.tg_id; + + if (call.tgid.tag) { + sf_tgtag[slot] = call.tgid.tag; + } else { + sf_tgtag[slot] = "Talkgroup " + call.tgid.tg_id; + call.tgid.color = $('#unk_default').val(); + } + + sf_tgcolor[slot] = call.tgid.color; + + sf_srcaddr[slot] = call.srcaddr.unit_id ? call.srcaddr.unit_id : " "; + sf_srctag[slot] = call.srcaddr.tag ? call.srcaddr.tag : " "; + sf_srccolor[slot] = call.srcaddr.color; + + sf_protected[slot] = call['protected']; // protected is a reserved word in JS so can't use dot notation here + sf_enc[slot] = (sf_protected[slot] == true) ? encsym : " "; + + } // end if call.end_time + + sf_count[slot] = call.count; + sf_lastactive[slot] = call.last_active; + + sf_la[slot] = parseInt(d.time - sf_lastactive[slot], 10); + + slot++; + + } // end for var call in calls + + sf_freq = freqDisplay(parseInt(freq) / 1000000); + + sf_last = d[nac][ft][freq]['last_active']; + sf_last = parseFloat(sf_last).toFixed(0); + + if (sf_la[0] > sf_timeout) { + sf_tgid[0] = " "; + sf_tgtag[0] = " "; + sf_srcaddr[0] = " "; + sf_srctag[0] = " "; + sf_enc[0] = " "; + } + + if (sf_la[1] > sf_timeout) { + sf_tgid[1] = " "; + sf_tgtag[1] = " "; + sf_srcaddr[1] = " "; + sf_srctag[1] = " "; + sf_enc[1] = " "; + } + + for (slot = 0; slot < 2; slot++ ) { + + var c = 0; + var src_c = 0; + + if (sf_tgcolor[slot]) { + c = sf_tgcolor[slot]; + } else { + c = smartColor(sf_tgtag[slot]) + } + + if (sf_srccolor[slot]) { + src_c = sf_srccolor[slot]; + } else { + src_c = smartColor(sf_srctag[slot]) + } + + sc_src = ""; + sc = ""; + + var tfile = d[nac]['tgid_tags_file']; + var sfile = d[nac]['unit_id_tags_file']; + + // Active Frequencies Table + + html += ''; + + if (slot == 0) + html += ''; // 1 + if (slot == 1) + html += ''; // 1 + if (cbState('showLast')) + html += ''; // 2 + html += ''; // 3 + html += ''; // 4 + html += ''; // 5 + html += ''; // 6 + html += ''; // 7 + + html += ''; +// if (!p2 || zt == " " ) break; + if (!p2 || zt == " " || ( sf_la[slot] > 120 && cbState('colSlot2')) ) break; + } // end for slot + } // end for freq + + html += '
    Frequency Last TalkgroupSource
    ' + sf_freq + ' ' + zt + ' / 2     ' + sf_la[slot] + '' + sc + sf_tgid[slot] + '' + sc + sf_tgtag[slot] + '' + sf_enc[slot] + '' + sc_src + sf_srcaddr[slot] + '' + sc_src + sf_srctag[slot] + '
    '; + } // end (freqTable == true) + + if (cbState('show_adj')) + html += adjacent_data(d[nac]['adjacent_data']); + + } // end for nac in d + + return html; + +} // end trunk_detail() + +function editTsv(click, source, file, nac) { // dbl click the ui to access the TSV editor + if (!click) + return; + + // source + // 1 = Active Frequencies + // 2 = Call History + + window.getSelection().removeAllRanges(); // unselect the text that was double-clicked on + var cname = click.attributes.name; + cname = (cname.value); + + + if (source == 2 && (cname == "tgid" || cname == "tag")) { // Call History Talkground dbl clicked + var sid = file; + file = tgid_files[sid]; + } + + if (source == 2 && (cname == "srcid" || cname == "srctag")) { // Call History Source dbl clicked + var sid = file; + file = srcid_files[sid]; + } + + if ( (cname =="tgid" || cname == "tag") && (!file || file == "null")) { // null is being stored as a string in window.tgid_files + jAlert ("Talkgroup tag TSV file not specified in config file. Cannot edit.", "Error"); + return; + } + + if ( (cname =="srcid" || cname == "srctag") && (!file || file == "null")) { + jAlert ("Source tag TSV file not specified in config file. Cannot edit.", "Error"); + return; + } + + var id = 0; + var url, x, y, tsv; + + // Active Frequencies // Call History + // cellIndex[2] = Talkgroup ID // cellIndex[3] = Talkgroup ID + // cellIndex[3] = Talkgroup Tag // cellIndex[4] = Talkgroup Tag + // cellIndex[5] = Source ID // cellIndex[5] = Source ID + // cellIndex[6] = Source Tag // cellIndex[6] = Source Tag + + switch (source) { + case 1: // sysfreq Table TD + x = 2; // the cell index of tgid + y = 5; // the cell index of srcid + + switch (cname) { + case 'tgid': + id = click.innerText; + url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=tg&id=' + id + '&nac=' + nac; + tsv = window.open(url, 'tsv'); + break; + + case 'tag': + id = click.parentElement.cells[x].innerText; + url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=tg&id=' + id + '&nac=' + nac; + tsv = window.open(url, 'tsv'); + break; + + case 'srcid': + id = click.innerText; + url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=src&id=' + id + '&nac=' + nac; + tsv = window.open(url, 'tsv'); + break; + + case 'srctag': + id = click.parentElement.cells[y].innerText; + url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=src&id=' + id + '&&nac=' + nac; + tsv = window.open(url, 'tsv'); + break; + } + break; + + case 2: // Call History Table SPAN + x = 3; // the cell index of tgid + y = 5; // the cell index of srcid + + switch (cname) { + case 'tgid': + id = click.parentElement.parentElement.cells[x].innerText; + url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=tg&id=' + id + '&nac=' + nac; + tsv = window.open(url, 'tsv'); + break; + + case 'tag': + id = click.parentElement.parentElement.cells[x].innerText; + url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=tg&id=' + id + '&nac=' + nac;; + tsv = window.open(url, 'tsv'); + break; + + case 'srcid': + id = click.parentElement.parentElement.cells[y].innerText; + url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=src&id=' + id + '&nac=' + nac; + tsv = window.open(url, 'tsv'); + break; + + case 'srctag': + id = click.parentElement.parentElement.cells[y].innerText; + url = 'tsv-edit.html?file=' + file + '&css=' + displayMode() + '&mode=src&id=' + id + '&nac=' + nac; + tsv = window.open(url, 'tsv'); + break; + } // end switch cname + break; + } // end switch source +} // end editTsv() + +// adjacent sites table +function adjacent_data(d) { + // d json_type = trunk_update + if (Object.keys(d).length < 1) + return ''; + // var html = "
    "; // open div-adjacent + var html = '
    '; + + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + + html += ''; + var ct = 0; + var alias, sysid, rfss, site; + for (var freq in d) { + sysid = d[freq]['sysid']; + rfss = d[freq]['rfid']; + site = d[freq]['stid']; + alias = "-"; + if (window.siteAlias != null && window.siteAlias[sysid] && window.siteAlias[sysid][rfss] && window.siteAlias[sysid][rfss][site]) { + alias = window.siteAlias[sysid][rfss][site]['alias']; + } else { + alias = 'Site ' + d[freq]['stid']; + } + + html += ''; + html += ''; + html += ''; + } + html += '
    Adjacent SitesFrequencySystem IDRFSS IDSite IDUplink
    ' + alias + ''+ freqDisplay(freq / 1000000) + '' + d[freq]['sysid'].toString(16).toUpperCase() + '' + d[freq]['rfid'] + '' + d[freq]['stid'] + '' + freqDisplay(d[freq]['uplink'] / 1000000) + '
    '; + return html; // end adjacent sites table +} // end adjacent_data() + +function trunk_update(d) { + + var html; + if (summary_mode) { + html = trunk_summary(d); // home screen + } else { + html = trunk_detail(d); // RX screen + + } + + $('#div_s1').html(html); + + if (!summary_mode) + sortTable('adjacent-sites', 4); + + // display hold indicator + if (d['data']['hold_mode']) { + $('#holdIndicator').show(); + } else { + $('#holdIndicator').hide(); + } + + // display last command unless it was more than 10 seconds ago + x2 = d['data']['last_command']; + if (x2 && d['data']['last_command_time'] > -10) { + $('#lastCommand').html('Last Command
    ' + x2.toUpperCase() + '
    ' + ' ' + d['data']['last_command_time'] * -1 + ' secs ago'); + } else { + $('#lastCommand').html(''); + } + + update_data(d); +} // end trunk_update() + +function update_data(d) { // d json type = trunk_update + if (active_nac == null || active_tgid == null) + return; + + var display_src = '—'; + var display_srctag = '—'; + var display_alg = '—'; + var display_keyid = '—'; + + var e_class = 'value'; + + if (last_srcaddr[active_nac] != null) { + display_src = last_srcaddr[active_nac]; + var ele = document.getElementById('dSrc'); + if (ele != null) + ele.innerHTML = display_src; + } + + if (last_srctag[active_nac] != null) { + display_srctag = last_srctag[active_nac]; + var ele = document.getElementById('dSrctag'); + if (ele != null) + ele.innerHTML = display_srctag; + } + + if (last_algid[active_nac] == null || last_alg[active_nac] == null || last_keyid[active_nac] == null) + return; + display_alg = last_alg[active_nac]; + if (last_algid[active_nac] != 128) { + display_keyid = last_keyid[active_nac]; + e_class = 'red_value'; + } + ele = document.getElementById('dAlg'); + if (ele != null) { + ele.innerHTML = display_alg; + ele.className = e_class; + } + ele = document.getElementById('dKey'); + if (ele != null) + ele.innerHTML = display_keyid; + +} // end update_data() + +function error_tracking() { + return; // empty right now, handled in dispatch_commands switch block +} // end error_tracking + +function dispatch_commands(txt) { + if (txt == '[]') { + return; + } + + var dl = JSON.parse(txt); + + var dispatch = { + 'trunk_update': trunk_update, + 'change_freq': change_freq, + 'rx_update': rx_update, + 'config_data': config_data, + 'config_list': config_list, + 'cc_event': cc_event, + 'freq_error_tracking': error_tracking + }; + + for (var i = 0; i < dl.length; i++) { + var d = dl[i]; + if (!('json_type' in d)) + continue; + if (!(d['json_type'] in dispatch)) + continue; + var j_type = d['json_type']; + var time = getTime(new Date()); + if (d.time) + time = getTime(d.time * 1000); + + switch (j_type) { + + case 'freq_error_tracking': + + var ele, bx, cx, dx, ex, fx, gx; + $('#error_tracking').show(); + bx = d.device; + cx = d.name; + dx = d.error_band; + ex = d.freq_correction; + fx = d.freq_error; + gx = d.tuning_error; + appendErrorTable (time, bx, cx, dx, ex, fx, gx, 'errors'); + break; + + case 'change_freq': + + cb = cbState('log_cf'); + time = getTime(d['effective_time'] * 1000); + if (d.tgid && !d.tag) { + d.tag = 'Talkgroup ' + d.tgid + ''; // talkgroup tag isn't in the tsv + d.tag_color = $('#unk_default').val(); + } + sysid = d.sysid ? hex(d.sysid).toUpperCase() : "—" + var freq = d['freq'] / 1000000; + var srctag = "—"; + var color, srccolor, srcaddr; + + + if (d['tag_color']) { + color = d['tag_color']; + } else { + color = smartColor(d.tag); + d['tag_color'] = color; + } + + if (d['srcaddr_color']) { + srccolor = d['srcaddr_color']; + } else { + srccolor = smartColor(d.tag); + } + + var tag = '' + d.tag + ''; + var tgid = '' + d.tgid + ''; + + // TODO - cb not defined keeps coming up a couple times in the console, right at start up. + if (cb && d.tgid) { + appendJsonTable(time, j_type, sysid, tag, tgid, 'OP25', freq, '--', 'history'); + } + + if (d.tgid) { + srctag = (window['srct' + d.srcaddr]) ? '' + window['srct' + d.srcaddr] : "—"; + srcaddr = '' + d.srcaddr; + } + window.g_change_freq = d; + break; + + case 'trunk_update': + + cb = cbState('log_tu'); + var tf, sf, sid, a, z; + var sysid, site, rfid, color; + var grpaddr, c_grpaddr, srcaddr, c_srcaddr, talkgroup, c_talkgroup, srctag, c_srctag; + for (nac in d) { + if (Number.isInteger(parseInt(nac))) { + sysid = d[nac]['sysid']; + sid = sysid; + sysid = hex(sysid).toUpperCase(); + site = d[nac]['stid']; + rfid = d[nac]['rfid']; + grpaddr = d[nac]['grpaddr']; + srcaddr = d[nac]['srcaddr']; + + tf = d[nac]['tgid_tags_file']; + tgid_files[sid] = tf; + + sf = d[nac]['unit_id_tags_file']; + srcid_files[sid] = sf; + + if (window['tgidt' + grpaddr]) + talkgroup = window['tgidt' + grpaddr]; + + srctag = (window['srct' + srcaddr]) ? (window['srct' + srcaddr]) : " "; + + color = smartColor(talkgroup); + srccolor = smartColor(talkgroup); + + if (window['tgidc' + grpaddr]) + color = window['tgidc' + grpaddr]; + + c_grpaddr = '' + grpaddr + ''; + c_talkgroup = '' + talkgroup + ''; + + color = (window['srcc' + srcaddr]) ? window['srcc' + srcaddr] : "0"; + c_srcaddr = '' + srcaddr + ''; + c_srctag = '' + srctag + ''; + + var sr = "Site: " + site + "     RFID: " + rfid; + + var col_f = "OP25"; // empty for now + + if (cb) { + if (grpaddr) { // do not append the table if no grpaddr is present - TU into Events table is pretty much useless anyway + appendJsonTable(time, j_type, sysid, sr, c_grpaddr, col_f, "Update", "--", "history"); + } + } //end if cb + } // end if nac is number + } // end for nac in d + sources(d); + window.g_trunk_update = d; + break; + + case 'rx_update': + cb = cbState('log_rx'); + if (d['files'][0]) { + var ps = "Plots: True"; + } else { + var ps = "Plots: False" + } + if (d['fine_tune']) { + var ft = d['fine_tune']; + } else { + var ft = "n/a"; + } + + if (cb) { + appendJsonTable(time, j_type, ps, 'Fine Tune: ' + ft, 'Tune Err: ' + d['error'], 'OP25', 'RX Update', '--', 'history'); + } // this Events table entry doesn't add much value either. + + sessionStorage.fineTune = d['fine_tune'] ? d['fine_tune'] : "—"; + sessionStorage.errorVal = d['error'] ? d['error'] : "—"; + break; + + case 'cc_event': + + time = getTime(d['time'] * 1000); + cb = cbState('log_cc'); + var opcode, n_opcode, sysid, tag, target, source, srctag, src_c, color; + var logCall = 0; + var noLog = 0; + var xp = 0; + + if (d.opcode !== null) { + opcode = d['opcode'].toString(16); + if (g_opcode[opcode]) { + n_opcode = g_opcode[opcode]; + } else { + n_opcode = 'Opcode Not Found: ' + opcode; + } + } + + tag = target = source = '—'; + srctag = " "; // used + + // [group] does not appear in every cc_event! + if (d.group) { + + if (d.group && d.group.tag) { + tag = d.group.tag; + } else { + d.group.tag = 'Talkgroup ' + d.group.tg_id; + d.group.color = $('#unk_default').val(); + } + + } // end if d.group + + if (d.reason) { + tag = getReason(hex(d.reason)); + } + + n_opcode = g_opcode[opcode] ? g_opcode[opcode] : opcode; + + if (d['srcaddr']) { + source = (d.srcaddr.unit_id) ? d.srcaddr.unit_id : "— No ID"; // This condition is reached when there is traffic + srctag = (d.srcaddr.tag) ? d.srcaddr.tag : " "; // but no source unit id is present. on ebrcs, this + // happens when an old u/vhf system is patched onto ebrcs. + } + + // handle manufacturer opcodes: + // 0x10 = Relm/BK + // 0x68 = Kenwood + // 144(10) 0x90 = Motorola + // 164(10) 0xA4 = Harris + // 0xD8 = Tait + // 0xF8 = Vertex Standard + // trunking.py sends the following opcodes: + // -1, 0x00, 01, 02, 03, 09, 20, 27, 28, 2a, 2b, 2c, 2d, 2f, 31, 33, 34, 3d + + switch (opcode) { + + case "-1": // end call (not a P25 standard) + tag = d.tgid.tag; + target = d.tgid.tg_id; + n_opcode = "End Call"; + noLog = 1; // Events + // TODO - maybe nothing? + break; + + case "0": + if (d.mfrid != null) { + switch (d.mfrid) { + case 0: + n_opcode = "Call"; // GRP_V_CH_GRANT + target = d['group']['tg_id']; + srctag = d['srcaddr']['tag']; + noLog = cbState('je_calls') ? 0 : 1; //events + logCall = 1; // callHistory + break; + case 144: // MOTOROLA + n_opcode = "XP Adds"; //mot_grg_add_cmd 0x00" + tag = d.sg.tag; + target = d.sg.tg_id; + noLog = 1; // chatty af + logCall = 0; // callHistory + break; + case "default": + n_opcode = "Call"; + target = d['group']['tg_id']; + srctag = d['srcaddr']['tag']; + noLog = cbState('je_calls') ? 0 : 1; + logCall = 1; // callHistory + break; + } // end switch + } // end if + break; + + case "1": // 0x01 - RESERVED + if (d.mfrid != null) { + switch (d.mfrid) { + case 0: + n_opcode = "Reserved 0x01"; + break; + case 144: // MOTOROLA + n_opcode = "XP Drops"; + tag = d.sg.tag; + target = d.sg.tg_id; + break; + } // end switch + } // end if + break; + + case "2": // 0x02 - grp_v_ch_grant_updt + if (d.mfrid != null) { + switch (d.mfrid) { + case 0: + n_opcode = "grp_v_ch_grant_updt 0x02"; + noLog = cbState('je_calls') ? 0 : 1; + break; + case 144: // MOTOROLA + d['group'] = d['sg']; + d['srcaddr'] = d['sa']; + srctag = d.sa.tag; + n_opcode = "XP Call"; // MOT XP Call mot_grg_cn_grant 0x02 + tag = d.sg.tag; + target = d.sg.tg_id; + source = d.sa.unit_id; + noLog = cbState('je_calls') ? 0 : 1; + logCall = 1; // callHistory + xp = 1; + break; + } // end switch + } // end if + break; + + case "3": // 0x03 - GRP_V_CH_GRANT_UPDT_EXP + if (d.mfrid != null) { + switch (d.mfrid) { + case 0: + n_opcode = "grp_v_ch_grant_updt_exp 0x03"; + break; + case 144: // MOTOROLA + n_opcode = "mot_grg_cn_grant_updt 0x03"; + tag = d.sg1.tag; + target = d.sg1.tg_id; + noLog = 1; // used for late entry, very chatty, probably should not log to JSON Events + break; + } // end switch + } // end if + break; + + case "9": // MOT System Load ? Could be "Motorola Scan Marker" + if (d.mfrid != null) { + switch (d.mfrid) { + case 0: + n_opcode = "Opcode 0x03"; + break; + case 144: // MOTOROLA + n_opcode = "System Load " + d.test1; + noLog = 1; + break; + } // end switch + } // end if + break; + + case "20": // 0x20 - ACK_RSP_FNE + noLog = 1; + break; + + case "24": // 0x24 - Extended Function Command (inhibit) TODO: get reason code, log it + var efclass = d.efclass; + var efoperand = d.efoperand; + var efargs = d.efargs; + var target = d.target; + n_opcode = "Ext Fnct Cmd: " + efoperand; + source = efargs; + noLog = 0; + break; + + case "27": // 0x27 - DENY_RSP + source = d.target.unit_id; + srctag = d.target.tag; + noLog = cbState('je_deny') ? 0 : 1; + break; + + case "28": // 0x28 - GRP_AFF_RSP + source = d.target.unit_id; + target = d.group.tg_id; + noLog = cbState('je_joins') ? 0 : 1; + break; + + case "2a": // 0x2A - GRP_AFF_Q - Group Affiliate Query + noLog = 1; + break; + + case "2b": // 0x2B - LOC_REG_RSP - Location Registration Response + if (d['rv'] != null) { + switch (d['rv']) { + case 0: + n_opcode = "Joins"; + target = d.group.tg_id; + source = d.target.unit_id; + srctag = d.target.tag; + noLog = cbState('je_joins') ? 0 : 1; + break; + case 1: + n_opcode = "Reg Fail"; + target = d.group.tg_id; + source = d.target.unit_id; + srctag = d.target.tag; + noLog = cbState('je_deny') ? 0 : 1; + break; + case 2: + n_opcode = "Reg Denied"; + target = d.group.tg_id; + source = d.target.unit_id; + srctag = d.target.tag; + noLog = cbState('je_deny') ? 0 : 1; + break; + case 3: + n_opcode = "Reg Refused"; + target = d.group.tg_id; + source = d.target.unit_id; + srctag = d.target.tag; + noLog = cbState('je_deny') ? 0 : 1; + break; + } // end rv switch + } // end if + break; + + case "2c": // 0x2C - U_REG_RSP - Login TODO: source and target are the same from the json + source = d.source.unit_id; + srctag = d.source.tag; + // target = d.target.unit_id; + // tag = d.target.tag; + noLog = cbState('je_log') ? 0 : 1; + break; + + case "2d": // 0x2D - U_REG_CMD - Force SU Registration + source = d.source.unit_id; + srctag = d.source.tag; + target = d.target.unit_id; + tag = d.target.tag; + noLog = cbState('je_log') ? 0 : 1; + break; + + case "2f": // 0x2F - U_DE_REG_ACK - Logout + source = d.source.unit_id; + srctag = d.source.tag; + noLog = cbState('je_log') ? 0 : 1; + break; + + case "31": // 0x31 - AUTH_DMD - Authentication Demand + source = d.target_id.unit_id; + srctag = d.target_id.tag; + target = d.target_address.unit_id; + tag = d.target_address.tag; + break; + + case "33": // 0x33 - iden up tdma + var sysid = d.sysid; + var type = 'TDMA'; + var iden = d.iden; + var freq = d.freq / 1000000; + var offset = d.offset / 1000000; + var step = d.step/100000; + var slots = d.slots; + channelId (sysid, iden, type, freq, offset, step, slots); + noLog = 1; + break; + + case "34": // 0x34 - iden up vhf/uhf + // TODO - test this + var sysid = d.sysid; + var type = 'FDMA'; + var iden = d.iden; + var freq = d.freq / 1000000; + var offset = d.offset / 1000000; + var step = d.step/100000; + var slots = 1; + channelId (sysid, iden, type, freq, offset, step, slots); + noLog = 1; + break; + + case "3d": // 0x3D - iden up (7/800) + var sysid = d.sysid; + var type = 'FDMA'; + var iden = d.iden; + var freq = d.freq / 1000000; + var offset = d.offset / 1000000; + var step = d.step/100000; + var slots = 1; + channelId (sysid, iden, type, freq, offset, step, slots); + noLog = 1; + break; + + } // end switch - end handle manf opcodes + + if (d.options) { + n_opcode = ( (d.options >> 6) & 1 ) ? n_opcode = n_opcode + "   ∅" : n_opcode; //encrypted + } + + c = smartColor(tag); + src_c = 0; + + if (d['group']) + c = (d['group']['color']) ? d['group']['color'] : c; + + if (d['sg']) + c = (d['sg']['color']) ? d['sg']['color'] : c; + + tag = "" + tag; + target = "" + target; + + + if (d['group']) { + src_c = d['group']['color']; + } + + if (d['source']) { + src_c = d['source']['color']; + } + + if (d['srcaddr']) { // present for voice calls, not present for other cc_events + src_c = (d['srcaddr']['color']) ? d['srcaddr']['color'] : src_c; + } + + + + source = "" + source; + srctag = "" + srctag; + + if (cb && noLog == 0) { + appendJsonTable(time, j_type, n_opcode, tag, target, source, srctag, opcode, 'history'); + } + + if (target != "—" && logCall) { + + target = "" + target; + tag = "" + tag; + + source = "" + source; + srctag = "" + srctag; + + var tdma_slot = d.tdma_slot; // s/b null if not running tdma + + appendCallHistory(time, "--", target, tag, source, srctag, 'callHistory', d.options, xp, d.sysid, d.nac, tdma_slot); + } + + window.g_cc_event = d; + break; + } // end switch + + dispatch[d['json_type']](d); // correct function is called based on json type in the dataset + } // end for + f_debug(d); +} // end dispatch_commands() + +function cc_event(d) { + // does nothing right now + return; +} // end cc_event() + +function do_update() { + f_debug(); +} // do_update() + +function f_scan_button(command) { + if (current_tgid == null) + send_command(command, -1); + else + send_command(command, current_tgid); +} + +function f_goto_button(command) { + var _tgid = 0; + if (command == 'goto') { + command = 'hold'; + if (current_tgid != null) + _tgid = current_tgid; + _tgid = parseInt(prompt('Enter TGID to hold.', _tgid)); + if (isNaN(_tgid) || _tgid < 0 || _tgid > 65535) { + return; // Cancel was pressed or invalid entry + } + + // it's necessary to release current hold before trying to hold on a new tg + if ($('#holdIndicator').is(':visible') ){ + send_command('skip', -1); + setTimeout(function() { + send_command(command, _tgid); + }, 500); + return; + } + send_command(command, _tgid); + } +} + +function f_debug(d) { + if (!d_debug) + return; + + var html = "
    Debug:
    "; +// html += 'window.g_nac = ' + window.g_nac; + html += '
    '; + html += 'json type: ' + d['json_type']; + html += '
    '; + html += "busy " + send_busy; + html += " qfull " + send_qfull; + html += " sendq size " + send_queue.length; + html += " requests " + request_count; + html += "
    callbacks: "; + html += " total=" + req_cb_count; + html += " incomplete=" + nfinal_count; + html += " error=" + n200_count; + html += " OK=" + r200_count; + html += "
    "; + + $('#div_debug').html(html); +} + +function popOut() { + var myWindow = window.open(window.location.href, '', 'width=760,height=400'); +} + +function toggleCSS() { + $(document.documentElement).attr('data-theme') == 'light' ? + $(document.documentElement).attr('data-theme', 'dark') : + $(document.documentElement).attr('data-theme', 'light'); + sdmode(); +} + +function comma(x) { + // add comma formatting to whatever you give it (xx,xxxx,xxxx) + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); +} + +document.onkeydown = function(evt) { + + console.log(evt.altKey); + // keyboard shortcuts + evt = evt || window.event; + var x = document.activeElement.tagName; + if (x == "INPUT" || evt.altKey == true || evt.ctrlKey == true) + return; // don't do anything if user is typing in an input field or if alt, ctrl key is bing used + + switch (evt.keyCode) { + + + case 70: + // 'f' key - show/hide frequency table + $('#lastCommand').html('F - Freq Tbl

    ').show(); + freqTable = !freqTable; + setTimeout(function() {$('#lastCommand').html('').hide();}, 2000); + break; + + case 71: + // 'g' key - GOTO + $('#lastCommand').html('G - GOTO

    ').show(); + f_goto_button('goto'); + break; + case 76: + // 'l' key - LOCKOUT + $('#lastCommand').html('L - LOCKOUT

    ').show(); + f_scan_button('lockout'); + break; + case 72: + // 'h' key - HOLD + $('#lastCommand').html('H - HOLD

    ').show(); + f_scan_button('hold'); + break; + case 80: + // 'p' show/hide plots + $('#lastCommand').html('P - Plots

    ').show(); + minify('div_images'); + break; + case 83: + // 's' key - SKIP + $('#lastCommand').html('S - Skip

    ').show(); + f_scan_button('skip'); + break; + case 86: + // 'v' key - VIEW (light/dark) + $('#lastCommand').html('V - View

    ').show(); + toggleCSS(); + break; + case 82: + // 'r' key - RX screen + f_select('rx'); + break; + case 74: + // 'j' key - HOME screen + f_select('status'); + break; + case 77: + // 'm' key - MINIFY + minify('nav-bar'); + minify('div_images'); + minify('div_s1'); + break; + case 48: // '0' key - show/hide Main Display + minify('controlsDisplay'); + break; + case 49: // '1' key - show/hide callHistory + $('#lastCommand').html('1 - Calls

    ').show(); + minify('log_container_1'); + break; + case 50: + $('#lastCommand').html('2 - Events

    ').show(); + // '2' key - show/hide history (json log) + minify('log_container_2'); + break; + case 51: + // '3' key - show/hide logs block + $('#lastCommand').html('3 - Logs

    ').show(); + if ( $('#div_logs').is(":hidden") ) { + $('#div_logs').show(window.animateSpeed); + } else { + $('#div_logs').hide(window.animateSpeed); + } + break; // showBandPlan + case 52: + // '4' key - show/hide Band Plan + $('#lastCommand').html('4 - Bandplan

    ').show(); + $('#showBandPlan').trigger('click'); + setTimeout(function() {$('#lastCommand').html('').hide();}, 2000); + break; + case 65: + $('#lastCommand').html('A - Neighbors

    ').show(); + $('#show_adj').trigger('click'); + break; + case 66: + // 'b' key - bold + $('#valFontStyle').val('bold'); + break; + case 78: + // 'n' key - normal + $('#valFontStyle').val('normal'); + break; + } // end switch +}; // end onkeydown + +function minify(div) { + $('#' + div).toggle(window.animateSpeed); +} + +function appendJsonTable(a, b, c, d, e, f, srctag, opcode, target) { + var numRows = document.getElementById(target).rows.length; + var size = parseInt($('#log_len').val()); + if (!Number.isInteger(size)) { + // entry in Config / Display Options must be a number + size = 1500; + } + + // shorter friendly view + var fv = { + 'cc_event': "CC", + 'rx_update': 'RX', + 'change_freq': 'CF', + 'trunk_update': 'TU' + }; + + if (numRows > size) + document.getElementById(target).deleteRow(-1); + var table = document.getElementById(target); + var lastRowIndex = table.rows.length - 1; + + $('#eh-count').html(' '); + if (d_debug == 1) + $('#eh-count').html('   ' + comma(table.rows.length)); + var skip = 0; + + var prevTime = nohtml(table.rows[1].cells[0].innerHTML); // time + + // do not duplicate history enteries - uses window object to store previous enteries and compares them with current data + // seems to work... + + if (target == 'history' && b == 'trunk_update' && window.g_trunk_update_c && window.g_trunk_update_d) { + if (c == window.g_trunk_update_c && d == window.g_trunk_update_d && a == prevTime) { + skip = 1; + } + } + if (target == 'history' && b.includes('cc_event') && window.g_cc_event_c && window.g_cc_event_e) { + if (c == window.g_cc_event_c && (e == window.g_cc_event_e || f == window.g_cc_event_f) && a == prevTime) { + skip = 1; + } + + } + if (target == 'history' && b.includes('change_freq') && window.g_change_freq_c && window.g_change_freq_e) { + if (c == window.g_change_freq_c && e == window.g_change_freq_e && a == prevTime) { + skip = 1; + } + } + + if (!skip) { + var row = table.insertRow(1); // 2nd row insert + + var cell0 = row.insertCell(0); + var cell1 = row.insertCell(1); + var cell2 = row.insertCell(2); + var cell3 = row.insertCell(3); + var cell4 = row.insertCell(4); + var cell5 = row.insertCell(5); + var cell6 = row.insertCell(6); + var cell7 = row.insertCell(7); + + opcode = opcode.toString(); + opcode = opcode.length == 1 ? "0" + opcode : opcode; + + cell0.innerHTML = a; //time + cell1.innerHTML = (target == 'history') ? '
    ' + fv[b] + '
    ' : '
    ' + b + '
    '; // type + cell2.innerHTML = f; // source id + cell3.innerHTML = srctag; + cell4.innerHTML = '
    ' + opcode.toUpperCase() + '
    '; // opcode + cell5.innerHTML = c; // n_coode (event) + cell6.innerHTML = e; // target + cell7.innerHTML = d; // tag + + // only update the window globals if we haven't skipped, so do this inside if !skip + if (target == 'history' && b == 'trunk_update') { + window.g_trunk_update_c = c; + window.g_trunk_update_d = d; + } + if (target == 'history' && b.includes('cc_event')) { + window.g_cc_event_c = c; + window.g_cc_event_e = e; + window.g_cc_event_f = f; + } + + if (target == 'history' && b.includes('change_freq')) { + window.g_change_freq_c = c; + window.g_change_freq_e = e; + } + + } // end if !skip +} // end appendJsonTable + +function appendCallHistory(a, b, c, d, e, f, target, options, xp, sysid, nac, tdma_slot) { + var numRows = document.getElementById(target).rows.length; +// var size = document.getElementById('log_len').value; + var size = parseInt($('#log_len').val()); + if (!Number.isInteger(size)) { + // entry in Config / Display Options must be a number + size = 1500; + } + if (numRows > size) + $('#' + target + ' tr:last').remove(); + var table = document.getElementById(target); + + $('#ch-count').html(' '); + if (d_debug == 1) + $('#ch-count').html('   ' + comma((table.rows.length))); + + var lastRowIndex = table.rows.length - 1; + var skip = 0; + var pri, enc, xpatch, x, y; + + + b = (options) ? options : " "; + enc = ((b >> 6) & 1 ) ? "∅ " : ""; + pri = ((b >> 2) & 1).toString() + ((b >> 1) & 1).toString() + ((b >> 0) & 1).toString(); + pri = parseInt(pri, 2); + pri = (xp) ? "XP " : pri; + + var prevTime, prevTg, prevSrc; + + // avoid duplicate channel grant enteries into Call History table. + // Search previous 9 rows, compares current data with existing tgid, srcaddr, and time + // if tgid and srcaddr are equal and time is within 2 seconds, skip the entry, + search: { + for (x = 1; x < 8; x++) { + if (!table.rows[x]) { + break search; + } + prevTime = nohtml(table.rows[x].cells[0].innerHTML); // time + prevTg = nohtml(table.rows[x].cells[3].innerHTML); // tgid + prevSrc = nohtml(table.rows[x].cells[5].innerHTML); // source addr + + var psec = prevTime.slice(-1); + var asec = a.slice(-1); + var diff = Math.abs(psec - asec); + if (nohtml(c) == prevTg && nohtml(e) == prevSrc && (a == prevTime || diff <= 2)) { + skip = 1; + } // end if + } // end for + } // end search + + if (((b >> 6) & 1) && cbState('hide_enc')) { // hide encrypted calls if selected in Config + skip = 1; + } + + // do not append the Call History table if not enabled on Home tab + if (enable_status[nac] == false) { + skip = 1; + } + + var tslot = ''; + if (cbState('showSlot') == true && tdma_slot != null) { + tslot = 'S' + ( tdma_slot +1 ) + ' '; + } + + if (!skip) { + + var row = table.insertRow(1); // 2nd row insert + + var cell0 = row.insertCell(0); + var cell1 = row.insertCell(1); + var cell2 = row.insertCell(2); + var cell3 = row.insertCell(3); + var cell4 = row.insertCell(4); + var cell5 = row.insertCell(5); + var cell6 = row.insertCell(6); + + cell0.innerHTML = a; + cell1.innerHTML = '
    ' + hex(sysid).toUpperCase() + '
    '; + cell2.innerHTML = '
    ' + tslot + enc + pri + '
    '; + cell3.innerHTML = c; + cell4.innerHTML = d; + cell5.innerHTML = e; + cell6.innerHTML = f; + + } // end if !skip +} // end appendCallHistory + +function appendErrorTable(ax, bx, cx, dx, ex, fx, gx, target) { + var numRows = document.getElementById(target).rows.length; + var size = parseInt($('#log_len').val()); + if (!Number.isInteger(size)) { + // entry in Config / Display Options must be a number + size = 1500; + } + if (numRows > size) + document.getElementById(target).deleteRow(-1); + var table = document.getElementById(target); + var lastRowIndex = table.rows.length - 1; + var skip = 0; + + if (!skip) { + var row = table.insertRow(1); // 2nd row insert + var cell0 = row.insertCell(0); + var cell1 = row.insertCell(1); + var cell2 = row.insertCell(2); + var cell3 = row.insertCell(3); + var cell4 = row.insertCell(4); + var cell5 = row.insertCell(5); + var cell6 = row.insertCell(6); + + cell0.innerHTML = ax; //time + cell1.innerHTML = bx; + cell2.innerHTML = cx; + cell3.innerHTML = dx; + cell4.innerHTML = ex; + cell5.innerHTML = fx; + cell6.innerHTML = gx; + + } // end if !skip +} // end appendErrorTable + +function update_freq(d) { + return; // not currently used +} + +function channelId (sysid, iden, type, freq, offset, step, slots) { + !(sysid in channel_id) && (channel_id[sysid] = {}); + !(iden in channel_id[sysid]) && (channel_id[sysid][iden] = {}); + channel_id[sysid][iden] = { + 'iden': iden, 'type': type, 'freq': freq, 'offset': offset, 'step': step, 'slots': slots + }; +} + +function smartColor(t) { + // searches string t for items in sc1 and sc2, returns a color if found. + + var z = 4; // number of smart colors - TOTO add the UI elements in index.html + + if (!t || !cbState('smartcolors')) + return 0; // do nothing if there is no talkgroup name passed, or if the box is not checked (cb default is false!) + var tag = t.toString().toUpperCase(); // throws an error if t is an int + var color = 0; + var ele, x, i, sc, alen; + + for (i = 1; i < (z+1); i++) { + ele = document.getElementById('sc'+i).value; + sc = ele.split(" "); + for (var x = 0; x < sc.length; x++) { + if (tag.includes(sc[x].toUpperCase())) { + color = i; + return color; + } + } + } + return color; +} // end smartColor() + +function divExpand(div) { + console.log(div); + var h = $('#' + div).height(); + console.log(typeof(h)); + switch (true) { + case (h < 2): + console.log('case 1:' + h); + $('#' + div).height(301); + $('#' + div).show(); + break; + case (h <= 301): + console.log('case 2:' + h); + $('#' + div).height(702); + break; + case (h < 9999): + console.log('case 3:' + h); + $('#' + div).height(1); + $('#' + div).hide(); + break; + default: + console.log(h < 2); + console.log(h < 301); + console.log(h < 9999); + } +} + +function openTable(div, ref) { + // popout window for review/search of log tables + var divText = $('#' + div).prop('outerHTML'); + divText = divText.replace('table id="history"', 'table id="searchTable"'); + divText = divText.replace('table id="callHistory"', 'table id="searchTable"'); + divText = divText.replace('table id="errors"', 'table id="searchTable"'); + + var myWindow = window.open('', '', 'width=900,height=600'); + var view = document.documentElement.getAttribute('data-theme'); + var doc = myWindow.document; + + doc.open(); + doc.write(''); + doc.write(''); + doc.write(''); + doc.write(''); + doc.write(''); + doc.write(''); + // search icon 🔎 + doc.write('' + ref.id + '

    '); + doc.write(''); + doc.write('
    '); + doc.write(''); + doc.write(''); + if (view == 'dark') + doc.documentElement.setAttribute('data-theme', 'dark'); + doc.write(divText); + doc.close(); +} + +function searchTable() { + var input, filter, table, tr, i, x, s, cols; + var td = []; + cols = document.getElementById('searchTable').rows[1].cells.length; + input = document.getElementById('searchInput'); + filter = input.value.toUpperCase(); + table = document.getElementById('searchTable'); + tr = table.getElementsByTagName('tr'); + for (i = 1; i < tr.length; i++) { + var s = undefined; + for (x = 0; x < cols; x++) { + td[x] = tr[i].getElementsByTagName('td')[x]; + s = s + ' ' + nohtml(td[x].innerHTML).toUpperCase(); + } + if ( s.includes(filter)) { + tr[i].style.display = ''; + } else { + tr[i].style.display = 'none' + } + } // end for +} // end function + +function searchTsvTable() { + var input, filter, table, tr, i, x, s, y, cols; + var td = []; + table = document.getElementById('talkgroups'); + cols = table.rows[1].cells.length; + input = document.getElementById('searchInput'); + filter = input.value.toUpperCase(); + tr = table.getElementsByTagName('tr'); + for (i = 1; i < tr.length; i++) { + var s = undefined; + for (x = 1; x < cols; x++) { // don't search the first column [0] + td[x] = tr[i].getElementsByTagName('td')[x]; + s += ' ' + td[x].firstChild.value.toUpperCase(); + } + if ( s.includes(filter)) { + tr[i].style.display = ''; + } else { + tr[i].style.display = 'none' + } + } // end for +} // end function + +function hex(dec) { + if (!dec) return; + return dec.toString(16); +} + +function dec(hex) { + if (!hex) return; + return parseInt(hex, 16); +} + +function nohtml(str) { + if (typeof str != 'string') return str; + var x = str.replace(/(<([^>]+)>)/gi, ""); + return x; +} + +function freqDisplay(f) { + var freq; + if (!f) return; + if (cbState('trailing_zeros')) { + freq = f.toFixed(5); + } else { + if (Number.isInteger(f)) { + freq = f + ".0"; + } else { + freq = f; + } + } + return freq; +} + +function displayMode() { // just returns the display mode + var x,y; + x = document.documentElement.getAttribute('data-theme'); + y = (x == "dark") ? "dark" : "light"; + return y; +} + +function sdmode() { + $('#selDispMode').val(displayMode()); +} + +function sdmodeChange() { + var y = document.getElementById('selDispMode'); + document.documentElement.setAttribute('data-theme', y.value); +} + +function is_digit(s) { + return ((s >= '0' && s <= '9')); +} + +function getTime(x) { + //expects Unix timestamp, returns 24 hour time, hrs:min:sec + date = new Date(parseInt(x)); + var time = zeroPad(date.getHours(), 2) + ':' + zeroPad(date.getMinutes(), 2) + ':' + zeroPad(date.getSeconds(), 2); + return time; +} + +function cbState(x) { + // returns the state (true / false) of whatever checkbox you ask it to + return $('#' + x).is(':checked'); +} + +function csvTable(table_id, separator = ',') { // Quick and simple export target #table_id into a csv + // Select rows from table_id + var rows = document.querySelectorAll('table#' + table_id + ' tr'); + // Construct csv + var csv = []; + for (var i = 0; i < rows.length; i++) { + var row = [], + cols = rows[i].querySelectorAll('td, th'); + for (var j = 0; j < cols.length; j++) { + // Clean innertext to remove multiple spaces and jumpline (break csv) + var data = cols[j].innerText.replace(/(\r\n|\n|\r)/gm, '').replace(/(\s\s)/gm, ' '); + // Escape double-quote with double-double-quote + data = data.replace(/"/g, '""'); + // Push escaped string + row.push('"' + data + '"'); + } + csv.push(row.join(separator)); + } + var csv_string = csv.join('\n'); + // Download it + var filename = 'export_' + table_id + '_' + new Date().toLocaleDateString() + '.csv'; + var link = document.createElement('a'); + link.style.display = 'none'; + link.setAttribute('target', '_blank'); + link.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv_string)); + link.setAttribute('download', filename); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +} + +function exportToCsv(filename, rows) { // another csv export tool, but this one accepts an array. + var processRow = function (row) { + var finalVal = ''; + for (var j = 0; j < row.length; j++) { + var innerValue = row[j] === null ? '' : row[j].toString(); + if (row[j] instanceof Date) { + innerValue = row[j].toLocaleString(); + }; + var result = innerValue.replace(/"/g, '""'); + if (result.search(/("|,|\n)/g) >= 0) + result = '"' + result + '"'; + if (j > 0) + finalVal += ','; + finalVal += result; + } + return finalVal + '\n'; + }; + + var csvFile = ''; + for (var i = 0; i < rows.length; i++) { + csvFile += processRow(rows[i]); + } + + var blob = new Blob([csvFile], { type: 'text/csv;charset=utf-8;' }); + if (navigator.msSaveBlob) { // IE 10+ + navigator.msSaveBlob(blob, filename); + } else { + var link = document.createElement("a"); + if (link.download !== undefined) { // feature detection + // Browsers that support HTML5 download attribute + var url = URL.createObjectURL(blob); + link.setAttribute("href", url); + link.setAttribute("download", filename); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } +} + +function sortTable(t, c) { // table id, column index + if (!cbState('show_adj')) + return; + if (!t) return; + var table, rows, switching, i, x, y, shouldSwitch; + table = document.getElementById(t); + switching = true; + while (switching) { + switching = false; + rows = table.rows; + // (skip the first row (0), which contains table headers) + for (i = 1; i < (rows.length - 1); i++) { + shouldSwitch = false; + x = rows[i].getElementsByTagName("TD")[c]; + y = rows[i + 1].getElementsByTagName("TD")[c]; + if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) { + shouldSwitch = true; + break; + } + } + if (shouldSwitch) { + rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); + switching = true; + } + } +} // end sort table + +function sources(d) { // d json type = trunk_update + + // stores source IDs and tags and colors in a global object + + // USAGE: + + // window['tgidc' + tgid] = tgid color + // window['tgidt' + tgid] = tgid tag "talkgroup" + + // window['srct' + srcaddr] = srcaddr tag + // window['srcc' + srcaddr] = srcaddr color + + if (Object.keys(d).length < 1) + return; + var f, nac, srcaddr, srcaddr_tag; + var ft = 'frequency_tracking'; + var calls = 'calls'; + for (nac in d) { + if (Number.isInteger(parseInt(nac))) { + for (f in d[nac][ft]) { + + srcaddr_tag = d[nac][ft][f]['calls'][0]['srcaddr']['tag'] ? d[nac][ft][f]['calls'][0]['srcaddr'] : null; + srcaddr = d[nac][ft][f]['calls'][0]['srcaddr']['unit_id'] ? d[nac][ft][f]['calls'][0]['srcaddr'] : null; + + if (srcaddr && srcaddr_tag) { + window['tgidc' + d[nac][ft][f]['calls'][0]['tgid']['tg_id']] = d[nac][ft][f]['calls'][0]['tgid']['color']; + window['tgidt' + d[nac][ft][f]['calls'][0]['tgid']['tg_id']] = d[nac][ft][f]['calls'][0]['tgid']['tag']; + + window['srct' + d[nac][ft][f]['calls'][0]['srcaddr']['unit_id']] = d[nac][ft][f]['calls'][0]['srcaddr']['tag']; + window['srcc' + d[nac][ft][f]['calls'][0]['srcaddr']['unit_id']] = d[nac][ft][f]['calls'][0]['srcaddr']['color']; + } + + if (d[nac][ft][f]['tdma'] == true ) { + window[nac + 'flavor'] = "2"; // set it as p2 and leave it that way for the duration + } + } // end for f + } // end if number + } // end for x +} // end function + +function getReason(r) { + // denial reason lookup + if (g_reason[r]) { + result = '0x' + r + ' - ' + g_reason[r]; + } else { + result = '0x' + r; + } + return result; +} + +function saveDisplaySettings() { + var settings = {}; + $('#saveSettings').html('Saved'); + setTimeout(() => { $('#saveSettings').html('Save Display Settings');}, 2000); + + var s = [ + "valSystemFont", + "valTagFont", + "valTruncate", + "valFontStyle", + "sc1", + "sc2", + "sc3", + "sc4", + "log_len", + "color_main_tag", + "color_main_sys", + "smartcolors", + "log_cc", + "log_cf", + "log_tu", + "log_rx", + "show_adj", + "je_joins", + "je_calls", + "je_deny", + "je_log", + "hide_enc", + "trailing_zeros", + "selDispMode", + "acc1", + "acc2", + "sysColor", + "valColor", + "sysColor", + "btnColor", + "unk_default", + "ani_speed", + "showBandPlan", + "showSlot", + "oplogip", + "oplogport", + "showLast", + "colSlot2" ]; + + for (r in s) { + if ($('#' + s[r]).attr('type') == "checkbox") { + settings[s[r]] = $('#' + s[r]).is(':checked') ? true : false; + } else { + settings[s[r]] = $('#' + s[r]).val(); + } + } // end for + + var settingsJSON = JSON.stringify(settings, null, 2); + send_command('config-savesettings', settingsJSON); +} + + +function loadHelp(){ // this is also called from editor.js + f = 'help.html'; + $.ajax({ + url : f, + type : 'GET', + success : popHelp, + error : function(XMLHttpRequest, textStatus, errorThrown) {alert('Settings File Acces Error: \n\nFile:' + f + '\n\n' + errorThrown + '\n\n');} + }); +} + +function popHelp(h){ + $('#div_help').html(h) +} + + +function beginJsonSettings(){ // this is also called from editor.js + f = 'ui-settings.json'; + $.ajax({ + url : f, + type : 'GET', + success : loadJsonDisplaySettings, + error : function(XMLHttpRequest, textStatus, errorThrown) {alert('Settings File Acces Error: \n\nFile:' + f + '\n\n' + errorThrown + '\n\n');} + }); +} + +function loadJsonDisplaySettings(settings) { + $('#loadSettings').html('Loaded'); + setTimeout(() => { $('#loadSettings').html('Load Settings'); }, 2000); + + var ele, m; + for (item in settings) { + ele = document.getElementById(item); + if (ele) { + if (ele.type == "checkbox") { + ele.checked = settings[item] == true ? true : false; + continue; + } + + if (ele.type == "select") { + ele.value = settings[item]; + continue; + } + + ele.value = settings[item]; + } + + if (item == "selDispMode") { + m = settings[item]; + document.documentElement.setAttribute('data-theme', m); + sdmode(); + } + + // populate url into oplog url field + var x = window.location.origin.split( ':' ); +// var y = x[0] + ':' + x[1] + ':5000'; +// $('#oplogUrl').val(y); + $('#oplogip').val(x[1].substring(2)); + $('#oplogport').val('5000'); + + + if (item == "oplogip") { + $('#oplogip').val(settings[item]); + } + + if (item == "oplogport") { + $('#oplogport').val(settings[item]); + } + + $('#oplogUrl').val('http://' + $('#oplogip').val() + ':' + $('#oplogport').val()); + + } // end for item + + uiColorRefresh(); + +} + +function uiColorRefresh() { // this is also called from editor.js + $('#acc1').trigger('change'); + $('#valColor').trigger('change'); + $('#sysColor').trigger('change'); + $('#btnColor').trigger('change'); + $('#ani_speed').trigger('change'); +} + +function changeCss(className, classValue) { + + // we need invisible container to store additional css definitions + var cssMainContainer = $('#css-modifier-container'); + if (cssMainContainer.length == 0) { + cssMainContainer = $('
    '); + cssMainContainer.hide(); + cssMainContainer.appendTo($('body')); + } + + // and we need one div for each class + var classContainer = cssMainContainer.find('div[data-class="' + className + '"]'); + if (classContainer.length == 0) { + classContainer = $('
    '); + classContainer.appendTo(cssMainContainer); + } + + // append additional style + classContainer.html(''); +} + +function getSiteAlias() { + + if (localStorage.AliasTableUpdated == false) + return; + if (event_source == null || event_source.readyState != 1) // the server has gone out to lunch + return; + $.ajax({ + url : 'site-alias.json', + type : 'GET', + success : loadSiteAlias, + error : function(XMLHttpRequest, textStatus, errorThrown) {console.log('site-alias.json file not found. \n\nFile:' + url + '\n\n' + errorThrown + '\n\n');} + }); +} + +function loadSiteAlias(json) { + window.siteAlias = json; + localStorage.AliasTableUpdated == false; +} + +function accColorSel() { // this is also called from editor.js + + $('.accColor').on('change', function() { + this.blur(); + var c1 = $('#acc1').val(); + var c2 = $('#acc2').val(); + var z = 'linear-gradient(' + c1 + ', ' + c2 +')'; + $('.controlsDisplay').css('background', z); + }); + + $('#valColor').on('change', function() { + this.blur(); + var c1 = $('#valColor').val(); + changeCss('.value', 'color: ' + c1 + ';'); + changeCss('.nac', 'color: ' + c1 + ';'); + }); + + $('#sysColor').on('change', function() { + this.blur(); + var c1 = $('#sysColor').val(); + changeCss('.systgid', 'color: ' + c1 + ';'); + }); + + $('#btnColor').on('change', function() { + this.blur(); + var c1 = $('#btnColor').val(); + changeCss('button', 'color: ' + c1 + ';'); + changeCss('.nav-button-active', 'color: ' + c1 + ';'); + changeCss('.nav-button:hover', 'color: ' + c1 + ';'); + changeCss('.nav-button-active:hover', 'color: ' + c1 + ';'); + changeCss('.btn', 'color: ' + c1 + ';'); + changeCss('.control-button', 'color: ' + c1 + ';'); + changeCss('.control-button:hover', 'color: ' + c1 + ';'); + }); + + $('#ani_speed').on('change', function() { + this.blur(); + window.animateSpeed = parseInt(this.value); + }); + +} + +// color, animation, backgroundColor - used for main talkgroup/system display +// returns the property value of the selector supplied. sheet is document.styleSheets[index].title +function getProperty(selector, property, sheet) { + for (y in document.styleSheets) { + if (document.styleSheets[y].title == sheet) // this title is set in the json color map builder func + break; + } + rules = document.styleSheets[y].cssRules; + for(i in rules) { + if(rules[i].selectorText == selector) + return rules[i]['style'][property]; + } + return false; +} + +function getStyle(className) { //test, not used + var cssText = ""; + var classes = document.styleSheets[1].rules || document.styleSheets[0].cssRules; + for (var x = 0; x < classes.length; x++) { + if (classes[x].selectorText == className) { + cssText += classes[x].cssText || classes[x].style.cssText; + } + } + return cssText; +} + +var g_opcode = { // global opcodes + '0': 'Call', // Group Voice Grant grp_v_ch_grant dec=0 + '1': 'Reserved', // REserved 0x01 + '2': 'Update 0x02', + '3': 'Update 0x03', + '4': 'UnitVoiceGrant', + '5': 'UU_ANS_RSP', // unit to unit answer response + '6': 'UnitVoiceUpdate', + '8': 'PhoneGrant', + '9': 'TELE_INT_PSTN_AEQ', + '0a': 'Phone Alert', // tele interconnect + '0b': 'Reserved', + '10': 'UnitDataGrant', + '11': 'GroupDataGrant', + '12': 'GroupDataUpdate', + '13': 'GroupDataUpdateExplicit', + '18': 'UnitStatusUpdate', + '1a': 'UnitStatusQuery', + '1c': 'UnitShortMessage', + '1d': 'UnitMonitor', + '1f': 'UnitCallAlert', // Call Alert + '20': 'Ack Response', // AckResponse - ACK_RESP_U - This is the generic response supplied by a unit to acknowledge an action when there is no other expected response. Response from radio to system poll ("are you still there?") + '21': 'QueuedResponse', + '24': 'ExtFunctionCommand', + '27': 'Denied', // DenyResponse + '28': 'Joins', // GroupAffiliationResponse - GRP_AFF_RSP - This is the response to the request for group affiliation by a unit. This will present the necessary information to the requesting unit to allow it to perform group operations for the indicated group identity. + '29': 'Ack Response FNE', // This is the generic response supplied to a unit to acknowledge an action when there is no other expected response. This response is sent to a subscriber unit in response to an earlier action or service request. + '2a': 'Group Aff Q', // GRP_AFF_Q - This transaction is to be used to determine what a targeted subscriber unit maintains as the group affiliation data for the unit. The Query will usually originate in the system, but the standard enables other originators. + '2b': 'Joins', // LocRegResponse - This transaction is to be used to respond to a Location Registration Request. The response indicates that the subscriber is registered in the new location area. + '2c': 'Login', // UnitRegResponse + '2d': 'Force SU Reg', // UnitRegCommand - U_REG_CMD - This transaction is to be used to force an SU to initiate Unit Registration + '2e': 'UnitAuthCommand', + '2f': 'Logout', // UnitDeregAck + '31': 'Auth Demand', + '36': 'RoamingAddrCommand', + '37': 'RoamingAddrUpdate', + '38': 'SystemServiceBroadcast', // This broadcast will inform the subscriber units of the current system services supported and currently offered on the Primary control channel of this site. + '39': 'AltControlChannel', + '3a': 'RfssStatusBroadcast', + '3b': 'NetworkStatusBroadcast', + '3c': 'AdjacentSite', // Adjacent Site Broadcast + '3d': 'ChannelParamsUpdate', // IDEN_UP + '3e': 'ProtectionParamBroadcast', + '3f': 'ProtectionParamUpdate' +}; + +var g_reason = { // TIA-10.AABC-B Annex B Deny Response Reason Codes + '10': 'Unit not valid', + '11': 'Unit not authoirized', + '20': 'Target unit not valid', + '21': 'Target unit not authorized', + '2f': 'Target unit refused', + '30': 'Target group invalid', + '31': 'Target group not authoirzed', + '40': 'Invalid dialing', + '41': 'Telephone number not authroized', + '42': 'PSTN address invalid', + '50': 'Call time-out', + '51': 'Call terminated by landline', + '52': 'Call terminated by subscriber', + '5f': 'Call pre-empted', + '60': 'Site access denial', + '61': 'User/system def', // 0x61 - 0xEF per standard + '67': 'User/sys def', + '77': 'User/sys def', + 'c0': 'User/sys def', // seen on MOT system + 'f0': 'Call options invalid', + 'f1': 'Protection service option invalid', + 'f2': 'Duples service option invalid', + 'f3': 'circuit/packet mode service option invalid', + 'ff': 'Service not supported by system' +}; + +var g_moto_opcode = { // opcodes when mfrid is MOT (0x90, 144) + '0': 'MOT Add Patch Group', + '1': 'MOT Del Patch Group', + '3': 'MOT Patch Voice Channel Grant Update', + '4': 'MOT Unknown', + '5': 'MOT Traffic Chan Stn ID', + '6': 'MOT Unknown', + '7': 'MOT Unknown', + '8': 'MOT Unknown', + '9': 'MOT System Load', + '0a': 'MOT Unknown', + '0b': 'MOT Control Chan Base Stn ID', + '0c': 'MOT Unknown', + '0d': 'MOT Unknown', + '0e': 'MOT Planned Control Channel Shutdown', + // 0f through 3f = unknown +}; + +var g_serviceOption = { + // TODO - populate this, not used currently. + // bits 0-2 priority + '0': 'reserved', + '1': 'Lowest', + '2': 'User/system def', + '3': 'User/system def', + '4': 'Default', + '5': 'User/system def', + '6': 'User/system def', + '7': 'Highest', + + // bit 3 - reserved + // bit 4 - mode + +}; + + +// const value_string MFIDS[] = { +// { 0x00, "Standard MFID (pre-2001)" }, +// { 0x01, "Standard MFID (post-2001)" }, +// { 0x09, "Aselsan Inc." }, +// { 0x10, "Relm / BK Radio" }, +// { 0x18, "EADS Public Safety Inc." }, +// { 0x20, "Cycomm" }, +// { 0x28, "Efratom Time and Frequency Products, Inc" }, +// { 0x30, "Com-Net Ericsson" }, +// { 0x34, "Etherstack" }, +// { 0x38, "Datron" }, +// { 0x40, "Icom" }, +// { 0x48, "Garmin" }, +// { 0x50, "GTE" }, +// { 0x55, "IFR Systems" }, +// { 0x5A, "INIT Innovations in Transportation, Inc" }, +// { 0x60, "GEC-Marconi" }, +// { 0x64, "Harris Corp." }, +// { 0x68, "Kenwood Communications" }, +// { 0x70, "Glenayre Electronics" }, +// { 0x74, "Japan Radio Co." }, +// { 0x78, "Kokusai" }, +// { 0x7C, "Maxon" }, +// { 0x80, "Midland" }, +// { 0x86, "Daniels Electronics Ltd." }, +// { 0x90, "Motorola" }, +// { 0xA0, "Thales" }, +// { 0xA4, "M/A-COM" }, +// { 0xB0, "Raytheon" }, +// { 0xC0, "SEA" }, +// { 0xC8, "Securicor" }, +// { 0xD0, "ADI" }, +// { 0xD8, "Tait Electronics" }, +// { 0xE0, "Teletec" }, +// { 0xF0, "Transcrypt International" }, +// { 0xF8, "Vertex Standard" }, +// { 0xFC, "Zetron, Inc" }, +// }; + + +// const value_string ALGIDS[] = { +// /* Type I */ +// { 0x00, "ACCORDION 1.3" }, +// { 0x01, "BATON (Auto Even)" }, +// { 0x02, "FIREFLY Type 1" }, +// { 0x03, "MAYFLY Type 1" }, +// { 0x04, "SAVILLE" }, +// { 0x05, "Motorola Assigned - PADSTONE" }, +// { 0x41, "BATON (Auto Odd)" }, +// /* Type III */ +// { 0x80, "Unencrypted" }, +// { 0x81, "DES-OFB, 56 bit key" }, +// { 0x83, "3 key Triple DES, 168 bit key" }, +// { 0x84, "AES-256-OFB" }, +// { 0x85, "AES-128-ECB"}, +// { 0x88, "AES-CBC"}, +// { 0x89, "AES-128-OFB"}, +// /* Motorola proprietary - some of these have been observed over the air, +// some have been taken from firmware dumps on various devices, others +// have come from the TIA's FTP website while it was still public, +// from document "ALGID Guide 2015-04-15.pdf", and others have been +// have been worked out with a little bit of "guesswork" ;) */ +// { 0x9F, "Motorola DES-XL 56-bit key" }, +// { 0xA0, "Motorola DVI-XL" }, +// { 0xA1, "Motorola DVP-XL" }, +// { 0xA2, "Motorola DVI-SPFL"}, +// { 0xA3, "Motorola HAYSTACK" }, +// { 0xA4, "Motorola Assigned - Unknown" }, +// { 0xA5, "Motorola Assigned - Unknown" }, +// { 0xA6, "Motorola Assigned - Unknown" }, +// { 0xA7, "Motorola Assigned - Unknown" }, +// { 0xA8, "Motorola Assigned - Unknown" }, +// { 0xA9, "Motorola Assigned - Unknown" }, +// { 0xAA, "Motorola ADP (40 bit RC4)" }, +// { 0xAB, "Motorola CFX-256" }, +// { 0xAC, "Motorola Assigned - Unknown" }, +// { 0xAD, "Motorola Assigned - Unknown" }, +// { 0xAE, "Motorola Assigned - Unknown" }, +// { 0xAF, "Motorola AES-256-GCM (possibly)" }, +// { 0xB0, "Motorola DVP"}, +// }; + + + + + +