fix(code-review): resolve Batch 1 open findings (AdminUI auth, AlarmHistorian dispose guards, docs)
- AdminUI-001: gate Script editor pages at Administrator,Designer + loosen ScriptAnalysis backend to match - AdminUI-004: explicit [Authorize] on FleetStatus/Alert/ScriptLog hubs - Core.AlarmHistorian-014: ObjectDisposedException guards on GetStatus/RetryDeadLettered (+ regression test) - Core.Scripting.Abstractions-004/-007: Deadband tolerance doc + stale ScriptedAlarms.md path - Host-003: correct config-overlay precedence in ServiceHosting.md - Configuration-014: LdapGroupRoleMapping collation-dependency doc - Driver.TwinCAT.Contracts-002: Structure enum doc (discovery-only sentinel)
This commit is contained in:
@@ -20,7 +20,11 @@ public interface ILdapGroupRoleMappingService
|
||||
/// <summary>List every mapping whose LDAP group matches one of <paramref name="ldapGroups"/>.</summary>
|
||||
/// <remarks>
|
||||
/// Hot path — fires on every sign-in. The default EF implementation relies on the
|
||||
/// <c>IX_LdapGroupRoleMapping_Group</c> index. Case-insensitive per LDAP conventions.
|
||||
/// <c>IX_LdapGroupRoleMapping_Group</c> index. The match is a SQL <c>IN (…)</c> whose
|
||||
/// case-sensitivity is determined by the <c>LdapGroup</c> column collation. Case-insensitive
|
||||
/// behaviour requires a case-insensitive (CI) server or column collation — this is a
|
||||
/// deployment requirement. On a case-sensitive-collation server the lookup will silently
|
||||
/// miss rows that differ only in case.
|
||||
/// </remarks>
|
||||
/// <param name="ldapGroups">The LDAP groups to search for.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
|
||||
@@ -520,6 +520,7 @@ public sealed class SqliteStoreAndForwardSink : IAlarmHistorianSink, IDisposable
|
||||
/// <summary>Gets the current status of the historian sink including queue depth and drain state.</summary>
|
||||
public HistorianSinkStatus GetStatus()
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(SqliteStoreAndForwardSink));
|
||||
// Core.AlarmHistorian-008: read the non-dead-lettered count from the in-memory
|
||||
// counter so a busy Admin UI / health probe does not hammer the DB. Dead-letter
|
||||
// depth is rare-path only (it lives in the queue until retention) so a real
|
||||
@@ -563,6 +564,7 @@ public sealed class SqliteStoreAndForwardSink : IAlarmHistorianSink, IDisposable
|
||||
/// <summary>Operator action from Admin UI — retry every dead-lettered row. Non-cascading: they rejoin the regular queue + get a fresh backoff.</summary>
|
||||
public int RetryDeadLettered()
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(SqliteStoreAndForwardSink));
|
||||
using var conn = OpenConnection();
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = "UPDATE Queue SET DeadLettered = 0, AttemptCount = 0, LastError = NULL WHERE DeadLettered = 1";
|
||||
|
||||
@@ -87,7 +87,8 @@ public abstract class ScriptContext
|
||||
/// </summary>
|
||||
/// <param name="current">The current value to check.</param>
|
||||
/// <param name="previous">The previous value to compare against.</param>
|
||||
/// <param name="tolerance">The minimum difference threshold for a change to be detected.</param>
|
||||
/// <param name="tolerance">The minimum change magnitude to detect (must be >= 0).
|
||||
/// Negative values cause the function to always return true; NaN always returns false.</param>
|
||||
public static bool Deadband(double current, double previous, double tolerance)
|
||||
=> Math.Abs(current - previous) > tolerance;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,12 @@ public enum TwinCATDataType
|
||||
Date, // DATE — seconds since 1970-01-01 truncated to a day boundary, stored as UDINT
|
||||
DateTime, // DT (DATE_AND_TIME) — seconds since 1970-01-01, stored as UDINT
|
||||
TimeOfDay,// TOD — milliseconds since midnight, stored as UDINT
|
||||
/// <summary>UDT / FB instance. Resolved per member at discovery time.</summary>
|
||||
/// <summary>
|
||||
/// UDT / FB instance. Used internally by <c>DiscoverAsync</c> when browsing controller
|
||||
/// symbols — members are resolved to atomic types and emitted individually. Must not be
|
||||
/// used in pre-declared tags (rejected at initialisation by the factory with
|
||||
/// <see cref="InvalidOperationException"/>) or in AdminUI equipment-tag configs (the typed
|
||||
/// editor does not offer it; the parser falls back to <c>DInt</c>).
|
||||
/// </summary>
|
||||
Structure,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@page "/scripts/{ScriptId}"
|
||||
@* Script CRUD. SourceHash is computed automatically from SourceCode on save so the
|
||||
integrity check in v2's deployment pipeline doesn't require operator action. *@
|
||||
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
|
||||
@attribute [Microsoft.AspNetCore.Authorization.Authorize(Roles = "Administrator,Designer")]
|
||||
@rendermode RenderMode.InteractiveServer
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@page "/scripts"
|
||||
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
|
||||
@attribute [Microsoft.AspNetCore.Authorization.Authorize(Roles = "Administrator,Designer")]
|
||||
@rendermode RenderMode.InteractiveServer
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using ZB.MOM.WW.OtOpcUa.Configuration
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
|
||||
@@ -8,6 +9,7 @@ namespace ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
|
||||
/// clears, or is acknowledged on any cluster node. Bridge: <c>AlertSignalRBridge</c> subscribes
|
||||
/// to the <c>alerts</c> DPS topic and forwards to every connected SignalR client.
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public sealed class AlertHub : Hub
|
||||
{
|
||||
public const string Endpoint = "/hubs/alerts";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Fleet;
|
||||
|
||||
@@ -10,6 +11,7 @@ namespace ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
|
||||
/// Server pushes fleet-status updates to connected clients via <c>FleetStatusSignalRBridge</c>
|
||||
/// (DistributedPubSub 'fleet-status' → <c>IHubContext<FleetStatusHub></c>).
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public sealed class FleetStatusHub : Hub
|
||||
{
|
||||
public const string Endpoint = "/hubs/fleet-status";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
|
||||
@@ -9,6 +10,7 @@ namespace ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
|
||||
/// <c>ScriptLogSignalRBridge</c> subscribes to the <c>script-logs</c> DPS topic and forwards
|
||||
/// to every connected SignalR client.
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
public sealed class ScriptLogHub : Hub
|
||||
{
|
||||
public const string Endpoint = "/hubs/script-log";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
@@ -9,7 +10,9 @@ public static class ScriptAnalysisEndpoints
|
||||
{
|
||||
public static IEndpointRouteBuilder MapScriptAnalysisEndpoints(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
var group = endpoints.MapGroup("/api/script-analysis").RequireAuthorization("FleetAdmin");
|
||||
// Require Administrator or Designer — matches the Script page gate and the /deployments gate.
|
||||
var group = endpoints.MapGroup("/api/script-analysis")
|
||||
.RequireAuthorization(new AuthorizeAttribute { Roles = "Administrator,Designer" });
|
||||
group.MapPost("/diagnostics", (DiagnoseRequest r, ScriptAnalysisService s) => Results.Ok(s.Diagnose(r)));
|
||||
group.MapPost("/completions", async (CompletionsRequest r, ScriptAnalysisService s) => Results.Ok(await s.CompleteAsync(r)));
|
||||
group.MapPost("/hover", async (HoverRequest r, ScriptAnalysisService s) => Results.Ok(await s.Hover(r)));
|
||||
|
||||
Reference in New Issue
Block a user