forked from mirrors_public/oddlama_nix-config
feat(topology): add automatic network coloring
This commit is contained in:
parent
dc4d82c828
commit
c3dcc619af
6 changed files with 217 additions and 65 deletions
|
@ -223,12 +223,12 @@
|
||||||
networks.home-lan = {
|
networks.home-lan = {
|
||||||
name = "Home LAN";
|
name = "Home LAN";
|
||||||
cidrv4 = "192.168.1.0/24";
|
cidrv4 = "192.168.1.0/24";
|
||||||
color = "#78dba9";
|
#color = "#78dba9";
|
||||||
};
|
};
|
||||||
networks.home-fritzbox = {
|
networks.home-fritzbox = {
|
||||||
name = "Home Fritzbox";
|
name = "Home Fritzbox";
|
||||||
cidrv4 = "192.168.178.0/24";
|
cidrv4 = "192.168.178.0/24";
|
||||||
color = "#f1cf8a";
|
#color = "#f1cf8a";
|
||||||
};
|
};
|
||||||
|
|
||||||
nodes.ward.interfaces.lan.network = "home-lan";
|
nodes.ward.interfaces.lan.network = "home-lan";
|
||||||
|
|
|
@ -6,8 +6,6 @@ f: {
|
||||||
}: let
|
}: let
|
||||||
inherit
|
inherit
|
||||||
(lib)
|
(lib)
|
||||||
all
|
|
||||||
assertMsg
|
|
||||||
attrNames
|
attrNames
|
||||||
attrValues
|
attrValues
|
||||||
concatLines
|
concatLines
|
||||||
|
@ -18,51 +16,26 @@ f: {
|
||||||
flatten
|
flatten
|
||||||
flip
|
flip
|
||||||
foldl'
|
foldl'
|
||||||
isAttrs
|
|
||||||
length
|
length
|
||||||
mapAttrsToList
|
mapAttrsToList
|
||||||
mkDefault
|
mkDefault
|
||||||
mkIf
|
mkIf
|
||||||
mkOption
|
mkOption
|
||||||
mkOptionType
|
|
||||||
optional
|
optional
|
||||||
optionalAttrs
|
optionalAttrs
|
||||||
recursiveUpdate
|
recursiveUpdate
|
||||||
reverseList
|
reverseList
|
||||||
showOption
|
|
||||||
types
|
types
|
||||||
unique
|
unique
|
||||||
warnIf
|
warnIf
|
||||||
;
|
;
|
||||||
|
|
||||||
# Checks whether the value is a lazy value without causing
|
inherit
|
||||||
# it's value to be evaluated
|
(import ../topology/lazy.nix lib)
|
||||||
isLazyValue = x: isAttrs x && x ? _lazyValue;
|
isLazyValue
|
||||||
# Constructs a lazy value holding the given value.
|
lazyOf
|
||||||
lazyValue = value: {_lazyValue = value;};
|
lazyValue
|
||||||
|
;
|
||||||
# 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;
|
allNodes = attrNames config.nodes;
|
||||||
allInterfacesOf = node: attrNames config.nodes.${node}.interfaces;
|
allInterfacesOf = node: attrNames config.nodes.${node}.interfaces;
|
||||||
|
@ -323,6 +296,12 @@ in
|
||||||
description = "The other node's interface id.";
|
description = "The other node's interface id.";
|
||||||
type = types.str;
|
type = types.str;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderer.reverse = mkOption {
|
||||||
|
description = "Whether to reverse the edge. Can be useful to affect node positioning if the layouter is directional.";
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,16 +1,101 @@
|
||||||
f: {
|
f: {
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
|
options,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
inherit
|
inherit
|
||||||
(lib)
|
(lib)
|
||||||
|
attrNames
|
||||||
attrValues
|
attrValues
|
||||||
|
elemAt
|
||||||
flatten
|
flatten
|
||||||
flip
|
flip
|
||||||
|
foldl'
|
||||||
|
imap0
|
||||||
|
length
|
||||||
|
mapAttrsToList
|
||||||
mkOption
|
mkOption
|
||||||
|
mod
|
||||||
|
optional
|
||||||
|
optionalAttrs
|
||||||
|
recursiveUpdate
|
||||||
|
subtractLists
|
||||||
types
|
types
|
||||||
|
unique
|
||||||
|
warnIf
|
||||||
;
|
;
|
||||||
|
|
||||||
|
inherit
|
||||||
|
(import ../topology/lazy.nix lib)
|
||||||
|
isLazyValue
|
||||||
|
lazyOf
|
||||||
|
lazyValue
|
||||||
|
;
|
||||||
|
|
||||||
|
style = primaryColor: secondaryColor: pattern: {
|
||||||
|
inherit primaryColor secondaryColor pattern;
|
||||||
|
};
|
||||||
|
|
||||||
|
predefinedStyles = [
|
||||||
|
(style "#f1cf8a" null "solid")
|
||||||
|
(style "#70a5eb" null "solid")
|
||||||
|
(style "#9dd68d" null "solid")
|
||||||
|
(style "#5fe1ff" null "solid")
|
||||||
|
(style "#e05f65" null "solid")
|
||||||
|
(style "#f9a872" null "solid")
|
||||||
|
(style "#78dba9" null "solid")
|
||||||
|
(style "#9378de" null "solid")
|
||||||
|
(style "#c68aee" null "solid")
|
||||||
|
(style "#f5a6b8" null "solid")
|
||||||
|
(style "#70a5eb" null "dashed")
|
||||||
|
(style "#9dd68d" null "dashed")
|
||||||
|
(style "#f1cf8a" null "dashed")
|
||||||
|
(style "#5fe1ff" null "dashed")
|
||||||
|
(style "#e05f65" null "dashed")
|
||||||
|
(style "#f9a872" null "dashed")
|
||||||
|
(style "#78dba9" null "dashed")
|
||||||
|
(style "#9378de" null "dashed")
|
||||||
|
(style "#c68aee" null "dashed")
|
||||||
|
(style "#f5a6b8" null "dashed")
|
||||||
|
(style "#e05f65" "#e3e6eb" "dashed")
|
||||||
|
(style "#70a5eb" "#e3e6eb" "dashed")
|
||||||
|
(style "#9378de" "#e3e6eb" "dashed")
|
||||||
|
(style "#9dd68d" "#707379" "dashed")
|
||||||
|
(style "#f1cf8a" "#707379" "dashed")
|
||||||
|
(style "#5fe1ff" "#707379" "dashed")
|
||||||
|
(style "#f9a872" "#707379" "dashed")
|
||||||
|
(style "#78dba9" "#707379" "dashed")
|
||||||
|
(style "#c68aee" "#707379" "dashed")
|
||||||
|
(style "#f5a6b8" "#707379" "dashed")
|
||||||
|
];
|
||||||
|
|
||||||
|
# A map containing all networks that have an explicitly assigned style, and the style.
|
||||||
|
explicitStyles = foldl' recursiveUpdate {} (flatten (
|
||||||
|
flip map options.networks.definitions (mapAttrsToList (
|
||||||
|
netId: net:
|
||||||
|
optional (net ? style && !isLazyValue net.style) {
|
||||||
|
${netId} = net.style;
|
||||||
|
}
|
||||||
|
))
|
||||||
|
));
|
||||||
|
|
||||||
|
# All unused predefined styles
|
||||||
|
remainingStyles = subtractLists (unique (attrValues explicitStyles)) predefinedStyles;
|
||||||
|
# All networks without styles
|
||||||
|
remainingNets = subtractLists (attrNames explicitStyles) (attrNames config.networks);
|
||||||
|
# Fold over all networks that have no style and assign the next free one. Warn and repeat from beginning if necessary.
|
||||||
|
computedStyles =
|
||||||
|
explicitStyles
|
||||||
|
// warnIf
|
||||||
|
(length remainingNets > length remainingStyles)
|
||||||
|
"topology: There are more networks without styles than predefined styles. Some styles will have to be reused!"
|
||||||
|
(
|
||||||
|
foldl' recursiveUpdate {} (imap0 (i: net: {
|
||||||
|
${net} = elemAt remainingStyles (mod i (length remainingStyles));
|
||||||
|
})
|
||||||
|
remainingNets)
|
||||||
|
);
|
||||||
in
|
in
|
||||||
f {
|
f {
|
||||||
options.networks = mkOption {
|
options.networks = mkOption {
|
||||||
|
@ -37,15 +122,20 @@ in
|
||||||
default = null;
|
default = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
color = mkOption {
|
style =
|
||||||
description = "The color of this network";
|
mkOption {
|
||||||
default = "random";
|
description = ''
|
||||||
apply = x:
|
A style for this network, usually used to draw connections.
|
||||||
if x == "random"
|
Must be an attrset consisting of three attributes:
|
||||||
then "#ff00ff" # FIXME: TODO: lookuptable linear probing hashing into palette
|
- primaryColor (#rrggbb): The primary color, usually the color of edges.
|
||||||
else x;
|
- secondaryColor (#rrggbb): The secondary color, usually the background of a dashed line and only shown when pattern != solid. Set to null for transparent.
|
||||||
type = types.either (types.strMatching "^#[0-9a-f]{6}$") (types.enum ["random"]);
|
- pattern (solid, dashed, dotted): The pattern to use.
|
||||||
};
|
'';
|
||||||
|
type = lazyOf types.attrs;
|
||||||
|
}
|
||||||
|
// optionalAttrs config.topology.isMainModule {
|
||||||
|
default = lazyValue computedStyles.${networkSubmod.config.id};
|
||||||
|
};
|
||||||
|
|
||||||
cidrv4 = mkOption {
|
cidrv4 = mkOption {
|
||||||
description = "The CIDRv4 address space of this network or null if it doesn't use ipv4";
|
description = "The CIDRv4 address space of this network or null if it doesn't use ipv4";
|
||||||
|
|
42
topology/topology/lazy.nix
Normal file
42
topology/topology/lazy.nix
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
lib: let
|
||||||
|
inherit
|
||||||
|
(lib)
|
||||||
|
all
|
||||||
|
assertMsg
|
||||||
|
isAttrs
|
||||||
|
mkOptionType
|
||||||
|
showOption
|
||||||
|
types
|
||||||
|
;
|
||||||
|
|
||||||
|
# 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;
|
||||||
|
in {
|
||||||
|
inherit isLazyValue lazyValue lazyValueOf lazyOf;
|
||||||
|
}
|
|
@ -54,14 +54,17 @@
|
||||||
else value
|
else value
|
||||||
);
|
);
|
||||||
|
|
||||||
mkEdge = from: to: extra: {
|
mkEdge = from: to: reverse: extra:
|
||||||
edges."${from}__${to}" =
|
if reverse
|
||||||
{
|
then mkEdge to from false extra
|
||||||
sources = [from];
|
else {
|
||||||
targets = [to];
|
edges."${from}__${to}" =
|
||||||
}
|
{
|
||||||
// extra;
|
sources = [from];
|
||||||
};
|
targets = [to];
|
||||||
|
}
|
||||||
|
// extra;
|
||||||
|
};
|
||||||
|
|
||||||
mkPort = recursiveUpdate {
|
mkPort = recursiveUpdate {
|
||||||
width = 8;
|
width = 8;
|
||||||
|
@ -81,6 +84,32 @@
|
||||||
// extraStyle;
|
// extraStyle;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pathStyleFromNetworkStyle = style:
|
||||||
|
{
|
||||||
|
solid = {
|
||||||
|
stroke = style.primaryColor;
|
||||||
|
};
|
||||||
|
dashed =
|
||||||
|
{
|
||||||
|
stroke = style.primaryColor;
|
||||||
|
stroke-dasharray = "10,8";
|
||||||
|
stroke-linecap = "round";
|
||||||
|
}
|
||||||
|
// optionalAttrs (style.secondaryColor != null) {
|
||||||
|
background = style.secondaryColor;
|
||||||
|
};
|
||||||
|
dotted =
|
||||||
|
{
|
||||||
|
stroke = style.primaryColor;
|
||||||
|
stroke-dasharray = "2,6";
|
||||||
|
stroke-linecap = "round";
|
||||||
|
}
|
||||||
|
// optionalAttrs (style.secondaryColor != null) {
|
||||||
|
background = style.secondaryColor;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
.${style.pattern};
|
||||||
|
|
||||||
netToElk = net: [
|
netToElk = net: [
|
||||||
{
|
{
|
||||||
children.network.children."net:${net.id}" = {
|
children.network.children."net:${net.id}" = {
|
||||||
|
@ -100,15 +129,18 @@
|
||||||
idForInterface = node: interfaceId: "children.node:${node.id}.ports.interface:${interfaceId}";
|
idForInterface = node: interfaceId: "children.node:${node.id}.ports.interface:${interfaceId}";
|
||||||
|
|
||||||
nodeInterfaceToElk = node: interface: let
|
nodeInterfaceToElk = node: interface: let
|
||||||
|
netStyle = optionalAttrs (interface.network != null) {
|
||||||
|
fill = config.networks.${interface.network}.style.primaryColor;
|
||||||
|
};
|
||||||
interfaceLabels =
|
interfaceLabels =
|
||||||
{
|
{
|
||||||
"00-name" = mkLabel interface.id 1 {};
|
"00-name" = mkLabel interface.id 1 {};
|
||||||
}
|
}
|
||||||
// optionalAttrs (interface.mac != null) {
|
// optionalAttrs (interface.mac != null) {
|
||||||
"50-mac" = mkLabel interface.mac 1 {fill = "#70a5eb";};
|
"50-mac" = mkLabel interface.mac 1 netStyle;
|
||||||
}
|
}
|
||||||
// optionalAttrs (interface.addresses != []) {
|
// optionalAttrs (interface.addresses != []) {
|
||||||
"60-addrs" = mkLabel (toString interface.addresses) 1 {fill = "#f9a872";};
|
"60-addrs" = mkLabel (toString interface.addresses) 1 netStyle;
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
[
|
[
|
||||||
|
@ -131,8 +163,8 @@
|
||||||
|
|
||||||
# Edge in network-centric view
|
# Edge in network-centric view
|
||||||
(optionalAttrs (interface.network != null) (
|
(optionalAttrs (interface.network != null) (
|
||||||
mkEdge ("children.network." + idForInterface node interface.id) "children.network.children.net:${interface.network}.ports.default" {
|
mkEdge ("children.network." + idForInterface node interface.id) "children.network.children.net:${interface.network}.ports.default" false {
|
||||||
style.stroke = config.networks.${interface.network}.color;
|
style = pathStyleFromNetworkStyle config.networks.${interface.network}.style;
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
]
|
]
|
||||||
|
@ -149,10 +181,11 @@
|
||||||
mkEdge
|
mkEdge
|
||||||
(idForInterface node interface.id)
|
(idForInterface node interface.id)
|
||||||
(idForInterface config.nodes.${conn.node} conn.interface)
|
(idForInterface config.nodes.${conn.node} conn.interface)
|
||||||
|
conn.renderer.reverse
|
||||||
{
|
{
|
||||||
style = optionalAttrs (interface.network != null) {
|
style = optionalAttrs (interface.network != null) (
|
||||||
stroke = config.networks.${interface.network}.color;
|
pathStyleFromNetworkStyle config.networks.${interface.network}.style
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -196,7 +229,7 @@
|
||||||
labels."00-name" = mkLabel "guests" 1 {};
|
labels."00-name" = mkLabel "guests" 1 {};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// mkEdge "children.node:${node.parent}.ports.guests" "children.node:${node.id}" {
|
// mkEdge "children.node:${node.parent}.ports.guests" "children.node:${node.id}" false {
|
||||||
style.stroke-dasharray = "10,8";
|
style.stroke-dasharray = "10,8";
|
||||||
style.stroke-linecap = "round";
|
style.stroke-linecap = "round";
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,10 +113,18 @@
|
||||||
mkCard = net: {
|
mkCard = net: {
|
||||||
width = 480;
|
width = 480;
|
||||||
html = let
|
html = let
|
||||||
netColor =
|
netStylePreview = let
|
||||||
if net.color != null
|
secondaryColor =
|
||||||
then net.color
|
if net.style.secondaryColor == null
|
||||||
else "#b6beca";
|
then "#00000000"
|
||||||
|
else net.style.secondaryColor;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
solid = ''<div tw="flex flex-none bg-[${net.style.primaryColor}] w-8 h-4 mr-4 rounded-md"></div>'';
|
||||||
|
dashed = ''<div tw="flex flex-none w-8 h-4 mr-4 rounded-md" style="backgroundImage: linear-gradient(90deg, ${net.style.primaryColor} 0%, ${net.style.primaryColor} 50%, ${secondaryColor} 50.01%, ${secondaryColor} 100%);"></div>'';
|
||||||
|
dotted = ''<div tw="flex flex-none w-8 h-4 mr-4 rounded-md" style="backgroundImage: radial-gradient(circle, ${net.style.primaryColor} 30%, ${secondaryColor} 30.01%);"></div>'';
|
||||||
|
}
|
||||||
|
.${net.style.pattern};
|
||||||
in
|
in
|
||||||
mkCardContainer
|
mkCardContainer
|
||||||
/*
|
/*
|
||||||
|
@ -124,7 +132,7 @@
|
||||||
*/
|
*/
|
||||||
''
|
''
|
||||||
<div tw="flex flex-row mx-6 mt-2 items-center">
|
<div tw="flex flex-row mx-6 mt-2 items-center">
|
||||||
<div tw="flex flex-none bg-[${netColor}] w-8 h-8 mr-4 rounded-lg"></div>
|
${netStylePreview}
|
||||||
<h2 tw="text-2xl font-bold">${net.name}</h2>
|
<h2 tw="text-2xl font-bold">${net.name}</h2>
|
||||||
<div tw="flex grow min-w-8"></div>
|
<div tw="flex grow min-w-8"></div>
|
||||||
${mkImageMaybe "w-12 h-12 ml-4" (config.lib.icons.get net.icon)}
|
${mkImageMaybe "w-12 h-12 ml-4" (config.lib.icons.get net.icon)}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue