opcua-server 0.9.1

OPC UA server API
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Server Diagnostics</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <style>
        table {
            border-collapse: collapse;
        }

        td, th {
            border: 1px solid black;
            padding: 0.5rem;
        }

        table.side_headings td, table.side_headings th {
            text-align: left;
        }

        th {
            background: lightblue;
            font-weight: bold;
        }
    </style>
</head>
<body onload="requestMetrics()">

<script>
    function requestMetrics() {
        // JQuery request to "/server/metrics"
        $.getJSON("/server/metrics").done(json => {
            fillMetrics(json);
        });
    }

    function abortServer() {
        $.get("/server/abort").done(json => {
            alert("Server will be aborted (debug build only)")
        });
    }

    function attribute_id_string(attribute_id) {
        switch (attribute_id) {
            case 1:
                return "NodeId";
            case 2:
                return "NodeClass";
            case 3:
                return "BrowseName";
            case 4:
                return "DisplayName";
            case 5:
                return "Description";
            case 6:
                return "WriteMask";
            case 7:
                return "UserWriteMask";
            case 8:
                return "IsAbstract";
            case 9:
                return "Symmetric";
            case 10:
                return "InverseName";
            case 11:
                return "ContainsNoLoops";
            case 12:
                return "EventNotifier";
            case 13:
                return "Value";
            case 14:
                return "DataType";
            case 15:
                return "ValueRank";
            case 16:
                return "ArrayDimensions";
            case 17:
                return "AccessLevel";
            case 18:
                return "UserAccessLevel";
            case 19:
                return "MinimumSamplingInterval";
            case 20:
                return "Historizing";
            case 21:
                return "Executable";
            case 22:
                return "UserExecutable";
            default:
                return "Unknown";
        }
    }

    function node_id_str(node_id) {
        const identifier = node_id.identifier;
        if (identifier["String"]) {
            return `ns=${node_id.namespace};s=${identifier["String"].value}`;
        } else if (identifier["Numeric"]) {
            return `ns=${node_id.namespace};i=${identifier["Numeric"]}`;
        } else if (identifier["Guid"]) {
            return `ns=${node_id.namespace};g=${identifier["Guid"].value}`;
        } else if (identifier["ByteString"]) {
            return `ns=${node_id.namespace};b=......`;
        } else {
            return `Invalid node id`
        }
    }

    function fillMetrics(json) {
        let html = `<h1 class='server'>Server</h1>`;

        html += dump_server(json["config"], json["server"]);

        // diagnostic summary
        html += dump_diagnostics(json["diagnostics"]);

        // runtime components
        html += dump_runtime_components(json["runtime_components"]);

        // Sessions
        const connections = json["connections"];
        html += "<h1 class='connections'>Client connections</h1>";
        if (connections.length === 0) {
            html += "<p>No connections</p>";
        } else {
            connections.forEach(connection => {
                html += dump_connection(connection);
            });
        }
        $("#metrics").html(html);
    }

    function msToTime(duration) {
        let milliseconds = parseInt("" + (duration % 1000) / 100);
        let seconds = Math.floor((duration / 1000) % 60);
        let minutes = Math.floor((duration / (1000 * 60)) % 60);
        let hours = Math.floor((duration / (1000 * 60 * 60)) % 24);

        hours = (hours < 10) ? "0" + hours : hours;
        minutes = (minutes < 10) ? "0" + minutes : minutes;
        seconds = (seconds < 10) ? "0" + seconds : seconds;

        return hours + ":" + minutes + ":" + seconds + "." + milliseconds;
    }

    function dump_server(config, server) {
        return `<table class="side_headings">

<tr><th>Server name</th><td>${config["application_name"]}</td></tr>

<tr><th>Server URI</th><td>${config["application_uri"]}</td></tr>

<tr><th>Start Time</th><td>${new Date(server["start_time"]).toString()}</td></tr>

<tr><th>Uptime</th><td>${msToTime(server["uptime_ms"])}</td></tr>

<tr><th>Actions</th><td><button onclick="abortServer()">Terminate Server</button>&nbsp;Debug Builds Only</td></tr>

</table>



`;
    }

    function dump_connection(connection) {
        let html = `<h2>Client "${connection["id"]}"</h2>

<table class="side_headings">

<tr><th>Client address</th><td>${connection["client_address"]}</td></tr>

<tr><th>Transport state</th><td>${connection["transport_state"]}</td></tr>

<tr><th>Session activated</th><td>${connection["session_activated"]}</td></tr>

<tr><th>Session terminated</th><td>${connection["session_terminated"]}</td></tr>function

<tr><th>Session terminated at</th><td>${connection["session_terminated_at"]}</td></tr>

</table>`;

        // Subscriptions
        html += `<h2 class='subscriptions'>Subscriptions</h2>`
        if (connection.hasOwnProperty("subscriptions")) {
            const subscription_metrics = connection["subscriptions"];
    html += `<table>

    <tr><th>Publish Requests</th><td>${subscription_metrics.publish_request_queue_len}</td></tr>

    <tr><th>Publish Responses</th><td>${subscription_metrics.publish_response_queue_len}</td></tr>

    <tr><th>Transmission Queue</th><td>${subscription_metrics.transmission_queue_len}</td></tr>

    <tr><th>Retransmission Queue</th><td>${subscription_metrics.retransmission_queue_len}</td></tr>

</table>`;

            const subscriptions = subscription_metrics["subscriptions"];
            if (subscriptions.length === 0) {
                html += `<p>No subscriptions</p>`;
            } else {
                subscriptions.forEach(subscription => {
                    html += dump_subscription(subscription);
                });
            }
        }
        return html;
    }

    function dump_subscription(subscription) {
        let html = `<h3>Subscription ${subscription.subscription_id}</h3>

<table class="side_headings">

<tr><th>Priority</th><td>${subscription.priority}</td></tr>

<tr><th>Publishing enabled</th><td>${subscription.publishing_enabled}</td></tr>

<tr><th>Publishing interval</th><td>${subscription.publishing_interval}ms</td></tr>

<tr><th>Last Timer Expiration</th><td>${subscription.last_timer_expired_time}</td></tr>

<tr><th>Lifetime Counter</th><td>${subscription.lifetime_counter} / ${subscription.max_lifetime_counter}</td></tr>

<tr><th>Keep-Alive Counter</th><td>${subscription.keep_alive_counter} / ${subscription.max_keep_alive_counter}</td></tr>

</table>`;
        var monitored_items = subscription.monitored_items;
        if (monitored_items.length === 0) {
            html += "<p>No monitored items</p>";
        } else {
            html += dump_monitored_items(monitored_items);
        }
        return html;
    }

    function dump_monitored_items(monitored_items) {
        let html = `

<h3>Monitored Items</h3>

<table>

<thead>

<tr>

    <th>Node Id</th>

    <th>Last Value</th>

    <th>Mode</th>

    <th>Client #</th>

    <th>Filter</th>

    <th>Sampling interval</th>

    <th>Discard Oldest</th>

    <th>Queue Size</th>

    <th>Timestamps to return</th>

    <th>Last Sample Time</th>

</tr>

</thead>

<tbody>`;
        Object.keys(monitored_items).forEach(key => {
            const item = monitored_items[key];
            html += dump_monitored_item(item);
        });
        html += `

</tbody>

</table>`;
        return html;
    }

    function dump_monitored_item(item) {
        let node_id = node_id_str(item.item_to_monitor.node_id);
        // Value (13) is the common thing to monitor
        if (item.item_to_monitor.attribute_id != 13) {
            node_id += ` / ${attribute_id_string(item.item_to_monitor.attribute_id)}`;
        }

        let last_data_value = item.last_data_value;
        if (!last_data_value) {
            last_data_value = "-";
        } else if (typeof last_data_value === "object") {
            last_data_value = last_data_value.value;
            if ("Boolean" in last_data_value) {
                last_data_value = last_data_value.Boolean + " / Boolean";
            } else if ("String" in last_data_value) {
                last_data_value = (last_data_value.String.value) ? last_data_value.String.value : "NULL";
                last_data_value += " / String"
            } else if ("Byte" in last_data_value) {
                last_data_value = last_data_value.Byte + " / Byte";
            } else if ("SByte" in last_data_value) {
                last_data_value = last_data_value.SByte + " / SByte";
            } else if ("UInt16" in last_data_value) {
                last_data_value = last_data_value.UInt16 + " / UInt16";
            } else if ("Int16" in last_data_value) {
                last_data_value = last_data_value.Int16 + " / Int16";
            } else if ("UInt32" in last_data_value) {
                last_data_value = last_data_value.UInt32 + " / UInt32";
            } else if ("Int32" in last_data_value) {
                last_data_value = last_data_value.Int32 + " / Int32";
            } else if ("UInt64" in last_data_value) {
                last_data_value = last_data_value.UInt64 + " / UInt64";
            } else if ("Int64" in last_data_value) {
                last_data_value = last_data_value.Int64 + " / Int64";
            } else if ("Float" in last_data_value) {
                last_data_value = last_data_value.Float + " / Float";
            } else if ("Double" in last_data_value) {
                last_data_value = last_data_value.Double + " / Double";
            } else if ("Guid" in last_data_value) {
                last_data_value = last_data_value.Guid + " / Guid";
            } else {
                last_data_value = JSON.stringify(last_data_value);
            }
            // Date, ByteString
        }

        let filter = "";
        if (typeof item.filter === "string") {
            filter = item.filter;
        } else if ("EventFilter" in item.filter) {
            filter = "EventFilter";
        } else if ("DataChangeFilter" in item.filter) {
            filter = "DataChangeFilter";
        }
        return `

<tr>

    <td>${node_id}</td>

    <td>${last_data_value}</td>

    <td>${item.monitoring_mode}</td>

    <td>${item.client_handle}</td>

    <td>${filter}</td>

    <td>${item.sampling_interval}ms</td>

    <td>${item.discard_oldest}</td>

    <td>${item.queue_size}</td>

    <td>${item.timestamps_to_return}</td>

    <td>${item.last_sample_time}</td>

</tr>

`;
    }

    function dump_runtime_components(runtime_components) {
        let html = `<h2 class='runtime_components_summary'>Runtime Summary</h2>

<p>Represents tasks that are running.</p>

`;
        html += `<table>`;
        runtime_components.forEach(component => {
            html += `<tr><td>${component}</td></tr>`;
        });
        html += `</table>`;
        return html;
    }

    function dump_diagnostics(diagnostics) {
        const diagnostics_summary = diagnostics["server_diagnostics_summary"];
        return `<h2 class='diagnostic_summary'>Diagnostic Summary</h2>

<table>

    <thead>

        <tr>

            <th colspan="6">Sessions</th>

            <th colspan="3">Subscriptions</th>

            <th colspan="2">Requests</th>

        </tr>

        <tr>

            <!-- Sesssions -->

        <th>Current</th>

        <th>Cumulative</th>

        <th>Security Rejected</th>

        <th>Rejected</th>

        <th>Timeouts</th>

        <th>Aborts</th>

            <!-- Subscriptions -->

        <th>Current</th>

        <th>Cumulative</th>

        <th>Publishing Intervals</th>

            <!-- Requests -->

        <th>Security Rejected</th>

        <th>Rejected</th>

        </tr>

        </thead>

        <tbody>

        <tr>

        <td>${diagnostics_summary["current_session_count"]}</td>

        <td>${diagnostics_summary["cumulated_session_count"]}</td>

        <td>${diagnostics_summary["security_rejected_session_count"]}</td>

        <td>${diagnostics_summary["rejected_session_count"]}</td>

        <td>${diagnostics_summary["session_timeout_count"]}</td>

        <td>${diagnostics_summary["session_abort_count"]}</td>

        <td>${diagnostics_summary["current_subscription_count"]}</td>

        <td>${diagnostics_summary["cumulated_subscription_count"]}</td>

        <td>${diagnostics_summary["publishing_interval_count"]}</td>

        <td>${diagnostics_summary["security_rejected_requests_count"]}</td>

        <td>${diagnostics_summary["rejected_requests_count"]}</td>

        </tr>

        </tbody>

        </table>

        `

            ;
    }

</script>

<div><span><a href="#" onclick="requestMetrics()">Reload</a></span></div>

<div id="metrics"><span>Please wait...</span></div>

<div><span><a href="#" onclick="requestMetrics()">Reload</a></span></div>

</body>
</html>