mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-10 14:50:40 +02:00
feat(topology): add html card templating and svg rendering
This commit is contained in:
parent
11562ead05
commit
c5ff8418ac
13 changed files with 523 additions and 28 deletions
|
@ -25,6 +25,7 @@
|
|||
segoe-ui-ttf = prev.callPackage ./segoe-ui-ttf.nix {};
|
||||
zsh-histdb-skim = prev.callPackage ./zsh-skim-histdb.nix {};
|
||||
awakened-poe-trade = prev.callPackage ./awakened-poe-trade.nix {};
|
||||
html-to-svg = prev.callPackage ./html-to-svg {};
|
||||
neovim-clean = prev.neovim-unwrapped.overrideAttrs (old: {
|
||||
nativeBuildInputs = (old.nativeBuildInputs or []) ++ [prev.makeWrapper];
|
||||
postInstall =
|
||||
|
|
22
pkgs/html-to-svg/default.nix
Normal file
22
pkgs/html-to-svg/default.nix
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
buildNpmPackage,
|
||||
lib,
|
||||
}:
|
||||
buildNpmPackage rec {
|
||||
pname = "html-to-svg";
|
||||
version = "1.0.0";
|
||||
|
||||
src = ./.;
|
||||
npmDepsHash = "sha256-0gm43QSUBg219ueFuNSjz857Y1OttSKFc4VltXF78yg=";
|
||||
dontNpmBuild = true;
|
||||
|
||||
#passthru.updateScript = nix-update-script { };
|
||||
|
||||
meta = with lib; {
|
||||
description = "Convert satori compatible HTML to SVG";
|
||||
#homepage = "https://github.com/oddlama/html-to-svg";
|
||||
license = licenses.mit;
|
||||
maintainers = with maintainers; [oddlama];
|
||||
mainProgram = "html-to-svg";
|
||||
};
|
||||
}
|
51
pkgs/html-to-svg/main.js
Normal file
51
pkgs/html-to-svg/main.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
import fs from "node:fs/promises";
|
||||
import satori from "satori";
|
||||
import { html } from "satori-html";
|
||||
import { Command } from "commander";
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name("html-to-svg")
|
||||
.description("Convert satori compatible HTML to SVG")
|
||||
.version("1.0.0")
|
||||
.argument("<input>", "satori html file to render")
|
||||
.argument("<output>", "output svg")
|
||||
.option("--font <font>", "Sets the font")
|
||||
.option("--font-bold <font>", "Sets the bold font")
|
||||
.option("-w, --width <width>", "Sets the width of the output", 680)
|
||||
.action(async (input, output, options) => {
|
||||
if (!options.font) {
|
||||
console.error("--font is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!options.fontBold) {
|
||||
console.error("--font-bold is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const markup = html(await fs.readFile(input, { encoding: "utf8" }));
|
||||
const svg = await satori(markup, {
|
||||
width: options.width,
|
||||
embedFont: true,
|
||||
fonts: [
|
||||
{
|
||||
name: "Font",
|
||||
data: await fs.readFile(options.font),
|
||||
weight: 400,
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
name: "Font",
|
||||
data: await fs.readFile(options.fontBold),
|
||||
weight: 700,
|
||||
style: "normal",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await fs.writeFile(output, svg);
|
||||
});
|
||||
|
||||
program.parse();
|
201
pkgs/html-to-svg/package-lock.json
generated
Normal file
201
pkgs/html-to-svg/package-lock.json
generated
Normal file
|
@ -0,0 +1,201 @@
|
|||
{
|
||||
"name": "render-svg",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "render-svg",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^12.0.0",
|
||||
"satori": "^0.10.13",
|
||||
"satori-html": "^0.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@shuding/opentype.js": {
|
||||
"version": "1.4.0-beta.0",
|
||||
"resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
|
||||
"integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==",
|
||||
"dependencies": {
|
||||
"fflate": "^0.7.3",
|
||||
"string.prototype.codepointat": "^0.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"ot": "bin/ot"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
|
||||
"integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/camelize": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
||||
"integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
|
||||
"integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/css-background-parser": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz",
|
||||
"integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA=="
|
||||
},
|
||||
"node_modules/css-box-shadow": {
|
||||
"version": "1.0.0-3",
|
||||
"resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
|
||||
"integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="
|
||||
},
|
||||
"node_modules/css-color-keywords": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
|
||||
"integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-to-react-native": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
|
||||
"integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
|
||||
"dependencies": {
|
||||
"camelize": "^1.0.0",
|
||||
"css-color-keywords": "^1.0.0",
|
||||
"postcss-value-parser": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
|
||||
"integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
|
||||
"integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="
|
||||
},
|
||||
"node_modules/hex-rgb": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz",
|
||||
"integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/linebreak": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
|
||||
"integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
|
||||
"dependencies": {
|
||||
"base64-js": "0.0.8",
|
||||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="
|
||||
},
|
||||
"node_modules/parse-css-color": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz",
|
||||
"integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==",
|
||||
"dependencies": {
|
||||
"color-name": "^1.1.4",
|
||||
"hex-rgb": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-value-parser": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"node_modules/satori": {
|
||||
"version": "0.10.13",
|
||||
"resolved": "https://registry.npmjs.org/satori/-/satori-0.10.13.tgz",
|
||||
"integrity": "sha512-klCwkVYMQ/ZN5inJLHzrUmGwoRfsdP7idB5hfpJ1jfiJk1ErDitK8Hkc6Kll1+Ox2WtqEuGecSZLnmup3CGzvQ==",
|
||||
"dependencies": {
|
||||
"@shuding/opentype.js": "1.4.0-beta.0",
|
||||
"css-background-parser": "^0.1.0",
|
||||
"css-box-shadow": "1.0.0-3",
|
||||
"css-to-react-native": "^3.0.0",
|
||||
"emoji-regex": "^10.2.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"linebreak": "^1.1.0",
|
||||
"parse-css-color": "^0.2.1",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"yoga-wasm-web": "^0.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/satori-html": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/satori-html/-/satori-html-0.3.2.tgz",
|
||||
"integrity": "sha512-wjTh14iqADFKDK80e51/98MplTGfxz2RmIzh0GqShlf4a67+BooLywF17TvJPD6phO0Hxm7Mf1N5LtRYvdkYRA==",
|
||||
"dependencies": {
|
||||
"ultrahtml": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string.prototype.codepointat": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
|
||||
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
|
||||
},
|
||||
"node_modules/tiny-inflate": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
|
||||
},
|
||||
"node_modules/ultrahtml": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.5.3.tgz",
|
||||
"integrity": "sha512-GykOvZwgDWZlTQMtp5jrD4BVL+gNn2NVlVafjcFUJ7taY20tqYdwdoWBFy6GBJsNTZe1GkGPkSl5knQAjtgceg=="
|
||||
},
|
||||
"node_modules/unicode-trie": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
||||
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
||||
"dependencies": {
|
||||
"pako": "^0.2.5",
|
||||
"tiny-inflate": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yoga-wasm-web": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz",
|
||||
"integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA=="
|
||||
}
|
||||
}
|
||||
}
|
17
pkgs/html-to-svg/package.json
Normal file
17
pkgs/html-to-svg/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "html-to-svg",
|
||||
"version": "1.0.0",
|
||||
"description": "Convert satori compatible HTML to SVG",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
"author": "oddlama <oddlama@oddlama.org>",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"html-to-svg": "./main.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^12.0.0",
|
||||
"satori": "^0.10.13",
|
||||
"satori-html": "^0.3.2"
|
||||
}
|
||||
}
|
2
topology/icons/interfaces/wifi.svg
Normal file
2
topology/icons/interfaces/wifi.svg
Normal file
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 -5 34 34" xmlns="http://www.w3.org/2000/svg"><path d="m29.294 7.765c-.572.016-1.03.484-1.03 1.059s.458 1.043 1.029 1.059h.002c.572-.016 1.03-.484 1.03-1.059s-.458-1.043-1.029-1.059zm-1.059 7.412v.029c0 .585.474 1.059 1.059 1.059s1.059-.474 1.059-1.059c0-.01 0-.02 0-.031v.001-3.53c0-.009 0-.019 0-.029 0-.585-.474-1.059-1.059-1.059s-1.059.474-1.059 1.059v.031-.002zm-2.47-7.412c.572.016 1.03.484 1.03 1.059s-.458 1.043-1.029 1.059h-.002-3.882v1.412h2.47c.572.016 1.03.484 1.03 1.059s-.458 1.043-1.029 1.059h-.002-2.47v1.765.029c0 .585-.474 1.059-1.059 1.059s-1.059-.474-1.059-1.059c0-.01 0-.02 0-.031v.002-6.354c0-.001 0-.003 0-.005 0-.582.472-1.054 1.054-1.054h.005zm8.117 2.117v4.235c0 3.119-2.529 5.647-5.647 5.647h-2.118c-2.225 2.599-5.51 4.235-9.176 4.235s-6.951-1.636-9.163-4.219l-.014-.016h-2.118c-3.119 0-5.647-2.529-5.647-5.647v-4.235c0-3.119 2.529-5.647 5.647-5.647h2.118c2.225-2.599 5.51-4.235 9.176-4.235s6.951 1.636 9.163 4.219l.014.016h2.118c3.119 0 5.647 2.529 5.647 5.647zm-19.412-2.117c-.572.016-1.03.484-1.03 1.059s.458 1.043 1.029 1.059h.001c.572-.016 1.03-.484 1.03-1.059s-.458-1.043-1.029-1.059h-.002zm-1.059 7.412c.016.572.484 1.03 1.059 1.03s1.043-.458 1.059-1.029v-.002-3.53c-.016-.572-.484-1.03-1.059-1.03s-1.043.458-1.059 1.029v.002zm-3.177.117 1.647-6c.068-.136.107-.297.107-.466 0-.589-.478-1.067-1.067-1.067-.504 0-.927.35-1.039.821l-.001.007-.706 2.588-.706-2.588c-.138-.462-.56-.793-1.059-.793s-.92.331-1.057.786l-.002.008-.706 2.588-.706-2.588c-.124-.46-.538-.794-1.029-.794-.588 0-1.064.476-1.064 1.064 0 .158.034.307.096.442l-.003-.007 1.647 6c.072.527.518.928 1.059.928s.987-.402 1.058-.923l.001-.006.706-2.47.706 2.47c.051.544.506.967 1.059.967s1.008-.422 1.058-.962v-.004zm18-9.647h-7.411c-.004 0-.009 0-.014 0-1.747 0-3.163 1.416-3.163 3.163v.014-.001 6.353.019c0 1.669-1.292 3.037-2.93 3.157l-.01.001h13.53.018c2.329 0 4.218-1.888 4.218-4.218 0-.006 0-.012 0-.019v.001-4.236c0-.005 0-.011 0-.018 0-2.329-1.888-4.218-4.218-4.218-.006 0-.012 0-.019 0h.001z"/></svg>
|
After Width: | Height: | Size: 2.1 KiB |
10
topology/icons/services/openssh.svg
Normal file
10
topology/icons/services/openssh.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="-9.312 -18.987 141.495 141.495" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;}</style>
|
||||
</defs>
|
||||
<rect x="-9.312" y="-18.987" width="141.495" height="141.495" style="fill: rgb(235, 235, 235);"/>
|
||||
<g>
|
||||
<path class="st0" d="M5.47,0h111.93c3.01,0,5.47,2.46,5.47,5.47v92.58c0,3.01-2.46,5.47-5.47,5.47H5.47 c-3.01,0-5.47-2.46-5.47-5.47V5.47C0,2.46,2.46,0,5.47,0L5.47,0z M31.84,38.55l17.79,18.42l2.14,2.13l-2.12,2.16L31.68,80.31 l-5.07-5l15.85-16.15L26.81,43.6L31.84,38.55L31.84,38.55z M94.1,79.41H54.69v-6.84H94.1V79.41L94.1,79.41z M38.19,9.83 c3.19,0,5.78,2.59,5.78,5.78s-2.59,5.78-5.78,5.78c-3.19,0-5.78-2.59-5.78-5.78S35,9.83,38.19,9.83L38.19,9.83z M18.95,9.83 c3.19,0,5.78,2.59,5.78,5.78s-2.59,5.78-5.78,5.78c-3.19,0-5.78-2.59-5.78-5.78S15.75,9.83,18.95,9.83L18.95,9.83z M7.49,5.41 h107.91c1.15,0,2.09,0.94,2.09,2.09v18.32H5.4V7.5C5.4,6.35,6.34,5.41,7.49,5.41L7.49,5.41z" style=""/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1,023 B |
7
topology/icons/services/vaultwarden.svg
Normal file
7
topology/icons/services/vaultwarden.svg
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="imgur" role="img"
|
||||
viewBox="0 0 512 512"><rect
|
||||
width="512" height="512"
|
||||
rx="15%"
|
||||
fill="#175DDC"/><path fill="#ffffff" d="M372 297V131H256v294c47-28 115-74 116-128zm49-198v198c0 106-152 181-165 181S91 403 91 297V99s0-17 17-17h296s17 0 17 17z"/></svg>
|
After Width: | Height: | Size: 419 B |
|
@ -5,14 +5,29 @@
|
|||
}: let
|
||||
inherit
|
||||
(lib)
|
||||
concatStringsSep
|
||||
mkDefault
|
||||
mkIf
|
||||
;
|
||||
in {
|
||||
topology.self.services = {
|
||||
vaultwarden = mkIf config.services.vaultwarden.enable {
|
||||
name = "Vaultwarden";
|
||||
info = "https://pw.example.com";
|
||||
details.listen.text = "[::]:3000";
|
||||
openssh = mkIf config.services.openssh.enable {
|
||||
hidden = mkDefault true; # Causes a lot of much clutter
|
||||
name = "OpenSSH";
|
||||
icon = "openssh";
|
||||
info = "port: ${concatStringsSep ", " (map toString config.services.openssh.ports)}";
|
||||
};
|
||||
|
||||
vaultwarden = let
|
||||
domain = config.services.vaultwarden.config.domain or config.services.vaultwarden.config.DOMAIN or null;
|
||||
address = config.services.vaultwarden.config.rocketAddress or config.services.vaultwarden.config.ROCKET_ADDRESS or null;
|
||||
port = config.services.vaultwarden.config.rocketPort or config.services.vaultwarden.config.ROCKET_PORT or null;
|
||||
in
|
||||
mkIf config.services.vaultwarden.enable {
|
||||
name = "Vaultwarden";
|
||||
icon = "vaultwarden";
|
||||
info = mkIf (domain != null) domain;
|
||||
details.listen = mkIf (address != null && port != null) {text = "${address}:${toString port}";};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ f: {
|
|||
attrValues
|
||||
flatten
|
||||
flip
|
||||
mkDefault
|
||||
mkOption
|
||||
types
|
||||
;
|
||||
|
@ -47,9 +48,8 @@ in
|
|||
};
|
||||
|
||||
icon = mkOption {
|
||||
description = "The icon for this interface. If null, an icon will be selected from `icons.interfaces` based on the specified type.";
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
description = "The icon for this interface. Must be a valid entry in icons.interfaces. If null, an icon will be selected based on the type.";
|
||||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
addresses = mkOption {
|
||||
|
@ -88,6 +88,18 @@ in
|
|||
});
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
icon = mkDefault (
|
||||
{
|
||||
ethernet = "ethernet";
|
||||
wireguard = "wireguard";
|
||||
wifi = "wifi";
|
||||
}
|
||||
.${submod.config.type}
|
||||
or null
|
||||
);
|
||||
};
|
||||
}));
|
||||
};
|
||||
};
|
||||
|
@ -104,6 +116,10 @@ in
|
|||
assertion = interface.network != null -> config.networks ? ${interface.network};
|
||||
message = "topology: nodes.${node.id}.interfaces.${interface.id} refers to an unknown network '${interface.network}'";
|
||||
}
|
||||
{
|
||||
assertion = interface.icon != null -> config.icons.interfaces ? ${interface.icon};
|
||||
message = "topology: nodes.${node.id}.interfaces.${interface.id} refers to an unknown icon icons.interfaces.${interface.icon}";
|
||||
}
|
||||
]
|
||||
++ flip map interface.physicalConnections (
|
||||
physicalConnection: {
|
||||
|
|
|
@ -5,6 +5,9 @@ f: {
|
|||
}: let
|
||||
inherit
|
||||
(lib)
|
||||
attrValues
|
||||
flatten
|
||||
flip
|
||||
mkOption
|
||||
types
|
||||
;
|
||||
|
@ -30,19 +33,27 @@ in
|
|||
type = types.str;
|
||||
};
|
||||
|
||||
hidden = mkOption {
|
||||
description = "Whether this service should be hidden from graphs";
|
||||
default = true;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
icon = mkOption {
|
||||
description = "The icon for this service";
|
||||
type = types.nullOr types.path;
|
||||
description = "The icon for this service. Must be a valid entry in icons.services.";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
info = mkOption {
|
||||
description = "Additional high-profile information about this service, usually the url or listen address. Most likely shown directly below the name.";
|
||||
default = "";
|
||||
type = types.lines;
|
||||
};
|
||||
|
||||
details = mkOption {
|
||||
description = "Additional detail sections that should be shown to the user.";
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule (detailSubmod: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
|
@ -71,4 +82,18 @@ in
|
|||
};
|
||||
});
|
||||
};
|
||||
|
||||
config = {
|
||||
assertions = flatten (flip map (attrValues config.nodes) (
|
||||
node:
|
||||
flip map (attrValues node.services) (
|
||||
service: [
|
||||
{
|
||||
assertion = service.icon != null -> config.icons.services ? ${service.icon};
|
||||
message = "topology: nodes.${node.id}.services.${service.id} refers to an unknown icon icons.services.${service.icon}";
|
||||
}
|
||||
]
|
||||
)
|
||||
));
|
||||
};
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ in {
|
|||
|
||||
config.renderers.d2.output = pkgs.runCommand "build-d2-topology" {} ''
|
||||
mkdir -p $out
|
||||
cp ${import ./network.nix args} $out/network.d2
|
||||
# cp ${import ./network.nix args} $out/network.d2
|
||||
ln -s ${import ./network.nix args} $out/svgs
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -8,26 +8,49 @@
|
|||
(lib)
|
||||
attrValues
|
||||
concatLines
|
||||
filter
|
||||
hasSuffix
|
||||
head
|
||||
optionalString
|
||||
splitString
|
||||
tail
|
||||
;
|
||||
|
||||
#toD2 = _nodeName: node: ''
|
||||
# ${node.id}: |md
|
||||
# # ${node.id}
|
||||
getIcon = registry: iconName:
|
||||
if iconName == null
|
||||
then null
|
||||
else config.icons.${registry}.${iconName}.file or null;
|
||||
|
||||
# ## Disks:
|
||||
# ${concatLines (mapAttrsToList (_: v: "- ${v.id}") node.disks)}
|
||||
mkImage = twAttrs: file:
|
||||
if file == null
|
||||
then ''
|
||||
<div tw="flex flex-none bg-[#000000] ${twAttrs}"></div>
|
||||
''
|
||||
else if hasSuffix ".svg" file
|
||||
then let
|
||||
withoutPrefix = head (tail (splitString "<svg " (builtins.readFile file)));
|
||||
content = head (splitString "</svg>" withoutPrefix);
|
||||
in ''<svg tw="${twAttrs}" ${content}</svg>''
|
||||
else if hasSuffix ".png" file
|
||||
# FIXME: TODO png, jpg, ...
|
||||
then ''
|
||||
<img tw="${twAttrs}" src="data:image/png;base64,${"TODO"}/>"
|
||||
''
|
||||
else builtins.throw "Unsupported icon file type: ${file}";
|
||||
|
||||
# ## Interfaces:
|
||||
# ${concatLines (mapAttrsToList (_: v: "- ${v.id}, mac ${toString v.mac}, addrs ${toString v.addresses}, network ${toString v.network}") node.interfaces)}
|
||||
|
||||
# ## Firewall Zones:
|
||||
# ${concatLines (mapAttrsToList (_: v: "- ${v.id}, mac ${toString v.mac}, addrs ${toString v.addresses}, network ${toString v.network}") node.firewallRules)}
|
||||
|
||||
# ## Services:
|
||||
# ${concatLines (mapAttrsToList (_: v: "- ${v.id}, name ${toString v.name}, icon ${toString v.icon}, url ${toString v.url}") node.services)}
|
||||
# |
|
||||
#'';
|
||||
mkSpacer = name:
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-row w-full items-center">
|
||||
<div tw="flex grow h-0.5 my-4 bg-[#242931] border-0"></div>
|
||||
<div tw="flex px-4">
|
||||
<span tw="text-[#b6beca] font-bold">${name}</span>
|
||||
</div>
|
||||
<div tw="flex grow h-0.5 my-4 bg-[#242931] border-0"></div>
|
||||
</div>
|
||||
'';
|
||||
|
||||
netToD2 = net: ''
|
||||
${net.id}: ${net.name} {
|
||||
|
@ -56,13 +79,117 @@
|
|||
# ${node.id}.${interface.id} -- ${x.node}.${x.interface}
|
||||
#''));
|
||||
|
||||
nodeInterfaceHtmlSpacing = ''
|
||||
<div tw="flex mt-2"></div>
|
||||
'';
|
||||
nodeInterfaceHtml = interface: let
|
||||
color =
|
||||
if interface.virtual
|
||||
then "#242931"
|
||||
else "#70a5eb";
|
||||
in
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-row items-center my-2">
|
||||
<div tw="flex flex-row flex-none bg-[${color}] w-4 h-1"></div>
|
||||
<div tw="flex flex-row flex-none items-center bg-[${color}] text-[#101419] rounded-lg px-2 py-1 w-46 h-8 mr-4">
|
||||
${mkImage "w-6 h-6 mr-2" (getIcon "interfaces" interface.icon)}
|
||||
<span tw="font-bold">${interface.id}</span>
|
||||
</div>
|
||||
<span>addrs: ${toString interface.addresses}</span>
|
||||
</div>
|
||||
'';
|
||||
|
||||
nodeServiceDetailsHeader =
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex pt-2"></div>
|
||||
'';
|
||||
|
||||
nodeServiceDetail = detail:
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-row mt-1">
|
||||
<span tw="flex flex-none w-20 font-bold pl-1">${detail.name}</span>
|
||||
<span tw="flex grow">${detail.text}</span>
|
||||
</div>
|
||||
'';
|
||||
|
||||
nodeServiceDetails = service:
|
||||
optionalString (service.details != {}) nodeServiceDetailsHeader
|
||||
# FIXME: order not respected
|
||||
+ concatLines ((map nodeServiceDetail) (attrValues service.details));
|
||||
|
||||
nodeServiceHtml = service:
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-col mx-4 mt-4 bg-[#21262e] rounded-lg p-2">
|
||||
<div tw="flex flex-row items-center">
|
||||
${mkImage "w-16 h-16 mr-4 rounded-lg" (getIcon "services" service.icon)}
|
||||
<div tw="flex flex-col grow">
|
||||
<h1 tw="text-xl font-bold m-0">${service.name}</h1>
|
||||
${optionalString (service.info != "") ''<p tw="text-base m-0">${service.info}</p>''}
|
||||
</div>
|
||||
</div>
|
||||
${nodeServiceDetails service}
|
||||
</div>
|
||||
'';
|
||||
|
||||
nodeHtml = node: let
|
||||
services = filter (x: !x.hidden) (attrValues node.services);
|
||||
in
|
||||
/*
|
||||
html
|
||||
*/
|
||||
''
|
||||
<div tw="flex flex-col w-full h-full items-center font-mono text-[#e3e6eb]" style="font-family: 'JetBrains Mono'">
|
||||
<div tw="flex flex-col w-full h-full bg-[#101419] py-2 rounded-xl">
|
||||
<div tw="flex flex-row mx-6 my-2">
|
||||
<h2 tw="grow text-4xl font-bold">${node.name}</h2>
|
||||
<div tw="flex grow"></div>
|
||||
<h2 tw="text-4xl" style="font-family: 'Segoe UI Emoji'">${node.type}</h2>
|
||||
</div>
|
||||
|
||||
${optionalString (node.interfaces != {}) (mkSpacer "Interfaces" + nodeInterfaceHtmlSpacing)}
|
||||
${concatLines (map nodeInterfaceHtml (attrValues node.interfaces))}
|
||||
${optionalString (node.interfaces != {}) nodeInterfaceHtmlSpacing}
|
||||
|
||||
${optionalString (services != []) (mkSpacer "Services")}
|
||||
${concatLines (map nodeServiceHtml services)}
|
||||
|
||||
<div tw="flex mb-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
'';
|
||||
|
||||
nodeToD2 = node: ''
|
||||
${node.id}: ${node.name} {}
|
||||
|
||||
${concatLines (map (nodeInterfaceToD2 node) (attrValues node.interfaces))}
|
||||
'';
|
||||
|
||||
generateNodeSvg = node: ''
|
||||
${lib.getExe pkgs.html-to-svg} \
|
||||
--font ${pkgs.jetbrains-mono}/share/fonts/truetype/JetBrainsMono-Regular.ttf \
|
||||
--font-bold ${pkgs.jetbrains-mono}/share/fonts/truetype/JetBrainsMono-Bold.ttf \
|
||||
--width 680 \
|
||||
${pkgs.writeText "${node.name}.html" (nodeHtml node)} \
|
||||
$out/${node.name}.svg
|
||||
'';
|
||||
in
|
||||
pkgs.writeText "network.d2" ''
|
||||
${concatLines (map netToD2 (attrValues config.networks))}
|
||||
${concatLines (map nodeToD2 (attrValues config.nodes))}
|
||||
#pkgs.writeText "network.d2" ''
|
||||
# ${concatLines (map netToD2 (attrValues config.networks))}
|
||||
# ${concatLines (map nodeToD2 (attrValues config.nodes))}
|
||||
#''
|
||||
pkgs.runCommand "generate-node-svgs" {} ''
|
||||
mkdir -p $out
|
||||
${concatLines (map generateNodeSvg (attrValues config.nodes))}
|
||||
''
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue