mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-10 14:50:40 +02:00
feat(topology): add automatic lazy network propagation
This commit is contained in:
parent
65f1fc4bd7
commit
dc4d82c828
9 changed files with 412 additions and 137 deletions
11
topology/README.md
Normal file
11
topology/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
## Options
|
||||
|
||||
## Renderers
|
||||
|
||||
### svg
|
||||
|
||||
### elk
|
||||
|
||||
## NixOS Extractors
|
||||
|
||||
## Network propagation
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="800" height="800" viewBox="0 0 512 512"><path d="M168 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M256 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M212 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12"/><path d="M460 0H52C23.28 0 0 23.28 0 52v408c0 28.72 23.28 52 52 52h408c28.72 0 52-23.28 52-52V52c0-28.72-23.28-52-52-52m-16 284h-88.024c-2.212 0-3.976 1.64-3.976 3.848V348H160v-60.152c0-2.208-1.616-3.848-3.828-3.848H68V68h44v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h44z"/><path d="M124 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M388 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M344 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M300 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="800" height="800" viewBox="0 0 512 512"><path fill="#e3e6eb" d="M168 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M256 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M212 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12"/><path fill="#e3e6eb" d="M460 0H52C23.28 0 0 23.28 0 52v408c0 28.72 23.28 52 52 52h408c28.72 0 52-23.28 52-52V52c0-28.72-23.28-52-52-52m-16 284h-88.024c-2.212 0-3.976 1.64-3.976 3.848V348H160v-60.152c0-2.208-1.616-3.848-3.828-3.848H68V68h44v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h20v92.184c0 6.624 5.368 12 12 12 6.624 0 12-5.376 12-12V68h44z"/><path fill="#e3e6eb" d="M124 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M388 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M344 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12M300 184.036c-6.632 0-12 5.376-12 12v23.752c0 6.628 5.368 12 12 12 6.624 0 12-5.372 12-12v-23.752c0-6.624-5.376-12-12-12"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -13,7 +13,6 @@
|
|||
mkIf
|
||||
mkMerge
|
||||
filter
|
||||
optionals
|
||||
;
|
||||
|
||||
headOrNull = xs:
|
||||
|
@ -49,7 +48,6 @@ in {
|
|||
wgName: wgCfg: let
|
||||
inherit
|
||||
(lib.wireguard inputs wgName)
|
||||
participatingClientNodes
|
||||
participatingServerNodes
|
||||
wgCfgOf
|
||||
;
|
||||
|
@ -57,11 +55,6 @@ in {
|
|||
isServer = wgCfg.server.host != null;
|
||||
filterSelf = filter (x: x != config.node.name);
|
||||
|
||||
# All nodes that use our node as the via into the wireguard network
|
||||
ourClientNodes =
|
||||
optionals isServer
|
||||
(filter (n: (wgCfgOf n).client.via == config.node.name) participatingClientNodes);
|
||||
|
||||
# The list of peers that are "physically" connected in the wireguard network,
|
||||
# meaning they communicate directly with each other.
|
||||
connectedPeers =
|
||||
|
@ -69,13 +62,12 @@ in {
|
|||
then
|
||||
# Other servers in the same network
|
||||
filterSelf participatingServerNodes
|
||||
# Our clients
|
||||
++ ourClientNodes
|
||||
else [wgCfg.client.via];
|
||||
in {
|
||||
${wgCfg.linkName} = {
|
||||
network = networkId wgName;
|
||||
virtual = true;
|
||||
renderer.hidePhysicalConnections = true;
|
||||
physicalConnections = flip map connectedPeers (peer: {
|
||||
node = inputs.self.nodes.${peer}.config.topology.id;
|
||||
interface = (wgCfgOf peer).linkName;
|
||||
|
|
|
@ -1,23 +1,231 @@
|
|||
f: {
|
||||
lib,
|
||||
config,
|
||||
options,
|
||||
...
|
||||
}: let
|
||||
inherit
|
||||
(lib)
|
||||
all
|
||||
assertMsg
|
||||
attrNames
|
||||
attrValues
|
||||
concatLines
|
||||
concatMap
|
||||
concatStringsSep
|
||||
const
|
||||
elem
|
||||
flatten
|
||||
flip
|
||||
foldl'
|
||||
isAttrs
|
||||
length
|
||||
mapAttrsToList
|
||||
mkDefault
|
||||
mkIf
|
||||
mkOption
|
||||
mkOptionType
|
||||
optional
|
||||
optionalAttrs
|
||||
recursiveUpdate
|
||||
reverseList
|
||||
showOption
|
||||
types
|
||||
unique
|
||||
warnIf
|
||||
;
|
||||
|
||||
# Checks whether the value is a lazy value without causing
|
||||
# it's value to be evaluated
|
||||
isLazyValue = x: isAttrs x && x ? _lazyValue;
|
||||
# Constructs a lazy value holding the given value.
|
||||
lazyValue = value: {_lazyValue = value;};
|
||||
|
||||
# Represents a lazy value of the given type, which
|
||||
# holds the actual value as an attrset like { _lazyValue = <actual value>; }.
|
||||
# This allows the option to be defined and filtered from a defintion
|
||||
# list without evaluating the value.
|
||||
lazyValueOf = type:
|
||||
mkOptionType rec {
|
||||
name = "lazyValueOf ${type.name}";
|
||||
inherit (type) description descriptionClass emptyValue getSubOptions getSubModules;
|
||||
check = isLazyValue;
|
||||
merge = loc: defs:
|
||||
assert assertMsg
|
||||
(all (x: type.check x._lazyValue) defs)
|
||||
"The option `${showOption loc}` is defined with a lazy value holding an invalid type";
|
||||
types.mergeOneOption loc defs;
|
||||
substSubModules = m: types.uniq (type.substSubModules m);
|
||||
functor = (types.defaultFunctor name) // {wrapped = type;};
|
||||
nestedTypes.elemType = type;
|
||||
};
|
||||
|
||||
# Represents a value or lazy value of the given type that will
|
||||
# automatically be coerced to the given type when merged.
|
||||
lazyOf = type: types.coercedTo (lazyValueOf type) (x: x._lazyValue) type;
|
||||
|
||||
allNodes = attrNames config.nodes;
|
||||
allInterfacesOf = node: attrNames config.nodes.${node}.interfaces;
|
||||
allNodeInterfacePairs = concatMap (node: map (interface: {inherit node interface;}) (allInterfacesOf node)) allNodes;
|
||||
|
||||
# Helper to add a value to a list if it doesn't already hold it
|
||||
addIfNotExists = list: x:
|
||||
if elem x list
|
||||
then list
|
||||
else list ++ [x];
|
||||
|
||||
# The list of networks that were specifically assigned by the user
|
||||
# to other interfaces which are sharing their network with us.
|
||||
networkDefiningInterfaces = foldl' recursiveUpdate {} (flatten (
|
||||
flip map options.nodes.definitions (mapAttrsToList (
|
||||
nodeId: node:
|
||||
flip mapAttrsToList (node.interfaces or {}) (
|
||||
interfaceId: interface:
|
||||
optional (interface ? network && !isLazyValue interface.network) {
|
||||
${nodeId}.${interfaceId} = interface.network;
|
||||
}
|
||||
)
|
||||
))
|
||||
));
|
||||
|
||||
# A list of all connections between all interfaces. Bidirectional.
|
||||
connectionList = unique (flatten (flip mapAttrsToList config.nodes (
|
||||
nodeId: node:
|
||||
flip mapAttrsToList node.interfaces (
|
||||
interfaceId: interface:
|
||||
flip map interface.physicalConnections (conn: [
|
||||
{
|
||||
src = {
|
||||
node = nodeId;
|
||||
interface = interfaceId;
|
||||
};
|
||||
dst = conn;
|
||||
}
|
||||
{
|
||||
src = conn;
|
||||
dst = {
|
||||
node = nodeId;
|
||||
interface = interfaceId;
|
||||
};
|
||||
}
|
||||
])
|
||||
)
|
||||
)));
|
||||
|
||||
# A map of all connections constructed from the connection list
|
||||
connections = foldl' (acc: {
|
||||
src,
|
||||
dst,
|
||||
}:
|
||||
recursiveUpdate acc {
|
||||
${src.node}.${src.interface} = addIfNotExists (acc.${src.node}.${src.interface} or []) dst;
|
||||
}) {}
|
||||
connectionList;
|
||||
|
||||
# Propagates all networks from the source interface to the destination interface
|
||||
propagateNetworks = state: snode: sinterface: dnode: dinterface:
|
||||
#builtins.trace " propagate all nets (${toString (attrNames state.${snode}.${sinterface})}) of ${snode}.${sinterface} to ${dnode}.${dinterface}"
|
||||
(
|
||||
# Fold over each network that the source interface shares
|
||||
# and propagate it if the destination doesn't already have this network
|
||||
foldl' (
|
||||
acc: net:
|
||||
recursiveUpdate acc (
|
||||
optionalAttrs (!(acc ? ${dnode}.${dinterface}.${net}))
|
||||
#builtins.trace " adding ${net} to ${dnode}.${dinterface} via ${toString (acc.${snode}.${sinterface}.${net} ++ ["${snode}.${sinterface}"])}"
|
||||
{
|
||||
# Create the network on the destination interface and append our interface
|
||||
# to the list which indicates from where the network was received
|
||||
${dnode}.${dinterface}.${net} = acc.${snode}.${sinterface}.${net} ++ ["${snode}.${sinterface}"];
|
||||
}
|
||||
)
|
||||
)
|
||||
state (attrNames state.${snode}.${sinterface})
|
||||
);
|
||||
|
||||
# Propagates all shared networks for the given interface to other interfaces connected to it
|
||||
propagateToConnections = state: src:
|
||||
#builtins.trace "propagate via connections of ${src.node}.${src.interface}"
|
||||
(
|
||||
# Fold over each connection of the current interface
|
||||
foldl' (
|
||||
acc: dst:
|
||||
propagateNetworks acc src.node src.interface dst.node dst.interface
|
||||
)
|
||||
state (connections.${src.node}.${src.interface} or [])
|
||||
);
|
||||
|
||||
# Propagates all shared networks for the given interface to other interfaces on the same
|
||||
# node to which the network is shared
|
||||
propagateLocally = state: src:
|
||||
#builtins.trace "propagate locally on ${src.node}.${src.interface}"
|
||||
(
|
||||
# Fold over each local interface of the current node
|
||||
foldl' (
|
||||
acc: dstInterface:
|
||||
if src.interface != dstInterface && config.nodes.${src.node}.interfaces.${src.interface}.sharesNetworkWith dstInterface
|
||||
then propagateNetworks acc src.node src.interface src.node dstInterface
|
||||
else acc
|
||||
)
|
||||
state (allInterfacesOf src.node)
|
||||
);
|
||||
|
||||
# Assigns each interface a list of networks that were propagated to it
|
||||
# from another interface or connection.
|
||||
propagatedNetworks = let
|
||||
# Initially set sharedNetworks based on whether the interface has a network assigned to it.
|
||||
# This list will then be expaned iteratively.
|
||||
initial = foldl' recursiveUpdate {} (flatten (flip map allNodes (
|
||||
node:
|
||||
flip map (allInterfacesOf node) (interface: {
|
||||
${node}.${interface} = optionalAttrs (networkDefiningInterfaces ? ${node}.${interface}) {
|
||||
${networkDefiningInterfaces.${node}.${interface}} = [];
|
||||
};
|
||||
})
|
||||
)));
|
||||
|
||||
# Takes a state and propagates networks via local sharing on the same node
|
||||
propagateEachLocally = state: foldl' propagateLocally state allNodeInterfacePairs;
|
||||
# Takes a state and propagates networks via sharing over connections
|
||||
propagateEachToConnections = state: foldl' propagateToConnections state allNodeInterfacePairs;
|
||||
|
||||
# The update function that propagates state from all interfaces to all neighbors.
|
||||
# The fixpoint of this function is the solution.
|
||||
update = state: propagateEachToConnections (propagateEachLocally state);
|
||||
converged = lib.converge update initial;
|
||||
|
||||
# Extract all interfaces that were assigned multiple interfaces, to issue a warning
|
||||
interfacesWithMultipleNets = flatten (
|
||||
flip mapAttrsToList converged (
|
||||
node: ifs:
|
||||
flip mapAttrsToList ifs (
|
||||
interface: nets:
|
||||
optional (length (attrNames nets) > 1) {
|
||||
inherit node interface nets;
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
in
|
||||
warnIf (interfacesWithMultipleNets != []) ''
|
||||
topology: Some interfaces have received multiple networks via network propagation!
|
||||
This is an error in your network configuration that must be addressed.
|
||||
Evaluation can still continue by considering only one of them (effectively at random).
|
||||
The affected interfaces are:
|
||||
${concatLines (flip map interfacesWithMultipleNets (
|
||||
x:
|
||||
" - ${x.node}.${x.interface}:\n"
|
||||
+ concatLines (
|
||||
flip mapAttrsToList x.nets (
|
||||
net: assignments: " ${net} assigned via ${concatStringsSep " -> " (reverseList assignments)}"
|
||||
)
|
||||
)
|
||||
))}''
|
||||
converged;
|
||||
in
|
||||
f {
|
||||
options.nodes = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
type = types.attrsOf (types.submodule (nodeSubmod: {
|
||||
options = {
|
||||
interfaces = mkOption {
|
||||
description = "TODO";
|
||||
|
@ -67,10 +275,38 @@ in
|
|||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
network = mkOption {
|
||||
description = "The id of the network to which this interface belongs, if any.";
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
network =
|
||||
mkOption {
|
||||
description = "The id of the network to which this interface belongs, if any.";
|
||||
type = lazyOf (types.nullOr types.str);
|
||||
}
|
||||
// optionalAttrs config.topology.isMainModule {
|
||||
default = let
|
||||
sharedNetworks = propagatedNetworks.${nodeSubmod.config.id}.${submod.config.id} or {};
|
||||
sharedNetwork =
|
||||
if sharedNetworks == {}
|
||||
then null
|
||||
else builtins.head (attrNames sharedNetworks);
|
||||
in
|
||||
lazyValue sharedNetwork;
|
||||
};
|
||||
|
||||
sharesNetworkWith = mkOption {
|
||||
description = ''
|
||||
Defines a predicate that determines whether this interface shares its connected network with another provided local interface.
|
||||
The predicates takes the name of another interface and returns true if our network should be shared with the given interface.
|
||||
|
||||
Sharing here means that if a network is set on this interface, it will also be set as the network for any
|
||||
shared interface. Setting the same predicate on multiple interfaces causes them to share a network regardless
|
||||
on which port the network is actually defined.
|
||||
|
||||
An unmanaged switch for example would set this to `const true`, effectively
|
||||
propagating the network set on one port to all other ports. Having two assigned
|
||||
networks within one predicate group will cause a warning to be issued.
|
||||
'';
|
||||
default = const false;
|
||||
defaultText = ''const false'';
|
||||
type = types.functionTo types.bool;
|
||||
};
|
||||
|
||||
physicalConnections = mkOption {
|
||||
|
@ -90,6 +326,21 @@ in
|
|||
};
|
||||
});
|
||||
};
|
||||
|
||||
# Rendering related hints and settings
|
||||
renderer = {
|
||||
hidePhysicalConnections = mkOption {
|
||||
description = ''
|
||||
Whether to hide physical connections of this interface in renderings.
|
||||
Affects both outgoing connections defined here and incoming connections
|
||||
defined on other interfaces.
|
||||
|
||||
Usually only affects rendering of the main topology view, not network-centric views.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
|
@ -101,10 +352,13 @@ in
|
|||
}));
|
||||
};
|
||||
};
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
config = {
|
||||
lib.a.a = connections;
|
||||
lib.a.b = networkDefiningInterfaces;
|
||||
lib.a.c = propagatedNetworks;
|
||||
assertions = flatten (flip map (attrValues config.nodes) (
|
||||
node:
|
||||
flip map (attrValues node.interfaces) (
|
||||
|
|
|
@ -41,8 +41,8 @@ in
|
|||
};
|
||||
|
||||
hardware = {
|
||||
description = mkOption {
|
||||
description = "A description of this node's hardware. Usually the model name or a description the most important components.";
|
||||
info = mkOption {
|
||||
description = "A single line of information about this node's hardware. Usually the model name or a description the most important components.";
|
||||
type = types.str;
|
||||
default = "";
|
||||
};
|
||||
|
@ -64,7 +64,7 @@ in
|
|||
description = ''
|
||||
The device type of the node. This can be set to anything, but some special
|
||||
values exist that will automatically set some other defaults, most notably
|
||||
the deviceIcon and preferredRenderType.
|
||||
the deviceIcon and renderer.preferredType.
|
||||
'';
|
||||
type = types.either (types.enum ["nixos" "internet" "router" "switch" "device"]) types.str;
|
||||
};
|
||||
|
@ -87,15 +87,18 @@ in
|
|||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
preferredRenderType = mkOption {
|
||||
description = ''
|
||||
An optional hint to the renderer to specify whether this node should preferrably
|
||||
rendered as a full card, or just as an image with name. If there is no hardware
|
||||
image, this will usually still render a small card.
|
||||
'';
|
||||
type = types.enum ["card" "image"];
|
||||
default = "card";
|
||||
defaultText = ''"card" # defaults to card but is also derived from the deviceType if possible.'';
|
||||
# Rendering related hints and settings
|
||||
renderer = {
|
||||
preferredType = mkOption {
|
||||
description = ''
|
||||
An optional hint to the renderer to specify whether this node should preferrably
|
||||
rendered as a full card, or just as an image with name. If there is no hardware
|
||||
image, this will usually still render a small card.
|
||||
'';
|
||||
type = types.enum ["card" "image"];
|
||||
default = "card";
|
||||
defaultText = ''"card" # defaults to card but is also derived from the deviceType if possible.'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -103,16 +106,19 @@ in
|
|||
nodeCfg = nodeSubmod.config;
|
||||
in
|
||||
mkIf config.topology.isMainModule (mkMerge [
|
||||
# Set the default icon, if an icon exists with a matching name
|
||||
{
|
||||
# Set the default icon, if an icon exists with a matching name
|
||||
deviceIcon = mkIf (config.icons.devices ? ${nodeCfg.deviceType}) (
|
||||
mkDefault ("devices." + nodeCfg.deviceType)
|
||||
);
|
||||
|
||||
# Set the hardware info to the guest type if nothing else was set
|
||||
hardware.info = mkIf (nodeCfg.guestType != null) (mkDefault nodeCfg.guestType);
|
||||
}
|
||||
|
||||
# If the device type is not a full nixos node, try to render it as an image with name.
|
||||
(mkIf (elem nodeCfg.deviceType ["internet" "router" "switch" "device"]) {
|
||||
preferredRenderType = mkDefault "image";
|
||||
renderer.preferredType = mkDefault "image";
|
||||
})
|
||||
]);
|
||||
}));
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
mkOption
|
||||
optional
|
||||
optionalAttrs
|
||||
optionals
|
||||
recursiveUpdate
|
||||
stringLength
|
||||
types
|
||||
|
@ -64,6 +63,13 @@
|
|||
// extra;
|
||||
};
|
||||
|
||||
mkPort = recursiveUpdate {
|
||||
width = 8;
|
||||
height = 8;
|
||||
style.stroke = "#485263";
|
||||
style.fill = "#b6beca";
|
||||
};
|
||||
|
||||
mkLabel = text: scale: extraStyle: {
|
||||
height = scale * 12;
|
||||
width = scale * 7.2 * (stringLength text);
|
||||
|
@ -83,82 +89,72 @@
|
|||
scale = 0.8;
|
||||
};
|
||||
properties."portLabels.placement" = "OUTSIDE";
|
||||
|
||||
ports.default = mkPort {
|
||||
labels."00-name" = mkLabel "*" 1 {};
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
idForInterface = node: interfaceId: "children.node:${node.id}.ports.interface:${interfaceId}";
|
||||
|
||||
nodeInterfaceToElk = node: interface:
|
||||
nodeInterfaceToElk = node: interface: let
|
||||
interfaceLabels =
|
||||
{
|
||||
"00-name" = mkLabel interface.id 1 {};
|
||||
}
|
||||
// optionalAttrs (interface.mac != null) {
|
||||
"50-mac" = mkLabel interface.mac 1 {fill = "#70a5eb";};
|
||||
}
|
||||
// optionalAttrs (interface.addresses != []) {
|
||||
"60-addrs" = mkLabel (toString interface.addresses) 1 {fill = "#f9a872";};
|
||||
};
|
||||
in
|
||||
[
|
||||
# Interface for node in main view
|
||||
{
|
||||
children."node:${node.id}".ports."interface:${interface.id}" = {
|
||||
properties = optionalAttrs (node.preferredRenderType == "card") {
|
||||
children."node:${node.id}".ports."interface:${interface.id}" = mkPort {
|
||||
properties = optionalAttrs (node.renderer.preferredType == "card") {
|
||||
"port.side" = "WEST";
|
||||
};
|
||||
width = 8;
|
||||
height = 8;
|
||||
style.stroke = "#485263"; # FIXME: TODO color based on attached network color (autoshade)?
|
||||
style.fill = "#b6beca";
|
||||
labels =
|
||||
{
|
||||
"00-name" = mkLabel interface.id 1 {};
|
||||
}
|
||||
// optionalAttrs (interface.mac != null) {
|
||||
"50-mac" = mkLabel interface.mac 1 {fill = "#70a5eb";};
|
||||
}
|
||||
// optionalAttrs (interface.addresses != []) {
|
||||
"60-addrs" = mkLabel (toString interface.addresses) 1 {fill = "#f9a872";};
|
||||
};
|
||||
labels = interfaceLabels;
|
||||
};
|
||||
}
|
||||
|
||||
# Interface for node in network-centric view
|
||||
{
|
||||
children.network.children."node:${node.id}".ports."interface:${interface.id}" = {
|
||||
# FIXME: TODO: deduplicate, same as above
|
||||
# FIXME: TODO: deduplicate, same as above
|
||||
# FIXME: TODO: deduplicate, same as above
|
||||
width = 8;
|
||||
height = 8;
|
||||
style.stroke = "#485263"; # FIXME: TODO color based on attached network color (autoshade)?
|
||||
style.fill = "#b6beca";
|
||||
labels =
|
||||
{
|
||||
"00-name" = mkLabel interface.id 1 {};
|
||||
}
|
||||
// optionalAttrs (interface.mac != null) {
|
||||
"50-mac" = mkLabel interface.mac 1 {fill = "#70a5eb";};
|
||||
}
|
||||
// optionalAttrs (interface.addresses != []) {
|
||||
"60-addrs" = mkLabel (toString interface.addresses) 1 {fill = "#f9a872";};
|
||||
};
|
||||
children.network.children."node:${node.id}".ports."interface:${interface.id}" = mkPort {
|
||||
labels = interfaceLabels;
|
||||
};
|
||||
}
|
||||
|
||||
# Edge in network-centric view
|
||||
(optionalAttrs (interface.network != null) (
|
||||
mkEdge ("children.network." + idForInterface node interface.id) "children.network.children.net:${interface.network}" {
|
||||
mkEdge ("children.network." + idForInterface node interface.id) "children.network.children.net:${interface.network}.ports.default" {
|
||||
style.stroke = config.networks.${interface.network}.color;
|
||||
}
|
||||
))
|
||||
]
|
||||
++ optionals (!interface.virtual) (flip map interface.physicalConnections (
|
||||
conn:
|
||||
++ flatten (flip map interface.physicalConnections (
|
||||
conn: let
|
||||
otherInterface = config.nodes.${conn.node}.interfaces.${conn.interface};
|
||||
in
|
||||
optionalAttrs (
|
||||
(!any (y: y.node == node.id && y.interface == interface.id) config.nodes.${conn.node}.interfaces.${conn.interface}.physicalConnections)
|
||||
(!any (y: y.node == node.id && y.interface == interface.id) otherInterface.physicalConnections)
|
||||
|| (node.id < conn.node)
|
||||
) (
|
||||
# Edge in main view
|
||||
mkEdge
|
||||
(idForInterface node interface.id)
|
||||
(idForInterface config.nodes.${conn.node} conn.interface)
|
||||
{
|
||||
# FIXME: in interface definition ensure that the two ends of physical connections don't have different networks (output warning only)
|
||||
style = optionalAttrs (interface.network != null) {
|
||||
stroke = config.networks.${interface.network}.color;
|
||||
};
|
||||
}
|
||||
optional (!interface.renderer.hidePhysicalConnections && !otherInterface.renderer.hidePhysicalConnections) (
|
||||
# Edge in main view
|
||||
mkEdge
|
||||
(idForInterface node interface.id)
|
||||
(idForInterface config.nodes.${conn.node} conn.interface)
|
||||
{
|
||||
style = optionalAttrs (interface.network != null) {
|
||||
stroke = config.networks.${interface.network}.color;
|
||||
};
|
||||
}
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
|
@ -175,7 +171,7 @@
|
|||
{
|
||||
"portLabels.placement" = "OUTSIDE";
|
||||
}
|
||||
// optionalAttrs (node.preferredRenderType == "card") {
|
||||
// optionalAttrs (node.renderer.preferredType == "card") {
|
||||
"portConstraints" = "FIXED_SIDE";
|
||||
};
|
||||
};
|
||||
|
@ -193,10 +189,8 @@
|
|||
]
|
||||
++ optional (node.parent != null) (
|
||||
{
|
||||
children."node:${node.parent}".ports.guests = {
|
||||
children."node:${node.parent}".ports.guests = mkPort {
|
||||
properties."port.side" = "EAST";
|
||||
width = 8;
|
||||
height = 8;
|
||||
style.stroke = "#49d18d";
|
||||
style.fill = "#78dba9";
|
||||
labels."00-name" = mkLabel "guests" 1 {};
|
||||
|
|
|
@ -1,27 +1,20 @@
|
|||
# TODO:
|
||||
# - stack interfaces horizontally, information is now on the ports anyway.
|
||||
# - systemd extractor remove cidr mask
|
||||
# - address port label render make newline capable (multiple port labels)
|
||||
# - mac address show!
|
||||
# - split network layout or make rectpacking of childs
|
||||
# - NAT indication
|
||||
# - bottom hw image distorted in card view (move to top anyway)
|
||||
# - embed font globally, try removing satori embed?
|
||||
# - network overview card (list all networks with name and cidr, legend style)
|
||||
# - colors!
|
||||
# - ip labels on edges
|
||||
# - network centric view
|
||||
# - better layout for interfaces in svg
|
||||
# - sevice infos
|
||||
# - disks (from disko) + render
|
||||
# - stable pseudorandom colors from palette with no-reuse until necessary
|
||||
# - network centric view as standalone
|
||||
# - split network layout or make rectpacking of childs
|
||||
# - hardware info (image small top and image big bottom and full (no card), maybe just image and render position)
|
||||
# - more service info
|
||||
# - disks (from disko) + render
|
||||
# - impermanence render?
|
||||
# - nixos nftables firewall render?
|
||||
# - stable pseudorandom colors from palette with no-reuse until necessary
|
||||
# - search todo and do
|
||||
# - podman / docker harvesting
|
||||
# - systemd extractor remove cidr mask
|
||||
# - nixos-container extractor
|
||||
# - search todo and do
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
|
@ -144,24 +137,16 @@
|
|||
};
|
||||
|
||||
node = rec {
|
||||
mkInterface = interface: let
|
||||
color =
|
||||
if interface.virtual
|
||||
then "#7a899f"
|
||||
else "#70a5eb";
|
||||
in
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-row items-center my-2">
|
||||
<div tw="flex flex-row flex-none items-center bg-[${color}] text-[#101419] rounded-lg px-2 py-1 w-46 h-8 mx-4">
|
||||
${mkImage "w-6 h-6 mr-2" (config.lib.icons.get interface.icon)}
|
||||
<span tw="font-bold">${interface.id}</span>
|
||||
</div>
|
||||
<span>${toString interface.addresses}</span>
|
||||
</div>
|
||||
'';
|
||||
mkInterface = interface:
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-col flex-none items-center border-[#21262e] border-2 rounded-lg px-2 py-1 m-1">
|
||||
${mkImage "w-8 h-8 m-1" (config.lib.icons.get interface.icon)}
|
||||
<span tw="font-bold text-xs">${interface.id}</span>
|
||||
</div>
|
||||
'';
|
||||
|
||||
serviceDetail = detail:
|
||||
/*
|
||||
|
@ -217,19 +202,6 @@
|
|||
</div>
|
||||
'';
|
||||
|
||||
mkTitle = node:
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-row mx-6 mt-2 items-center">
|
||||
${mkImageMaybe "w-8 h-8 mr-4" (config.lib.icons.get node.icon)}
|
||||
<h2 tw="text-2xl font-bold">${node.name}</h2>
|
||||
<div tw="flex grow min-w-8"></div>
|
||||
${mkImageMaybe "w-12 h-12 ml-4" (config.lib.icons.get node.deviceIcon)}
|
||||
</div>
|
||||
'';
|
||||
|
||||
mkCard = node: let
|
||||
services = filter (x: !x.hidden) (attrValues node.services);
|
||||
guests = filter (x: x.parent == node.id) (attrValues config.nodes);
|
||||
|
@ -241,18 +213,25 @@
|
|||
html
|
||||
*/
|
||||
''
|
||||
${mkTitle node}
|
||||
<div tw="flex flex-row mx-6 mt-2 items-center">
|
||||
${mkImageMaybe "w-8 h-8 mr-4" (config.lib.icons.get node.icon)}
|
||||
<div tw="flex flex-col min-h-18 justify-center">
|
||||
<span tw="text-2xl font-bold">${node.name}</span>
|
||||
${optionalString (node.hardware.info != null) ''<span tw="text-xs">${node.hardware.info}</span>''}
|
||||
</div>
|
||||
<div tw="flex grow min-w-8"></div>
|
||||
${mkImageMaybe "w-12 h-12 ml-4" (config.lib.icons.get node.deviceIcon)}
|
||||
</div>
|
||||
|
||||
${optionalString (node.interfaces != {}) ''<div tw="flex flex-row flex-wrap items-center my-2 mx-3">''}
|
||||
${concatLines (map mkInterface (attrValues node.interfaces))}
|
||||
${optionalString (node.interfaces != {}) spacingMt2}
|
||||
${optionalString (node.interfaces != {}) ''</div>''}
|
||||
|
||||
${concatLines (map mkGuest guests)}
|
||||
${optionalString (guests != []) spacingMt2}
|
||||
|
||||
${concatLines (map (mkService {}) services)}
|
||||
${optionalString (services != []) spacingMt2}
|
||||
|
||||
${mkImageMaybe "w-full h-24" node.hardware.image}
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -267,21 +246,26 @@
|
|||
''
|
||||
<div tw="flex flex-row mx-6 mt-2 items-center">
|
||||
${mkImageMaybe "w-8 h-8" (config.lib.icons.get node.icon)}
|
||||
<h2 tw="text-2xl font-bold">${node.name}</h2>
|
||||
${optionalString (node.hardware.image != null -> deviceIconImage != node.hardware.image)
|
||||
<div tw="flex flex-col min-h-18 justify-center">
|
||||
<span tw="text-2xl font-bold">${node.name}</span>
|
||||
${optionalString (node.hardware.info != null) ''<span tw="text-xs">${node.hardware.info}</span>''}
|
||||
</div>
|
||||
${optionalString (deviceIconImage != null && node.hardware.image != null -> deviceIconImage != node.hardware.image)
|
||||
''
|
||||
<div tw="flex grow min-w-4"></div>
|
||||
${mkImageMaybe "w-12 h-12" deviceIconImage}
|
||||
''}
|
||||
</div>
|
||||
|
||||
${mkImageMaybe "h-24" node.hardware.image}
|
||||
<div tw="flex flex-row w-full justify-center">
|
||||
${mkImageMaybe "h-24" node.hardware.image}
|
||||
</div>
|
||||
'';
|
||||
};
|
||||
|
||||
mkPreferredRender = node:
|
||||
(
|
||||
if node.preferredRenderType == "image" && node.hardware.image != null
|
||||
if node.renderer.preferredType == "image" && node.hardware.image != null
|
||||
then mkImageWithName
|
||||
else mkCard
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue