feat(adminui): inline script-source editor in the virtual-tag modal

This commit is contained in:
Joseph Doherty
2026-06-09 15:17:25 -04:00
parent 088fc50ef2
commit fc7dc3b57d
4 changed files with 478 additions and 4 deletions
@@ -1,3 +1,5 @@
using System.Security.Cryptography;
using System.Text;
using Microsoft.EntityFrameworkCore;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
@@ -1014,6 +1016,67 @@ public sealed class UnsTreeService(IDbContextFactory<OtOpcUaConfigDbContext> dbF
}
}
/// <inheritdoc />
public async Task<(string SourceCode, byte[] RowVersion, string Name)?> GetScriptSourceAsync(
string scriptId,
CancellationToken ct = default)
{
await using var db = await dbFactory.CreateDbContextAsync(ct);
var row = await db.Scripts
.AsNoTracking()
.Where(s => s.ScriptId == scriptId)
.Select(s => new { s.SourceCode, s.RowVersion, s.Name })
.FirstOrDefaultAsync(ct);
return row is null ? null : (row.SourceCode, row.RowVersion, row.Name);
}
/// <inheritdoc />
public async Task<int> CountVirtualTagsUsingScriptAsync(string scriptId, CancellationToken ct = default)
{
await using var db = await dbFactory.CreateDbContextAsync(ct);
return await db.VirtualTags.CountAsync(v => v.ScriptId == scriptId, ct);
}
/// <inheritdoc />
public async Task<(bool Ok, string? Error)> UpdateScriptSourceAsync(
string scriptId,
string sourceCode,
byte[] rowVersion,
CancellationToken ct = default)
{
await using var db = await dbFactory.CreateDbContextAsync(ct);
var entity = await db.Scripts.FirstOrDefaultAsync(s => s.ScriptId == scriptId, ct);
if (entity is null)
{
return (false, "Script not found.");
}
db.Entry(entity).Property(e => e.RowVersion).OriginalValue = rowVersion;
entity.SourceCode = sourceCode;
entity.SourceHash = HashSource(sourceCode);
try
{
await db.SaveChangesAsync(ct);
return (true, null);
}
catch (DbUpdateConcurrencyException)
{
return (false, "This script was changed by someone else. Reload and try again.");
}
}
/// <summary>
/// Computes the SHA-256 of a script body as lower-case hex — the same algorithm the Script-edit
/// page uses, so a body saved from either surface yields an identical compile-cache key.
/// </summary>
private static string HashSource(string source) =>
Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(source)));
/// <summary>
/// Validates a virtual tag's script binding and trigger configuration (mirrors the DbContext CHECK
/// constraints): a script must be chosen, at least one of change-trigger / timer must be set, and a