diff --git a/src/ScadaLink.CentralUI/Components/Shared/TreeView.razor b/src/ScadaLink.CentralUI/Components/Shared/TreeView.razor
index 897fc84..cddb90f 100644
--- a/src/ScadaLink.CentralUI/Components/Shared/TreeView.razor
+++ b/src/ScadaLink.CentralUI/Components/Shared/TreeView.razor
@@ -163,4 +163,95 @@ else
await SelectedKeyChanged.InvokeAsync(key);
}
}
+
+ /// Expand every branch node in the tree.
+ public void ExpandAll()
+ {
+ if (_items is { Count: > 0 })
+ {
+ ExpandAllRecursive(_items);
+ }
+
+ PersistExpandedState();
+ StateHasChanged();
+ }
+
+ private void ExpandAllRecursive(IReadOnlyList items)
+ {
+ foreach (var item in items)
+ {
+ if (HasChildrenSelector(item))
+ {
+ _expandedKeys.Add(KeySelector(item));
+ }
+
+ var children = ChildrenSelector(item);
+ if (children is { Count: > 0 })
+ {
+ ExpandAllRecursive(children);
+ }
+ }
+ }
+
+ /// Collapse every node in the tree.
+ public void CollapseAll()
+ {
+ _expandedKeys.Clear();
+ PersistExpandedState();
+ StateHasChanged();
+ }
+
+ ///
+ /// Expand all ancestors of the given key so it becomes visible.
+ /// Optionally select the node.
+ ///
+ public async Task RevealNode(object key, bool select = false)
+ {
+ var parentLookup = BuildParentLookup();
+
+ // If key is not in the tree at all, no-op
+ if (!parentLookup.ContainsKey(key))
+ return;
+
+ // Walk up through ancestors
+ var current = key;
+ while (parentLookup.TryGetValue(current, out var parentKey) && parentKey != null)
+ {
+ _expandedKeys.Add(parentKey);
+ current = parentKey;
+ }
+
+ if (select && Selectable)
+ {
+ await SelectedKeyChanged.InvokeAsync(key);
+ }
+
+ PersistExpandedState();
+ StateHasChanged();
+ }
+
+ private Dictionary