99 lines
3.6 KiB
TypeScript
99 lines
3.6 KiB
TypeScript
import type { GroupConfig, GroupNode, Host } from '../types';
|
|
import { sortByVaultOrder, sortVaultStringsByOrder } from './vaultOrder';
|
|
|
|
function countAllHostsInNode(node: GroupNode): number {
|
|
let count = node.hosts.length;
|
|
for (const child of Object.values(node.children)) {
|
|
count += countAllHostsInNode(child);
|
|
}
|
|
node.totalHostCount = count;
|
|
return count;
|
|
}
|
|
|
|
export function buildHostGroupTree(
|
|
hosts: Host[],
|
|
customGroups: string[],
|
|
groupConfigs: GroupConfig[] = [],
|
|
): { groupTree: GroupNode[]; ungroupedHosts: Host[] } {
|
|
const groupOrderByPath = new Map(
|
|
groupConfigs
|
|
.filter((config) => typeof config.order === 'number' && Number.isFinite(config.order))
|
|
.map((config) => [config.path, config.order as number]),
|
|
);
|
|
const orderedCustomGroups = sortVaultStringsByOrder(customGroups, groupOrderByPath);
|
|
const sortGroupNodesBySavedOrder = (nodes: GroupNode[]) => {
|
|
const originalIndex = new Map(nodes.map((node, index) => [node.path, index]));
|
|
return [...nodes].sort((a, b) => {
|
|
const orderA = groupOrderByPath.get(a.path);
|
|
const orderB = groupOrderByPath.get(b.path);
|
|
const hasOrderA = typeof orderA === 'number' && Number.isFinite(orderA);
|
|
const hasOrderB = typeof orderB === 'number' && Number.isFinite(orderB);
|
|
if (hasOrderA && hasOrderB && orderA !== orderB) return orderA - orderB;
|
|
if (hasOrderA) return -1;
|
|
if (hasOrderB) return 1;
|
|
return (originalIndex.get(a.path) ?? 0) - (originalIndex.get(b.path) ?? 0);
|
|
});
|
|
};
|
|
const sortChildrenBySavedOrder = (node: GroupNode) => {
|
|
const sortedChildren = sortGroupNodesBySavedOrder(Object.values(node.children));
|
|
node.children = Object.fromEntries(sortedChildren.map((child) => [child.name, child]));
|
|
sortedChildren.forEach(sortChildrenBySavedOrder);
|
|
};
|
|
const root: Record<string, GroupNode> = {};
|
|
const insertPath = (path: string, host?: Host) => {
|
|
const parts = path.split('/').filter(Boolean);
|
|
let currentLevel = root;
|
|
let currentPath = '';
|
|
parts.forEach((part, index) => {
|
|
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
|
if (!currentLevel[part]) {
|
|
currentLevel[part] = {
|
|
name: part,
|
|
path: currentPath,
|
|
children: {},
|
|
hosts: [],
|
|
};
|
|
}
|
|
if (host && index === parts.length - 1) {
|
|
currentLevel[part].hosts.push(host);
|
|
}
|
|
currentLevel = currentLevel[part].children;
|
|
});
|
|
};
|
|
|
|
orderedCustomGroups.forEach((path) => insertPath(path));
|
|
const ungroupedHosts: Host[] = [];
|
|
for (const host of hosts) {
|
|
const group = host.group?.trim();
|
|
if (group) {
|
|
insertPath(group, host);
|
|
} else {
|
|
ungroupedHosts.push(host);
|
|
}
|
|
}
|
|
|
|
Object.values(root).forEach(countAllHostsInNode);
|
|
const groupTree = sortGroupNodesBySavedOrder(Object.values(root));
|
|
groupTree.forEach(sortChildrenBySavedOrder);
|
|
const orderedUngroupedHosts = sortByVaultOrder(ungroupedHosts);
|
|
return { groupTree, ungroupedHosts: orderedUngroupedHosts };
|
|
}
|
|
|
|
export function groupNodeContainsHost(node: GroupNode, hostId: string | null | undefined): boolean {
|
|
if (!hostId) return false;
|
|
if (node.hosts.some((host) => host.id === hostId)) return true;
|
|
return Object.values(node.children).some((child) => groupNodeContainsHost(child, hostId));
|
|
}
|
|
|
|
export function collectGroupTreePaths(nodes: GroupNode[]): string[] {
|
|
const paths: string[] = [];
|
|
const walk = (node: GroupNode) => {
|
|
if (node.path) paths.push(node.path);
|
|
for (const child of Object.values(node.children)) {
|
|
walk(child);
|
|
}
|
|
};
|
|
for (const node of nodes) walk(node);
|
|
return paths;
|
|
}
|