well-api/well_web_files/my_devices.html
2025-06-15 18:39:43 -07:00

1737 lines
53 KiB
HTML

<!--
Written By: Robert Zmrzli robert@zmrinc.com
my_devices.html
Ver 2.32
-->
<!DOCTYPE html>
<html>
<head>
<title>WellPlugs Administrator</title>
<style>
html, body {
height: 100%;
margin: 0;
}
div.helper {
position: fixed;
bottom: 0;
left: 0px;
}
.full-height {
height: 80%;
}
input[type="text"] {
font-size: 12px;
}
select.devs_in_depl {
width: 100px;
}
/*IE FIX */
select#devs_in_depl {
width: 100px;
}
select:focus#devs_in_depl {
width: 100px\9;
}
.small-button {
border: none;
color: white;
padding: 1px 1px;
text-align: center;
display: inline-block;
font-size: 12px;
margin: 1px 1px;
cursor: pointer;
background-color: #00A000;
height: 24px;
width: 100px;
border-radius: 5px;
box-shadow: 0 2px #999;
}
.small-button:hover {
background-color: #808080;
font-color: #0000ff
}
.small-button:active {
background-color: #00ff00;
box-shadow: 0 4px #666;
transform: translateY(2px);
}
.wider-button {
border: none;
color: white;
padding: 1px 1px;
text-align: center;
display: inline-block;
font-size: 12px;
margin: 1px 1px;
cursor: pointer;
background-color: #00A000;
height: 24px;
width: 80px;
border-radius: 5px;
box-shadow: 0 2px #999;
}
.container {
display: flex;
height: 360px;
margin-bottom: 20px;
}
.box {
margin: 10px;
}
.table-container {
position: relative;
height: calc(100vh - 670px); /* Subtract top panel + boxes + margins */
min-height: 200px;
overflow: auto;
border: 1px solid #ddd;
}
.table-header {
position: sticky;
top: 0;
background-color: white;
z-index: 1;
}
.table-body {
height: 100%;
overflow-y: auto;
}
#devices {
width: 100%;
border-collapse: collapse;
}
#devices th, #devices td {
padding: 8px;
border: 1px solid #ddd;
}
.x_panel {
position: relative;
width: 100%;
margin-bottom: 10px;
padding: 10px;
background: #fff;
border: 1px solid #E6E9ED;
}
/* Bottom panel takes remaining space */
.row:last-child .x_panel {
/* Calculate height as viewport height minus top margin and approximate top panel height */
height: calc(100vh - 180px);
overflow: hidden;
}
/* Adjust the content area inside bottom panel */
.x_panel:last-child .x_content {
height: calc(100% - 50px); /* Subtract header height */
overflow: hidden;
}
/* Remove any height settings from other elements */
.x_content {
padding: 0 5px;
}
/* Ensure the form in the top panel stays compact */
#selectorsDiv {
padding: 10px 0;
}
#devices tbody {
/* Ensure tbody can scroll */
overflow: auto;
}
#devices th {
background-color: #f8f8f8;
}
/* Ensure inputs and content align properly */
#devices td input[type="text"] {
width: 90%;
border: none;
text-align: center;
background: transparent;
box-sizing: border-box;
}
/* Add some spacing for the scrollbar */
.table-container::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.table-container::-webkit-scrollbar-track {
background: #f1f1f1;
}
.table-container::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.table-container::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Adjust table container position and height */
#devslist {
position: relative;
width: 100%;
}
.wrapper {
position: relative;
height: auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(0,0,0,.3);
border-radius: 50%;
border-top-color: #000;
animation: spin 1s linear infinite;
}
</style>
<meta charset="UTF-8" />
<!--[if lt IE 9]>
<script src="http://bellacon.com/js/src/excanvas/excanvas_text.js"></script>
<script src="http://bellacon.com/js/src/excanvas/canvas.text.js"></script>
<![endif]-->
<script src= 'https://eluxnetworks.net/js/src/teechart.js'; type="text/javascript"></script>
<script
src='https://eluxnetworks.net/js/src/teechart-extras.js'
type="text/javascript"
></script>
<script src= 'https://eluxnetworks.net/js/src/jquery-2.1.3.js' type="text/javascript"></script>
<script src='https://eluxnetworks.net/js/src/demo.js' type="text/javascript"></script>
<!-- Bootstrap -->
<link
href = 'https://eluxnetworks.net/js/3rd_party/bootstrap/dist/css/bootstrap.min.css'
rel="stylesheet"
/>
<!-- Font Awesome -->
<link
href='https://eluxnetworks.net/js/3rd_party/font-awesome-4.6.3/css/font-awesome.min.css'
rel="stylesheet"
/>
<!-- NProgress -->
<!-- Custom Theme Style -->
<link href='https://eluxnetworks.net/js/3rd_party/build/css/custom.min.css' rel='stylesheet' />
<link rel="stylesheet" type="text/css" href='https://eluxnetworks.net/js/src/demo.css' />
<script src='https://eluxnetworks.net/js/src/date.format.js' type="text/javascript"></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js"
type="text/javascript"
></script>
<script src="https://cdn.jsdelivr.net/pako/1.0.3/pako.min.js"></script>
<script type="text/javascript">
var MAXESChart;
var AsChart;
var sensed_value_glob = 0;
var tim_value_glob = 0;
var chart_index_glob = 0;
var MAX_POINTS = 3600;
var start_time = Date.now();
var GLOB_HEIGHT = "100";
var fresh = true;
var secs_in_day = 86400;
var signature = "my_devices.htm" + start_time.toString();
var token = localStorage.getItem('token') || '0';
var user_name = localStorage.getItem('user_name') || '';
var user_id = localStorage.getItem('user_id') || '0';
var key = localStorage.getItem('key') || '0';
var client = "";
var Indexes = {
Maximums_RAW_max_val: 0,
Maximums_RAW_a: 1,
Maximums_RAW_b: 2,
Maximums_RAW_c: 3,
};
const baseURL = window.location.origin;
const api_url = `${baseURL}/function/well-api`;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializePage);
} else {
initializePage();
}
function initializePage() {
window.draw = function() {
console.log("Initializing...");
try {
ShowDeployment(false);
DeploymentChange();
SelDesel();
MAXESChart = new Tee.Chart("canvas1");
privileges = localStorage.getItem('privileges');
console.log(privileges);
if (parseInt(privileges) >0) {
document.getElementById("WELL_ID").readOnly = true;
//document.getElementById("GroupPanel").style.display = 'none';
document.getElementById("details1").style.display = 'none';
//document.getElementById("single_info").style.left = '440px';
//document.getElementById("deployment_info").style.left = '960px';
//document.getElementById("deployment_info1").style.left = '870px';
}
else {
document.getElementById("WELL_ID").readOnly = false;
//document.getElementById("GroupPanel").style.visibility = 'visible';
document.getElementById("details1").style.display = 'visible';
//document.getElementById("single_info").style.left = '540px';
//document.getElementById("deployment_info").style.left = '1160px';
//document.getElementById("deployment_info1").style.left = '1260px';
}
ConnectMQTT();
resize();
} catch(err) {
console.error("Error in draw function:", err);
}
}
}
function sendToBackend(topicl, send_what) {
// Once a connection has been made, make a subscription and send a message.
console.log("sendToBackend "+topicl+":" + send_what);
if (topicl.substr(topicl.length - 4) == "_cmp") {
//var djson = new TextDecoder("utf-8").encode(send_what);
var compressedJSON = pako.deflate(send_what);
//var message.payloadBytes= compressedJSON;
message = new Paho.MQTT.Message(compressedJSON);
message.destinationName = topicl;
}
else {
message = new Paho.MQTT.Message(send_what);
message.destinationName = topicl;
}
client.send(message);
}
async function RequestFilteredDevices(group_id, deployment_id, location, is_fresh) {
const formData = new URLSearchParams();
formData.append('function', "request_devices");
formData.append('token', token);
formData.append('user_name', user_name);
formData.append('group_id', group_id);
formData.append('deployment_id', deployment_id);
formData.append('location', location);
formData.append('fresh', is_fresh);
data = await SubmitForm(formData);
if (data.status !== "200 OK") {
alert('Bad response from API!');
return;
}
matching_devices = data["devices"];
var doc = document;
var table = document.getElementById("devices");
var rowCount = table.rows.length;
for (var i = rowCount - 1; i > 0; i--) {
table.deleteRow(i);
}
let topics = [];
now_time = parseInt(Date.now()/1000);
for (i = 0; i < matching_devices.length; i++) {
age = now_time - matching_devices[i][3];
var tr = doc.createElement("tr");
if (age < 300)
tr.style.backgroundColor = "#90FFD7";
else
tr.style.backgroundColor = "";
// <th scope="row">568</th>
// <td style="text-align:center">283</td>
// <td style="text-align:center"><a href="google.com">64B70888F8C0</a></td>
// <td style="text-align:center">1722361900</td>
// <td style="text-align:center">?</td>
// <td style="text-align:center">None</td>
// <td style="text-align:center"><a href="#" onclick="OpenDeployment('23')" title="Daisy Nguyen">23</a></td>
// <td style="text-align:center"><input type="checkbox" onchange="IsItSingle();"></td>
var th = doc.createElement("th");
th.scope = "row";
th.style="text-align:center";
th.innerHTML = matching_devices[i][0];
tr.appendChild(th);
var td = doc.createElement("td");
td.style="text-align:center";
td.innerHTML = matching_devices[i][1];
tr.appendChild(td);
var td = doc.createElement("td");
td.style="text-align:center";
mac = matching_devices[i][2];
//cell_content = "<a href=\"/?user_id="+user_id+"&user_id="+user_id+"&token="+token+"&MAC="+mac+"\" target=\"_blank\">"+mac+"</a>"
cell_content = '<a href="#" onclick="OpenDevice(\''+mac+'\')">'+mac+'</a>';
topics.push("/"+mac)
//console.log(td.outerHTML);
//http://192.168.1.22:5002/?user=john&key=mykey&MAC=716798E056AC
td.innerHTML = cell_content;
tr.appendChild(td);
var td = doc.createElement("td");
td.style="text-align:center";
if (matching_devices[i][3] == 0){
age_string = "No data";
}
else {
age_string = ShowAge(age);
}
td.innerHTML = age_string;
tr.appendChild(td);
var td = doc.createElement("td");
td.style="text-align:center";
td.innerHTML = matching_devices[i][4];
tr.appendChild(td);
var td = doc.createElement("td");
td.style="text-align:center";
if( matching_devices[i][5] == "All") {
td.innerHTML = "";
}
else {
td.innerHTML = matching_devices[i][5];
}
tr.appendChild(td);
var td = doc.createElement("td");
td.style="text-align:center";
cell_content = '<a href="#" onclick="OpenDeployment(\''+matching_devices[i][6]+'\')">'+matching_devices[i][6]+'</a>';
td.innerHTML = cell_content;
tr.appendChild(td);
var ch = doc.createElement("input");
ch.type = "checkbox";
ch.onchange=function(){IsItSingle();};
var td = doc.createElement("td");
td.style="text-align:center";
td.appendChild(ch);
tr.appendChild(td);
table.appendChild(tr);
// <td style="text-align:center"><input type="checkbox" onchange="IsItSingle();"></td>
}
IsItSingle();
qos = 1;
if (client !=""){
// Subscribe to each topic individually
for (let i = 0; i < topics.length; i++) {
try {
client.subscribe(topics[i], {qos: qos});
console.log(`Subscribed to ${topics[i]} with QoS: ${qos}`);
} catch (error) {
console.error(`Error subscribing to ${topics[i]}:`, error);
}
}
}
//doc.getElementById("here_table").appendChild(table);
}
async function SubmitForm(formData) {
console.log(formData.toString());
const response = await fetch(api_url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
mode: 'cors',
body: formData.toString()
});
if (response.ok) {
const text = await response.text();
console.log(text);
if(text.includes("Log-Out")) {
LogOut();
}
else {
try {
data = JSON.parse(text);
return data;
}
catch {
data = {
ok: 0
}
return data;
}
}
} else {
//console.log(await response.json());
data = {
ok: 0
}
return data;
}
}
async function RequestDeploymentProximity(deployment_id, time) {
const formData = new URLSearchParams();
formData.append('function', "request_proximity");
formData.append('token', token);
formData.append('user_name', user_name);
formData.append('time', time);
formData.append('deployment_id', deployment_id);
data = await SubmitForm(formData);
if (data.status !== "200 OK") {
alert('Bad response from API!');
return;
}
//return;
well_ids = data["proximity"];
if (well_ids.length > 0) {
SetOptions('Proximity', well_ids);
}
}
function CopyDeploymentProximity() {
$("#Proximity").empty();
var oldSel = $("#Proximity").get(0);
var table = document.getElementById("devices");
var rowCount = table.rows.length;
for (var i = 1; i < rowCount; i++) {
cell = table.rows[i].cells[0];
if(cell.innerText.length > 0) {
var opt = document.createElement('option');
opt.text = cell.innerText;
opt.value = cell.innerText;
oldSel.add(opt, null);
}
}
}
function WriteDeploymentProximity() {
// timee = Date.now();
// //user = document.getElementById("user").value;
// key = document.getElementById("ps").value;
// deployment = document.getElementById("Deployments").value;
// proximity_string = "";
// var proximity_list = $("#Proximity").get(0);
// if(proximity_list.length > 0){
// proximity_string = proximity_list[0].innerText;
// for (i = 1; i < proximity_list.length; i++)
// {
// proximity_string = proximity_string +","+proximity_list[i].innerText;
// }
// }
// var obj = {
// function: "write_proximity",
// deployment: deployment,
// proximity_string: proximity_string,
// time:timee,
// user_name:user_name,
// ps:key,
// signature:signature
// };
// var json = JSON.stringify(obj);
// sendToBackend("/wellget_cmp", json);
}
function RequestFWsUpdate(device_settings) {
// timee = Date.now();
// user = document.getElementById("user").value;
// key = document.getElementById("ps").value;
// group = document.getElementById("group_id").value;
// deployment = document.getElementById("Deployments").value;
// locationn = document.getElementById("Locations").value;
// //lets prevent FW updates to devices lower than 200
// var table = document.getElementById("devices");
// var rowCount = table.rows.length;
// //If all are enabled, request group update, othervise request d
// which_tags = [];
// all_selected = true;
// for (var i = 1; i < rowCount; i++) {
// cell = table.rows[i].cells[8];
// checkm = cell.children[0];
// if (checkm.checked) {
// cell = table.rows[i].cells[0];
// device_id = parseInt(cell.innerText);
// if (device_id >= 200){ //Prevent updates to older devices
// MAC = table.rows[i].cells[1].innerText;
// //FW = table.rows[i].cells[3].childNodes[0].data;
// var tag_info = MAC;
// which_tags.push(tag_info);
// }
// }
// else {
// all_selected = false;
// }
// }
// var obj = {
// function: "update_fws",
// group: group,
// deployment: deployment,
// location: locationn,
// time:timee,
// user:user,
// ps:key,
// signature:signature,
// device_settings:device_settings,
// which_tags: which_tags,
// all_selected:all_selected
// };
// var json = JSON.stringify(obj);
// sendToBackend("/wellget_cmp", json);
}
function RequestDevsUpdate(device_settings) {
// timee = Date.now();
// user = document.getElementById("user").value;
// key = document.getElementById("ps").value;
// group = document.getElementById("group_id").value;
// deployment = document.getElementById("Deployments").value;
// locationn = document.getElementById("Locations").value;
// var table = document.getElementById("devices");
// var rowCount = table.rows.length;
// //If all are enabled, request group update, othervise request d
// which_tags = [];
// all_selected = true;
// for (var i = 1; i < rowCount; i++) {
// cell = table.rows[i].cells[8];
// checkm = cell.children[0];
// if (checkm.checked) {
// MAC = table.rows[i].cells[1].innerText;
// //FW = table.rows[i].cells[3].childNodes[0].data;
// var tag_info = MAC;
// which_tags.push(tag_info);
// }
// else {
// all_selected = false;
// };
// }
// var obj = {
// function: "update_devices",
// group: group,
// deployment: deployment,
// location: locationn,
// time:timee,
// user:user,
// ps:key,
// signature:signature,
// device_settings:device_settings,
// which_tags: which_tags,
// all_selected:all_selected
// };
// var json = JSON.stringify(obj);
// sendToBackend("/wellget_cmp", json);
}
async function DoRefresh() {
//Lets determine db_file,from_time, to_time from selected options
btn = document.getElementById("set_device_s");
group_id = document.getElementById("group_id").value;
deployment_id = document.getElementById("Deployments").value;
locations = document.getElementById("Locations").value;
RequestFilteredDevices(group_id, deployment_id, locations, fresh);
}
function ShowDeployment(do_show) {
var x = document.getElementById("deployment_info");
var x1 = document.getElementById("deployment_info1");
if (do_show == true) {
x.style.display = "block";
x1.style.display = "block";
} else {
x.style.display = "none";
x1.style.display = "none";
}
}
function DeploymentChange() {
deployment = document.getElementById("Deployments").value;
if(deployment == "0" || deployment == "-1" || deployment == "All" || deployment == "") {
ShowDeployment(false);
}
else {
ShowDeployment(true);
time = new Date().getTime();
RequestDeploymentProximity(deployment, time);
}
}
function resize() {
var body = document.body;
var count = 0;
//h = Math.min( window.innerHeight, document.body.clientHeight )
h = window.innerHeight;
try {
if (MAXESChart.canvas.clientHeight > 0) count = count + 1;
if (AsChart.canvas.clientHeight > 0) count = count + 1;
} catch (err) {
count = 3;
}
//var devlist = document.getElementById("devslist");
//devlist.offsetHeight = 50;
//how many are visible
GLOB_HEIGHT = (h - 2 * 100) / count;
//var devs_table = document.getElementById("devslist");
//devs_table.height = 100%;
try {
MAXESChart.canvas.height = 100;//GLOB_HEIGHT;
MAXESChart.canvas.width = body.clientWidth;
MAXESChart.bounds.width = MAXESChart.canvas.width - 100;
MAXESChart.bounds.height = MAXESChart.canvas.height;
//changeTheme(MAXESChart, "minimal");
//changeTheme(AsChart, "minimal");
} catch (err) {}
}
function GraphUpdate(which_Graph, tim_value, sensed_value, series_index) {
//sensed_value_glob =
function newData(now) {
if (!now) now = new Date().getTime();
}
if (document.getElementById("live-check").checked == false) {
which_Graph = "";
}
switch (which_Graph) {
case "PressuresChart":
chart_index_glob = 0;
series = PressuresChart.series.items[series_index];
var d = series.data.values,
x = series.data.x,
t,
l = d.length;
d[l] = sensed_value;
x[l] = 1000 * tim_value;
if (l > MAX_POINTS) {
d.shift();
x.shift();
}
changeTheme(PressuresChart, "minimal");
PressuresChart.draw();
requestAnimFrame(newData, PressuresChart, 1);
break;
case "TemperaturesChart":
chart_index_glob = 2;
series = TemperaturesChart.series.items[series_index];
var d = series.data.values,
x = series.data.x,
t,
l = d.length;
d[l] = sensed_value;
x[l] = 1000 * tim_value;
if (l > MAX_POINTS) {
d.shift();
x.shift();
}
changeTheme(TemperaturesChart, "minimal");
TemperaturesChart.draw();
requestAnimFrame(newData, TemperaturesChart, 1);
break;
case "AirQualsChart":
chart_index_glob = 1;
series = AirQualsChart.series.items[series_index];
var d = series.data.values,
x = series.data.x,
t,
l = d.length;
d[l] = sensed_value;
x[l] = 1000 * tim_value;
if (l > MAX_POINTS) {
d.shift();
x.shift();
}
changeTheme(AirQualsChart, "minimal");
AirQualsChart.draw();
requestAnimFrame(newData, AirQualsChart, 1);
break;
case "HumidChart":
chart_index_glob = 3;
series = HumidChart.series.items[series_index];
var d = series.data.values,
x = series.data.x,
t,
l = d.length;
d[l] = sensed_value;
x[l] = 1000 * tim_value;
if (l > MAX_POINTS) {
d.shift();
x.shift();
}
changeTheme(HumidChart, "minimal");
//HumidChart.draw();
requestAnimFrame(newData, HumidChart, 1);
break;
}
}
function showTutorial(name) {
document.myform.stage.value = name;
}
function ConnectMQTT() {
// Configure host and port correctly for WSS
const hostname = "mqtt.eluxnetworks.net";
const port = 443; // Use standard HTTPS port for Traefik
const topic = "/wellget"; // You need to define the topic, it was missing
console.log(`Connecting to: ${hostname} on port ${port} using WebSocket`);
// Create a client instance with the working path
client = new Paho.MQTT.Client(
hostname,
port,
"/wss", // Updated to use the working WebSocket path
signature // Your client ID
);
// Set callback handlers
client.onConnectionLost = onConnectionLost;
client.onMessageArrived = onMessageArrived;
// Connection options - removing any invalid properties
const options = {
useSSL: true, // Always use SSL since we're connecting through Traefik on HTTPS
userName: user_name,
password: key,
onSuccess: onConnect,
onFailure: function(err) {
console.log("Connection failed: " + err.errorMessage);
// Explicit reconnect with exponential backoff
setTimeout(function() {
console.log("Attempting to reconnect...");
client.connect(options);
}, 2000); // Wait 2 seconds before reconnecting
},
keepAliveInterval: 30
};
// Called when the client connects
function onConnect() {
console.log("onConnect");
let message = new Paho.MQTT.Message("Hello");
message.destinationName = topic;
client.send(message);
var from_time = 0; // Dummy, not used with "latest"
var to_time = 0;
db_file = "latest";
if (fresh == true) {
// Request only first time
groups = "0"; // Document.getElementById("group_id").value;
deployments = "0"; // Document.getElementById("Deployments").value;
locations = "0"; // Document.getElementById("Locations").value;
//RequestFilteredDevices(groups, deployments, locations, fresh);
//client.subscribe("/well_cmp" + signature); console.log("Subscribed to: /well_cmp" + signature);
fresh = false;
}
}
// Called when the client loses its connection
function onConnectionLost(responseObject) {
if (responseObject.errorCode !== 0) {
console.log("onConnectionLost:" + responseObject.errorMessage);
client.connect({
onSuccess: onConnect,
userName: user_name,
password: key,
useSSL: true // Always use SSL since we're connecting through Traefik
});
}
}
function onMessageArrived(message) {
}
// Attempt connection - only call connect ONCE
console.log("Attempting to connect to MQTT broker...");
try {
console.log(options);
client.connect(options);
} catch (err) {
console.log("Connection attempt failed:", err);
}
}
function ProcessClick(func_name) {
showTutorial("");
switch (func_name) {
case "save":
timee = Date.now();
//Lets find name of deployment
sel = document.getElementById("Deployments");
name = sel.options[sel.selectedIndex].text;
proximity_string = "";
var proximity_list = $("#Proximity").get(0);
if(proximity_list.length > 0){
proximity_string = proximity_list[0].innerText;
for (i = 1; i < proximity_list.length; i++)
{
proximity_string = proximity_string +","+proximity_list[i].innerText;
}
}
var obj = {
function: "save_deployments",
time:timee,
user_id:user_id,
token:token,
deployments:document.getElementById("Deployments").innerHTML,
deployment:document.getElementById("Deployments").value,
deployment_name:name,
deployment_owner_id:"1",
proximity:proximity_string,
visible_to:""
};
var json = JSON.stringify(obj);
sendToBackend("/wellget_cmp", json);
}
}
function moveUp(selectId) {
var selectList = document.getElementById(selectId);
var selectOptions = selectList.getElementsByTagName('option');
for (var i = 1; i < selectOptions.length; i++) {
var opt = selectOptions[i];
if (opt.selected) {
selectList.removeChild(opt);
selectList.insertBefore(opt, selectOptions[i - 1]);
}
}
}
function moveDown(selectId) {
var selectList = document.getElementById(selectId);
var selectOptions = selectList.getElementsByTagName('option');
for (var i = selectOptions.length - 2; i >= 0; i--) {
var opt = selectOptions[i];
if (opt.selected) {
var nextOpt = selectOptions[i + 1];
opt = selectList.removeChild(opt);
nextOpt = selectList.replaceChild(opt, nextOpt);
selectList.insertBefore(nextOpt, opt);
}
}
}
function ShowAge(seconds) {
var minutes = Math.floor(seconds/60);
var hours = Math.floor(minutes/60);
var days = Math.floor(hours/24);
if(seconds >=0) {
hours = hours-(days*24);
minutes = minutes-(days*24*60)-(hours*60);
seconds = seconds-(days*24*60*60)-(hours*60*60)-(minutes*60);
if (days > 0) {
report = days.toString()+" d "+hours.toString()+" h "+minutes.toString()+" m "+parseInt(seconds).toString() +" s";
}
else if(hours > 0) {
report = hours.toString()+" h "+minutes.toString()+" m "+parseInt(seconds).toString() +" s";
}
else if(minutes > 0) {
report = minutes.toString()+" m "+parseInt(seconds).toString() +" s";
}
else{
report = parseInt(seconds).toString() +" s";
}
return(report);
}
else {
return("0 s");
}
}
function AddGroup() {
var x = document.getElementById("group_id");
var old_values = $("#group_id").children('option').map(function(i, e){
return e.value || e.innerText;
}).get();
new_element = $("#NewGroup").val();
if( $.inArray(new_element, old_values) == -1 ) {
$("#group_id").append(
"<option value='" + new_element + "'>" + new_element + "</option>"
);
$("#group_id").val(new_element);
}
else {
alert("Group already exists!");
}
}
function AddDeployment() {
var x = document.getElementById("Deployments");
var old_values = $("#Deployments").children('option').map(function(i, e){
return e.innerText;
}).get();
new_element = $("#NewDeployment").val();
if (new_element == "Change_me"){
alert("Assign new name!");
}
else {
if (new_element.length < 3){
alert("Minimum length of deployment name is 3 characters!");
}
else {
if( $.inArray(new_element, old_values) == -1 ) {
$("#Deployments").append(
"<option value='" + new_element + "'>" + new_element + "</option>"
);
$("#Deployments").val(new_element);
}
else {
alert("Deployment already exists!");
}
}
}
}
function SelDesel() {
var x = document.getElementById("AllSel");
var table = document.getElementById("devices");
var rowCount = table.rows.length;
for (var i = 1; i < rowCount; i++) {
cell = table.rows[i].cells[7];
checkm = cell.children[0];
checkm.checked=x.checked;
}
IsItSingle();
}
function IsItSingle() {
var x = document.getElementById("AllSel");
var table = document.getElementById("devices");
var rowCount = table.rows.length;
var sel_count = 0;
var x = document.getElementById("caption");
for (var i = 1; i < rowCount; i++) {
cell = table.rows[i].cells[7];
MAC = table.rows[i].cells[2].innerText;
checkm = cell.children[0];
if (checkm.checked) {
sel_count +=1;
x.innerHTML = MAC;
}
if (sel_count != 1)
x.innerHTML = "Settings";
}
if (sel_count ==1)
ShowSingle(true);
else
ShowSingle(false);
}
function DeleteCreds(){
var table = document.getElementById("devices");
var rowCount = table.rows.length;
showTutorial("Deleting WiFi Credentials...");
//If all are enabled, request group update, othervise request d
which_tags = [];
all_selected = true;
for (var i = 1; i < rowCount; i++) {
cell = table.rows[i].cells[8];
checkm = cell.children[0];
if (checkm.checked) {
MAC = table.rows[i].cells[1].innerText;
//FW = table.rows[i].cells[4].childNodes[0].data;
var tag_info = MAC;
which_tags.push(tag_info);
}
else {
all_selected = false;
}
}
if (which_tags.length == 1) {
var obj = {
function: "erase_wifi",
time:timee,
user_id:user_id,
token:token,
which_tags: which_tags,
};
var json = JSON.stringify(obj);
sendToBackend("/wellget_cmp", json);
}
else {
alert("Only 1 device can be selected");
}
}
function ReadDevice() {
var table = document.getElementById("devices");
var rowCount = table.rows.length;
showTutorial("Reading device...");
//If all are enabled, request group update, othervise request d
which_tags = [];
all_selected = true;
for (var i = 1; i < rowCount; i++) {
cell = table.rows[i].cells[8];
checkm = cell.children[0];
if (checkm.checked) {
MAC = table.rows[i].cells[1].innerText;
//FW = table.rows[i].cells[4].childNodes[0].data;
var tag_info = MAC;
which_tags.push(tag_info);
}
else {
all_selected = false;
}
}
if (which_tags.length == 1) {
var obj = {
function: "read_device",
time:timee,
user_id:user_id,
token:token,
which_tags: which_tags
};
var json = JSON.stringify(obj);
sendToBackend("/wellget_cmp", json);
}
else {
alert("Only 1 device can be selected");
}
}
function WriteDevice() {
var par_table = document.getElementById("settings");
var par_rowCount = par_table.rows.length;
device_settings = {};
for (var i = 1; i < par_rowCount; i++) {
sensor_settings = {};
sensor_name = par_table.rows[i].cells[0].innerText;
sensor_settings["ut"] = par_table.rows[i].cells[1].childNodes[0].value;
sensor_settings["dt"] = par_table.rows[i].cells[2].childNodes[0].value;
sensor_settings["lp"] = par_table.rows[i].cells[3].childNodes[0].value;
sensor_settings["pn"] = par_table.rows[i].cells[4].childNodes[0].value;
sensor_settings["sn"] = par_table.rows[i].cells[5].childNodes[0].value;
sensor_settings["ee"] = par_table.rows[i].cells[6].childNodes[0].value;
device_settings[sensor_name] = sensor_settings;
}
device_settings["fw_v"] = document.getElementById("FWs").value;
//device_settings["led_s"] = document.getElementById("LEDSchemas").value;
device_settings["BLEScanPeriod"] = document.getElementById("BLEScanPeriod").value;
device_settings["BLEScanDuration"] = document.getElementById("BLEScanDuration").value;
device_settings["TemperatureOffset"] = document.getElementById("TemperatureOffset").value;
device_settings["RadarThreshold"] = document.getElementById("RadarThreshold").value;
device_settings["S2FSR"] = document.getElementById("S2FSR").value;
device_settings["reporting_period_s"] = document.getElementById("reporting_period_s").value;
device_settings["NCRE"] = document.getElementById("NCRE").value;
device_settings["e_key"] = document.getElementById("e_key").value;
device_settings["description"] = document.getElementById("DESCRIPTION").value;
device_settings["close_to"] = document.getElementById("CLOSETO").value;
device_settings["owner_id"] = document.getElementById("Owners").value;
device_settings["wifis"] = document.getElementById("WIFISSID").value;
device_settings["wifi_pass"] = document.getElementById("WIFIPASS").value;
device_settings["led_schema"] = document.getElementById("LEDPATTERN").value;
device_settings["other"] = document.getElementById("OTHER").value;
RequestDevsUpdate(device_settings);
}
function OpenDeployment(some_id){
variable_frame = parent.document.getElementById('variable_frame');
url = api_url + '/api?name=deployment_edit&deployment_id='+some_id+'&token='+token+'&user_name='+user_name;
variable_frame.src = url;
}
function OpenDevice(mac){
try {
const variable_frame = parent.document.getElementById('variable_frame');
if (!variable_frame) {
console.error('variable_frame not found');
return;
}
const url = `${api_url}/api?name=device_edit&mac=${mac}&token=${token}&user_name=${user_name}`;
variable_frame.src = url;
} catch (error) {
console.error('Error in OpenDevice:', error);
}
}
function FindDevices() {
showTutorial("Looking for new devices...");
timee = Date.now();
user_id = document.getElementById("user_id").value;
token = document.getElementById("token").value;
var obj = {
function: "find_devices",
time:timee,
user_id:user_id,
token:token
};
var json = JSON.stringify(obj);
sendToBackend("/wellget_cmp", json);
}
function SetFirmwares(){
showTutorial("Setting firmwares...");
var par_table = document.getElementById("settings");
var par_rowCount = par_table.rows.length;
device_settings = {};
device_settings["fw_v"] = document.getElementById("FWs").value;
RequestFWsUpdate(device_settings);
}
function SetDevices() {
showTutorial("Setting devices...");
var par_table = document.getElementById("settings");
var par_rowCount = par_table.rows.length;
device_settings = {};
for (var i = 1; i < par_rowCount; i++) {
sensor_settings = {};
sensor_name = par_table.rows[i].cells[0].innerText;
sensor_settings["ut"] = par_table.rows[i].cells[1].childNodes[0].value;
sensor_settings["dt"] = par_table.rows[i].cells[2].childNodes[0].value;
sensor_settings["lp"] = par_table.rows[i].cells[3].childNodes[0].value;
sensor_settings["pn"] = par_table.rows[i].cells[4].childNodes[0].value;
sensor_settings["sn"] = par_table.rows[i].cells[5].childNodes[0].value;
sensor_settings["ee"] = par_table.rows[i].cells[6].childNodes[0].value;
device_settings[sensor_name] = sensor_settings;
}
//Dont set FW here, there is separate function for it!
//device_settings["fw_v"] = document.getElementById("FWs").value;
//device_settings["led_s"] = document.getElementById("LEDSchemas").value;
device_settings["BLEScanPeriod"] = document.getElementById("BLEScanPeriod").value;
device_settings["BLEScanDuration"] = document.getElementById("BLEScanDuration").value;
device_settings["TemperatureOffset"] = document.getElementById("TemperatureOffset").value;
device_settings["RadarThreshold"] = document.getElementById("RadarThreshold").value;
device_settings["S2FSR"] = document.getElementById("S2FSR").value;
device_settings["reporting_period_s"] = document.getElementById("reporting_period_s").value;
device_settings["NCRE"] = document.getElementById("NCRE").value;
device_settings["e_key"] = document.getElementById("e_key").value;
device_settings["FWs"] = document.getElementById("FWs").value;
btn = document.getElementById("set_device_s");
if (btn.innerText == "Set Device") { //single
device_settings["id"] = document.getElementById("DEVICE_ID").value;
device_settings["description"] = document.getElementById("DESCRIPTION").value;
device_settings["close_to"] = document.getElementById("CLOSETO").value;
device_settings["owner_id"] = document.getElementById("Owners").value;
device_settings["wifis"] = document.getElementById("WIFISSID").value;
device_settings["wifi_pass"] = document.getElementById("WIFIPASS").value;
device_settings["led_schema"] = document.getElementById("LEDPATTERN").value;
device_settings["other"] = document.getElementById("OTHER").value;
}
RequestDevsUpdate(device_settings);
}
function ShowSingle(do_show) {
var x = document.getElementById("single_info");
btn = document.getElementById("set_device_s");
if (do_show == true) {
x.style.display = "block";
btn.innerText = "Set Device";
} else {
x.style.display = "none";
btn.innerText = "Set Devices";
var x = document.getElementById("caption");
x.innerHTML = "Settings";
}
}
function GenerateDeploymentLink(desiredLink) {
document.getElementById('deployment_link').setAttribute('href',desiredLink);
document.getElementById('deployment_link').setAttribute('target',"_blank");
}
function SetOptions(select_id, select_list) {
// Get the select element
const selectElement = document.getElementById(select_id);
// Clear existing options
selectElement.innerHTML = '';
// Check if select element exists
if (!selectElement) {
console.error(`Select element with id '${select_id}' not found`);
return;
}
// Ensure select_list is an array
if (!Array.isArray(select_list)) {
console.error('select_list must be an array');
return;
}
// Process each item in the list
select_list.forEach(item => {
// Create new option element
const option = document.createElement('option');
// Convert item to string to handle numbers or other types
const itemStr = String(item);
// Check if item contains comma
if (itemStr.includes(',')) {
// Split on comma and trim whitespace
const [value, display] = itemStr.split(',').map(str => str.trim());
option.value = value;
option.textContent = value;
} else {
// Use same value for both
option.value = itemStr;
option.textContent = itemStr;
}
// Add option to select element
selectElement.appendChild(option);
});
}
function OpenMap() {
// Sample data to post
deployment_id = document.getElementById("Deployments").value;
if(deployment_id == "0") return;
const data = {
function: "get_deployment",
user_id: user_id,
user_name: user_name,
token: token,
deployment_id: deployment_id
};
// Create a form dynamically
const form = document.createElement('form');
form.method = 'POST';
form.action = api_url;
form.target = '_blank'; // This makes it open in new tab
// Add hidden fields for the data
for (const key in data) {
const hiddenField = document.createElement('input');
hiddenField.type = 'hidden';
hiddenField.name = key;
hiddenField.value = data[key];
form.appendChild(hiddenField);
}
// Add form to document and submit it
document.body.appendChild(form);
form.submit();
// Clean up the form
document.body.removeChild(form);
}
</script>
</head>
<body onresize="resize()" onload="draw()">
<div
class="wrapper"
style="margin: 0 auto; width: 100%; left: 0; top: 0; position: absolute"
></div>
<div class="fixed" role="main">
<div class="row">
<div class="x_panel">
<div class="x_title">
<h2> Options <span class="smallLetters">Filter by:</span> </h2>
<ul class="nav navbar-right panel_toolbox">
<li> <a class="collapse-link" onclick="showHide(this);"
><i class="fa fa-chevron-up"></i
></a> </li>
<li> <a class="close-link"><i class="fa fa-close"></i></a> </li>
</ul>
<div class="clearfix"></div>
</div>
<div class="x_content">
<button class="small-button" onclick="DoRefresh()">Filter</button>
<button id="openNodeRed" class="small-button">Node Red</button>
<button id="AlarmSettings" class="small-button">Alarm Settings</button>
<div id="loadingIndicator" style="display:none; margin-top: 10px;">
<span>Loading Node-RED...</span>
<div class="spinner" style="display: inline-block; width: 20px; height: 20px; border: 3px solid rgba(0,0,0,.3); border-radius: 50%; border-top-color: #000; animation: spin 1s linear infinite;"></div>
</div>
<div id = "GroupPanel" style="visibility: hidden; width:20%;Text-align:center;float:left;"> Group:
<select name="group_id" id="group_id" size="1">
<option value="All">All</option>
<option value="0">0</option>
<option value="EyeTech">EyeTech</option>
</select>
<button class="small-button" onclick="AddGroup()">New</button>
<input type="text" id="NewGroup" name="NewGroup" title="New Group #" value="New Group" size="10">
</div>
<div id = "OtherPanel" style="width:50%;Text-align:center;float:left;"> Deployment:
<select name="Deployments" id="Deployments" onchange="DeploymentChange()" size="1">
###INSTALLS###
</select>
<!---
<button class="small-button" title="Create new Deployment with name specified to the right" onclick="AddDeployment()">New</button>
<input type="text" id="NewDeployment" name="NewDeployment" title="New Deployment (address), mimum 3 characters long" value="Change_me" size="10">
<button class="small-button" title="Save Deployments" onclick="ProcessClick('save')">Save</button>
--->
Location:
<select name="Locations" id="Locations" size="1">
<option value="All">All</option>
<option value="Bathroom">Bathroom</option>
<option value="Kitchen">Kitchen</option>
<option value="Dining Room">Dining Room</option>
<option value="Bedroom">Bedroom</option>
<option value="Living Room">Living Room</option>
<option value="Hallway">Hallway</option>
<option value="Office">Office</option>
<option value="Conference Room">Conference Room</option>
<option value="Outside">Outside</option>
<option value="Attic">Attic</option>
<option value="Basement">Basement</option>
<option value="Garage">Garage</option>
<option value="Other">Other</option>
</select>
</div>
</div>
</div>
</div>
<div class="row">
<div class="x_panel">
<div class="x_title">
<h2><span id="naslov" class="smallLetters">Parameters</span></h2>
<ul class="nav navbar-right panel_toolbox">
<li> <a class="collapse-link" onclick="showHide(this)"
><i class="fa fa-chevron-up"></i
></a> </li>
</ul>
<div class="clearfix"></div>
<h2 id="caption">Settings</h2>
</div>
<div class="x_content">
<div style="position: relative; height: 100vh;">
<div class="container">
<div class="box" id = "left_panel" style="width: 460px; height: 360px;">
LED pattern:
<input name="LEDPATTERN" type="text" id="LEDPATTERN" style="text-align:center" value="00112233445566778899AA" size="20">
<br>
<br>
Scan BLE every [s]:
<input style="text-align:center" type="text" name="BLEScanPeriod" id="BLEScanPeriod" size="3">
for [s]:
<input style="text-align:center" type="text" name="BLEScanDuration" id="BLEScanDuration" size="3">
<br><br>
R threshold:
<input style="text-align:center" type="text" name="RadarThreshold" id="RadarThreshold" size="3">
<br><br>
Reporting period [s]:
<input style="text-align:center" type="text" name="reporting_period_s" id="reporting_period_s" size="3">
<br>
<br>
<button title="Writes device parameters to DB!" class="wider-button" id="set_device_s" onclick="SetDevices()">Set Devices</button>
<button title="Reports data found in DB including latest updates!" class="wider-button" id="find_new_s" onclick="FindDevices()">Received?</button>
</div>
<div class = "box" id="single_info" style="width: 460px; height: 360px;"> Well ID:
<input style="text-align:center" type="text" name="WELL_ID" id="WELL_ID" size="4">
Time Zone:
<input style="text-align:center" type="text" name="time_zone" id="time_zone" size="6">
<br>
<br>
<p>Description:
<textarea name="DESCRIPTION" id="DESCRIPTION" rows="1" cols="100"></textarea>
<br>
<div id = "details1">
<br>
SSID(s):
<input style="text-align:center" type="text" name="WIFISSID" id="WIFISSID" size="16">
<br>
Pass:
<input style="text-align:center" type="text" name="WIFIPASS" id="WIFIPASS" size="26">
<button class="wider-button" id="delete_CR_s" onclick="DeleteCreds()">Delete</button>
<br>
<br>
</div>
</p>
<button class="wider-button" id="read_device_s" onclick="ReadDevice()">Read Device</button>
</div>
<div class="box" id="deplink" style="width: 460px; height: 360px;">
<a id="deployment_link" href="#" onclick="OpenMap(); return false;">Devices In Deployment (Well Id's):</a>
<div id="deployment_info">
<div style="display: flex; gap: 20px;">
<select class="devs_in_depl" name="Proximity" id="Proximity" style="position: relative; left: 30px; top: 10px;" size="10">
<option value="3">3</option>
<option value="37">37</option>
<option value="42">42</option>
</select>
<div id="deployment_info1" style="position: relative; left: 30px; top: 10px;">
<button class="wider-button" id="move_up" title="Move selected item up" onclick="moveUp('Proximity');">Up</button>
<br>
<button class="wider-button" id="move_down" title="Move selected item down" onclick="moveDown('Proximity');">Down</button>
<br>
<button class="wider-button" id="read_proximity" title="Retrieve proximity of devices" onclick="DeploymentChange();">Read</button>
<br>
<button class="wider-button" id="write_proximity" title="Store proximity of devices" onclick="WriteDeploymentProximity();">Write</button>
<br>
<button class="wider-button" id="copy_proximity" title="Copy all visible devices to proximity list" onclick="CopyDeploymentProximity();">Copy</button>
</div>
</div>
</div>
</div>
</div>
<div id="devslist" style="position: relative; left: 0px; margin-top: 20px; width: 100%;">
<h2>Devices</h2>
<div class="table-container">
<table name="devices" id="devices">
<thead class="table-header">
<tr>
<th style="text-align:center" scope="col">Id</th>
<th style="text-align:center" scope="col">Well Id</th>
<th style="text-align:center" scope="col">MAC</th>
<th style="text-align:center" scope="col">Last Message</th>
<th style="text-align:center" scope="col">Location</th>
<th style="text-align:center" scope="col">Description</th>
<th style="text-align:center" scope="col">Deployment</th>
<th style="text-align:center" scope="col">Enable change
<input type="checkbox" id="AllSel" onchange="SelDesel();"></th>
</tr>
</thead>
<tbody name="devices_body" id="devices_body">
###ROWS###
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div id="helper" style="position: absolute; left: 400px; top: 24px; height: 50px; width: 200px;">
<form name = "myform">
<input type = "text" name = "stage" size = "32" style="border:0"/>
</form>
</div>
<script>
document.getElementById('openNodeRed').addEventListener('click', async function() {
// Get the base URL of the current page
const baseUrl = window.location.protocol + '//' + window.location.host;
// Show loading indicator
const loadingIndicator = document.getElementById('loadingIndicator');
loadingIndicator.style.display = 'block';
// Variables needed for API calls
const apiEndpoint = baseUrl + '/function/well-api/api';
try {
// First API call to request Node-RED
const formData = new URLSearchParams();
formData.append('function', 'request_node_red');
formData.append('user_name', user_name);
formData.append('token', token);
const requestResponse = await fetch(apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formData.toString()
});
const requestData = await requestResponse.json();
if (requestData.ok !== 1) {
throw new Error('Failed to request Node-RED');
}
// Poll for port number
let port = 0;
let attempts = 0;
const maxAttempts = 15; // Allow up to 15 seconds
const checkPort = async function() {
attempts++;
const formData = new URLSearchParams();
formData.append('function', 'get_node_red_port');
formData.append('user_name', user_name);
formData.append('token', token);
const portResponse = await fetch(apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: formData.toString()
});
const portData = await portResponse.json();
port = parseInt(portData.port, 10);
// Check if the result is a valid number
if (isNaN(port)) {
// Handle invalid input
port = 0; // Provide a default port value
}
if (port === 0 && attempts < maxAttempts) {
// Continue polling
setTimeout(checkPort, 1000);
} else if (port > 0) {
// We have a port, open Node-RED
loadingIndicator.style.display = 'none';
window.open(`${baseUrl.replace(/:\d+$/, '')}:${port}/?access_token=${token}&username=${user_name}`, '_blank');
} else {
// Max attempts reached, show error
loadingIndicator.style.display = 'none';
alert('Could not start Node-RED. Please try again later.');
}
};
// Start polling
checkPort();
} catch (error) {
console.error('Error:', error);
loadingIndicator.style.display = 'none';
alert('An error occurred: ' + error.message);
}
});
function openAlarmSettings() {
// You can change this URL to any website you want to open
window.open('https://eluxnetworks.net/shared/alarms.html', '_blank');
}
document.getElementById('AlarmSettings').addEventListener('click', async function() {
openAlarmSettings();
});
</script>
</body>
</html>