diff --git a/MTConnect.NET.sln b/MTConnect.NET.sln
index 9bf4d7374..a1ef3b9fe 100644
--- a/MTConnect.NET.sln
+++ b/MTConnect.NET.sln
@@ -83,6 +83,8 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MTConnect.NET-AgentModule-HttpServer", "agent\Modules\MTConnect.NET-AgentModule-HttpServer\MTConnect.NET-AgentModule-HttpServer.csproj", "{7025F1F6-F914-4830-8171-30872F184918}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MTConnect.NET-AgentModule-ShdrAdapter", "agent\Modules\MTConnect.NET-AgentModule-ShdrAdapter\MTConnect.NET-AgentModule-ShdrAdapter.csproj", "{B6AAE7CE-AEBF-4F1A-929F-01665E85109E}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MTConnect.NET-AgentModule-WebUI", "agent\Modules\MTConnect.NET-AgentModule-WebUI\MTConnect.NET-AgentModule-WebUI.csproj", "{0443E446-52DA-4812-B8BD-F2555B5AD7C8}"
+EndProject
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MTConnect.NET-AdapterModule-MQTT", "adapter\Modules\MTConnect.NET-AdapterModule-MQTT\MTConnect.NET-AdapterModule-MQTT.csproj", "{BEBFD8B9-5448-49F9-BC92-C98564AA7C72}"
EndProject
@@ -336,13 +338,21 @@ Global
{7025F1F6-F914-4830-8171-30872F184918}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7025F1F6-F914-4830-8171-30872F184918}.Release|Any CPU.Build.0 = Release|Any CPU
{B6AAE7CE-AEBF-4F1A-929F-01665E85109E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0443E446-52DA-4812-B8BD-F2555B5AD7C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6AAE7CE-AEBF-4F1A-929F-01665E85109E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0443E446-52DA-4812-B8BD-F2555B5AD7C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6AAE7CE-AEBF-4F1A-929F-01665E85109E}.Docker|Any CPU.ActiveCfg = Debug|Any CPU
+ {0443E446-52DA-4812-B8BD-F2555B5AD7C8}.Docker|Any CPU.ActiveCfg = Debug|Any CPU
{B6AAE7CE-AEBF-4F1A-929F-01665E85109E}.Docker|Any CPU.Build.0 = Debug|Any CPU
+ {0443E446-52DA-4812-B8BD-F2555B5AD7C8}.Docker|Any CPU.Build.0 = Debug|Any CPU
{B6AAE7CE-AEBF-4F1A-929F-01665E85109E}.Package|Any CPU.ActiveCfg = Package|Any CPU
+ {0443E446-52DA-4812-B8BD-F2555B5AD7C8}.Package|Any CPU.ActiveCfg = Package|Any CPU
{B6AAE7CE-AEBF-4F1A-929F-01665E85109E}.Package|Any CPU.Build.0 = Package|Any CPU
+ {0443E446-52DA-4812-B8BD-F2555B5AD7C8}.Package|Any CPU.Build.0 = Package|Any CPU
{B6AAE7CE-AEBF-4F1A-929F-01665E85109E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0443E446-52DA-4812-B8BD-F2555B5AD7C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6AAE7CE-AEBF-4F1A-929F-01665E85109E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0443E446-52DA-4812-B8BD-F2555B5AD7C8}.Release|Any CPU.Build.0 = Release|Any CPU
{BEBFD8B9-5448-49F9-BC92-C98564AA7C72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BEBFD8B9-5448-49F9-BC92-C98564AA7C72}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BEBFD8B9-5448-49F9-BC92-C98564AA7C72}.Docker|Any CPU.ActiveCfg = Debug|Any CPU
@@ -552,6 +562,7 @@ Global
{2104A3F5-A157-4763-8B90-38CD103D93D8} = {0F01D2F5-0CFA-4DAE-A0FD-5733D291F6FB}
{7025F1F6-F914-4830-8171-30872F184918} = {0F01D2F5-0CFA-4DAE-A0FD-5733D291F6FB}
{B6AAE7CE-AEBF-4F1A-929F-01665E85109E} = {0F01D2F5-0CFA-4DAE-A0FD-5733D291F6FB}
+ {0443E446-52DA-4812-B8BD-F2555B5AD7C8} = {0F01D2F5-0CFA-4DAE-A0FD-5733D291F6FB}
{BEBFD8B9-5448-49F9-BC92-C98564AA7C72} = {494ACCF3-8FB7-429D-9119-92F7DD646B5F}
{D2125645-0149-4D2D-BBF6-5E36E6EBB6C1} = {494ACCF3-8FB7-429D-9119-92F7DD646B5F}
{B4A1AFBE-5D57-45BA-B735-A2C2C20A971C} = {B7CCD58C-A3F3-43BE-BBF8-2B20D118C39D}
diff --git a/agent/MTConnect.NET-Agent/agent.config.yaml b/agent/MTConnect.NET-Agent/agent.config.yaml
index 7f525d95f..fe56b8207 100644
--- a/agent/MTConnect.NET-Agent/agent.config.yaml
+++ b/agent/MTConnect.NET-Agent/agent.config.yaml
@@ -9,6 +9,9 @@ processors:
# - Modules -
modules:
+- web-ui: # - Add Web UI module (browse data and edit this configuration at http://localhost:5080)
+ port: 5080
+
- http-server: # - Add HTTP Server module
port: 5000
indentOutput: true
diff --git a/agent/MTConnect.NET-Applications-Agents/MTConnect.NET-Applications-Agents.csproj b/agent/MTConnect.NET-Applications-Agents/MTConnect.NET-Applications-Agents.csproj
index 2b8fd4748..401036c9a 100644
--- a/agent/MTConnect.NET-Applications-Agents/MTConnect.NET-Applications-Agents.csproj
+++ b/agent/MTConnect.NET-Applications-Agents/MTConnect.NET-Applications-Agents.csproj
@@ -58,6 +58,7 @@
+
diff --git a/agent/MTConnect.NET-Applications-Agents/agent.config.default.yaml b/agent/MTConnect.NET-Applications-Agents/agent.config.default.yaml
index 478ec0fb9..95ba7d572 100644
--- a/agent/MTConnect.NET-Applications-Agents/agent.config.default.yaml
+++ b/agent/MTConnect.NET-Applications-Agents/agent.config.default.yaml
@@ -1,6 +1,11 @@
# - Modules -
modules:
+# - web-ui: # - Add Web UI module (browse data and edit this configuration at http://localhost:5080)
+# server: localhost
+# port: 5080
+# allowConfigurationEdit: true
+
- http-server: # - Add HTTP Server module
# hostname: localhost
port: 5000
diff --git a/agent/Modules/MTConnect.NET-AgentModule-WebUI/MTConnect.NET-AgentModule-WebUI.csproj b/agent/Modules/MTConnect.NET-AgentModule-WebUI/MTConnect.NET-AgentModule-WebUI.csproj
new file mode 100644
index 000000000..e4fe2410c
--- /dev/null
+++ b/agent/Modules/MTConnect.NET-AgentModule-WebUI/MTConnect.NET-AgentModule-WebUI.csproj
@@ -0,0 +1,65 @@
+
+
+
+ net8.0
+
+
+ net461;net462;net47;net471;net472;net48;netstandard2.0;net6.0;net7.0;net8.0;net9.0;net10.0
+ false
+ None
+ true
+
+
+ net461;net462;net47;net471;net472;net48;netstandard2.0;net6.0;net7.0;net8.0;net9.0;net10.0
+ true
+
+
+
+ MTConnect
+ Debug;Release;Package
+
+ MTConnect.NET-AgentModule-WebUI hosts a lightweight browser user interface for the MTConnectAgentApplication class in the MTConnect.NET-Applications-Agents library. It lets a user browse Devices, current and sampled Observations, and Assets, and edit the Agent configuration file. Supports .NET Framework 4.6.1 up to .NET 10
+ README-Nuget.md
+
+
+ true
+
+
+ true
+
+
+ true
+ snupkg
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ \licenses\
+
+
+
+
+
+ True
+ \
+
+
+ True
+ \
+
+
+
+
diff --git a/agent/Modules/MTConnect.NET-AgentModule-WebUI/Module.cs b/agent/Modules/MTConnect.NET-AgentModule-WebUI/Module.cs
new file mode 100644
index 000000000..0e17fce39
--- /dev/null
+++ b/agent/Modules/MTConnect.NET-AgentModule-WebUI/Module.cs
@@ -0,0 +1,78 @@
+// Copyright (c) 2026 TrakHound Inc., All Rights Reserved.
+// TrakHound Inc. licenses this file to you under the MIT license.
+
+using MTConnect.Agents;
+using MTConnect.Configurations;
+using MTConnect.Logging;
+
+namespace MTConnect.Modules.WebUI
+{
+ ///
+ /// Agent module that hosts a browser user interface for the Agent.
+ /// The UI lets a user browse Devices, current / sampled Observations
+ /// and Assets, and view or edit the Agent configuration file.
+ ///
+ public class Module : MTConnectAgentModule
+ {
+ ///
+ /// Token used in agent.config.yaml to bind this module
+ /// (- web-ui:).
+ ///
+ public const string ConfigurationTypeId = "web-ui";
+ private const string ModuleId = "Web UI";
+
+ private readonly WebUiModuleConfiguration _configuration;
+ private WebUiServer _server;
+
+
+ ///
+ /// Initialises the module and binds the supplied controller-
+ /// configuration payload to .
+ ///
+ /// Agent broker the UI reads from.
+ /// Raw configuration payload
+ /// bound to .
+ public Module(IMTConnectAgentBroker mtconnectAgent, object controllerConfiguration) : base(mtconnectAgent)
+ {
+ Id = ModuleId;
+
+ _configuration = AgentApplicationConfiguration.GetConfiguration(controllerConfiguration);
+ if (_configuration == null) _configuration = new WebUiModuleConfiguration();
+ }
+
+
+ ///
+ /// Module lifecycle hook: creates the UI web server before the Agent
+ /// loads its devices so the per-Device Observation counts include
+ /// the Observations added while the devices initialize.
+ ///
+ /// Inherited flag; unused by
+ /// this module.
+ protected override void OnStartBeforeLoad(bool initializeDataItems)
+ {
+ _server = new WebUiServer(_configuration, Agent);
+ }
+
+ ///
+ /// Module lifecycle hook: starts the UI web server once the Agent
+ /// has loaded its devices.
+ ///
+ /// Inherited flag; unused by
+ /// this module.
+ protected override void OnStartAfterLoad(bool initializeDataItems)
+ {
+ if (_server == null) _server = new WebUiServer(_configuration, Agent);
+ _server.LogReceived += (s, message) => Log(MTConnectLogLevel.Information, message);
+ _server.ErrorReceived += (s, message) => Log(MTConnectLogLevel.Warning, message);
+ _server.Start();
+ }
+
+ ///
+ /// Module lifecycle hook: stops the UI web server.
+ ///
+ protected override void OnStop()
+ {
+ if (_server != null) _server.Stop();
+ }
+ }
+}
diff --git a/agent/Modules/MTConnect.NET-AgentModule-WebUI/README-Nuget.md b/agent/Modules/MTConnect.NET-AgentModule-WebUI/README-Nuget.md
new file mode 100644
index 000000000..e8e874fb4
--- /dev/null
+++ b/agent/Modules/MTConnect.NET-AgentModule-WebUI/README-Nuget.md
@@ -0,0 +1,34 @@
+
+
+# MTConnect Web UI Agent Module
+This Agent Module hosts a browser user interface for the MTConnect.NET Agent. It can be used to:
+
+* View Agent information and buffer state
+* Browse Devices / Components and their current Observations (with auto refresh)
+* View the recent history of any Data Item
+* Browse Assets and view them as MTConnectAssets XML
+* View and edit the Agent configuration file (`agent.config.yaml`)
+
+## Configuration
+```yaml
+modules:
+ - web-ui:
+ server: localhost
+ port: 5080
+ allowConfigurationEdit: true
+```
+
+* `server` - The hostname to bind to. Defaults to `localhost` so the UI is only reachable from the Agent machine. Set to `*` to listen on all interfaces (on Windows this requires administrator rights or a `netsh http add urlacl` reservation).
+
+* `port` - The port the UI listens on. Default is `5080`.
+
+* `allowConfigurationEdit` - Sets whether the Agent configuration file can be saved from the UI. Default is `true`. When `false` the configuration is shown read-only.
+
+Open `http://localhost:5080/` in a browser once the Agent is running.
+
+The page and its fonts (Roboto and Roboto Mono, licensed under the SIL Open Font License 1.1) are embedded in the module assembly, so the UI makes no requests outside the Agent and works on networks without internet access.
+
+## Applying configuration changes
+Saving validates the YAML, keeps a copy of the previous file as `agent.config.yaml.bak`, and writes the new file. When `monitorConfigurationFiles` is enabled (the default) the Agent detects the change and restarts itself to apply it; otherwise restart the Agent manually.
+
+> **Security** - The UI has no authentication. Anyone who can reach it can change the Agent configuration. Keep `server: localhost` or set `allowConfigurationEdit: false` when exposing it on a network.
diff --git a/agent/Modules/MTConnect.NET-AgentModule-WebUI/README.md b/agent/Modules/MTConnect.NET-AgentModule-WebUI/README.md
new file mode 100644
index 000000000..e8e874fb4
--- /dev/null
+++ b/agent/Modules/MTConnect.NET-AgentModule-WebUI/README.md
@@ -0,0 +1,34 @@
+
+
+# MTConnect Web UI Agent Module
+This Agent Module hosts a browser user interface for the MTConnect.NET Agent. It can be used to:
+
+* View Agent information and buffer state
+* Browse Devices / Components and their current Observations (with auto refresh)
+* View the recent history of any Data Item
+* Browse Assets and view them as MTConnectAssets XML
+* View and edit the Agent configuration file (`agent.config.yaml`)
+
+## Configuration
+```yaml
+modules:
+ - web-ui:
+ server: localhost
+ port: 5080
+ allowConfigurationEdit: true
+```
+
+* `server` - The hostname to bind to. Defaults to `localhost` so the UI is only reachable from the Agent machine. Set to `*` to listen on all interfaces (on Windows this requires administrator rights or a `netsh http add urlacl` reservation).
+
+* `port` - The port the UI listens on. Default is `5080`.
+
+* `allowConfigurationEdit` - Sets whether the Agent configuration file can be saved from the UI. Default is `true`. When `false` the configuration is shown read-only.
+
+Open `http://localhost:5080/` in a browser once the Agent is running.
+
+The page and its fonts (Roboto and Roboto Mono, licensed under the SIL Open Font License 1.1) are embedded in the module assembly, so the UI makes no requests outside the Agent and works on networks without internet access.
+
+## Applying configuration changes
+Saving validates the YAML, keeps a copy of the previous file as `agent.config.yaml.bak`, and writes the new file. When `monitorConfigurationFiles` is enabled (the default) the Agent detects the change and restarts itself to apply it; otherwise restart the Agent manually.
+
+> **Security** - The UI has no authentication. Anyone who can reach it can change the Agent configuration. Keep `server: localhost` or set `allowConfigurationEdit: false` when exposing it on a network.
diff --git a/agent/Modules/MTConnect.NET-AgentModule-WebUI/WebUiModuleConfiguration.cs b/agent/Modules/MTConnect.NET-AgentModule-WebUI/WebUiModuleConfiguration.cs
new file mode 100644
index 000000000..487ce1dba
--- /dev/null
+++ b/agent/Modules/MTConnect.NET-AgentModule-WebUI/WebUiModuleConfiguration.cs
@@ -0,0 +1,45 @@
+// Copyright (c) 2026 TrakHound Inc., All Rights Reserved.
+// TrakHound Inc. licenses this file to you under the MIT license.
+
+namespace MTConnect.Configurations
+{
+ ///
+ /// Configuration shape for the Web UI agent module. Controls the
+ /// address the UI listens on and whether the Agent configuration
+ /// file may be edited from the browser.
+ ///
+ public class WebUiModuleConfiguration
+ {
+ ///
+ /// The hostname the UI binds to. Defaults to localhost so the
+ /// UI (and its configuration editor) is only reachable from the
+ /// machine the Agent runs on. Use * or + to listen on
+ /// every interface (may require elevated permissions on Windows).
+ ///
+ public string Server { get; set; }
+
+ ///
+ /// The port the UI listens on.
+ ///
+ public int Port { get; set; }
+
+ ///
+ /// Gets or Sets whether the Agent configuration file can be edited
+ /// and saved from the UI. When false the configuration is
+ /// shown read-only.
+ ///
+ public bool AllowConfigurationEdit { get; set; }
+
+
+ ///
+ /// Initialises a new instance bound to localhost:5080 with
+ /// configuration editing enabled.
+ ///
+ public WebUiModuleConfiguration()
+ {
+ Server = "localhost";
+ Port = 5080;
+ AllowConfigurationEdit = true;
+ }
+ }
+}
diff --git a/agent/Modules/MTConnect.NET-AgentModule-WebUI/WebUiServer.cs b/agent/Modules/MTConnect.NET-AgentModule-WebUI/WebUiServer.cs
new file mode 100644
index 000000000..08ee3d137
--- /dev/null
+++ b/agent/Modules/MTConnect.NET-AgentModule-WebUI/WebUiServer.cs
@@ -0,0 +1,546 @@
+// Copyright (c) 2026 TrakHound Inc., All Rights Reserved.
+// TrakHound Inc. licenses this file to you under the MIT license.
+
+using MTConnect.Agents;
+using MTConnect.Configurations;
+using MTConnect.Devices;
+using MTConnect.Formatters;
+using MTConnect.Observations;
+using MTConnect.Observations.Output;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using YamlDotNet.Core;
+using YamlDotNet.Serialization;
+using YamlDotNet.Serialization.NamingConventions;
+
+namespace MTConnect.Modules.WebUI
+{
+ ///
+ /// Minimal -based web server that serves the
+ /// embedded UI page and the JSON API it calls.
+ ///
+ ///
+ /// Routes:
+ ///
+ /// GET / – the UI page
+ /// GET /fonts/{name}.woff2 – the embedded Roboto / Roboto Mono fonts
+ /// GET /api/agent – Agent information and buffer state
+ /// GET /api/devices – Device / Component / DataItem tree
+ /// GET /api/current?device= – current Observations for a Device
+ /// GET /api/sample?device=&dataItemId=&count= – most recent Observations
+ /// GET /api/assets?device= – Asset list
+ /// GET /api/asset?id= – a single Asset as an MTConnectAssets XML document
+ /// GET /api/config / PUT /api/config – read / save the Agent configuration file
+ ///
+ ///
+ public class WebUiServer
+ {
+ private const string IndexResourceName = "MTConnect.WebUI.index.html";
+ private const string FontResourcePrefix = "MTConnect.WebUI.fonts.";
+ private const int MaxSampleCount = 1000;
+
+ private static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+ };
+
+ private readonly WebUiModuleConfiguration _configuration;
+ private readonly IMTConnectAgentBroker _agent;
+ private readonly ConcurrentDictionary _observationCounts = new ConcurrentDictionary();
+ private HttpListener _listener;
+ private CancellationTokenSource _stop;
+
+
+ ///
+ /// Raised with an informational message (for example the listening address).
+ ///
+ public event EventHandler LogReceived;
+
+ ///
+ /// Raised with a message when the listener or a request fails.
+ ///
+ public event EventHandler ErrorReceived;
+
+
+ ///
+ /// Initialises a new server for the given configuration and Agent.
+ ///
+ /// Listen address and edit policy.
+ /// Agent broker the API reads from.
+ public WebUiServer(WebUiModuleConfiguration configuration, IMTConnectAgentBroker agent)
+ {
+ _configuration = configuration ?? new WebUiModuleConfiguration();
+ _agent = agent;
+
+ // Count Observations per Device from the moment the server is created
+ if (_agent != null) _agent.ObservationAdded += ObservationAdded;
+ }
+
+
+ ///
+ /// Starts listening on a background task. Returns immediately.
+ ///
+ public void Start()
+ {
+ var host = _configuration.Server;
+ if (string.IsNullOrEmpty(host) || host == "*" || host == "0.0.0.0") host = "+";
+ var prefix = $"http://{host}:{_configuration.Port}/";
+
+ try
+ {
+ _stop = new CancellationTokenSource();
+ _listener = new HttpListener();
+ _listener.Prefixes.Add(prefix);
+ _listener.Start();
+
+ LogReceived?.Invoke(this, $"Web UI listening at {prefix.Replace("+", "localhost")}");
+
+ _ = Task.Run(() => Listen(_stop.Token));
+ }
+ catch (Exception ex)
+ {
+ ErrorReceived?.Invoke(this, $"Web UI failed to start at {prefix} : {ex.Message}");
+ }
+ }
+
+ ///
+ /// Stops the listener and releases the port.
+ ///
+ public void Stop()
+ {
+ if (_agent != null) _agent.ObservationAdded -= ObservationAdded;
+
+ try
+ {
+ if (_stop != null) _stop.Cancel();
+ if (_listener != null) _listener.Close();
+ }
+ catch { }
+ }
+
+
+ private void ObservationAdded(object sender, IObservation observation)
+ {
+ if (observation != null && observation.DeviceUuid != null)
+ {
+ _observationCounts.AddOrUpdate(observation.DeviceUuid, 1, (key, count) => count + 1);
+ }
+ }
+
+ private async Task Listen(CancellationToken cancellationToken)
+ {
+ while (!cancellationToken.IsCancellationRequested && _listener.IsListening)
+ {
+ HttpListenerContext context;
+ try
+ {
+ context = await _listener.GetContextAsync();
+ }
+ catch
+ {
+ // Listener was closed
+ break;
+ }
+
+ _ = Task.Run(() => HandleRequest(context));
+ }
+ }
+
+ private void HandleRequest(HttpListenerContext context)
+ {
+ var request = context.Request;
+ var response = context.Response;
+
+ try
+ {
+ var path = request.Url.AbsolutePath.TrimEnd('/').ToLowerInvariant();
+ var method = request.HttpMethod;
+
+ if (method == "GET" && (path == "" || path == "/index.html"))
+ {
+ WriteIndex(response);
+ }
+ else if (method == "GET" && path.StartsWith("/fonts/")) WriteFont(response, path.Substring("/fonts/".Length));
+ else if (method == "GET" && path == "/api/agent") WriteJson(response, GetAgentInfo());
+ else if (method == "GET" && path == "/api/devices") WriteJson(response, GetDevices());
+ else if (method == "GET" && path == "/api/current") WriteJson(response, GetCurrent(request.QueryString["device"]));
+ else if (method == "GET" && path == "/api/sample") WriteJson(response, GetSample(request.QueryString["device"], request.QueryString["dataItemId"], request.QueryString["count"]));
+ else if (method == "GET" && path == "/api/assets") WriteJson(response, GetAssets(request.QueryString["device"]));
+ else if (method == "GET" && path == "/api/asset") WriteAsset(response, request.QueryString["id"]);
+ else if (method == "GET" && path == "/api/config") WriteJson(response, GetConfiguration());
+ else if (method == "PUT" && path == "/api/config") SaveConfiguration(request, response);
+ else WriteJson(response, new { error = "Not Found" }, 404);
+ }
+ catch (Exception ex)
+ {
+ ErrorReceived?.Invoke(this, $"Web UI request error ({request.HttpMethod} {request.Url.AbsolutePath}) : {ex.Message}");
+ try { WriteJson(response, new { error = ex.Message }, 500); } catch { }
+ }
+ finally
+ {
+ try { response.Close(); } catch { }
+ }
+ }
+
+
+ #region "API"
+
+ private object GetAgentInfo()
+ {
+ var configuration = _agent.Configuration;
+ var applicationConfiguration = configuration as IAgentApplicationConfiguration;
+
+ return new
+ {
+ uuid = _agent.Uuid,
+ instanceId = _agent.InstanceId,
+ sender = _agent.Sender,
+ version = _agent.Version?.ToString(),
+ mtconnectVersion = _agent.MTConnectVersion?.ToString(),
+ deviceModelChangeTime = _agent.DeviceModelChangeTime,
+ bufferSize = _agent.BufferSize,
+ firstSequence = _agent.FirstSequence,
+ lastSequence = _agent.LastSequence,
+ nextSequence = _agent.NextSequence,
+ assetBufferSize = _agent.AssetBufferSize,
+ assetCount = _agent.AssetCount,
+ configurationPath = configuration?.Path,
+ monitorConfigurationFiles = applicationConfiguration?.MonitorConfigurationFiles ?? false,
+ allowConfigurationEdit = _configuration.AllowConfigurationEdit,
+ observationCounts = _observationCounts.ToDictionary(o => o.Key, o => o.Value),
+ mtconnectServer = GetMTConnectServer(applicationConfiguration)
+ };
+ }
+
+ ///
+ /// Minimal view of an http-server module entry, used to link to the
+ /// Agent's MTConnect REST API without referencing the HTTP server assembly.
+ ///
+ private class HttpServerModuleEntry
+ {
+ public int Port { get; set; } = 5000;
+ public string Server { get; set; }
+ public object Tls { get; set; }
+ }
+
+ private static object GetMTConnectServer(IAgentApplicationConfiguration configuration)
+ {
+ // Link to the first configured HTTP server module (the Agent's MTConnect REST API)
+ var server = configuration?.GetModules("http-server")?.FirstOrDefault();
+ if (server == null) return null;
+
+ return new
+ {
+ port = server.Port,
+ server = server.Server,
+ tls = server.Tls != null
+ };
+ }
+
+ private object GetDevices()
+ {
+ var devices = _agent.GetDevices();
+ if (devices == null) return new object[0];
+
+ return devices.Select(device => new
+ {
+ id = device.Id,
+ uuid = device.Uuid,
+ name = device.Name,
+ type = device.Type,
+ dataItems = ToDataItems(device.DataItems),
+ components = ToComponents(device.Components)
+ }).ToList();
+ }
+
+ private static IEnumerable