refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)

Solution + 23 src projects + 26 test projects renamed; folders, csproj,
namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated.
ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated.
SQL roles/logins, LDAP domains, CLI command name, and CLI config dir
(~/.scadalink → ~/.scadabridge) also renamed.

Build green; 5 Host.Tests fail awaiting SQL login rename in next commit.
Pre-existing StaleTagMonitor timing flakes unchanged.

Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
Joseph Doherty
2026-05-28 09:37:45 -04:00
parent 6d87ee3c3b
commit 7b0b9c7365
1531 changed files with 11180 additions and 11054 deletions
@@ -0,0 +1,228 @@
@* Shared template folder/template tree.
Wraps TreeView<TemplateTreeNode> with template-folder-specific layout: folder
nodes carry their child folders + templates, leaves are templates. Used by:
- Templates page (Single mode, navigation on click)
- Transport Export wizard (Checkbox mode, bulk selection)
Optional ExtraTemplateChildren lets callers nest extra leaves (e.g. composition
slots) under a template without forking the component. *@
@using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates
@using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared
<TreeView @ref="_tree" TItem="TemplateTreeNode"
Items="_visibleRoots"
ChildrenSelector="n => n.Children"
HasChildrenSelector="n => n.Children.Count > 0"
KeySelector="n => (object)n.Key"
Selectable="@(SelectionMode == TreeViewSelectionMode.Single)"
SelectionMode="SelectionMode"
SelectedKeys="SelectedKeys"
SelectedKeysChanged="SelectedKeysChanged"
InitiallyExpanded="@(_initiallyExpanded)"
StorageKey="@StorageKey">
<NodeContent Context="node">
@if (NodeContent != null)
{
@NodeContent(node)
}
else
{
<span class="tv-glyph">
<i class="bi @(NodeGlyph(node))"></i>
</span>
<span class="tv-label @(node.Children.Count > 0 ? "fw-semibold" : "")"
title="@node.Name"
@onclick="() => InvokeNodeClick(node)">@node.Name</span>
@if (NodeExtras != null)
{
@NodeExtras(node)
}
}
</NodeContent>
<ContextMenu Context="node">
@if (ContextMenu != null)
{
@ContextMenu(node)
}
</ContextMenu>
<EmptyContent>
@if (EmptyContent != null)
{
@EmptyContent
}
else
{
<span class="text-muted fst-italic">No templates.</span>
}
</EmptyContent>
</TreeView>
@code {
[Parameter] public IReadOnlyList<TemplateFolder> Folders { get; set; } = Array.Empty<TemplateFolder>();
[Parameter] public IReadOnlyList<Template> Templates { get; set; } = Array.Empty<Template>();
[Parameter] public TreeViewSelectionMode SelectionMode { get; set; } = TreeViewSelectionMode.Single;
[Parameter] public HashSet<object>? SelectedKeys { get; set; }
[Parameter] public EventCallback<HashSet<object>> SelectedKeysChanged { get; set; }
[Parameter] public string Filter { get; set; } = "";
[Parameter] public RenderFragment<TemplateTreeNode>? NodeExtras { get; set; }
[Parameter] public RenderFragment<TemplateTreeNode>? NodeContent { get; set; }
[Parameter] public RenderFragment<TemplateTreeNode>? ContextMenu { get; set; }
[Parameter] public RenderFragment? EmptyContent { get; set; }
[Parameter] public EventCallback<TemplateTreeNode> OnNodeClick { get; set; }
[Parameter] public string? StorageKey { get; set; }
/// <summary>
/// Optional: caller-supplied extra leaves to nest under a template. Used by
/// the Templates page to surface composition slots; left null by the
/// Transport Export wizard (compositions aren't exportable as standalone
/// items, so the wizard's checkbox tree intentionally hides them).
/// </summary>
[Parameter] public Func<Template, IReadOnlyList<TemplateTreeNode>>? ExtraTemplateChildren { get; set; }
private TreeView<TemplateTreeNode>? _tree;
private List<TemplateTreeNode> _allRoots = new();
private List<TemplateTreeNode> _visibleRoots = new();
private HashSet<string>? _filterRevealed;
private Func<TemplateTreeNode, bool>? _initiallyExpanded;
protected override void OnParametersSet()
{
BuildTree();
ApplyFilter();
}
private void BuildTree()
{
var folderNodes = Folders.ToDictionary(
f => f.Id,
f => new TemplateTreeNode
{
Kind = TemplateTreeNodeKind.Folder,
Id = f.Id,
Name = f.Name,
});
var roots = new List<TemplateTreeNode>();
foreach (var f in Folders.OrderBy(f => f.Name, StringComparer.OrdinalIgnoreCase))
{
var node = folderNodes[f.Id];
if (f.ParentFolderId is int pid && folderNodes.TryGetValue(pid, out var parent))
parent.Children.Add(node);
else
roots.Add(node);
}
foreach (var t in Templates
.Where(t => !t.IsDerived)
.OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase))
{
var tNode = new TemplateTreeNode
{
Kind = TemplateTreeNodeKind.Template,
Id = t.Id,
Name = t.Name,
};
if (ExtraTemplateChildren != null)
{
foreach (var extra in ExtraTemplateChildren(t))
{
tNode.Children.Add(extra);
}
}
if (t.FolderId is int fid && folderNodes.TryGetValue(fid, out var parentFolder))
parentFolder.Children.Add(tNode);
else
roots.Add(tNode);
}
SortChildren(roots);
foreach (var node in folderNodes.Values)
SortChildren(node.Children);
_allRoots = roots;
}
private static void SortChildren(List<TemplateTreeNode> children)
{
children.Sort((a, b) =>
{
var kindOrder = (int)a.Kind - (int)b.Kind;
if (kindOrder != 0) return kindOrder;
return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase);
});
}
private void ApplyFilter()
{
if (string.IsNullOrWhiteSpace(Filter))
{
_visibleRoots = _allRoots;
_filterRevealed = null;
_initiallyExpanded = null;
return;
}
var needle = Filter.Trim();
var revealed = new HashSet<string>(StringComparer.Ordinal);
var filtered = new List<TemplateTreeNode>();
foreach (var root in _allRoots)
{
if (CopyMatching(root, needle, revealed) is { } copy)
filtered.Add(copy);
}
_visibleRoots = filtered;
_filterRevealed = revealed;
// Force every ancestor of a match to be expanded so the matched leaf is
// visible without the user clicking through.
_initiallyExpanded = n => revealed.Contains(n.Key);
}
private static TemplateTreeNode? CopyMatching(TemplateTreeNode node, string needle, HashSet<string> revealed)
{
var selfMatch = node.Name.Contains(needle, StringComparison.OrdinalIgnoreCase);
var keptChildren = new List<TemplateTreeNode>();
foreach (var child in node.Children)
{
var copy = CopyMatching(child, needle, revealed);
if (copy != null) keptChildren.Add(copy);
}
if (!selfMatch && keptChildren.Count == 0) return null;
var clone = new TemplateTreeNode
{
Kind = node.Kind,
Id = node.Id,
Name = node.Name,
};
foreach (var k in keptChildren) clone.Children.Add(k);
if (keptChildren.Count > 0)
{
// Mark this node as an ancestor on the match path so it auto-expands.
revealed.Add(clone.Key);
}
return clone;
}
private static string NodeGlyph(TemplateTreeNode node) =>
node.Kind == TemplateTreeNodeKind.Folder ? "bi-folder" : "bi-file-earmark-text";
private async Task InvokeNodeClick(TemplateTreeNode node)
{
if (OnNodeClick.HasDelegate)
{
await OnNodeClick.InvokeAsync(node);
}
}
/// <summary>Forwarded to the inner TreeView so callers can drive expand/collapse.</summary>
public void ExpandAll() => _tree?.ExpandAll();
/// <summary>Forwarded to the inner TreeView so callers can drive expand/collapse.</summary>
public void CollapseAll() => _tree?.CollapseAll();
}