var fs = require("fs");
const { CredentialManager } = require('@strychnine-labs/windows-credential-manager');
const credManager = new CredentialManager();
// Only reading existing stored credentials
const cred = credManager.getCredential('opc-connect');
const API_AUTH_USERNAME = cred.username;
const API_AUTH_PASSWORD = cred.password.replace("opc-connect", '');
var exec = require("child_process").exec;
const { config } = require("process");
const propertiesReader = require("properties-reader");
const properties = propertiesReader(__dirname + "\\config.properties");
let restart_time = Number(properties.get("RESTART_TIME"));
var responseTime = 0;
let network_status = 1;
let restart = 0;
let mq = null;
if (isNaN(restart_time) || restart_time === 0) {
restart_time = 1;
}
let global_header = null;
console.info(properties.get("MQTT_URL"));
console.info(__dirname + "\config.properties");
function statusCodesToCause(code) {
const statusCodes = {
0: "Connecting",
1: "Success",
2: "Unable to upload to database",
3: "Program Stopped",
4: "Data Stopped Receiving, Restarting Client in sometime"
};
return statusCodes[code.toString()];
}
// --- Place this function outside your controller for reusability ---
function checkNetworkSpeed($http, properties) {
var epochTime1 = Date.now();
var CONFIG_URL_PREFIX = properties.get("CONFIG_URL_PREFIX");
var userApiUrl = `${CONFIG_URL_PREFIX}/ingestconfigs?q=${epochTime1}`;
var startTime = Date.now();
$http
.get(userApiUrl, { responseType: "arraybuffer" })
.then(function (response) {
var endTime = Date.now();
var requestTime = endTime - startTime;
var downloadSpeedMbps =
(response.data.byteLength * 8) /
(requestTime / 1000) /
1024 /
1024;
var networkSpeedElement = document.getElementById("network-speed-mbps");
if (networkSpeedElement) {
networkSpeedElement.style.color = downloadSpeedMbps < 2 ? "red" : "green";
networkSpeedElement.textContent = downloadSpeedMbps.toFixed(2) + " Mbps";
}
})
.catch(function (error) {
console.error("Error making request:", error);
var networkSpeedElement = document.getElementById("network-speed-mbps");
if (networkSpeedElement) {
networkSpeedElement.textContent = "Speed test failed";
networkSpeedElement.style.color = "red";
}
});
}
function validateStatusOfAllConfigs(arr) {
return arr.every((val, i, arr) => val === 1);
}
function isStaleTimestamp(ts, configId) {
console.info("time: ", restart_time);
var tmp = (+new Date() - new Date(ts)) / 60000;
console.info(configId, ts, tmp);
return tmp > restart_time ? true : false;
}
function csvJSON(csv) {
var lines = csv.split("\n");
var result = [];
var headers = lines[0].split(",");
for (var i = 1; i < lines.length; i++) {
var obj = {};
var currentline = lines[i].split(",");
for (var j = 0; j < headers.length; j++) {
obj[headers[j].replace("\r", "")] = currentline[j].replace("\r", "");
}
result.push(obj);
}
return JSON.stringify(result);
}
function hasTagsColumnAndNonEmpty(tags) {
return tags.filter(function (tag) {
if (tag["tags"]) {
tag["dataTagId"] = tag["tags"];
delete tag["tags"];
return tag;
}
});
}
// Function to check the health endpoint and update global_header
async function updateAuthToken() {
try {
const healthResponse = await fetch(properties.get("CONFIG_URL_PREFIX").replace("/exactapi", "") + "/device/health");
if (healthResponse.status === 200) {
console.log(`[${new Date().toISOString()}] Health check successful, using device ID as authToken`);
// $scope.displayForm();
let clientId = localStorage.getItem("clientId")
console.log("clientId update", clientId);
const myHeaders = new Headers();
myHeaders.append("Authorization", clientId);
const requestOptions = {
method: "POST",
headers: myHeaders,
redirect: "follow"
};
const registerResponse = await fetch(properties.get("CONFIG_URL_PREFIX").replace("/exactapi", "") + "/device/register", requestOptions);
const data = await registerResponse.json();
global_header = data.id; // Use the ID from device registration as the auth token
return global_header;
} else {
console.log(`[${new Date().toISOString()}] Health check failed, using login endpoint for authToken`);
// Fallback to using the login endpoint
await getAuthToken();
}
} catch (error) {
console.error("Error updating auth token:", error);
}
}
// Function to get auth token from login endpoint
async function getAuthToken() {
const authHeaders = new Headers();
authHeaders.append("Content-Type", "application/json");
const authRaw = JSON.stringify({
email: API_AUTH_USERNAME,
password: API_AUTH_PASSWORD
});
const authRequestOptions = {
method: "POST",
headers: authHeaders,
body: authRaw,
redirect: "follow"
};
try {
const response = await fetch(`${properties.get("CONFIG_URL_PREFIX")}/Users/login`, authRequestOptions);
const result = await response.json();
// console.log("result.id", result.id);
global_header = result.id; // Update global header with login token
return global_header;
} catch (error) {
console.error("Failed to retrieve global header from login:", error);
throw new Error("Failed to retrieve global header");
}
}
// Function to check /opc-network and update header if 401
async function checkAndUpdateHeader() {
console.log(`checkAndUpdateHeader called at ${new Date().toLocaleTimeString()}`);
const myHeaders = new Headers();
if (global_header) {
myHeaders.append("Authorization", `Bearer ${global_header}`);
}
const requestOptions = {
method: "GET",
headers: myHeaders,
redirect: "follow"
};
try {
const response = await fetch(properties.get("CONFIG_URL_PREFIX").replace("/exactapi", "") + "/opc-network", requestOptions);
console.log("opc-network status", response.status);
if (response.status === 401) {
console.warn("Received 401, updating global_header...");
await updateAuthToken(); // Update the global header if 401
} else {
const result = await response.text();
console.log(result);
}
} catch (error) {
console.error("Error in checkAndUpdateHeader:", error);
}
}
// Initialize the auth token on startup
updateAuthToken();
// Call the function to check and update header every 60 seconds
setInterval(checkAndUpdateHeader, 60000);
let startFlag = true;
let restart_interval = Number(properties.get("RESTART_INTERVAL"));
if (isNaN(restart_interval) || restart_interval === 0) {
restart_interval = 1;
}
let max_attempts = Number(properties.get("MAX_ATTEMPTS"));
if (isNaN(max_attempts) || max_attempts === 0) {
max_attempts = 5;
}
let backoff_interval = Number(properties.get("BACKOFF_INTERVAL"));
if (isNaN(backoff_interval) || backoff_interval === 0) {
backoff_interval = 0.01;
}
let START_INTERVAL = Number(properties.get("START_INTERVAL"));
if (isNaN(START_INTERVAL) || START_INTERVAL === 0) {
START_INTERVAL = 1000;
}
var attempts = 1;
var inBackoffPeriod = false;
var backoffEndTime = 0;
function manageRestart($http, $scope, configId, index, form) {
var clientId = localStorage.getItem("clientId") || $scope.clientId;
var currentTime = Date.now();
var lastRestartTime =
parseInt(localStorage.getItem(configId + "_last_restart_time")) || 0;
// $scope.forms.forEach(function (form) {
// if (form.id === configId) {
// var unitsId = form.unitsId;
// var divElement = document.getElementById(`${configId}+unit`);
// if (unitsId) {
// divElement.textContent = "unitId: " + unitsId;
// divElement.style.color = "black";
// } else {
// divElement.textContent = "Please link this to unitsId from pulse";
// divElement.style.color = "red";
// }
// }
// });
// $scope.forms.forEach(function (form) {
// if (form.id === configId) {
// var divElement = document.getElementById(`${configId}+config`);
// divElement.textContent = "configId: " + configId;
// divElement.style.color = "black";
// }
// });
if (inBackoffPeriod && currentTime < backoffEndTime) {
console.info("Backoff period time");
return;
} else if (inBackoffPeriod && currentTime >= backoffEndTime) {
console.info("Backoff period over, resetting attempts");
attempts = 1;
inBackoffPeriod = false;
}
if (attempts > max_attempts && !inBackoffPeriod) {
console.info(
"Maximum restart attempts reached. Backing off for " +
backoff_interval +
" minutes."
);
inBackoffPeriod = true;
backoffEndTime = currentTime + backoff_interval * 60 * 1000;
return;
}
if (currentTime - lastRestartTime < restart_interval * 60 * 1000) {
console.info("Restart interval not elapsed yet.");
return;
}
$http
.get(
properties.get("CONFIG_URL_PREFIX") +
"/ingestconfigs/" +
configId +
"/statuses"
)
.then(function (res) {
if (res.status == 200) {
console.info(
"configId",
configId,
"requiredRestart",
res.data["requireRestart"],
"status",
res.data["status"],
"url",
properties.get("CONFIG_URL_PREFIX") +
"/ingestconfigs/" +
configId +
"/statuses"
);
// status check
if (res.data["status"] === 1) {
network_status = 1;
} else network_status = 0;
// requireRestart check
if (res.data["requireRestart"]) {
restart = 1;
console.info("Required Restart Detected");
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
'/statuses/update?where={"ingestconfigId":"' +
configId +
'"}',
(json = { requireRestart: false })
)
.then(function (response) {
if (response.status == 200) {
console.info("Required Restart Requested");
$scope.restart(form);
console.info("Restarted-Attempts:", attempts);
localStorage.setItem(
configId + "_last_restart_time",
Date.now()
);
attempts++;
} else {
console.warn(response.status);
}
})
.catch(function (response) {
console.error(response);
});
} else {
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
'/statuses/update?where={"ingestconfigId":"' +
configId +
'"}',
(json = { requireRestart: false })
)
.then(function (response) {
if (response.status == 200) {
console.info("required restart key updated");
}
})
.catch(function (response) {
console.error(response);
});
}
if (res.data["stopAll"]) {
console.info("stopAll Detected");
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
'/statuses/update?where={"ingestconfigId":"' +
configId +
'"}',
(json = { stopAll: false, opcConnect: true })
)
.then(function (response) {
if (response.status == 200) {
console.info("stopAll Requested");
$scope.stopAll($scope.forms);
} else {
console.warn(
"stopAll failed with response code",
response.status
);
}
})
.catch(function (response) {
console.error("stopAll failed with error", response);
});
} else {
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
'/statuses/update?where={"ingestconfigId":"' +
configId +
'"}',
(json = { stopAll: false, opcConnect: true })
)
.then(function (response) {
// console.log("log...",response);
if (response.status == 200) {
console.info("stopAll key updated");
}
})
.catch(function (response) {
console.error(response);
});
}
if (res.data["startAll"]) {
console.info("startAll Detected");
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
'/statuses/update?where={"ingestconfigId":"' +
configId +
'"}',
(json = { startAll: false })
)
.then(function (response) {
if (response.status == 200) {
console.info("startAll Requested");
$scope.startAll($scope.forms);
} else {
console.warn(
"startAll failed with response code",
response.status
);
}
})
.catch(function (response) {
console.error("startAll failed with error", response);
});
} else {
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
'/statuses/update?where={"ingestconfigId":"' +
configId +
'"}',
(json = { startAll: false })
)
.then(function (response) {
if (response.status == 200) {
console.info("startAll key updated");
}
})
.catch(function (response) {
console.error(response);
});
}
if (res.data["time"] && isStaleTimestamp(res.data["time"], configId)) {
console.info("Stale condition detected");
if (attempts <= max_attempts) {
console.info("Restarting");
$scope.restart(form);
console.info("Restarted- Attempts:", attempts);
localStorage.setItem(configId + "_last_restart_time", Date.now());
attempts++;
} else {
console.warn(
"Maximum restart attempts reached. Backing off for " +
backoff_interval +
" minutes."
);
inBackoffPeriod = true;
backoffEndTime = currentTime + backoff_interval * 60 * 1000;
}
} else {
restart = 0;
console.warn("No restart required.");
}
var d = new Date();
var epochTime = d.getTime();
console.info(
clientId + "/" + configId + "/" + configId + "_network_status"
);
console.info(clientId + "/" + configId + "/" + configId + "_restart");
function safePublish(topic, message, options = {}) {
if (typeof mq !== 'undefined' && mq && mq.connected) {
mq.publish(topic, message, options);
} else {
console.warn(`⚠️ MQTT not connected. Cannot publish to topic: ${topic}`);
}
}
safePublish(
`${clientId}/${configId}/${configId}_network_status`,
JSON.stringify({ t: epochTime, v: network_status })
);
safePublish(
`${clientId}/${configId}/${configId}_restart`,
JSON.stringify({ t: epochTime, v: restart })
);
$scope.forms[index]["status"] = statusCodesToCause(res.data["status"]);
$scope.forms[index].status_class =
res.data["status"] == 1 ? "success" : "error";
}
})
.catch(function (e2) {
console.error(e2);
});
}
angular
.module("desktopApp", [])
.factory('authInterceptor', function () {
return {
request: function (config) {
config.headers = config.headers || {};
config.headers.Authorization = `Bearer ${global_header}`;
return config;
}
};
})
.config(function ($httpProvider) {
$httpProvider.interceptors.push('authInterceptor');
})
.controller(
"indexController",
function ($scope, $sce, $http, $interval, $window) {
$scope.network = false;
$scope.registration = true;
$scope.config = false;
$scope.dataTagIds = [];
$scope.TagsRender = {};
$scope.TsRender = {};
$http.defaults.headers.common["Authorization"] = $scope.global_header;
$scope.onMessageCallback = function (topic, msg) {
var dataTagId = topic.split("/")[2];
let d = new Date(JSON.parse(msg.toString())["t"]);
$scope.TagsRender[dataTagId] = JSON.parse(msg.toString())["v"];
$scope.TsRender[dataTagId] = d.toLocaleString("en-US");
};
let reconnecting = false;
// 🌟 Connect to MQTT Broker
const connectMQTT = async () => {
if (!global_header) {
console.error("MQTT authorization token is missing.");
return;
}
// Disconnect previous connection if exists
if (mq && mq.connected) {
try {
mq.end(true); // Clean disconnect
} catch (e) {
console.warn("Error closing previous MQTT connection:", e);
}
}
const mqttUrl = `${properties.get("MQTT_URL")}?access_token=${global_header}`;
const options = {
keepalive: 60, // ✅ Broker friendly interval
clean: true,
protocol: 'ws',
reconnectPeriod: 5000 // ✅ Automatic reconnect every 5s if disconnected
};
mq = mqtt.connect(mqttUrl, options);
mq.on("message", $scope.onMessageCallback);
mq.on("connect", () => {
reconnecting = false;
console.log(`[${new Date().toISOString()}] ✅ MQTT Connection established`);
});
mq.on("error", async (err) => {
console.error(`[${new Date().toISOString()}] ❌ MQTT Connection error:`, err);
if (!reconnecting && err.message && err.message.includes("Authentication failed")) {
reconnecting = true;
await updateAuthToken();
connectMQTT(); // 🔄 Reconnect with new token
}
});
mq.on("close", () => {
console.warn(`[${new Date().toISOString()}] ⚠️ MQTT Connection closed`);
});
mq.on("disconnect", () => {
console.warn(`[${new Date().toISOString()}] ⚠️ MQTT Connection disconnected`);
});
mq.on("reconnect", () => {
console.warn(`[${new Date().toISOString()}] 🔄 MQTT Connection reconnecting`);
});
};
// 🌟 Update Token + Connect
async function updateAuthTokenAndConnectMQTT() {
await updateAuthToken(); // ✅ This should set global_header
if (global_header) {
connectMQTT();
} else {
console.error("Failed to retrieve global header for MQTT connection.");
}
}
// 🌟 One week (or slightly earlier) dynamic token refresh
function scheduleWeeklyTokenRefresh() {
const SAFETY_WEEK_MS = (7 * 24 * 60 * 60 * 1000) - (2 * 60 * 60 * 1000); // Refresh after 6d 22h
setTimeout(async () => {
await updateAuthToken();
console.log(`[${new Date().toISOString()}] 🔑 MQTT token refreshed after 1 week`);
scheduleWeeklyTokenRefresh(); // 🔄 Schedule next refresh
}, SAFETY_WEEK_MS);
}
// 🌟 Start everything
updateAuthTokenAndConnectMQTT();
scheduleWeeklyTokenRefresh();
function safePublish(topic, message, options = {}) {
if (mq && mq.connected) {
mq.publish(topic, message, options);
} else {
console.warn(`⚠️ MQTT not connected. Can't publish to ${topic}`);
}
}
$scope.start = function (form) {
start(form);
localStorage.setItem(form.id, true);
};
$scope.startAll = function (forms) {
console.info("Calling startAll");
forms.forEach(function (form, index) {
setTimeout(function () {
start(form);
localStorage.setItem(form.id, true);
}, index * START_INTERVAL); // Delay of 5 seconds * index
});
};
$scope.stop = function (form) {
stop(form);
localStorage.removeItem(form.id);
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
'/statuses/update?where={"ingestconfigId":"' +
form.id +
'"}',
(json = { status: 3 })
)
.then(function (response) {
if (response.status == 200) {
console.info("status went to stop");
if (response.data.count == 0 || response.status != 200) {
console.info("Not found configs, in post statuses update");
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
"/ingestconfigs/" +
form.id +
"/statuses",
(json = { status: 0 })
)
.then(function (response) {
if (response.status == 200) {
console.info("created status");
} else {
console.warn(response.status);
}
})
.catch(function (response) {
console.error(response);
});
}
} else {
console.warn(response.status);
}
})
.catch(function (e2) {
console.error(e2);
});
};
$scope.restart = function (form) {
console.info("restart configId:", form);
console.info("Calling stop");
stop(form);
localStorage.removeItem(form.id);
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
'/statuses/update?where={"ingestconfigId":"' +
form.id +
'"}',
(json = { status: 3 })
)
.then(function (response) {
if (response.status == 200) {
console.info("updated status for stop");
console.info("created status");
$scope.start(form);
} else if (response.data.count == 0 || response.status != 200) {
console.info("Not found configs, in post statuses update");
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
"/ingestconfigs/" +
form.id +
"/statuses",
(json = { status: 0 })
)
.then(function (response) {
if (response.status == 200) {
console.info(response.status);
} else {
console.warn(response.status);
}
})
.catch(function (response) {
console.error(response);
});
} else {
console.warn(response.status);
}
})
.catch(function (e2) {
console.error(e2);
});
};
$scope.stopAll = function (forms) {
console.info("stopping all");
var clientId = localStorage.getItem("clientId") || $scope.clientId;
$http
.get(
properties.get("CONFIG_URL_PREFIX") +
"/clients/" +
clientId +
"/ingestconfigs"
)
.then(function (response) {
if (response.status == 200) {
console.info("got all configs to delete!");
for (var i = 0; i < response.data.length; i++) {
localStorage.removeItem(response.data[i]["id"]);
}
stopAll(forms);
} else {
console.warn(response.status);
}
})
.catch(function (response) {
console.error(response);
});
};
$scope.addCSV = function (form) {
if (document.getElementById("file").files.length == 0) {
console.info("no files");
return;
}
console.info("fileame");
var f = document.getElementById("file").files[0],
r = new FileReader();
console.info(f.name);
if (!f.name.endsWith(".csv")) {
alert(f.name + " is not a CSV file");
return;
}
r.onloadend = function (e) {
var data = e.target.result;
try {
var taglist = JSON.parse(csvJSON(data));
taglist = hasTagsColumnAndNonEmpty(taglist);
if (taglist.length > 0) {
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
"/ingestconfigs/" +
form.id +
"/tags",
(json = taglist)
)
.then(function (response) {
if (response.status == 200) {
console.info("saved!");
form.tags.concat(taglist);
} else {
console.warn(response.status);
}
})
.catch(function (response) {
console.error(response);
});
} else {
alert(
"CSV must contain `tags` column and value should not be empty"
);
}
} catch (err) {
console.error("Internal error: CSV Parsing");
console.error(err);
}
};
r.readAsBinaryString(f);
};
$scope.lget = function (arg) {
return localStorage.getItem(arg);
};
$scope.test = function (form) {
console.info(form);
test(form);
};
$scope.loadTags = function (form) {
$scope.tagSearchQuery = "";
var clientId = localStorage.getItem("clientId") || $scope.clientId;
const local_config = form;
console.info(clientId + "/" + local_config["id"]);
$http
.get(
properties.get("CONFIG_URL_PREFIX") +
"/ingestconfigs/" +
local_config["id"] +
"/tags?ts=" +
(+new Date()).toString()
)
.then(function (res) {
if (res.status == 200) {
form.tags = res.data;
var prefix = form.TAG_PREFIX;
form.tags.forEach((tag_el) => {
mq.subscribe(
clientId +
"/" +
local_config["id"] +
"/" +
prefix +
tag_el.dataTagId
);
});
}
});
};
$scope.filteredTags = function (form, query) {
if (!form || !Array.isArray(form.tags)) {
return [];
}
var filteredTags = [];
if (!query) {
form.tags.forEach(function (tag) {
tag.highlighted = $sce.trustAsHtml(tag.dataTagId);
filteredTags.push(tag);
});
} else {
var regex = new RegExp("(" + preg_quote(query) + ")", "gi");
form.tags.forEach(function (tag) {
if (tag.dataTagId.toLowerCase().includes(query.toLowerCase())) {
var highlighted = tag.dataTagId.replace(
regex,
'$1'
);
tag.highlighted = $sce.trustAsHtml(highlighted);
filteredTags.push(tag);
}
});
}
return filteredTags;
};
function preg_quote(str) {
return (str + "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
$scope.loadConfig = function ($http) {
var clientId = localStorage.getItem("clientId") || $scope.clientId;
$http
.get(
properties.get("CONFIG_URL_PREFIX") +
"/clients/" +
clientId +
"/ingestconfigs"
)
.then(function (response) {
if (response.status == 200) {
if (!angular.equals($scope.forms, response.data)) {
var urlprefix =
properties.get("CONFIG_URL_PREFIX") + "/ingestconfigs/";
// var urlsuffix = "/statuses?ts=" + (+new Date()).toString();
var urlsuffix = "/statuses";
Promise.all(
response.data.map((u) =>
$http.get(urlprefix + u["id"] + urlsuffix)
)
)
.then((responses) =>
Promise.all(responses.map((res) => res.data))
)
.then((texts) => {
var statuses = texts.map((x) => x["status"]);
if (statuses.length == 0) {
$scope.config_color = "red";
} else if (validateStatusOfAllConfigs(statuses)) {
console.info("all valid status");
$scope.config_color = "green";
} else {
$scope.config_color = "red";
}
});
}
if ($scope.forms && $scope.forms.length > 0) {
console.log("startFlag", startFlag);
if (startFlag) {
setTimeout(function () {
$scope.stopAll($scope.forms);
}, 2000);
setTimeout(function () {
$scope.startAll($scope.forms);
}, 4000);
startFlag = false;
}
for (let index = 0; index < $scope.forms.length; index++) {
setTimeout(function () {
manageRestart(
$http,
$scope,
$scope.forms[index]["id"],
index,
$scope.forms[index]
);
}, index * START_INTERVAL);
}
} else {
console.warn(response.status);
}
}
})
.catch(function (e) {
console.error(e);
});
};
$scope.searchInLogs = function (configId, typedWords) {
var logsElement = angular.element(document.getElementById(configId));
var logsContent = logsElement.text();
logsContent = logsContent.replace(/Logs:/g, "
Logs:");
if (typedWords) {
var escapedTypedWords = typedWords.replace(
/[-\/\\^$*+?.()|[\]{}]/g,
"\\$&"
);
var highlightedLogsContent = logsContent.replace(
new RegExp("(" + escapedTypedWords + ")", "gi"),
'$1'
);
highlightedLogsContent = highlightedLogsContent.replace(
/Logs:/g,
"Logs:"
);
logsElement.html("" + highlightedLogsContent + "");
} else {
logsContent = logsContent.replace(/
Logs:/g, "
Logs:");
logsElement.html(logsContent);
}
};
$scope.copyLogs = function (configId) {
var logsContent = angular
.element(document.getElementById(configId))
.text();
navigator.clipboard
.writeText(logsContent)
.then(function () {
alert("Logs copied to clipboard!");
})
.catch(function (error) {
console.error("Error copying text: ", error);
});
};
$scope.copyToClipboard = function (configId) {
var dynamicPathElement = angular.element(
document.getElementById("dynamic-path_" + configId)
);
var textArea = document.createElement("textarea");
textArea.value = dynamicPathElement.text();
document.body.appendChild(textArea);
textArea.select();
document.execCommand("Copy");
textArea.remove();
alert("Path copied to clipboard!");
};
$scope.fetchDynamicPath = function (configId) {
var username =
window.process && window.process.env && window.process.env.USERPROFILE
? window.process.env.USERPROFILE.split("\\")[2]
: "";
var path = `C:\\Users\\${username}\\AppData\\Roaming\\node-opcua-default-nodejs\\Config\\PKI\\trusted\\certs`;
var dynamicPathElement = document.getElementById(
"dynamic-path_" + configId
);
dynamicPathElement.textContent = path;
dynamicPathElement.style.display = "block";
};
$scope.togglePasswordVisibility = function (configId) {
var passwordInput = document.getElementById("password_" + configId);
var eyeIcon = document.getElementById("eyeIcon_" + configId);
$scope.passwordVisible = !$scope.passwordVisible;
if ($scope.passwordVisible) {
passwordInput.type = "text";
} else {
passwordInput.type = "password";
}
};
$scope.toggleCertificatePathVisibility = function (configId) {
var checkbox = document.getElementById(
"certificates-checkbox_" + configId
);
var getCertificateButton = document.getElementById(
"get-certificate-button_" + configId
);
var copyPathButton = document.getElementById(
"copy-path-button_" + configId
);
var dynamicPathElement = document.getElementById(
"dynamic-path_" + configId
);
if (checkbox.checked) {
getCertificateButton.style.display = "block";
copyPathButton.style.display = "block";
dynamicPathElement.style.display = "block";
} else {
getCertificateButton.style.display = "none";
copyPathButton.style.display = "none";
dynamicPathElement.style.display = "none";
}
};
$scope.submitClientRegistration = function () {
if ($scope.clientId) {
localStorage.setItem("clientId", $scope.clientId);
$http
.get(
properties.get("CONFIG_URL_PREFIX") +
"/clients/" +
$scope.clientId +
"/ingestconfigs"
)
.then(function (response) {
if (response.status == 200) {
$scope.registration_color = "green";
$scope.registration_msg = "Valid Client";
$scope.registration_class = "success";
localStorage.setItem("clientId", $scope.clientId);
localStorage.setItem("registration_color", "green");
$scope.displayForm();
} else {
$scope.registration_color = "red";
localStorage.setItem("registration_color", "red");
$scope.registration_msg = "Cloud error";
$scope.registration_class = "error";
}
})
.catch(function (response) {
$scope.registration_color = "red";
localStorage.setItem("registration_color", "red");
$scope.registration_msg =
"ClientID doesn't exist on remote machine";
$scope.registration_class = "error";
});
}
};
var formObj = {
name: "name",
OPC_SERVER_USER: "",
OPC_SERVER_PASS: "",
OPC_SERVER_HOST: "localhost",
OPC_SERVER_PROGID: "",
OPC_SERVER_NAMESPACE: "",
TAG_PREFIX: "",
SUBSCRIBE_INTERVAL: 60000,
};
$scope.addForm = function () {
$scope.forms.push(formObj);
};
$scope.addTag = function (form) {
if (form.unitsId) {
var unitId = form.unitsId;
console.info("unitId form:", form.name, unitId);
if (form.newTag) {
var capturedNewTag = form.newTag;
var equipment_url =
properties.get("CONFIG_URL_PREFIX") +
"/units/" +
unitId +
"/equipment?filter={%22where%22:{%22name%22:%22Unassigned%22}}";
$http
.get(equipment_url)
.then(function (response) {
var equipmentId;
console.log("unitId:", form.unitsId);
if (response.status == 200 && response.data.length == 0) {
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
"/units/" +
form.unitsId +
"/equipment",
(json = { name: "Unassigned", equipment: "Unassigned" })
)
.then(function (response) {
equipmentId = response.data.id;
console.info("EquipmentId created: ", equipmentId);
});
} else if (
response.status == 200 &&
response.data.length >= 1
) {
equipmentId = response.data[0].id;
console.info("EquipmentId Found: ", equipmentId);
}
if (equipmentId) {
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
"/units/" +
unitId +
"/tagmeta",
(json = {
dataTagId: form.TAG_PREFIX + capturedNewTag,
description: "Unassigned",
equipmentName: "Unassigned",
equipmentType: "Unassigned",
equipmentId: equipmentId,
})
)
.then(function (response) {
console.info("meta response: ", response.data);
console.info("tag saved! to the meta");
});
}
})
.catch(function (response) {
console.error(response);
form.newTag = "";
});
}
} else {
$window.alert(
"Please assign unitId from pulse and reload OPCConnect by CTRL+R"
);
}
if (form.newTag) {
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
"/ingestconfigs/" +
form.id +
"/tags",
(json = {
dataTagId: form.newTag.trim(),
ingestconfigId: form.id,
address: "",
scalefactor: "",
})
)
.then(function (response) {
if (response.status == 200) {
console.info("tag saved!");
console.info(form.newTag);
if (form.newTag) {
console.info("in inf");
if (form.hasOwnProperty("tags")) {
form.tags.push({
dataTagId: form.newTag,
ingestconfigId: form.id,
});
} else {
form.tags = [
{ dataTagId: form.newTag, ingestconfigId: form.id },
];
}
form.newTag = "";
}
} else {
console.error(response.status);
form.newTag = "";
}
})
.catch(function (response) {
console.error(response);
form.newTag = "";
});
}
};
$scope.saveTag = function (form, index) {
if (form.tags[index].id && form.tags[index].dataTagId) {
var updateUrl =
properties.get("CONFIG_URL_PREFIX") +
"/tags/update?where=%7B%22id%22:%22" +
form.tags[index].id +
"%22%7D";
var dataToUpdate = {
dataTagId: form.tags[index].dataTagId.trim(),
address: form.tags[index].address.trim(),
};
console.info("dataToUpdate:", dataToUpdate);
$http
.post(updateUrl, dataToUpdate)
.then(function (response) {
if (response.status === 200) {
console.info("Updated data:", response.data);
} else {
console.warn("Update failed with status:", response.status);
}
})
.catch(function (error) {
console.error("Update error:", error);
});
}
};
$scope.deleteTag = function (form, index) {
if (form.tags[index].dataTagId) {
var delete_url =
properties.get("CONFIG_URL_PREFIX") +
"/ingestconfigs/" +
form.tags[index].ingestconfigId +
"/tags/" +
form.tags[index].id;
$http
.delete(delete_url)
.then(function (response) {
if (response.status == 204) {
console.info("tag deleted: ", form.tags[index].dataTagId);
form.tags.splice(index, 1);
} else {
console.warn(response.status);
}
})
.catch(function (response) {
console.error(response);
});
}
};
$scope.editTag = function (tag) {
tag.editMode = true;
};
$scope.displayForm = function () {
var clientId = localStorage.getItem("clientId") || $scope.clientId;
$http
.get(
properties.get("CONFIG_URL_PREFIX") +
"/clients/" +
clientId +
"/ingestconfigs?ts=" +
(+new Date()).toString()
)
.then(function (response) {
if (response.status == 200) {
if (!angular.equals($scope.forms, response.data)) {
$scope.forms = response.data;
}
$scope.loadConfig($http);
} else {
console.warn(response.status);
}
})
.catch(function (e) {
console.error(e);
});
};
$scope.deleteForm = function (index) {
console.info(index);
console.info("Calling delete");
var configId = $scope.forms[index]["id"];
if (confirm("Do you really want to delete this OPC server?")) {
if (!configId) {
$scope.forms.splice(index, 1);
} else {
$http
.delete(
properties.get("CONFIG_URL_PREFIX") +
"/ingestconfigs/" +
configId
)
.then(function (response) {
console.info(response.status);
if (response.status == 200) {
$scope.forms.splice(index, 1);
console.info("deleted!");
} else {
console.warn(response.status);
}
})
.catch(function (response) {
console.error(response);
});
}
}
};
$scope.classConvert = function (x) {
if (x == "success") {
return "greencircle";
} else {
return "redcircle";
}
};
$scope.setForm = function (configId, formindex) {
var clientId = localStorage.getItem("clientId") || $scope.clientId;
var formcopy = angular.copy($scope.forms[formindex]);
delete formcopy["id"];
delete formcopy["$$hashKey"];
delete formcopy["status"];
delete formcopy["status_class"];
delete formcopy["config_msg"];
delete formcopy["taglist"];
formcopy["TAG_PREFIX"] = formcopy["TAG_PREFIX"];
var tags = formcopy["tags"];
delete formcopy["tags"];
if (configId) {
console.info("update form");
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
'/ingestconfigs/update?where={"id":"' +
configId +
'"}',
(json = formcopy)
)
.then(function (response) {
if (response.status == 200) {
console.info("saved!");
console.info("formcopy", formcopy);
fs.writeFileSync(
__dirname + "\\" + configId + ".json",
JSON.stringify(formcopy, null, 2)
);
} else {
console.warn(response.status);
}
})
.catch(function (response) {
console.error(response);
});
} else {
console.info("in new form");
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
"/clients/" +
clientId +
"/ingestconfigs",
(json = formcopy)
)
.then(function (response) {
if (response.status == 200) {
console.info("saved!");
console.info(response.data);
$scope.forms[formindex] = response.data;
} else {
console.warn(response.status);
}
})
.catch(function (response) {
console.error(response);
});
}
};
$scope.unsubscribeTags = function (form) {
if (form.hasOwnProperty("tags")) {
console.info("unsubs");
var tags = form["tags"];
var prefix = form["TAG_PREFIX"];
var configId = form.id;
var clientId = localStorage.getItem("clientId") || $scope.clientId;
if (prefix && configId && clientId) {
tags.forEach((tag_el) => {
mq.unsubscribe(
clientId + "/" + configId + "/" + prefix + tag_el["dataTagId"]
);
});
} else {
console.warn("Empty form or tags");
}
}
};
$scope.registration_color = "red";
$scope.config_color = "red";
$scope.network_color = "red";
$scope.network_class = "error";
$scope.network_msg = "Connecting";
$scope.loadNetwork = function () {
var clientId = localStorage.getItem("clientId") || "empty";
const start = performance.now();
$http
.get(
properties.get("CONFIG_URL_PREFIX").replace("/exactapi", "") +
"/opc-network")
.then(function (response) {
const end = performance.now();
responseTime = (end - start) / 10;
console.info(
"status:" +
response.status +
", response time: " +
responseTime.toFixed(1)
);
$scope.network_color = "green";
$scope.network_class = "success";
$scope.network_msg = "Connected";
})
.catch(function (e) {
const end = performance.now();
responseTime = end - start;
if (e.status === 401) {
$scope.network_msg = "Authenticating...";
updateAuthToken().then(() => {
$http.defaults.headers.common.Authorization = global_header;
$scope.network_color = "green";
$scope.network_class = "success";
$scope.network_msg = "Connected";
})
.catch((err) => {
if (e.status !== 404) {
$scope.network_msg = "Wrong credentials";
} else if (e.status !== 500) {
$scope.network_msg = "Internal Server Error";
} else if (e.status !== 502) {
$scope.network_msg = "Bad Gateway";
}
});
} else {
$scope.network_color = "red";
$scope.network_class = "error";
$scope.network_msg = "Unable to contact cloud";
}
console.info(
"status:" +
e.status +
", response time: " +
responseTime.toFixed(1)
);
});
};
setTimeout(function () {
$scope.displayForm();
$scope.loadNetwork();
}, 6000);
$scope.loadClientRegistration = function () {
var clientId = localStorage.getItem("clientId") || $scope.clientId;
$scope.clientId = clientId;
$scope.registration_color =
localStorage.getItem("registration_color") || "red";
};
$scope.loadClientRegistration();
$scope.displayForm();
$scope.loadNetwork();
$interval(function () {
$scope.loadConfig($http);
var clientId = localStorage.getItem("clientId") || $scope.clientId;
var d1 = new Date();
var epochTime1 = d1.getTime();
$scope.loadNetwork();
console.info(clientId + "/response_time");
// mq.publish(
// clientId + "/response_time",
// JSON.stringify({ t: epochTime1, v: responseTime.toFixed(1) })
// );
safePublish(
`${clientId}/response_time`,
JSON.stringify({ t: epochTime1, v: responseTime.toFixed(1) })
);
}, 60000);
// --- Place this function outside your controller for reusability ---
function checkNetworkSpeed($http, properties) {
var epochTime1 = Date.now();
var CONFIG_URL_PREFIX = properties.get("CONFIG_URL_PREFIX");
var userApiUrl = `${CONFIG_URL_PREFIX}/ingestconfigs?q=${epochTime1}`;
var startTime = Date.now();
$http
.get(userApiUrl, { responseType: "arraybuffer" })
.then(function (response) {
var endTime = Date.now();
var requestTime = endTime - startTime;
var downloadSpeedMbps =
(response.data.byteLength * 8) /
(requestTime / 1000) /
1024 /
1024;
var networkSpeedElement = document.getElementById("network-speed-mbps");
if (networkSpeedElement) {
networkSpeedElement.style.color = downloadSpeedMbps < 2 ? "red" : "green";
networkSpeedElement.textContent = downloadSpeedMbps.toFixed(2) + " Mbps";
}
})
.catch(function (error) {
console.error("Error making request:", error);
var networkSpeedElement = document.getElementById("network-speed-mbps");
if (networkSpeedElement) {
networkSpeedElement.textContent = "calculating ...";
networkSpeedElement.style.color = "red";
}
});
}
// Run speed test immediately on page load for quick UI update
checkNetworkSpeed($http, properties);
// Then run it every 60 seconds
$interval(function () {
checkNetworkSpeed($http, properties);
}, 60000);
$interval(function () {
$scope.forms.forEach(function (form) {
if (form.clientId === "660cdb4511499e000783f5ff") {
return;
}
var command_to_run = form.OPC_SERVER_DRIVER;
if (!command_to_run) {
command_to_run = "java -jar OPCClient_NEW.jar";
}
var firstword = command_to_run.split(" ")[0];
// console.log(command_to_run);
const findPidCommand = `tasklist | find "${firstword}"`;
console.log("findPidCommand: ", findPidCommand);
exec(findPidCommand, (error, stdout, stderr) => {
if (error && error.code !== 1) {
console.error(`Error executing command: ${error}`);
return;
}
if (stderr) {
console.error(`Error: ${stderr}`);
return;
}
const processes = stdout
.split("\n")
.filter((line) => line.trim() !== "");
if (processes.length === 0) {
console.log(`No processes found with name "${firstword}".`);
return;
}
console.log(`Processes with name containing "${firstword}":`);
processes.forEach((process) => console.log(process));
// Create a list of PIDs
const pids = processes
.map((process) => process.split(/\s+/)[1])
.filter((pid) => pid !== "");
console.log("List of PIDs:", pids);
console.log("no of pid in form:", $scope.forms.length);
if (pids.length <= 2 * $scope.forms.length) {
console.log(`Only ${pids.length} process found. Not killing.`);
} else {
const pidsToKill = pids.slice($scope.forms.length);
const killCommands = pidsToKill.map(
(pid) => `taskkill /f /pid ${pid}`
);
// Execute each taskkill command separately
killCommands.forEach((killCommand, index) => {
exec(killCommand, (killError, killStdout, killStderr) => {
if (killError) {
console.error(
`Error killing process with PID ${pidsToKill[index]}: ${killError}`
);
return;
}
if (killStderr) {
console.error(`Error: ${killStderr}`);
return;
}
console.log(
`Process with PID ${pidsToKill[index]} killed successfully.`
);
});
});
}
});
});
}, 60000);
$interval(function () {
const jsonString = fs.readFileSync(
"C:\\OPCConnect\\resources\\app\\version.json",
"utf8"
);
const jsonData = JSON.parse(jsonString);
const version = jsonData.version;
$http
.post(
properties.get("CONFIG_URL_PREFIX") +
'/clients/update?where={"id":"' +
$scope.clientId +
'"}',
(json = { "opcconnectversion": version })
)
.then(function (response) {
if (response.status == 200) {
console.info("version update success:", version);
}
else {
console.warn("version status update issue", response.status, version);
}
});
try {
var divElement = document.getElementById("version-value");
divElement.textContent = version;
} catch (error) {
var divElement = document.getElementById("version-value");
divElement.textContent = "ERROR";
}
}, 60000);
}
);