Commit c1cf0ef6 by Corey Koval

Receiver handling improvements

parent b3aa7c67
# DF Aggregator # DF Aggregator
## New Features 1 December 2020:
- Receivers can be added from the WebUI
- Click the + at the bottom of the receiver cards, enter the URL, click save.
Click the refresh button to update the cards and map.
- A list of receiver URLs is now optional. Receivers are saved to the database.
- Receivers are read from the database first. Duplicate receiver URLs are ignored.
- You can mark a receiver as mobile.
- Click the edit icon for the applicable receiver, click the checkbox to mark
it as mobile, then click save.
- You can now delete receivers from the list. This will remove it from the map
and database. No historical data is affected.
- You can now enable/disable LOB collection from individual receivers.
Click the power button to enable/disable.
- Black is enabled, red is disabled.
- If you lose connectivity to a receiver, that particular receiver will be disabled.
Click the power button to try to reconnect.
## Dependencies: ## Dependencies:
- Python >= 3.6 - Python >= 3.6
- [numpy](https://numpy.org/install/) - [numpy](https://numpy.org/install/)
......
...@@ -46,17 +46,16 @@ class receiver: ...@@ -46,17 +46,16 @@ class receiver:
self.isAuto = True self.isAuto = True
# hashed_url = hashlib.md5(station_url.encode('utf-8')).hexdigest() # hashed_url = hashlib.md5(station_url.encode('utf-8')).hexdigest()
# self.uid = hashed_url[:5] + hashed_url[-5:] # self.uid = hashed_url[:5] + hashed_url[-5:]
try: self.update(first_run=True)
self.update() self.isActive = True
except:
raise IOError
# Updates receiver from the remote URL # Updates receiver from the remote URL
def update(self): def update(self, first_run=False):
try: try:
xml_contents = etree.parse(self.station_url) xml_contents = etree.parse(self.station_url)
xml_station_id = xml_contents.find('STATION_ID') if first_run:
self.station_id = xml_station_id.text xml_station_id = xml_contents.find('STATION_ID')
self.station_id = xml_station_id.text
xml_doa_time = xml_contents.find('TIME') xml_doa_time = xml_contents.find('TIME')
self.doa_time = int(xml_doa_time.text) self.doa_time = int(xml_doa_time.text)
xml_freq = xml_contents.find('FREQUENCY') xml_freq = xml_contents.find('FREQUENCY')
...@@ -81,7 +80,20 @@ class receiver: ...@@ -81,7 +80,20 @@ class receiver:
except KeyboardInterrupt: except KeyboardInterrupt:
finish() finish()
except: except:
raise IOError if first_run:
self.station_id = "Unknown"
self.latitude = 0.0
self.longitude = 0.0
self.heading = 0.0
self.raw_doa = 0.0
self.doa = 0.0
self.frequency = 0.0
self.power = 0.0
self.confidence = 0
self.doa_time = 0
self.isActive = False
print(f"Problem connecting to {self.station_url}, receiver deactivated. Reactivate in WebUI.")
# raise IOError
# Returns receivers properties as a dict, # Returns receivers properties as a dict,
# useful for passing data to the WebUI # useful for passing data to the WebUI
...@@ -102,7 +114,6 @@ class receiver: ...@@ -102,7 +114,6 @@ class receiver:
confidence = 0 confidence = 0
doa_time = 0 doa_time = 0
isMobile = False isMobile = False
isActive = True
############################################### ###############################################
# Converts Lat/Lon to polar coordinates # Converts Lat/Lon to polar coordinates
...@@ -473,9 +484,11 @@ def update_cesium(): ...@@ -473,9 +484,11 @@ def update_cesium():
############################################### ###############################################
@get('/rx_params') @get('/rx_params')
def rx_params(): def rx_params():
write_czml(*process_data(database_name, geofile))
all_rx = {'receivers':{}} all_rx = {'receivers':{}}
rx_properties = [] rx_properties = []
for index, x in enumerate(receivers): for index, x in enumerate(receivers):
x.update()
rx = x.receiver_dict() rx = x.receiver_dict()
rx['uid'] = index rx['uid'] = index
rx_properties.append(rx) rx_properties.append(rx)
...@@ -497,6 +510,10 @@ def update_rx(action): ...@@ -497,6 +510,10 @@ def update_rx(action):
index = int(data['uid']) index = int(data['uid'])
del_receiver(receivers[index].station_id) del_receiver(receivers[index].station_id)
del receivers[index] del receivers[index]
elif action == "activate":
index = int(data['uid'])
receivers[index].isActive = data['state']
# print(f"RX {index} changed state to {data['state']}")
else: else:
action = int(action) action = int(action)
try: try:
...@@ -564,10 +581,10 @@ def run_receiver(receivers): ...@@ -564,10 +581,10 @@ def run_receiver(receivers):
for rx in receivers: for rx in receivers:
try: try:
rx.update() if rx.isActive: rx.update()
except IOError: except IOError:
print("Problem connecting to receiver.") print("Problem connecting to receiver.")
ms.receiving = False # ms.receiving = False
time.sleep(1) time.sleep(1)
if dots > 5: if dots > 5:
...@@ -607,8 +624,9 @@ def add_receiver(receiver_url): ...@@ -607,8 +624,9 @@ def add_receiver(receiver_url):
[new_rx['station_id']]).fetchone()[0] [new_rx['station_id']]).fetchone()[0]
receivers[-1].isMobile = bool(mobile) receivers[-1].isMobile = bool(mobile)
print("Created new DF Station at " + receiver_url) print("Created new DF Station at " + receiver_url)
except IOError: except AttributeError:
ms.receiving = False pass
conn.close() conn.close()
############################################### ###############################################
...@@ -716,7 +734,6 @@ if __name__ == '__main__': ...@@ -716,7 +734,6 @@ if __name__ == '__main__':
############################################### ###############################################
read_rx_table() read_rx_table()
if rx_file: if rx_file:
print("I got a file!")
with open(rx_file, "r") as file2: with open(rx_file, "r") as file2:
receiver_list = file2.readlines() receiver_list = file2.readlines()
for x in receiver_list: for x in receiver_list:
......
...@@ -110,6 +110,21 @@ body { ...@@ -110,6 +110,21 @@ body {
transition: transform 0.5s cubic-bezier(0.77,0.2,0.05,1.0); transition: transform 0.5s cubic-bezier(0.77,0.2,0.05,1.0);
} }
#add_station {
width: 23px;
height: 23px;
}
#new_rx_div {
position: relative;
background: #d4d4d4;
color: #111;
font-weight: bold;
padding: 5px;
margin: 5px;
vertical-align: middle;
}
.receiver { .receiver {
position: relative; position: relative;
background: #d4d4d4; background: #d4d4d4;
...@@ -138,6 +153,20 @@ body { ...@@ -138,6 +153,20 @@ body {
font-size: 23pt; font-size: 23pt;
} }
.delete-icon {
display: block;
position: absolute;
top: 5px;
right: 30px;
}
.activate-icon {
display: block;
position: absolute;
top: 5px;
right: 60px;
}
.edit-checkbox { .edit-checkbox {
cursor: pointer; cursor: pointer;
opacity: 0; /* hide this */ opacity: 0; /* hide this */
...@@ -146,13 +175,6 @@ body { ...@@ -146,13 +175,6 @@ body {
height: 20px; height: 20px;
} }
.delete-icon {
display: block;
position: absolute;
top: 5px;
right: 30px;
}
.no-select { .no-select {
-webkit-user-select: none; /* Safari */ -webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */ -khtml-user-select: none; /* Konqueror HTML */
......
...@@ -15,14 +15,14 @@ function updateRx(callBack, id) { ...@@ -15,14 +15,14 @@ function updateRx(callBack, id) {
// ****************************************************** // ******************************************************
function editReceivers(rx_json, id) { function editReceivers(rx_json, id) {
const receivers = rx_json['receivers']; const receivers = rx_json['receivers'];
var stationUrlHtml = // var stationUrlHtml =
"<input type=\"hidden\" id=\"url_" + id + "\"/>"; // "<input type=\"hidden\" id=\"url_" + id + "\"/>";
var stationIDhtml = var stationIDhtml =
"Station ID: <a href=\"" + receivers[id].station_url + "\" target=\"_blank\">" + receivers[id].station_id + "</a>"; "Station ID: <a href=\"" + receivers[id].station_url + "\" target=\"_blank\">" + receivers[id].station_id + "</a>";
var manualInfo = // var manualInfo =
"<input type=\"hidden\" id=\"manual_toggle_" + receivers[id].uid + "\"/>"; // "<input type=\"hidden\" id=\"manual_toggle_" + receivers[id].uid + "\"/>";
var locationHtml = var locationHtml =
"Location: " + receivers[id].latitude + "&#176;, " + receivers[id].longitude + "&#176;"; "Location: " + receivers[id].latitude + "&#176;, " + receivers[id].longitude + "&#176;";
...@@ -33,14 +33,14 @@ function editReceivers(rx_json, id) { ...@@ -33,14 +33,14 @@ function editReceivers(rx_json, id) {
var freqHtml = var freqHtml =
"Tuned to " + receivers[id].frequency + " MHz"; "Tuned to " + receivers[id].frequency + " MHz";
var edit_stationUrlHtml = // var edit_stationUrlHtml =
"Station URL:<input style=\"width: 300px;\" type=\"text\" value=\"" + receivers[id].station_url + "\" name=\"station_url_" + id + "\" />"; // "Station URL:<input style=\"width: 300px;\" type=\"text\" value=\"" + receivers[id].station_url + "\" name=\"station_url_" + id + "\" />";
var edit_stationIDhtml = var edit_stationIDhtml =
"Station ID:<input style=\"width: 105px;\" type=\"text\" value=\"" + receivers[id].station_id + "\" name=\"station_id_" + id + "\" />"; "Station ID:<input style=\"width: 105px;\" type=\"text\" value=\"" + receivers[id].station_id + "\" name=\"station_id_" + id + "\" />";
var edit_manualInfo = // var edit_manualInfo =
"Manually input receiver info: <input id=\"manual_toggle_" + id + "\" type=\"checkbox\" />"; // "Manually input receiver info: <input id=\"manual_toggle_" + id + "\" type=\"checkbox\" />";
var edit_locationHtml = var edit_locationHtml =
"Latitude:<input style=\"width: 105px;\" type=\"text\" value=\"" + receivers[id].latitude + "\" name=\"station_lat_" + id + "\" />" + "Latitude:<input style=\"width: 105px;\" type=\"text\" value=\"" + receivers[id].latitude + "\" name=\"station_lat_" + id + "\" />" +
...@@ -61,21 +61,21 @@ function editReceivers(rx_json, id) { ...@@ -61,21 +61,21 @@ function editReceivers(rx_json, id) {
document.getElementById(id + "-editicon").innerHTML = "save"; document.getElementById(id + "-editicon").innerHTML = "save";
document.getElementById(mobile).innerHTML = document.getElementById(mobile).innerHTML =
"Mobile Receiver: <input " + isMobile + " id=\"mobilerx_toggle_" + id + "\" type=\"checkbox\" />"; "Mobile Receiver: <input " + isMobile + " id=\"mobilerx_toggle_" + id + "\" type=\"checkbox\" />";
document.getElementById(id + "-manual").innerHTML = edit_manualInfo; // document.getElementById(id + "-manual").innerHTML = edit_manualInfo;
document.getElementById(id + "-url").innerHTML = edit_stationUrlHtml; // // document.getElementById(id + "-url").innerHTML = edit_stationUrlHtml;
document.getElementById("manual_toggle_" + id).onchange = function() { // document.getElementById("manual_toggle_" + id).onchange = function() {
if (document.getElementById("manual_toggle_" + id).checked) { // if (document.getElementById("manual_toggle_" + id).checked) {
document.getElementById(id + "-id").innerHTML = edit_stationIDhtml; // document.getElementById(id + "-id").innerHTML = edit_stationIDhtml;
document.getElementById(id + "-location").innerHTML = edit_locationHtml; // document.getElementById(id + "-location").innerHTML = edit_locationHtml;
document.getElementById(id + "-heading").innerHTML = edit_heading; // document.getElementById(id + "-heading").innerHTML = edit_heading;
document.getElementById(id + "-freq").innerHTML = edit_freqHtml; // document.getElementById(id + "-freq").innerHTML = edit_freqHtml;
} else { // } else {
document.getElementById(id + "-id").innerHTML = stationIDhtml; // document.getElementById(id + "-id").innerHTML = stationIDhtml;
document.getElementById(id + "-location").innerHTML = locationHtml; // document.getElementById(id + "-location").innerHTML = locationHtml;
document.getElementById(id + "-heading").innerHTML = heading; // document.getElementById(id + "-heading").innerHTML = heading;
document.getElementById(id + "-freq").innerHTML = freqHtml; // document.getElementById(id + "-freq").innerHTML = freqHtml;
} // }
} // }
} else { } else {
isMobileCheck = document.getElementById("mobilerx_toggle_" + id); isMobileCheck = document.getElementById("mobilerx_toggle_" + id);
if (isMobileCheck.checked) { if (isMobileCheck.checked) {
...@@ -128,6 +128,13 @@ function removerx(uid) { ...@@ -128,6 +128,13 @@ function removerx(uid) {
rxcard.remove(); rxcard.remove();
} }
// *****************************************
// * Removes ALL of the RX Cards
// *****************************************
function destroyRxCards() {
document.querySelectorAll('.receiver').forEach(e => e.remove());
}
// ******************************************* // *******************************************
// * Removes Rx from Backend and Reloads Map // * Removes Rx from Backend and Reloads Map
// ******************************************* // *******************************************
...@@ -144,7 +151,29 @@ function deleteReceiver(uid) { ...@@ -144,7 +151,29 @@ function deleteReceiver(uid) {
clearOld(); clearOld();
fetch("/rx_params/del", otherParams) fetch("/rx_params/del", otherParams)
.then(res => { .then(res => {
removerx(uid); // removerx(uid);
loadRx(createReceivers);
loadCzml();
})
}
// *******************************************************
// * Updates Rx active state from Backend and Reloads Map
// *******************************************************
function activateReceiver(uid, state) {
const activate_rx = { "uid": uid, "state": state };
console.log("I'm sending " + state)
const otherParams = {
headers: {
"content-type": "application/json"
},
body: JSON.stringify(activate_rx),
method: "PUT"
};
clearOld();
fetch("/rx_params/activate", otherParams)
.then(res => {
loadRx(refreshRx);
loadCzml(); loadCzml();
}) })
} }
...@@ -155,14 +184,14 @@ function deleteReceiver(uid) { ...@@ -155,14 +184,14 @@ function deleteReceiver(uid) {
function showReceivers(rx_json, id) { function showReceivers(rx_json, id) {
const receivers = rx_json['receivers']; const receivers = rx_json['receivers'];
var stationUrlHtml = // var stationUrlHtml =
"<input type=\"hidden\" id=\"url_" + id + "\"/>"; // "<input type=\"hidden\" id=\"url_" + id + "\"/>";
var stationIDhtml = var stationIDhtml =
"Station ID: <a href=\"" + receivers[id].station_url + "\" target=\"_blank\">" + receivers[id].station_id + "</a>"; "Station ID: <a href=\"" + receivers[id].station_url + "\" target=\"_blank\">" + receivers[id].station_id + "</a>";
var manualInfo = // var manualInfo =
"<input type=\"hidden\" id=\"manual_toggle_" + receivers[id].uid + "\"/>"; // "<input type=\"hidden\" id=\"manual_toggle_" + receivers[id].uid + "\"/>";
var locationHtml = var locationHtml =
"Location: " + receivers[id].latitude + "&#176;, " + receivers[id].longitude + "&#176;"; "Location: " + receivers[id].latitude + "&#176;, " + receivers[id].longitude + "&#176;";
...@@ -175,16 +204,28 @@ function showReceivers(rx_json, id) { ...@@ -175,16 +204,28 @@ function showReceivers(rx_json, id) {
const urlspan = document.getElementById(id + "-url"); const urlspan = document.getElementById(id + "-url");
const mobilespan = document.getElementById(id + "-mobile"); const mobilespan = document.getElementById(id + "-mobile");
const manualspan = document.getElementById(id + "-manual"); // const manualspan = document.getElementById(id + "-manual");
const idspan = document.getElementById(id + "-id"); const idspan = document.getElementById(id + "-id");
const locationspan = document.getElementById(id + "-location"); const locationspan = document.getElementById(id + "-location");
const headingspan = document.getElementById(id + "-heading"); const headingspan = document.getElementById(id + "-heading");
const freqspan = document.getElementById(id + "-freq"); const freqspan = document.getElementById(id + "-freq");
document.getElementById(id + "-activate")
.setAttribute('onclick', "activateReceiver(" + receivers[id].uid + ", " + !receivers[id].active + ")");
if (receivers[id].active == true) {
document.getElementById(id + "-activate")
.setAttribute("title", "Click to disable this receiver.");
document.getElementById(id + "-activateicon").style.color = "black";
} else {
document.getElementById(id + "-activateicon").style.color = "red";
document.getElementById(id + "-activate")
.setAttribute("title", "Click to enable this receiver.");
}
document.getElementById(id + "-mobile").innerHTML = ""; document.getElementById(id + "-mobile").innerHTML = "";
document.getElementById(id + "-editicon").innerHTML = "edit"; document.getElementById(id + "-editicon").innerHTML = "edit";
document.getElementById(id + "-manual").innerHTML = manualInfo; // document.getElementById(id + "-manual").innerHTML = manualInfo;
document.getElementById(id + "-url").innerHTML = stationUrlHtml; // document.getElementById(id + "-url").innerHTML = stationUrlHtml;
document.getElementById(id + "-id").innerHTML = stationIDhtml; document.getElementById(id + "-id").innerHTML = stationIDhtml;
document.getElementById(id + "-location").innerHTML = locationHtml; document.getElementById(id + "-location").innerHTML = locationHtml;
document.getElementById(id + "-heading").innerHTML = heading; document.getElementById(id + "-heading").innerHTML = heading;
...@@ -197,12 +238,8 @@ function showReceivers(rx_json, id) { ...@@ -197,12 +238,8 @@ function showReceivers(rx_json, id) {
// * Iterates through Rx objects on page load/Rx add. // * Iterates through Rx objects on page load/Rx add.
// **************************************************** // ****************************************************
function createReceivers(rx_json, id) { function createReceivers(rx_json, id) {
var receivers destroyRxCards();
if (id == true) { let receivers = rx_json['receivers'];
receivers = [rx_json['receivers'][Object.keys(rx_json['receivers']).length - 1]];
} else {
receivers = rx_json['receivers'];
}
// console.log(receivers); // console.log(receivers);
for (let i = 0; i < Object.keys(receivers).length; i++) { for (let i = 0; i < Object.keys(receivers).length; i++) {
...@@ -210,9 +247,9 @@ function createReceivers(rx_json, id) { ...@@ -210,9 +247,9 @@ function createReceivers(rx_json, id) {
rxcard.className = "receiver"; rxcard.className = "receiver";
rxcard.id = "rx-" + receivers[i].uid; rxcard.id = "rx-" + receivers[i].uid;
const urlspan = document.createElement('span'); // const urlspan = document.createElement('span');
const mobilespan = document.createElement('span'); const mobilespan = document.createElement('span');
const manualspan = document.createElement('span'); // const manualspan = document.createElement('span');
const idspan = document.createElement('span'); const idspan = document.createElement('span');
const locationspan = document.createElement('span'); const locationspan = document.createElement('span');
const headingspan = document.createElement('span'); const headingspan = document.createElement('span');
...@@ -221,6 +258,7 @@ function createReceivers(rx_json, id) { ...@@ -221,6 +258,7 @@ function createReceivers(rx_json, id) {
const editiconspan = document.createElement('span'); const editiconspan = document.createElement('span');
editiconspan.classList.add("material-icons", "edit-icon", "no-select"); editiconspan.classList.add("material-icons", "edit-icon", "no-select");
editiconspan.innerHTML = "edit"; editiconspan.innerHTML = "edit";
editiconspan.id = receivers[i].uid + "-editicon";
const editcheck = document.createElement('input'); const editcheck = document.createElement('input');
editcheck.classList.add("edit-checkbox", "edit-icon"); editcheck.classList.add("edit-checkbox", "edit-icon");
...@@ -238,28 +276,49 @@ function createReceivers(rx_json, id) { ...@@ -238,28 +276,49 @@ function createReceivers(rx_json, id) {
deletecheck.id = receivers[i].uid + "-delete"; deletecheck.id = receivers[i].uid + "-delete";
deletecheck.setAttribute('onclick', "deleteReceiver(" + receivers[i].uid + ")"); deletecheck.setAttribute('onclick', "deleteReceiver(" + receivers[i].uid + ")");
urlspan.id = receivers[i].uid + "-url"; const activateiconspan = document.createElement('span');
activateiconspan.classList.add("material-icons", "activate-icon", "no-select");
activateiconspan.innerHTML = "power_settings_new";
activateiconspan.id = receivers[i].uid + "-activateicon";
const activatecheck = document.createElement('input');
activatecheck.classList.add("edit-checkbox", "activate-icon");
activatecheck.type = 'checkbox';
activatecheck.id = receivers[i].uid + "-activate";
// const addnewiconspan = document.createElement('span');
// addnewiconspan.classList.add("material-icons", "add-icon", "no-select");
// addnewiconspan.innerHTML = "add_circle_outline"
// addnewiconspan.id="add_station_icon"
//
// const addnewcheck = document.createElement('input');
// addnewcheck.type = 'checkbox';
// addnewcheck.id="add_station";
// addnewcheck.classList.add("edit-checkbox", "add-icon")
// urlspan.id = receivers[i].uid + "-url";
mobilespan.id = receivers[i].uid + "-mobile"; mobilespan.id = receivers[i].uid + "-mobile";
manualspan.id = receivers[i].uid + "-manual"; // manualspan.id = receivers[i].uid + "-manual";
idspan.id = receivers[i].uid + "-id"; idspan.id = receivers[i].uid + "-id";
locationspan.id = receivers[i].uid + "-location"; locationspan.id = receivers[i].uid + "-location";
headingspan.id = receivers[i].uid + "-heading"; headingspan.id = receivers[i].uid + "-heading";
freqspan.id = receivers[i].uid + "-freq"; freqspan.id = receivers[i].uid + "-freq";
editiconspan.id = receivers[i].uid + "-editicon";
document.getElementById("menu").insertBefore(rxcard, document.getElementById("add_station")); document.getElementById("menu").insertBefore(rxcard, document.getElementById("add_station"));
rxcard.appendChild(urlspan); // rxcard.appendChild(urlspan);
rxcard.appendChild(mobilespan); rxcard.appendChild(mobilespan);
rxcard.appendChild(manualspan); // rxcard.appendChild(manualspan);
rxcard.appendChild(idspan); rxcard.appendChild(idspan);
rxcard.appendChild(locationspan); rxcard.appendChild(locationspan);
rxcard.appendChild(headingspan); rxcard.appendChild(headingspan);
rxcard.appendChild(freqspan); rxcard.appendChild(freqspan);
rxcard.appendChild(editiconspan); rxcard.appendChild(editiconspan);
rxcard.appendChild(deleteiconspan); rxcard.appendChild(deleteiconspan);
rxcard.appendChild(activateiconspan);
rxcard.appendChild(editcheck); rxcard.appendChild(editcheck);
rxcard.appendChild(deletecheck); rxcard.appendChild(deletecheck);
rxcard.appendChild(activatecheck);
showReceivers(rx_json, i); showReceivers(rx_json, i);
} }
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
loadRx(refreshRx); loadRx(refreshRx);
clearOld(); clearOld();
loadCzml(); loadCzml();
console.log(response); // console.log(response);
} }
}) })
} }
...@@ -45,13 +45,13 @@ ...@@ -45,13 +45,13 @@
function loadCzml() { function loadCzml() {
var dataSourcePromise = Cesium.CzmlDataSource.load('/static/output.czml'); var dataSourcePromise = Cesium.CzmlDataSource.load('/static/output.czml');
viewer.dataSources.add(dataSourcePromise); viewer.dataSources.add(dataSourcePromise);
console.log("Loaded CZML"); // console.log("Loaded CZML");
return dataSourcePromise; return dataSourcePromise;
} }
function clearOld() { function clearOld() {
viewer.dataSources.removeAll(true); viewer.dataSources.removeAll(true);
console.log("Cleared old"); // console.log("Cleared old");
} }
// Add Cesium OSM Buildings, a global 3D buildings layer. // Add Cesium OSM Buildings, a global 3D buildings layer.
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
<input id="add_station" class="edit-checkbox add-icon" type="checkbox" style="width: 23px; height: 23px;"/> <input id="add_station" class="edit-checkbox add-icon" type="checkbox" style="width: 23px; height: 23px;"/>
<span id="add_station_icon" class="material-icons add-icon no-select">add_circle_outline</span> <span id="add_station_icon" class="material-icons add-icon no-select">add_circle_outline</span>
<div style="visibility: hidden; height: 0;" id="new_rx_div" style="padding: 0;" class="receiver"> <div style="visibility: hidden; height: 0;" id="new_rx_div" style="padding: 0;">
<span id="new-url">Station URL: <span id="new-url">Station URL:
</span> </span>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment