Compare commits

..

3 Commits

Author SHA1 Message Date
pinpox
7c3b3ffd8e Update flake.lock 2025-10-23 12:05:12 +02:00
pinpox
ae5712229c Fix python linter errors 2025-10-23 12:04:56 +02:00
pinpox
0ea561f998 Regenerate vars 2025-10-23 12:04:08 +02:00
143 changed files with 3199 additions and 1711 deletions

View File

@@ -76,6 +76,7 @@ in
cmd = "su - text-user -c 'pytest -s -n0 -m service_runner -p no:cacheprovider -o addopts="" ${cli.passthru.sourceWithTests}/clan_lib/llm'" cmd = "su - text-user -c 'pytest -s -n0 -m service_runner -p no:cacheprovider -o addopts="" ${cli.passthru.sourceWithTests}/clan_lib/llm'"
print("Running tests with command: " + cmd) print("Running tests with command: " + cmd)
# Run tests as text-user (environment variables are set automatically) # Run tests as text-user (environment variables are set automatically)
peer1.succeed(cmd) peer1.succeed(cmd)
''; '';

View File

@@ -1,17 +1,29 @@
diff --git a/src/main.rs b/src/main.rs From 46ec73cdeac58807a49b738c44d2e8d5dfbc5fc8 Mon Sep 17 00:00:00 2001
index 8baf5924a7db..1234567890ab 100644 From: pinpox <git@pablo.tools>
--- a/src/main.rs Date: Mon, 20 Oct 2025 16:30:22 +0200
+++ b/src/main.rs Subject: [PATCH] patch switch-to-configuration-ng for container tests
@@ -1295,6 +1295,12 @@ won't take effect until you reboot the system.
---
pkgs/by-name/sw/switch-to-configuration-ng/src/src/main.rs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/pkgs/by-name/sw/switch-to-configuration-ng/src/src/main.rs b/pkgs/by-name/sw/switch-to-configuration-ng/src/src/main.rs
index f4d339ccf60e..c50863e15e32 100644
--- a/pkgs/by-name/sw/switch-to-configuration-ng/src/src/main.rs
+++ b/pkgs/by-name/sw/switch-to-configuration-ng/src/src/main.rs
@@ -1320,6 +1320,12 @@ won't take effect until you reboot the system.
for (mountpoint, current_filesystem) in current_filesystems { for (mountpoint, current_filesystem) in current_filesystems {
// Use current version of systemctl binary before daemon is reexeced. // Use current version of systemctl binary before daemon is reexeced.
+ +
+ // Skip filesystem comparison if x-initrd.mount is present in options + // Skip filesystem comparison if x-initrd.mount is present in options
+ if current_filesystem.options.contains("x-initrd.mount") { + if current_filesystem.options.contains("x-initrd.mount") {
+ continue; + continue;
+ } + }
+ +
let unit = path_to_unit_name(&current_system_bin, &mountpoint); let unit = path_to_unit_name(&current_system_bin, &mountpoint);
if let Some(new_filesystem) = new_filesystems.get(&mountpoint) { if let Some(new_filesystem) = new_filesystems.get(&mountpoint) {
if current_filesystem.fs_type != new_filesystem.fs_type if current_filesystem.fs_type != new_filesystem.fs_type
--
2.51.0

View File

@@ -16,7 +16,6 @@
options = { options = {
host = lib.mkOption { host = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "";
description = '' description = ''
ip address or hostname (domain) of the machine ip address or hostname (domain) of the machine
''; '';

View File

@@ -1,6 +1,6 @@
[ [
{ {
"publickey": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck", "publickey": "age1yr5dfj6sx0lrcyegc5jentmwsf779wsc24lkd65dhrpkney0m59q5j060j",
"type": "age" "type": "age"
} }
] ]

View File

@@ -1,15 +1,14 @@
{ {
"data": "ENC[AES256_GCM,data:ACFpRJRDIgVPurZwHYW0J1MnvyuiRGnXMeQj1nb9rDAIqHbZzZk8+E0Nu1+EdXwk78ziP6tHR1GQP2ILTtpLME4lXXRVjouW5Eo=,iv:ctR1HENO3XGIq1/gzYi47nateYzsSK317EKn92ptqDI=,tag:q1yuk/ZMx3nuORkiT/XXqg==,type:str]", "data": "ENC[AES256_GCM,data:ldQaNVkxK3vRIMIT28bnVL7Ls6XQowltSQAECmHC7/c9RzSPcIlHyPelZBfhgWWZpKVQiUG8juedn6W/cQ+zDZK6ViY1AWr/RYY=,iv:ZqOfwCl0YGctS/m/BGCz2XL9BBCt2IkpIaSqgxpuLaI=,tag:jcfqLJsAuH0ijlOtAyePBw==,type:str]",
"sops": { "sops": {
"age": [ "age": [
{ {
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvMUtabnp3V0dzNFFYRzk0\nd0ZJbUtDMXRPRGxpRjhYR1MyQzdJYWdJTUFrCjBNV0pPTTlIOHBBbzlEQkFzVy92\ndENxcDdIZlNDSm1oZTNveUtIeVc3MXcKLS0tIGtocENjMFNYT0s1LzhYNy92QU5G\nREVEdjErb0xPSE1yb0g5bGlackh6bEUKwxBoDteD7+JfnlFF71CHx4oEdV/TFYcF\n3JPYUbTWAIyMtUu/CLbX+Pn9Mv+McrEIqhwT7TWL/YbELKVadX/k5Q==\n-----END AGE ENCRYPTED FILE-----\n" "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGUWZrL294aTRBUjY1NkdV\nUHFRWW5VdTZkVThRQmdnb1hHS3M1U1VncXpBCkM1ejMxNUh1MGNFeUdRdWpMbVE5\nRk1TcUJuaFVBc01POXlRT3BaS0w1UUkKLS0tIG5mQ2xYN0Z5ZWVWOE04YmhHUmFO\neWxMSTNOOWR5UlBiYi95ZU5TK2RTdEEK6b0uvQ9TrLNw6x41hFMBM4hynl1H1MBu\nnMTxPjQbOP7iFHd5RFbexPHfOitcHowpt3+6wIwIS1sdTFOpqTc5cw==\n-----END AGE ENCRYPTED FILE-----\n"
} }
], ],
"lastmodified": "2025-09-18T14:33:37Z", "lastmodified": "2025-10-23T09:40:31Z",
"mac": "ENC[AES256_GCM,data:4631iJmioJ2vZ2PTFbdEJu7UqtyQbp43XBlgEbFAviGZdugb3weVI24rJ8m1Rdnxq8uciEeiX6YHBhURdWQY4JNm2wTGnjz7e2PwQ8FCwOmxCcIQPpdKKsziq/M4HArgD66eUxIWfTt1yJfHgBcUuuANbrbH8MirllT+hJTBhqE=,iv:rM8a/MpKbK7DlqjuR4BG77XDHLK11Q+E2rzZLDJalhk=,tag:bbGMn4anXrLHg4eLA0/CXA==,type:str]", "mac": "ENC[AES256_GCM,data:rjoogj7WVVxOJYMGvZpFkrYcttme3YEZFdHiBy6FY/wbW6vJhxtMVsMlmtg3Ak/fQwxFQOKlXRVSxo9qGk1b05iYxZdZrzvdpH4wVD3xjzDWhIurDsuSHFqHyrx6TqfZzpKKijPTZESe6C/CCvV3uWo/BgBlOM0CrH3QHig4spw=,iv:mKKxmzQPcw0m3qPOTvyjFNO84phyjnALpnqhZFf/vQ0=,tag:yXSrmSLipwDrUQQs/lS+0A==,type:str]",
"unencrypted_suffix": "_unencrypted", "version": "3.11.0"
"version": "3.10.2"
} }
} }

View File

@@ -1,33 +1,33 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@@ -1,18 +1,18 @@
{ {
"data": "ENC[AES256_GCM,data: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,iv:dGSmyDNBdVyF54bYS/Zxm2NNXZyGtLjkyYlrI9/nKvc=,tag:ip2fy76NjObWbW20HyuZUA==,type:str]", "data": "ENC[AES256_GCM,data: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,iv:cZnbdgqnDSWvbOkzjKKc/vC8xjiVSySrkpzOP+uxWKE=,tag:OA8KjOoyYJMpb2tAR8Y6OA==,type:str]",
"sops": { "sops": {
"age": [ "age": [
{ {
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck", "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNQS96MUFubVdOR2VCc2xO\ncTc5QnNHNTFpdURnSnF3dVhBQXQ3bnBuRW1RCngzSVlhSW9rNUxoSWdKcEtKVXc3\nQitLZ2NDUXBSUmxtVWpYRUlvOHVXcW8KLS0tIGZaWlRVak9NYmt2elpwYStYenRE\nanlkT3BET1FjQ2lFZkp3SXFMSkJSaVkKKkr+MNNqs6Ve3K5OrZfBEGlnc7OAthqf\nOZrP9NYOTMgkvhFsZTVpUS0zskry0iwmTNt+KeluYf0Tko8K53Kx2A==\n-----END AGE ENCRYPTED FILE-----\n" "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMMndqMFM2b3JxZDlSMUdM\nejJyWFZLU0lHN1ZYdFJJMmp2NExuY2tnYUZFCkVLbVNjQ0hYb2tld2xvQU4rNE1J\nRzFQMjV2eHhMc3AyUzlvSGV6cUFOQ1UKLS0tIEZpWVN3WHBEZ0Vjc2lkQXdJRVRB\nd296b2VZNmVQbUt6WENWNFYzb1p0NVkK/idbBu9CRAEmY2fIx3X9GJqOkpdkmdWP\nUVeNB+SAOxNK2a6ys6d1iGsnVvRmQuZ5V4GZN6xDvnpvibWdKCGKgQ==\n-----END AGE ENCRYPTED FILE-----\n"
}, },
{ {
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", "recipient": "age1yr5dfj6sx0lrcyegc5jentmwsf779wsc24lkd65dhrpkney0m59q5j060j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXd2dVYmJIbUVVeXk5Nk1E\nekFiUldVVUhRTmE4dHRiTHNDdEMyS1pRV1RrCkNScGdXVSs4UU5id29DV0pZWDQr\nenV1QmpnOFk5aFpTTUxmb0hDVHZDdFkKLS0tIHpmalJtRC94bEhaUStmeUlHT21w\nd3o3UzJHZklxK0RCYUUxc2c3aG1XclkKEPq1ZgyGiAK/Hy4zT7wfdDfPEE3vMHpR\nzwQV5y3M3DmlnKQEvJu0DpQ334CyAcubZC7cswQdUrM8TPqJhb/TuA==\n-----END AGE ENCRYPTED FILE-----\n" "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkQkprZTU1dGNONnQ3eTIr\ncVlheXlpUUlZM2wyL3l3NlY1azNkZTI2c0RvCm0rYXBkOXBMS1ZaQVVtU2gyRXMr\nd0JuclBDVEJabld6K0VVMTJ4SCtOTVkKLS0tIC9vbTdNRlVoNHZNWlB5enUyREhj\nc21XQ1U4bU1RdzMwMithMEpWSGx4UTQKxfPIPxh3yxmzCU50l+JGzj9YKlUmeuaA\nEpphjkaUokTZh2dXE7i7XOVNylB+o2f71+mlv1ak6ftL18i/VcgADQ==\n-----END AGE ENCRYPTED FILE-----\n"
} }
], ],
"lastmodified": "2025-10-21T13:17:17Z", "lastmodified": "2025-10-23T09:40:31Z",
"mac": "ENC[AES256_GCM,data:wdAFURkJZvclbz3UFPSPV9fma7zrZVEhMhsRqylGQMLepX/WohEAr8nJgeHl05be1Q8M8biPXCCoL0vfwg4BRZOkhD8PusJh8iBI3+STNQe/S1qoIK1ByfBFhJD+tIsVsgduLp6G32e6SRNvkuX3UpJqyViuRUavfQd3b8LRU4I=,iv:S3sMNTz5Kg4TxHj1tnk/ayiFuO74dR4aPnnomtkGByo=,tag:uive2bYe42s6VtPd03jTMw==,type:str]", "mac": "ENC[AES256_GCM,data:tY8mchq1LuM5j1jghjyvLeROgXEu/k+jkSf4CH3U+tnElyXYzcPt/UzbcuXVsL9xyr+n+uJz5kjtu+5sfNf2zeUgrMxaiThLk1ZnpNPq7FQKbcvysBMW4eId2hDWhi7/1T7/k9bYaGejPI9u6OFywbTW2Ic4Dw5kflw2LmXpe+E=,iv:c3bSj7TD63OtnDKaWjX/vjc0ffL8Hdxx4T5XNLa3YuI=,tag:IBXfB3ze1sSmbbkU35svxw==,type:str]",
"version": "3.11.0" "version": "3.11.0"
} }
} }

View File

@@ -1,19 +1,18 @@
{ {
"data": "ENC[AES256_GCM,data:Q0Vn7J0nERccBYT8HZxHF0Zi5qxmMu40n0H1c+L2SCRF6vRLdURxXKDwvh8xtTU=,iv:ucExjoYDFYy19GsBbNNldJRPBSpT+L+x4PrwTG+m2K8=,tag:/Quupyy/nnUNZsDudEMmNA==,type:str]", "data": "ENC[AES256_GCM,data:UAkF87pKrzkDHZIfBX4I3F7/qCvP40wC+73jGTrznhM97qG507rkNqc=,iv:U35X9C62qmFHRHFYO1UkKRR49jxxMrVKrbxORe1SZmg=,tag:hVRTMY+l+Fhszq2J9yGJhQ==,type:str]",
"sops": { "sops": {
"age": [ "age": [
{ {
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck", "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQWWo5OEJ5N1RTR0xMaDhL\nQnlUV2RrRXIzM01OemhQWjVkd3FNZjRhR2dzCi9IeE56b3VZTkNkdW9DMzVia3Zx\nbklxWmFpenRjdEIrc0ZDTGdmSTAxRTQKLS0tIHZJdjdYUzhhY0YzQjRqS0psZmpI\nVHJpUjNZNHRpc2ZWSml1TVNNejhiT28K8TTP/J+XspXZ7TVYj9YaBhEodPIXjojB\nRLqAIgJXRaK4NCLukC6l0IMii6w5J/512RnO2ZBTGhKfbdLfyLOFqg==\n-----END AGE ENCRYPTED FILE-----\n" "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBR2pTN1Z2czJOelM1SjQ4\nQ1BxK09iT0JlNjBEMThoRnRXTXBRejk3ZVVRClJMeVlHTklwTzRubldvdmdmUzRJ\ndDR1M3pEMHROUktyckxoODBxTXozMUUKLS0tIHBUTFFseTJSaSsyWU5PdThNQ2hG\nTXpjZlhIaG1CM2E3R2JmdWlndTZzZHcKYbFqPfQ5s157FBj2Xs8Q5lXgi+FUX9aZ\ne6nOnOHvmgq6MdDK2z6WjtqP4HM/WU9iFSrGSQCvhPFweY1ILmU9tQ==\n-----END AGE ENCRYPTED FILE-----\n"
}, },
{ {
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", "recipient": "age1yr5dfj6sx0lrcyegc5jentmwsf779wsc24lkd65dhrpkney0m59q5j060j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrZVc5b0FhbzNXcG1zUDlD\neEVWcWpSRkRCMkxBTHdBM3dCbjVpR3FBa0VjCitlTmx4eUJOMHlaU0dFZEhpK3ZD\nZzlMQXVuZWpnaUNmQW9kOGtOaGVDMU0KLS0tIFNlUi9LSzF0UEJCSVBiRlRSNFQz\nNHhMbmNlRXd4ZEJQWVcvTWdCRWEzMUkKls7RbmNOdPDx8z15F+7qay9qIWx6jNsN\nTahT+GgbG29t1aGQCb0yEzKuUyAp39maxxSWToPsfCgJSYJ8RYiUng==\n-----END AGE ENCRYPTED FILE-----\n" "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2dGVGd1A5dkt6QzAyL2Ja\nSUNoSEc0K0NQc04zREZSN3JORUdMRlpCdkI4CktSSS9NTjhKTUt1YUhTZERmK1Fi\nSjhmTG1CckNTYVNnL1MrR3d1VnRLMkkKLS0tIFNBS2Z0N3BFWjRUcGRDMm5mM2FR\nNlp3VkRYN1dtMGdRSFVjNXVZb2pjbXcKYaWNCnqIe85ldUWh5RWcbxA28k/1EJV9\ntoD5y6Qu0qZqm7vhgHPSZ8fCmuiafQTUj77XhjJbfw1exac1wP0m8w==\n-----END AGE ENCRYPTED FILE-----\n"
} }
], ],
"lastmodified": "2025-09-18T14:33:39Z", "lastmodified": "2025-10-23T09:40:34Z",
"mac": "ENC[AES256_GCM,data:g+9/fRiqom2+W28ZpiF+oBj9V6ieq5Xz3sRz3GyzvHoLr6yw51JvpG2QuYNYANW0WCiUjFDkU0qPj/9gLHcuX52nc+gNaTzznb1QGPg7WCGSQI7xaMzyYsPxHpg/BOdj5CL8GyLiOWstD1ch0kc3bJmyu68sJUs04uGtHAADzsE=,iv:oASrYaZarEPDu0R3hd/jMazLgwG5r//hIdMyU/tN15o=,tag:o1fgf5oy+rlWXg88FN5Nfw==,type:str]", "mac": "ENC[AES256_GCM,data:y4bNxCWif4JL/AfkfV2wQb22xooHQUgr6//ajzgyBJ5Z9xB8WboVhyTn0nKnA6G+1PpJmoaXUWFQcv0kq6RPFw3teATw5pdKK29NS9rxqnIht5v7mrqP0CnVoFQVDz1yGtY0rotGd7hUNAXsrjb6Ewfs6N3k8QPwpTX4V7STT48=,iv:+BMrr/Vjnd8DT7aC1/ckp5LZ7UuQkffsMeGbBFxjhGA=,tag:vuUWd2UjJaVY/20QrIzC+g==,type:str]",
"unencrypted_suffix": "_unencrypted", "version": "3.11.0"
"version": "3.10.2"
} }
} }

View File

@@ -1,19 +1,18 @@
{ {
"data": "ENC[AES256_GCM,data:4NIUEK05kEQAKjR8F9mU3M/XvtZXw+X6CejVI0usMcb4WzagNz7XTVDhLWXZ9St5Ev0Y,iv:bD2+rDLMoWSqUAIZRJof0wRrJVya1xwZUTIJBdCs98I=,tag:g2s4byFHTzwU3ikcBGMElA==,type:str]", "data": "ENC[AES256_GCM,data:6iT6HrBnOhKWfDKRzI73kobk2a1XsVUZhQpBK8kK9BQa8mv+tIc45gP5GwcD,iv:K5tWZHyLznul21de/RgXloJ/7g87N/bq3EGKSOOe1HY=,tag:q9Xm92SpgKcCI8W95E509A==,type:str]",
"sops": { "sops": {
"age": [ "age": [
{ {
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck", "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQeVh2M2tqSGlOVkpzNlhU\nd0pMd1R0c0tQWnZzdXViWmtxcjl1Wk1Ka0FNCnBUUWJVbjlyR1hSNGpXNWlPRHJB\nNnMzN3BMQ2NDamFBMlhHbVdJUEZ6cjQKLS0tIEJjWmI0ZDl1NXgrSW9uc0R0LzAr\neEwwOC9DdDg2RTJHQ0M3QTFlcVBaSE0K2Du4NguefdEyY1gS6OuVdO3gHga4omcR\n8B+K1wUfIQbArxZLawPxrj7WNDoW5d4mF9fA3MeV1DFyc4KwtYZmUw==\n-----END AGE ENCRYPTED FILE-----\n" "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQb3hNVW5BK05tRllUTFRR\nUzM3aFJYamVzZEZELzRieDMwNzBtMTY2ZnpBCjdFU25uSllRYjloTjV2Zjc3RjAz\nTTl4M1pRL0g0QklhUlNjK2oveHZQWVUKLS0tIGlLdHFWMHZ6QXltdEF1YmlicXgy\ndzZVUGRmQ1JKUFN6WEhIbEMvZy9xUU0KurnoOMO9YEbsqg8zYw7vqL1hjKGnq65B\nxI+uV0aGVS79zu6Sqtm62XmgRNdySdz+seYxtISZviGRvhKsvHWi0g==\n-----END AGE ENCRYPTED FILE-----\n"
}, },
{ {
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", "recipient": "age1yr5dfj6sx0lrcyegc5jentmwsf779wsc24lkd65dhrpkney0m59q5j060j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvWkdBakVrMVR4RU8xdDlF\nRDkvL0Mrb3ltazhIMjRLZDVlSTVlaFY2ODBBCnlQM2s0SGEvZjFDN3dGWDhIN0dK\nenhQbjZ1ZS9QZzg5SE5XazZXS3dFSkkKLS0tIHJhKzhadGpjTXd4L3hOQkhpR0Fy\nYzhTN2dxVSt3OE5uZFpuWmVlYW4vd1kKwHOxP0C5mLcm4oIT/sGQtUsdsmu3LSN0\nSola5+N+IrAZ+HKnuZlDLZ5JmJSc5j/YhGNn7KR1xhkhfGSS1e3UZw==\n-----END AGE ENCRYPTED FILE-----\n" "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPSEJWanVSNEdkTDd1T3ho\nUFE2UklxRmp4L0M4MHlnM3o3Q0FkR00rRjNBCkk2SEVpQjdCeUpxN21mbG5LdUZh\nUXhwRkpwVDZuTzhUb2g3WmFnR3c4NWMKLS0tIGcrbkIrYVYyVVlBN0NyYlI4cGx1\nTitVR083K3pwL1Zlc1I3VVdVakJCVkUKCcDSfeu4Md5iPcZOcCktDvwXh0G3wdFb\nGr73xyIY9lcP2+NupMBgKTBxck4beRcJxxyFhYf9ZSkmIK3e4pFQNg==\n-----END AGE ENCRYPTED FILE-----\n"
} }
], ],
"lastmodified": "2025-09-18T14:33:39Z", "lastmodified": "2025-10-23T09:40:34Z",
"mac": "ENC[AES256_GCM,data:ehbrYqTJcsBKGHUB25JHFnKXrJ6z3LkcElZ89xVr4XxLet+odbhsjIoP2FCcxex7PlXcegMduhHBpXwNGUbX+IUNAXTxlWA9CLDmYhWuS2WLiEVXrS11NE03/zUyHdVx/C38dbIPrWD9iaYSrAiuOyfqDTh9k/Bn7vehLTtadoE=,iv:Nk2WVuJydi5tfsb1Mib4A6NocBCDp9QoIbSadq3bIDI=,tag:IaoyfCv3SkmtemXMR9XnkA==,type:str]", "mac": "ENC[AES256_GCM,data:Qh2VQ43UqUhQtMBEhjBfky2hUjTNxJcKrIm6zGteJ/X+z0FxZ9LoUew48ml+HPHvPN+vLRtgVeOkP2d3x0kq/ag4RDNFuK7IgkzrVt8vzT0IpDHJmyw1ehGYy/MhMaDR5zv0ADZV/GrXrn9O+Wj74cgGqYg15TQJ3I/a68ViYhk=,iv:aJZCcd25xaOhmrfkJZuGUAdAGb7lFrP3D5kZtZnP/v4=,tag:se43YOxOEZHawE1SnnvTzA==,type:str]",
"unencrypted_suffix": "_unencrypted", "version": "3.11.0"
"version": "3.10.2"
} }
} }

View File

@@ -1,19 +1,18 @@
{ {
"data": "ENC[AES256_GCM,data:0BmP+NwG/NGe6R5yU55/MdPEQ8E5u+VXWtvstHc4GpDtmBY=,iv:vo8XBcN7KcYjiyKvvp+XDOdP9yR9B7wJi0XlaiCdVbk=,tag:brK9ntAPSuOvw/C+oDo51g==,type:str]", "data": "ENC[AES256_GCM,data:BxHELvbSQkvKnKLFXTAuRFYSO0lD2WuZLFah9S0=,iv:Lt8kE85YZcwfD5t5bEQWTOHoD66D77u/1CqimEI7DW0=,tag:LD8oeZ+imsh9pV75+3YzkQ==,type:str]",
"sops": { "sops": {
"age": [ "age": [
{ {
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck", "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4Tk1INGtybUVlejlNNlZE\nVms3TkdRVVF1T0E4TmV3NmxvYWVEL2U3WVhNCjJIaHhBcWVlMEYxRjg5bzJpTWdJ\neUhaRTNRTmtlTW0zUXQxTVZEMkQ2MFEKLS0tIFNGWDI4b2FXTE8xQ2xqb0cyK3FI\ncktHWnE5c1ZSVFpmQU1HZmU2VVB1QmcK/s1fVmwpMMg4BYkkAJzSY7hVQWae1F7g\nmfH8EGlr74mifWUNEbd49/K13nl8atQx6bcau83JIEQR+yyihuY4Jw==\n-----END AGE ENCRYPTED FILE-----\n" "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDRmExdUNwWGRnQ2FLWXpU\nWW40V09mNTJwMEk1d0xGRjRLZ2NGRk9uQVg4CmE5cWpZVE81TzJxZm04ZWowVW1y\naUVldkc4MHJsbW1vOTh6KzFNM3lGUncKLS0tIDlDdW90dzlJTVRwN2JlYWFTZHpC\nV1pnOStuanV3MmJwWXJVbitqV0dUK28KEp0VUW7F1kRB2VqINUu1yXLbDwvvLzJn\noFd9WnA6HXxfyZvwk45DSwnb6VskuQryy9cqNYgvfiVOAaPuK0rsHg==\n-----END AGE ENCRYPTED FILE-----\n"
}, },
{ {
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", "recipient": "age1yr5dfj6sx0lrcyegc5jentmwsf779wsc24lkd65dhrpkney0m59q5j060j",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsL2FXVytUUVZnVU90bG5L\nYURiYjgwN3RuTldWMGl4clpUWmxkeUsrVzM0CkhKZFgwWHl4dWhNSWRQRXVPNDR6\na3hHNmp2RG9YNDhNM2MyV2FuOGY2UlUKLS0tIFpNU2tNOHdhRDRTdHhYWVh2NGZa\nU3J3S0hpclZzWGIwTlFyczdNZkZSZTAKXCZrLaIOVq90ejoKMaRiK0xNw8WOPcnm\nz2uxProEYvQhY8k29mhCFX5HCN0tGn1XTtHeDL7uHuKuFsnSG/fgYQ==\n-----END AGE ENCRYPTED FILE-----\n" "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiWnhRMTkxd3l5VWVLMCty\nQ1JYRGlYZ25LeXVNL0Z1bHl1UzVpeEdFYm1RCit4ZFc3YmdGeC9FTENWOGVJaUJo\nOGFPZWZHSWhlcU54WGJ6U3RNWE9PUlEKLS0tIGd0cWpBSzBWcXZWS2R3Y254bFdC\nVnRMeGJCbHNNWE5JVWVXTVZFTDgxSVUKavBd6tPVqlkZGMo1dlYOZ3U3hkRa7IuP\ni/MoRY2J6IdDVoOq1bYJzZ30apG/ADXYUNCVnqDftyD9t5Ws04SkMg==\n-----END AGE ENCRYPTED FILE-----\n"
} }
], ],
"lastmodified": "2025-09-18T14:33:39Z", "lastmodified": "2025-10-23T09:40:34Z",
"mac": "ENC[AES256_GCM,data:QkGJKj/H+MI9Mr9Up5NDUToSddY5eTz47egc2+IatfxR8RebKJ2/mYaeLV26vPdmY60bIac4N/nZkoa6IVBhkHHMvsEHsx3nD6Lro9Wf/pWP8Zddzr90LF5p2+wusq25JutKQiPKOb2gmrcagmSsH/7V/UqI/my3PMeKmw6irhw=,iv:hOtHF/cDFdNfvqCKRhJsOwAHEiQmCPjENzsg23sKG+Q=,tag:K7qG9b4fQD0VbAV8OYp3vw==,type:str]", "mac": "ENC[AES256_GCM,data:k60YIgs+HdcrGkaW4XI3iu2O6JMwlX0ToV8o/Eix27M1xJ2ipcnJI7gghWGBG3GWlzuVHAl9QlkFPu4SRv6KtP62iGzbnzXlbIe+Z1eQgkn+GGaM39SZpsMlW8T1OtV3mW6oe+NRCP7zmPgc2Yr3U08wYSooDOn4XOze5qZNbGk=,iv:q8siSK4a8CHkXcJxO4QTsX12zZxKTwHdw6qirF8j/v0=,tag:aWrxb1x+7BJQ9vIrttc+Hw==,type:str]",
"unencrypted_suffix": "_unencrypted", "version": "3.11.0"
"version": "3.10.2"
} }
} }

View File

@@ -39,6 +39,7 @@
... ...
}: }:
let let
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
# Collect searchDomains from all servers in this instance # Collect searchDomains from all servers in this instance
allServerSearchDomains = lib.flatten ( allServerSearchDomains = lib.flatten (
lib.mapAttrsToList (_name: machineConfig: machineConfig.settings.certificate.searchDomains or [ ]) ( lib.mapAttrsToList (_name: machineConfig: machineConfig.settings.certificate.searchDomains or [ ]) (
@@ -46,7 +47,7 @@
) )
); );
# Merge client's searchDomains with all servers' searchDomains # Merge client's searchDomains with all servers' searchDomains
searchDomains = lib.uniqueStrings (settings.certificate.searchDomains ++ allServerSearchDomains); searchDomains = uniqueStrings (settings.certificate.searchDomains ++ allServerSearchDomains);
in in
{ {
clan.core.vars.generators.openssh-ca = lib.mkIf (searchDomains != [ ]) { clan.core.vars.generators.openssh-ca = lib.mkIf (searchDomains != [ ]) {

View File

@@ -1,23 +1,7 @@
🚧🚧🚧 Experimental 🚧🚧🚧
Use at your own risk.
We are still refining its interfaces, instability and breakages are expected.
---
This module sets up [yggdrasil](https://yggdrasil-network.github.io/) across your clan. This module sets up [yggdrasil](https://yggdrasil-network.github.io/) across your clan.
Yggdrasil is designed to be a future-proof and decentralised alternative to the Yggdrasil is designed to be a future-proof and decentralised alternative to
structured routing protocols commonly used today on the internet. Inside your the structured routing protocols commonly used today on the internet. Inside your clan, it will allow you to reach all of your machines.
clan, it will allow you to reach all of your machines.
If you have other services in your inventory which export peers (e.g. the
`internet` or the services) as [service
exports](https://docs.clan.lol/reference/options/clan_service/#exports), they
will be added as yggdrasil peers automatically. This allows using the stable
yggdrasil IPv6 address to refer to other hosts and letting yggdrasil decide on
the best routing based on available connections.
## Example Usage ## Example Usage

View File

@@ -29,13 +29,12 @@
]; ];
}; };
options.extraPeers = lib.mkOption { options.peers = lib.mkOption {
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = [ ]; default = [ ];
description = '' description = ''
Additional static peers to configure for this host. If you use a Static peers to configure for this host.
VPN clan service, it will automatically be added as peers to other hosts. If not set, local peers will be auto-discovered
Local peers are also auto-discovered and don't need to be added.
''; '';
example = [ example = [
"tcp://192.168.1.1:6443" "tcp://192.168.1.1:6443"
@@ -46,67 +45,16 @@
}; };
}; };
perInstance = perInstance =
{ { settings, ... }:
settings,
roles,
exports,
...
}:
{ {
nixosModule = nixosModule =
{ {
config, config,
pkgs, pkgs,
lib,
clan-core,
... ...
}: }:
let
mkPeers = ip: [
# "tcp://${ip}:6443"
"quic://${ip}:6443"
"ws://${ip}:6443"
"tls://${ip}:6443"
];
select' = clan-core.inputs.nix-select.lib.select;
# TODO make it nicer @lassulus, @picnoir wants microlens
# Get a list of all exported IPs from all VPN modules
exportedPeerIPs = builtins.foldl' (
acc: e:
if e == { } then
acc
else
acc ++ (lib.flatten (builtins.filter (s: s != "") (lib.attrValues (select' "peers.*.plain" e))))
) [ ] (lib.attrValues (select' "instances.*.networking.?peers.*.host.?plain" exports));
# Construct a list of peers in yggdrasil format
exportedPeers = lib.flatten (map mkPeers exportedPeerIPs);
in
{ {
# Set <yggdrasil ip> <hostname>.<tld> for all hosts.
# Networking modules will then add themselves as peers, so we can
# always use this to resolve a host via the best possible route,
# doing fail-over if needed.
networking.extraHosts = lib.strings.concatStringsSep "\n" (
lib.filter (n: n != "") (
map (
name:
let
ipPath = "${config.clan.core.settings.directory}/vars/per-machine/${name}/yggdrasil/address/value";
in
if builtins.pathExists ipPath then
"${builtins.readFile ipPath} ${name}.${config.clan.core.settings.tld}"
else
""
) (lib.attrNames roles.default.machines)
)
);
clan.core.vars.generators.yggdrasil = { clan.core.vars.generators.yggdrasil = {
files.privateKey = { }; files.privateKey = { };
@@ -151,7 +99,7 @@
settings = { settings = {
PrivateKeyPath = "/key"; PrivateKeyPath = "/key";
IfName = "ygg"; IfName = "ygg";
Peers = lib.lists.unique (exportedPeers ++ settings.extraPeers); Peers = settings.peers;
MulticastInterfaces = [ MulticastInterfaces = [
# Ethernet is preferred over WIFI # Ethernet is preferred over WIFI
{ {

View File

@@ -17,13 +17,6 @@
roles.default.machines.peer1 = { }; roles.default.machines.peer1 = { };
roles.default.machines.peer2 = { }; roles.default.machines.peer2 = { };
}; };
# Peers are set form exports of the internet service
instances."internet" = {
module.name = "internet";
roles.default.machines.peer1.settings.host = "peer1";
roles.default.machines.peer2.settings.host = "peer2";
};
}; };
}; };

View File

@@ -140,6 +140,9 @@
pkgs, pkgs,
... ...
}: }:
let
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
in
{ {
imports = [ imports = [
(import ./shared.nix { (import ./shared.nix {
@@ -156,7 +159,7 @@
config = { config = {
systemd.services.zerotier-inventory-autoaccept = systemd.services.zerotier-inventory-autoaccept =
let let
machines = lib.uniqueStrings ( machines = uniqueStrings (
(lib.optionals (roles ? moon) (lib.attrNames roles.moon.machines)) (lib.optionals (roles ? moon) (lib.attrNames roles.moon.machines))
++ (lib.optionals (roles ? controller) (lib.attrNames roles.controller.machines)) ++ (lib.optionals (roles ? controller) (lib.attrNames roles.controller.machines))
++ (lib.optionals (roles ? peer) (lib.attrNames roles.peer.machines)) ++ (lib.optionals (roles ? peer) (lib.attrNames roles.peer.machines))

18
devFlake/flake.lock generated
View File

@@ -3,10 +3,10 @@
"clan-core-for-checks": { "clan-core-for-checks": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1761204206, "lastModified": 1760361585,
"narHash": "sha256-A4KDudGblln1yh8c95OVow2NRlHtbGZXr/pgNenyrNc=", "narHash": "sha256-v4PnSmt1hXW4dSgVWxcd1ZeEBlhO7NksNRC5cX7L5iw=",
"ref": "main", "ref": "main",
"rev": "aabbe0dfac47b7cfbe2210bcb27fb7ecce93350f", "rev": "7e7e58eb64ef61beb0a938a6622ec0122382131b",
"shallow": true, "shallow": true,
"type": "git", "type": "git",
"url": "https://git.clan.lol/clan/clan-core" "url": "https://git.clan.lol/clan/clan-core"
@@ -105,11 +105,11 @@
}, },
"nixpkgs-dev": { "nixpkgs-dev": {
"locked": { "locked": {
"lastModified": 1761544814, "lastModified": 1761164809,
"narHash": "sha256-t5f0A+2MtSWTfA6hzMNiotpIMGLlSQF2JnK9m6nkzIY=", "narHash": "sha256-3uM91Lx9WZomE6MMEBorJyEyBNiHWRIxza/GganDxew=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "e5aa45ed6c45058ec109658b2b7352a9a062cdf3", "rev": "3d2db9755e7815937fb7b8f089fad9b44bc416d8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -208,11 +208,11 @@
"nixpkgs": [] "nixpkgs": []
}, },
"locked": { "locked": {
"lastModified": 1761311587, "lastModified": 1760945191,
"narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=", "narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc", "rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -150,61 +150,10 @@ Those are very similar to NixOS VM tests, as in they run virtualized nixos machi
As of now the container test driver is a downstream development in clan-core. As of now the container test driver is a downstream development in clan-core.
Basically everything stated under the NixOS VM tests sections applies here, except some limitations. Basically everything stated under the NixOS VM tests sections applies here, except some limitations.
### Using Container Tests vs VM Tests Limitations:
Container tests are **enabled by default** for all tests using the clan testing framework. - Cannot run in interactive mode, however while the container test runs, it logs a nsenter command that can be used to log into each of the container.
They offer significant performance advantages over VM tests: - setuid binaries don't work
- **Faster startup**
- **Lower resource usage**: No full kernel boot or hardware emulation overhead
To control whether a test uses containers or VMs, use the `clan.test.useContainers` option:
```nix
{
clan = {
directory = ./.;
test.useContainers = true; # Use containers (default)
# test.useContainers = false; # Use VMs instead
};
}
```
**When to use VM tests instead of container tests:**
- Testing kernel features, modules, or boot processes
- Testing hardware-specific features
- When you need full system isolation
### System Requirements for Container Tests
Container tests require the **`uid-range`** system feature** in the Nix sandbox.
This feature allows Nix to allocate a range of UIDs for containers to use, enabling `systemd-nspawn` containers to run properly inside the Nix build sandbox.
**Configuration:**
The `uid-range` feature requires the `auto-allocate-uids` setting to be enabled in your Nix configuration.
To verify or enable it, add to your `/etc/nix/nix.conf` or NixOS configuration:
```nix
settings.experimental-features = [
"auto-allocate-uids"
];
nix.settings.auto-allocate-uids = true;
nix.settings.system-features = [ "uid-range" ];
```
**Technical details:**
- Container tests set `requiredSystemFeatures = [ "uid-range" ];` in their derivation (see `lib/test/container-test-driver/driver-module.nix:98`)
- Without this feature, containers cannot properly manage user namespaces and will fail to start
### Limitations
- Cannot run in interactive mode, however while the container test runs, it logs a nsenter command that can be used to log into each of the containers.
- Early implementation and limited by features.
### Where to find examples for NixOS container tests ### Where to find examples for NixOS container tests

18
flake.lock generated
View File

@@ -71,11 +71,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1761339987, "lastModified": 1760721282,
"narHash": "sha256-IUaawVwItZKi64IA6kF6wQCLCzpXbk2R46dHn8sHkig=", "narHash": "sha256-aAHphQbU9t/b2RRy2Eb8oMv+I08isXv2KUGFAFn7nCo=",
"owner": "nix-darwin", "owner": "nix-darwin",
"repo": "nix-darwin", "repo": "nix-darwin",
"rev": "7cd9aac79ee2924a85c211d21fafd394b06a38de", "rev": "c3211fcd0c56c11ff110d346d4487b18f7365168",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -115,10 +115,10 @@
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 315532800, "lastModified": 315532800,
"narHash": "sha256-yDxtm0PESdgNetiJN5+MFxgubBcLDTiuSjjrJiyvsvM=", "narHash": "sha256-Zk4ebRwxwsWjR20i15LsNk13uVhgubF44pJQddcCt4w=",
"rev": "d7f52a7a640bc54c7bb414cca603835bf8dd4b10", "rev": "cb82756ecc37fa623f8cf3e88854f9bf7f64af93",
"type": "tarball", "type": "tarball",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre871443.d7f52a7a640b/nixexprs.tar.xz" "url": "https://releases.nixos.org/nixpkgs/nixpkgs-25.11pre880602.cb82756ecc37/nixexprs.tar.xz"
}, },
"original": { "original": {
"type": "tarball", "type": "tarball",
@@ -181,11 +181,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1761311587, "lastModified": 1760945191,
"narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=", "narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc", "rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -137,12 +137,6 @@ in
default = { }; default = { };
type = types.submoduleWith { type = types.submoduleWith {
specialArgs = { specialArgs = {
self = throw ''
'self' is banned in the use of clan.services
Use 'exports' instead: https://docs.clan.lol/reference/options/clan_service/#exports
---
If you really need to used 'self' here, that makes the module less portable
'';
inherit (config.clanSettings) inherit (config.clanSettings)
clan-core clan-core
nixpkgs nixpkgs

View File

@@ -7,10 +7,14 @@
... ...
}: }:
let let
inherit (lib) mkOption types uniqueStrings; inherit (lib) mkOption types;
inherit (types) attrsWith submoduleWith; inherit (types) attrsWith submoduleWith;
errorContext = "Error context: ${lib.concatStringsSep "." _ctx}"; errorContext = "Error context: ${lib.concatStringsSep "." _ctx}";
# TODO:
# Remove once this gets merged upstream; performs in O(n*log(n) instead of O(n^2))
# https://github.com/NixOS/nixpkgs/pull/355616/files
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
/** /**
Merges the role- and machine-settings using the role interface Merges the role- and machine-settings using the role interface
@@ -77,7 +81,6 @@ let
applySettings = applySettings =
instanceName: instance: instanceName: instance:
lib.mapAttrs (roleName: role: { lib.mapAttrs (roleName: role: {
settings = config.instances.${instanceName}.roles.${roleName}.finalSettings.config;
machines = lib.mapAttrs (machineName: _v: { machines = lib.mapAttrs (machineName: _v: {
settings = settings =
config.instances.${instanceName}.roles.${roleName}.machines.${machineName}.finalSettings.config; config.instances.${instanceName}.roles.${roleName}.machines.${machineName}.finalSettings.config;
@@ -155,29 +158,6 @@ in
( (
{ name, ... }@role: { name, ... }@role:
{ {
options.finalSettings = mkOption {
default = evalMachineSettings instance.name role.name null role.config.settings { };
type = types.raw;
description = ''
Final evaluated settings of the curent-machine
This contains the merged and evaluated settings of the role interface,
the role settings and the machine settings.
Type: 'configuration' as returned by 'lib.evalModules'
'';
apply = lib.warn ''
=== WANRING ===
'roles.<roleName>.settings' do not contain machine specific settings.
Prefer `machines.<machineName>.settings` instead. (i.e `perInstance: roles.<roleName>.machines.<machineName>.settings`)
If you have a use-case that requires access to the original role settings without machine overrides.
Contact us via matrix (https://matrix.to/#/#clan:clan.lol) or file an issue: https://git.clan.lol
This feature will be removed in the next release
'';
};
# instances.{instanceName}.roles.{roleName}.machines # instances.{instanceName}.roles.{roleName}.machines
options.machines = mkOption { options.machines = mkOption {
description = '' description = ''
@@ -236,7 +216,7 @@ in
options.extraModules = lib.mkOption { options.extraModules = lib.mkOption {
default = [ ]; default = [ ];
type = types.listOf types.deferredModule; type = types.listOf (types.either types.deferredModule types.str);
}; };
} }
) )
@@ -503,9 +483,6 @@ in
type = types.deferredModule; type = types.deferredModule;
default = { }; default = { };
description = '' description = ''
!!! Danger "Experimental Feature"
This feature is experimental and will change in the future.
export modules defined in 'perInstance' export modules defined in 'perInstance'
mapped to their instance name mapped to their instance name
@@ -634,9 +611,6 @@ in
type = types.deferredModule; type = types.deferredModule;
default = { }; default = { };
description = '' description = ''
!!! Danger "Experimental Feature"
This feature is experimental and will change in the future.
export modules defined in 'perMachine' export modules defined in 'perMachine'
mapped to their machine name mapped to their machine name
@@ -738,9 +712,6 @@ in
exports = mkOption { exports = mkOption {
description = '' description = ''
!!! Danger "Experimental Feature"
This feature is experimental and will change in the future.
This services exports. This services exports.
Gets merged with all other services exports. Gets merged with all other services exports.
@@ -879,11 +850,7 @@ in
instanceRes.nixosModule instanceRes.nixosModule
] ]
++ (map ( ++ (map (
s: s: if builtins.typeOf s == "string" then "${directory}/${s}" else s
if builtins.typeOf s == "string" then
lib.warn "String types for 'extraModules' will be deprecated - ${s}" "${directory}/${s}"
else
lib.setDefaultModuleLocation "via inventory.instances.${instanceName}.roles.${roleName}" s
) instanceCfg.roles.${roleName}.extraModules); ) instanceCfg.roles.${roleName}.extraModules);
}; };
} }

View File

@@ -137,7 +137,6 @@ in
settings = { }; settings = { };
}; };
}; };
settings = { };
}; };
peer = { peer = {
machines = { machines = {
@@ -147,9 +146,6 @@ in
}; };
}; };
}; };
settings = {
timeout = "foo-peer";
};
}; };
}; };
settings = { settings = {

View File

@@ -102,23 +102,18 @@ in
specificRoleSettings = specificRoleSettings =
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer; res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer;
}; };
expected = { expected = rec {
hasMachineSettings = true; hasMachineSettings = true;
hasRoleSettings = true; hasRoleSettings = false;
specificMachineSettings = { specificMachineSettings = {
timeout = "foo-peer-jon"; timeout = "foo-peer-jon";
}; };
specificRoleSettings = { specificRoleSettings = {
machines = { machines = {
jon = { jon = {
settings = { settings = specificMachineSettings;
timeout = "foo-peer-jon";
};
}; };
}; };
settings = {
timeout = "foo-peer";
};
}; };
}; };
}; };

View File

@@ -501,7 +501,7 @@ def setup_filesystems(container: ContainerInfo) -> None:
if file.is_symlink(): if file.is_symlink():
target = file.readlink() target = file.readlink()
sym = container.nix_store_dir / file.name sym = container.nix_store_dir / file.name
os.symlink(target, sym) sym.symlink_to(target)
# Read /proc/mounts and replicate every bind mount # Read /proc/mounts and replicate every bind mount
with Path("/proc/self/mounts").open() as f: with Path("/proc/self/mounts").open() as f:

View File

@@ -1,26 +1,4 @@
{ self, lib, ... }: { self, lib, ... }:
let
clanModule = lib.modules.importApply ./default.nix { clan-core = self; };
in
{ {
flake.modules.clan.default = clanModule; flake.modules.clan.default = lib.modules.importApply ./default.nix { clan-core = self; };
perSystem =
{
pkgs,
lib,
...
}:
let
jsonDocs = import ./eval-docs.nix {
inherit
pkgs
lib
clanModule
;
clanLib = self.clanLib;
};
in
{
legacyPackages.clan-options = jsonDocs.optionsJSON;
};
} }

View File

@@ -288,7 +288,7 @@ in
Global information about the clan. Global information about the clan.
''; '';
type = types.deferredModuleWith { type = types.deferredModuleWith {
staticModules = [ ../inventoryClass/meta.nix ]; staticModules = [ ../inventoryClass/meta-interface.nix ];
}; };
default = { }; default = { };
}; };

View File

@@ -222,18 +222,6 @@ in
inventoryClass = inventoryClass =
let let
flakeInputs = config.self.inputs; flakeInputs = config.self.inputs;
# Compute the relative directory path
selfStr = toString config.self;
dirStr = toString directory;
relativeDirectory =
if selfStr == dirStr then
""
else if lib.hasPrefix selfStr dirStr then
lib.removePrefix (selfStr + "/") dirStr
else
# This shouldn't happen in normal usage, but can occur when
# the flake is copied (e.g., in tests). Fall back to empty string.
"";
in in
{ {
_module.args = { _module.args = {
@@ -242,12 +230,7 @@ in
imports = [ imports = [
../inventoryClass/default.nix ../inventoryClass/default.nix
{ {
inherit inherit inventory directory flakeInputs;
inventory
directory
flakeInputs
relativeDirectory
;
exportsModule = config.exportsModule; exportsModule = config.exportsModule;
} }
( (

View File

@@ -1,20 +1,19 @@
{ {
pkgs, pkgs,
lib, lib,
clanModule, clan-core,
clanLib,
}: }:
let let
eval = lib.evalModules { eval = lib.evalModules {
modules = [ modules = [
clanModule clan-core.modules.clan.default
]; ];
}; };
evalDocs = pkgs.nixosOptionsDoc { evalDocs = pkgs.nixosOptionsDoc {
options = eval.options; options = eval.options;
warningsAreErrors = false; warningsAreErrors = false;
transformOptions = clanLib.docs.stripStorePathsFromDeclarations; transformOptions = clan-core.clanLib.docs.stripStorePathsFromDeclarations;
}; };
in in
{ {

View File

@@ -1,5 +1,24 @@
{
self,
...
}:
{ {
imports = [ imports = [
./clan/flake-module.nix ./clan/flake-module.nix
]; ];
perSystem =
{
pkgs,
lib,
...
}:
let
jsonDocs = import ./eval-docs.nix {
inherit pkgs lib;
clan-core = self;
};
in
{
legacyPackages.clan-options = jsonDocs.optionsJSON;
};
} }

View File

@@ -40,11 +40,12 @@ let
name: name:
let let
v = set.${name}; v = set.${name};
loc = path ++ [ name ];
in in
if pred loc v then if pred path v then
[ [
(lib.nameValuePair name (if lib.isAttrs v then filterAttrsRecursive' loc pred v else v)) (lib.nameValuePair name (
if lib.isAttrs v then filterAttrsRecursive' (path ++ [ name ]) pred v else v
))
] ]
else else
[ ] [ ]
@@ -55,7 +56,8 @@ let
# Remove extraModules from serialization, # Remove extraModules from serialization,
# identified by: prefix + pathLength + name # identified by: prefix + pathLength + name
# inventory.instances.*.roles.*.extraModules # inventory.instances.*.roles.*.extraModules
path: _value: !(lib.length path == 5 && ((lib.last path)) == "extraModules") path: _value:
lib.length path <= 5 || lib.head path != "instances" || (lib.elemAt path 5) != "extraModules"
) exposedInventory; ) exposedInventory;
in in
{ {
@@ -81,14 +83,6 @@ in
directory = mkOption { directory = mkOption {
type = types.path; type = types.path;
}; };
relativeDirectory = mkOption {
type = types.str;
internal = true;
description = ''
The relative directory path from the flake root to the clan directory.
Empty string if directory equals the flake root.
'';
};
machines = mkOption { machines = mkOption {
type = types.attrsOf (submodule ({ type = types.attrsOf (submodule ({
options = { options = {

View File

@@ -115,7 +115,7 @@ in
meta = lib.mkOption { meta = lib.mkOption {
type = lib.types.submoduleWith { type = lib.types.submoduleWith {
modules = [ modules = [
./meta.nix ./meta-interface.nix
]; ];
}; };
}; };
@@ -359,7 +359,7 @@ in
inherit clanLib; inherit clanLib;
}; };
} }
(import ./role.nix { }) (import ./roles-interface.nix { })
]; ];
} }
); );

View File

@@ -44,6 +44,12 @@ in
description = '' description = ''
List of additionally imported `.nix` expressions. List of additionally imported `.nix` expressions.
Supported types:
- **Strings**: Interpreted relative to the 'directory' passed to `lib.clan`.
- **Paths**: should be relative to the current file.
- **Any**: Nix expression must be serializable to JSON.
!!! Note !!! Note
**The import only happens if the machine is part of the service or role.** **The import only happens if the machine is part of the service or role.**
@@ -67,8 +73,15 @@ in
} }
``` ```
''; '';
apply = value: if lib.isString value then value else builtins.seq (builtins.toJSON value) value;
default = [ ]; default = [ ];
type = types.listOf types.raw; type = types.listOf (
types.oneOf [
types.str
types.path
(types.attrsOf types.anything)
]
);
}; };
}; };
} }

View File

@@ -0,0 +1,70 @@
{
flakeInputs,
clanLib,
}:
{ lib, config, ... }:
let
inspectModule =
inputName: moduleName: module:
let
eval = clanLib.evalService {
modules = [ module ];
prefix = [
inputName
"clan"
"modules"
moduleName
];
};
in
{
manifest = eval.config.manifest;
roles = lib.mapAttrs (_n: v: { inherit (v) description; }) eval.config.roles;
};
in
{
options.staticModules = lib.mkOption {
readOnly = true;
type = lib.types.raw;
apply = moduleSet: lib.mapAttrs (inspectModule "<clan-core>") moduleSet;
};
options.modulesPerSource = lib.mkOption {
# { sourceName :: { moduleName :: {} }}
readOnly = true;
type = lib.types.raw;
default =
let
inputsWithModules = lib.filterAttrs (_inputName: v: v ? clan.modules) flakeInputs;
in
lib.mapAttrs (
inputName: v: lib.mapAttrs (inspectModule inputName) v.clan.modules
) inputsWithModules;
};
options.moduleSchemas = lib.mkOption {
# { sourceName :: { moduleName :: { roleName :: Schema }}}
readOnly = true;
type = lib.types.raw;
default = lib.mapAttrs (
_inputName: moduleSet:
lib.mapAttrs (
_moduleName: module:
(clanLib.evalService {
modules = [ module ];
prefix = [ ];
}).config.result.api.schema
) moduleSet
) config.modulesPerSource;
};
options.templatesPerSource = lib.mkOption {
# { sourceName :: { moduleName :: {} }}
readOnly = true;
type = lib.types.raw;
default =
let
inputsWithTemplates = lib.filterAttrs (_inputName: v: v ? clan.templates) flakeInputs;
in
lib.mapAttrs (_inputName: v: lib.mapAttrs (_n: t: t) v.clan.templates) inputsWithTemplates;
};
}

View File

@@ -107,7 +107,8 @@ in
readOnly = true; readOnly = true;
}; };
tld = lib.mkOption { tld = lib.mkOption {
type = types.strMatching "[a-z]+"; default = "clan";
type = lib.types.str;
description = '' description = ''
the TLD for the clan the TLD for the clan
''; '';

View File

@@ -94,7 +94,7 @@ class TestHttpBridge:
def test_http_bridge_middleware_setup(self, http_bridge: tuple) -> None: def test_http_bridge_middleware_setup(self, http_bridge: tuple) -> None:
"""Test that middleware is properly set up.""" """Test that middleware is properly set up."""
api, middleware_chain = http_bridge _api, middleware_chain = http_bridge
# Test that we can create the bridge with middleware # Test that we can create the bridge with middleware
# The actual HTTP handling will be tested through the server integration tests # The actual HTTP handling will be tested through the server integration tests

View File

@@ -2,4 +2,5 @@ app/api
app/.fonts app/.fonts
.vite .vite
*.css.d.ts storybook-static
*.css.d.ts

View File

@@ -1,20 +1,31 @@
import type { StorybookConfig } from "storybook-solidjs-vite"; import { mergeConfig } from "vite";
import type { StorybookConfig } from "@kachurun/storybook-solid-vite";
export default { const config: StorybookConfig = {
framework: "storybook-solidjs-vite", framework: "@kachurun/storybook-solid-vite",
stories: ["../src/**/*.mdx", "../src/**/*.stories.tsx"], stories: ["../src/**/*.mdx", "../src/**/*.stories.tsx"],
addons: [ addons: [
"@storybook/addon-links", "@storybook/addon-links",
"@storybook/addon-docs", "@storybook/addon-docs",
"@storybook/addon-a11y", "@storybook/addon-a11y",
{
name: "@storybook/addon-vitest",
options: {
cli: false,
},
},
], ],
async viteFinal(config) {
return mergeConfig(config, {
define: { "process.env": {} },
});
},
core: { core: {
disableTelemetry: true, disableTelemetry: true,
}, },
} satisfies StorybookConfig; typescript: {
reactDocgen: "react-docgen-typescript",
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
// 👇 Default prop filter, which excludes props from node_modules
propFilter: (prop: any) =>
prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
},
},
};
export default config;

View File

@@ -1,4 +1,4 @@
import type { Preview } from "storybook-solidjs-vite"; import type { Preview } from "@kachurun/storybook-solid-vite";
import "../src/index.css"; import "../src/index.css";
import "./preview.css"; import "./preview.css";

View File

@@ -1,4 +1,4 @@
import { setProjectAnnotations } from "storybook-solidjs-vite"; import { setProjectAnnotations } from "@kachurun/storybook-solid-vite";
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview"; import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
import * as projectAnnotations from "./preview"; import * as projectAnnotations from "./preview";

View File

@@ -1,9 +1,19 @@
{ {
"ignore": [ "ignore": [
"gtk.webview.js", "gtk.webview.js",
"src/api/clan/client-fetch.ts",
"stylelint.config.js", "stylelint.config.js",
"util.ts",
"src/components/v2/**",
"api/**", "api/**",
"tailwind/**" "tailwind/**"
],
"ignoreDependencies": [
"@babel/plugin-syntax-import-attributes",
"@storybook/addon-viewport",
"@typescript-eslint/parser",
"@vitest/coverage-v8",
"http-server",
"playwright",
"wait-on"
] ]
} }

File diff suppressed because it is too large Load Diff

View File

@@ -12,35 +12,48 @@
"check": "tsc --noEmit --skipLibCheck && eslint ./src --fix", "check": "tsc --noEmit --skipLibCheck && eslint ./src --fix",
"test": "vitest run --project unit --typecheck", "test": "vitest run --project unit --typecheck",
"vite": "vite", "vite": "vite",
"storybook": "storybook",
"knip": "knip --fix", "knip": "knip --fix",
"storybook": "storybook dev -p 6006", "storybook-dev": "storybook dev -p 6006",
"test-storybook": "vitest run --project storybook", "test-storybook": "vitest run --project storybook",
"test-storybook-update-snapshots": "vitest run --project storybook --update" "test-storybook-update-snapshots": "vitest run --project storybook --update"
}, },
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@babel/plugin-syntax-import-attributes": "^7.27.1",
"@eslint/js": "^9.3.0", "@eslint/js": "^9.3.0",
"@kachurun/storybook-solid-vite": "^9.0.11",
"@linaria/core": "^6.3.0", "@linaria/core": "^6.3.0",
"@storybook/addon-a11y": "^9.1.13", "@sinonjs/fake-timers": "^14.0.0",
"@storybook/addon-docs": "^9.1.13", "@storybook/addon-a11y": "^9.0.8",
"@storybook/addon-links": "^9.1.13", "@storybook/addon-docs": "^9.0.8",
"@storybook/addon-vitest": "^9.1.13", "@storybook/addon-links": "^9.0.8",
"@storybook/addon-viewport": "^9.0.8",
"@storybook/addon-vitest": "^9.0.8",
"@types/node": "^22.15.19", "@types/node": "^22.15.19",
"@types/sinonjs__fake-timers": "^8.1.5",
"@types/three": "^0.176.0", "@types/three": "^0.176.0",
"@typescript-eslint/parser": "^8.32.1",
"@vitest/browser": "^3.2.3", "@vitest/browser": "^3.2.3",
"@vitest/coverage-v8": "^3.2.3",
"@wyw-in-js/vite": "^0.7.0", "@wyw-in-js/vite": "^0.7.0",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"concurrently": "^9.1.2",
"eslint": "^9.27.0", "eslint": "^9.27.0",
"eslint-plugin-tailwindcss": "^3.17.0", "eslint-plugin-tailwindcss": "^3.17.0",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^4.1.4",
"extend": "^3.0.2",
"http-server": "^14.1.1",
"jsdom": "^26.1.0",
"knip": "^5.61.2", "knip": "^5.61.2",
"markdown-to-jsx": "^7.7.10",
"playwright": "1.54.1", "playwright": "1.54.1",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"postcss-url": "^10.1.3", "postcss-url": "^10.1.3",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"storybook": "^9.1.13", "storybook": "^9.0.8",
"storybook-solidjs-vite": "^9.0.3", "swagger-ui-dist": "^5.26.2",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"typescript-eslint": "^8.32.1", "typescript-eslint": "^8.32.1",
@@ -49,9 +62,11 @@
"vite-css-modules": "^1.10.0", "vite-css-modules": "^1.10.0",
"vite-plugin-solid": "^2.8.2", "vite-plugin-solid": "^2.8.2",
"vite-plugin-solid-svg": "^0.8.1", "vite-plugin-solid-svg": "^0.8.1",
"vitest": "^3.2.4" "vitest": "^3.2.3",
"wait-on": "^8.0.3"
}, },
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.6.8",
"@kobalte/core": "^0.13.10", "@kobalte/core": "^0.13.10",
"@kobalte/tailwindcss": "^0.9.0", "@kobalte/tailwindcss": "^0.9.0",
"@modular-forms/solid": "^0.25.1", "@modular-forms/solid": "^0.25.1",
@@ -65,6 +80,22 @@
"solid-js": "^1.9.7", "solid-js": "^1.9.7",
"solid-toast": "^0.5.0", "solid-toast": "^0.5.0",
"three": "^0.176.0", "three": "^0.176.0",
"troika-three-text": "^0.52.4",
"valibot": "^1.1.0" "valibot": "^1.1.0"
},
"optionalDependencies": {
"@esbuild/darwin-arm64": "^0.25.4",
"@esbuild/darwin-x64": "^0.25.4",
"@esbuild/linux-arm64": "^0.25.4",
"@esbuild/linux-x64": "^0.25.4"
},
"overrides": {
"vite": {
"rollup": "npm:@rollup/wasm-node@^4.34.9"
},
"@rollup/rollup-darwin-x64": "npm:@rollup/wasm-node@^4.34.9",
"@rollup/rollup-linux-x64": "npm:@rollup/wasm-node@^4.34.9",
"@rollup/rollup-darwin-arm64": "npm:@rollup/wasm-node@^4.34.9",
"@rollup/rollup-linux-arm64": "npm:@rollup/wasm-node@^4.34.9"
} }
} }

View File

@@ -1,6 +1,7 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Alert, AlertProps } from "@/src/components/Alert/Alert"; import { Alert, AlertProps } from "@/src/components/Alert/Alert";
import { expect, fn } from "storybook/test"; import { expect, fn } from "storybook/test";
import { StoryContext } from "@kachurun/storybook-solid-vite";
const AlertExamples = (props: AlertProps) => ( const AlertExamples = (props: AlertProps) => (
<div class="grid w-fit grid-cols-2 gap-8"> <div class="grid w-fit grid-cols-2 gap-8">
@@ -19,14 +20,14 @@ const AlertExamples = (props: AlertProps) => (
</div> </div>
); );
const meta: Meta<typeof AlertExamples> = { const meta: Meta<AlertProps> = {
title: "Components/Alert", title: "Components/Alert",
component: AlertExamples, component: AlertExamples,
}; };
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<AlertProps>;
export const Info: Story = { export const Info: Story = {
args: { args: {
@@ -91,13 +92,10 @@ export const InfoDismiss: Story = {
args: { args: {
...Info.args, ...Info.args,
onDismiss: fn(), onDismiss: fn(),
}, play: async ({ canvas, step, userEvent, args }: StoryContext) => {
render(args) { await userEvent.click(canvas.getByRole("button"));
return <Alert {...args} />; await expect(args.onDismiss).toHaveBeenCalled();
}, },
play: async ({ canvas, userEvent, args }) => {
await userEvent.click(canvas.getByRole("button"));
await expect(args.onDismiss).toHaveBeenCalled();
}, },
}; };

View File

@@ -1,7 +1,8 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Button, ButtonProps } from "./Button"; import { Button, ButtonProps } from "./Button";
import { Component } from "solid-js"; import { Component } from "solid-js";
import { expect, fn, within } from "storybook/test"; import { expect, fn, within } from "storybook/test";
import { StoryContext } from "@kachurun/storybook-solid-vite";
const getCursorStyle = (el: Element) => window.getComputedStyle(el).cursor; const getCursorStyle = (el: Element) => window.getComputedStyle(el).cursor;
@@ -201,7 +202,7 @@ const ButtonExamples: Component<ButtonProps> = (props) => (
</> </>
); );
const meta: Meta<typeof ButtonExamples> = { const meta: Meta<ButtonProps> = {
title: "Components/Button", title: "Components/Button",
component: ButtonExamples, component: ButtonExamples,
}; };
@@ -210,13 +211,15 @@ export default meta;
type Story = StoryObj<ButtonProps>; type Story = StoryObj<ButtonProps>;
const timeout = process.env.NODE_ENV === "test" ? 500 : 2000;
export const Primary: Story = { export const Primary: Story = {
args: { args: {
hierarchy: "primary", hierarchy: "primary",
onClick: fn(), onClick: fn(),
}, },
play: async ({ canvasElement, step, userEvent, args }) => { play: async ({ canvasElement, step, userEvent, args }: StoryContext) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
const buttons = await canvas.findAllByRole("button"); const buttons = await canvas.findAllByRole("button");
@@ -261,7 +264,7 @@ export const GhostPrimary: Story = {
}, },
play: Primary.play, play: Primary.play,
decorators: [ decorators: [
(Story) => ( (Story: StoryObj) => (
<div class="p-10 bg-def-3"> <div class="p-10 bg-def-3">
<Story /> <Story />
</div> </div>

View File

@@ -8,11 +8,11 @@ import Icon, { IconVariant } from "@/src/components/Icon/Icon";
import { Loader } from "@/src/components/Loader/Loader"; import { Loader } from "@/src/components/Loader/Loader";
import { getInClasses, joinByDash, keepTruthy } from "@/src/util"; import { getInClasses, joinByDash, keepTruthy } from "@/src/util";
type Size = "default" | "s" | "xs"; export type Size = "default" | "s" | "xs";
type Hierarchy = "primary" | "secondary"; export type Hierarchy = "primary" | "secondary";
type Elasticity = "default" | "fit"; export type Elasticity = "default" | "fit";
type Action = () => Promise<void>; export type Action = () => Promise<void>;
export interface ButtonProps export interface ButtonProps
extends JSX.ButtonHTMLAttributes<HTMLButtonElement> { extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {

View File

@@ -1,7 +1,7 @@
import { CubeConstruction } from "./CubeConstruction"; import { CubeConstruction } from "./CubeConstruction";
import { Meta, StoryObj } from "storybook-solidjs-vite"; import { Meta, StoryObj } from "@kachurun/storybook-solid";
const meta: Meta<typeof CubeConstruction> = { const meta: Meta = {
title: "Components/CubeConstruction", title: "Components/CubeConstruction",
component: CubeConstruction, component: CubeConstruction,
globals: { globals: {
@@ -12,7 +12,7 @@ const meta: Meta<typeof CubeConstruction> = {
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj;
export const Default: Story = { export const Default: Story = {
args: {}, args: {},

View File

@@ -1,14 +1,14 @@
import { Meta, StoryObj } from "storybook-solidjs-vite"; import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Divider } from "@/src/components/Divider/Divider"; import { Divider, DividerProps } from "@/src/components/Divider/Divider";
const meta: Meta<typeof Divider> = { const meta: Meta<DividerProps> = {
title: "Components/Divider", title: "Components/Divider",
component: Divider, component: Divider,
}; };
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<DividerProps>;
export const Default: Story = {}; export const Default: Story = {};
@@ -30,7 +30,7 @@ export const Vertical: Story = {
orientation: "vertical", orientation: "vertical",
}, },
decorators: [ decorators: [
(Story) => ( (Story: Story) => (
<div class="h-32 w-full"> <div class="h-32 w-full">
<Story /> <Story />
</div> </div>
@@ -43,5 +43,5 @@ export const VerticalInverted: Story = {
inverted: true, inverted: true,
...Vertical.args, ...Vertical.args,
}, },
decorators: Vertical.decorators, decorators: [...Vertical.decorators],
}; };

View File

@@ -1,4 +1,4 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
import cx from "classnames"; import cx from "classnames";
import { Checkbox, CheckboxProps } from "@/src/components/Form/Checkbox"; import { Checkbox, CheckboxProps } from "@/src/components/Form/Checkbox";
@@ -23,17 +23,17 @@ const Examples = (props: CheckboxProps) => (
</div> </div>
); );
const meta: Meta<typeof Examples> = { const meta = {
title: "Components/Form/Checkbox", title: "Components/Form/Checkbox",
component: Examples, component: Examples,
decorators: [ decorators: [
(Story, { args }) => { (Story: StoryObj, context: StoryContext<CheckboxProps>) => {
return ( return (
<div <div
class={cx({ class={cx({
"w-[600px]": (args.orientation || "vertical") == "vertical", "w-[600px]": (context.args.orientation || "vertical") == "vertical",
"w-[1024px]": args.orientation == "horizontal", "w-[1024px]": context.args.orientation == "horizontal",
"bg-inv-acc-3": args.inverted, "bg-inv-acc-3": context.args.inverted,
})} })}
> >
<Story /> <Story />
@@ -41,7 +41,7 @@ const meta: Meta<typeof Examples> = {
); );
}, },
], ],
}; } satisfies Meta<CheckboxProps>;
export default meta; export default meta;

View File

@@ -1,4 +1,4 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
import { import {
Fieldset, Fieldset,
FieldsetFieldProps, FieldsetFieldProps,
@@ -18,17 +18,17 @@ const FieldsetExamples = (props: FieldsetProps) => (
</div> </div>
); );
const meta: Meta<typeof FieldsetExamples> = { const meta = {
title: "Components/Form/Fieldset", title: "Components/Form/Fieldset",
component: FieldsetExamples, component: FieldsetExamples,
decorators: [ decorators: [
(Story, { args }) => { (Story: StoryObj, context: StoryContext<FieldsetProps>) => {
return ( return (
<div <div
class={cx({ class={cx({
"w-[600px]": (args.orientation || "vertical") == "vertical", "w-[600px]": (context.args.orientation || "vertical") == "vertical",
"w-[512px]": args.orientation == "horizontal", "w-[512px]": context.args.orientation == "horizontal",
"bg-inv-acc-3": args.inverted, "bg-inv-acc-3": context.args.inverted,
})} })}
> >
<Story /> <Story />
@@ -36,7 +36,7 @@ const meta: Meta<typeof FieldsetExamples> = {
); );
}, },
], ],
}; } satisfies Meta<FieldsetProps>;
export default meta; export default meta;

View File

@@ -1,4 +1,4 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
import cx from "classnames"; import cx from "classnames";
import { import {
HostFileInput, HostFileInput,
@@ -31,17 +31,17 @@ const Examples = (props: HostFileInputProps) => (
</div> </div>
); );
const meta: Meta<typeof Examples> = { const meta = {
title: "Components/Form/HostFileInput", title: "Components/Form/HostFileInput",
component: Examples, component: Examples,
decorators: [ decorators: [
(Story, { args }) => { (Story: StoryObj, context: StoryContext<HostFileInputProps>) => {
return ( return (
<div <div
class={cx({ class={cx({
"w-[600px]": (args.orientation || "vertical") == "vertical", "w-[600px]": (context.args.orientation || "vertical") == "vertical",
"w-[1024px]": args.orientation == "horizontal", "w-[1024px]": context.args.orientation == "horizontal",
"bg-inv-acc-3": args.inverted, "bg-inv-acc-3": context.args.inverted,
})} })}
> >
<Story /> <Story />
@@ -49,7 +49,7 @@ const meta: Meta<typeof Examples> = {
); );
}, },
], ],
}; } satisfies Meta<HostFileInputProps>;
export default meta; export default meta;

View File

@@ -10,15 +10,15 @@ import styles from "./Label.module.css";
import cx from "classnames"; import cx from "classnames";
import { getInClasses } from "@/src/util"; import { getInClasses } from "@/src/util";
type Size = "default" | "s"; export type Size = "default" | "s";
type LabelComponent = export type LabelComponent =
| typeof TextField.Label | typeof TextField.Label
| typeof Checkbox.Label | typeof Checkbox.Label
| typeof Combobox.Label | typeof Combobox.Label
| typeof Select.Label; | typeof Select.Label;
type DescriptionComponent = export type DescriptionComponent =
| typeof TextField.Description | typeof TextField.Description
| typeof Checkbox.Description | typeof Checkbox.Description
| typeof Combobox.Description | typeof Combobox.Description

View File

@@ -1,12 +1,12 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { MachineTags } from "./MachineTags"; import { MachineTags, MachineTagsProps } from "./MachineTags";
import { createForm, setValue } from "@modular-forms/solid"; import { createForm, setValue } from "@modular-forms/solid";
import { Button } from "../Button/Button"; import { Button } from "../Button/Button";
const meta: Meta<typeof MachineTags> = { const meta = {
title: "Components/MachineTags", title: "Components/MachineTags",
component: MachineTags, component: MachineTags,
}; } satisfies Meta<MachineTagsProps>;
export default meta; export default meta;

View File

@@ -19,7 +19,7 @@ import { CollectionNode } from "@kobalte/core";
import styles from "./MachineTags.module.css"; import styles from "./MachineTags.module.css";
import { keepTruthy } from "@/src/util"; import { keepTruthy } from "@/src/util";
interface MachineTag { export interface MachineTag {
value: string; value: string;
disabled?: boolean; disabled?: boolean;
} }

View File

@@ -3,7 +3,7 @@ import { JSX, mergeProps } from "solid-js";
import styles from "./Orienter.module.css"; import styles from "./Orienter.module.css";
interface OrienterProps { export interface OrienterProps {
orientation?: "vertical" | "horizontal"; orientation?: "vertical" | "horizontal";
align?: "center" | "start"; align?: "center" | "start";
children: JSX.Element; children: JSX.Element;

View File

@@ -1,4 +1,4 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
import cx from "classnames"; import cx from "classnames";
import { TextArea, TextAreaProps } from "./TextArea"; import { TextArea, TextAreaProps } from "./TextArea";
@@ -23,17 +23,17 @@ const Examples = (props: TextAreaProps) => (
</div> </div>
); );
const meta: Meta<typeof Examples> = { const meta = {
title: "Components/Form/TextArea", title: "Components/Form/TextArea",
component: Examples, component: Examples,
decorators: [ decorators: [
(Story, { args }) => { (Story: StoryObj, context: StoryContext<TextAreaProps>) => {
return ( return (
<div <div
class={cx({ class={cx({
"w-[600px]": (args.orientation || "vertical") == "vertical", "w-[600px]": (context.args.orientation || "vertical") == "vertical",
"w-[1024px]": args.orientation == "horizontal", "w-[1024px]": context.args.orientation == "horizontal",
"bg-inv-acc-3": args.inverted, "bg-inv-acc-3": context.args.inverted,
})} })}
> >
<Story /> <Story />
@@ -41,7 +41,7 @@ const meta: Meta<typeof Examples> = {
); );
}, },
], ],
}; } satisfies Meta<TextAreaProps>;
export default meta; export default meta;

View File

@@ -1,4 +1,4 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
import cx from "classnames"; import cx from "classnames";
import { TextInput, TextInputProps } from "@/src/components/Form/TextInput"; import { TextInput, TextInputProps } from "@/src/components/Form/TextInput";
import Icon from "../Icon/Icon"; import Icon from "../Icon/Icon";
@@ -25,17 +25,17 @@ const Examples = (props: TextInputProps) => (
</div> </div>
); );
const meta: Meta<typeof Examples> = { const meta = {
title: "Components/Form/TextInput", title: "Components/Form/TextInput",
component: Examples, component: Examples,
decorators: [ decorators: [
(Story, { args }) => { (Story: StoryObj, context: StoryContext<TextInputProps>) => {
return ( return (
<div <div
class={cx({ class={cx({
"w-[600px]": (args.orientation || "vertical") == "vertical", "w-[600px]": (context.args.orientation || "vertical") == "vertical",
"w-[1024px]": args.orientation == "horizontal", "w-[1024px]": context.args.orientation == "horizontal",
"bg-inv-acc-3": args.inverted, "bg-inv-acc-3": context.args.inverted,
})} })}
> >
<Story /> <Story />
@@ -43,7 +43,7 @@ const meta: Meta<typeof Examples> = {
); );
}, },
], ],
}; } satisfies Meta<TextInputProps>;
export default meta; export default meta;

View File

@@ -1,4 +1,4 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryObj, StoryContext } from "@kachurun/storybook-solid";
import { Component, For } from "solid-js"; import { Component, For } from "solid-js";
import Icon, { IconProps, IconVariant } from "./Icon"; import Icon, { IconProps, IconVariant } from "./Icon";
import cx from "classnames"; import cx from "classnames";
@@ -57,12 +57,12 @@ const IconExamples: Component<IconProps> = (props) => (
</div> </div>
); );
const meta: Meta<typeof IconExamples> = { const meta: Meta<IconProps> = {
title: "Components/Icon", title: "Components/Icon",
component: IconExamples, component: IconExamples,
decorators: [ decorators: [
(Story, { args }) => ( (Story: StoryObj, context: StoryContext<IconProps>) => (
<div class={cx(args.inverted || false ? "bg-inv-acc-3" : "")}> <div class={cx(context.args.inverted || false ? "bg-inv-acc-3" : "")}>
<Story /> <Story />
</div> </div>
), ),
@@ -71,7 +71,7 @@ const meta: Meta<typeof IconExamples> = {
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<IconProps>;
export const Default: Story = {}; export const Default: Story = {};

View File

@@ -1,4 +1,4 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Loader, LoaderProps } from "@/src/components/Loader/Loader"; import { Loader, LoaderProps } from "@/src/components/Loader/Loader";
const LoaderExamples = (props: LoaderProps) => ( const LoaderExamples = (props: LoaderProps) => (
@@ -9,14 +9,14 @@ const LoaderExamples = (props: LoaderProps) => (
</div> </div>
); );
const meta: Meta<typeof LoaderExamples> = { const meta: Meta<LoaderProps> = {
title: "Components/Loader", title: "Components/Loader",
component: LoaderExamples, component: LoaderExamples,
}; };
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<LoaderProps>;
export const Primary: Story = { export const Primary: Story = {
args: { args: {

View File

@@ -3,7 +3,7 @@ import { mergeProps } from "solid-js";
import styles from "./Loader.module.css"; import styles from "./Loader.module.css";
import cx from "classnames"; import cx from "classnames";
type Hierarchy = "primary" | "secondary"; export type Hierarchy = "primary" | "secondary";
export interface LoaderProps { export interface LoaderProps {
hierarchy?: Hierarchy; hierarchy?: Hierarchy;

View File

@@ -1,11 +1,11 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
import { LoadingBar } from "./LoadingBar"; import { LoadingBar } from "./LoadingBar";
const meta: Meta<typeof LoadingBar> = { const meta: Meta = {
title: "Components/LoadingBar", title: "Components/LoadingBar",
component: LoadingBar, component: LoadingBar,
decorators: [ decorators: [
(Story) => { (Story: StoryObj, context: StoryContext<unknown>) => {
return ( return (
<div class={"flex w-fit items-center justify-center bg-slate-500 p-10"}> <div class={"flex w-fit items-center justify-center bg-slate-500 p-10"}>
<Story /> <Story />
@@ -17,6 +17,6 @@ const meta: Meta<typeof LoadingBar> = {
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj;
export const Default: Story = {}; export const Default: Story = {};

View File

@@ -2,7 +2,7 @@ import { JSX } from "solid-js";
import styles from "./LoadingBar.module.css"; import styles from "./LoadingBar.module.css";
import cx from "classnames"; import cx from "classnames";
type LoadingBarProps = JSX.HTMLAttributes<HTMLDivElement> & {}; export type LoadingBarProps = JSX.HTMLAttributes<HTMLDivElement> & {};
export const LoadingBar = (props: LoadingBarProps) => ( export const LoadingBar = (props: LoadingBarProps) => (
<div {...props} class={cx(styles.loading_bar, props.class)} /> <div {...props} class={cx(styles.loading_bar, props.class)} />
); );

View File

@@ -1,4 +1,4 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
import { Component, For } from "solid-js"; import { Component, For } from "solid-js";
import { Logo, LogoProps, LogoVariant } from "./Logo"; import { Logo, LogoProps, LogoVariant } from "./Logo";
import cx from "classnames"; import cx from "classnames";
@@ -11,12 +11,12 @@ const LogoExamples: Component<LogoProps> = (props) => (
</div> </div>
); );
const meta: Meta<typeof LogoExamples> = { const meta: Meta<LogoProps> = {
title: "Components/Logo", title: "Components/Logo",
component: LogoExamples, component: LogoExamples,
decorators: [ decorators: [
(Story, { args }) => ( (Story: StoryObj, context: StoryContext<LogoProps>) => (
<div class={cx(args.inverted || false ? "bg-inv-acc-3" : "")}> <div class={cx(context.args.inverted || false ? "bg-inv-acc-3" : "")}>
<Story /> <Story />
</div> </div>
), ),
@@ -25,7 +25,7 @@ const meta: Meta<typeof LogoExamples> = {
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<LogoProps>;
export const Default: Story = {}; export const Default: Story = {};

View File

@@ -1,11 +1,14 @@
import { MachineStatus } from "@/src/components/MachineStatus/MachineStatus"; import {
import { Meta, StoryObj } from "storybook-solidjs-vite"; MachineStatus,
MachineStatusProps,
} from "@/src/components/MachineStatus/MachineStatus";
import { Meta, StoryObj } from "@kachurun/storybook-solid";
const meta: Meta<typeof MachineStatus> = { const meta: Meta<MachineStatusProps> = {
title: "Components/MachineStatus", title: "Components/MachineStatus",
component: MachineStatus, component: MachineStatus,
decorators: [ decorators: [
(Story) => ( (Story: StoryObj) => (
<div class="p-5 bg-inv-1"> <div class="p-5 bg-inv-1">
<Story /> <Story />
</div> </div>
@@ -15,7 +18,7 @@ const meta: Meta<typeof MachineStatus> = {
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<MachineStatusProps>;
export const Loading: Story = { export const Loading: Story = {
args: {}, args: {},

View File

@@ -1,16 +1,17 @@
import { Meta, StoryObj } from "storybook-solidjs-vite"; import { TagProps } from "@/src/components/Tag/Tag";
import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { fn } from "storybook/test"; import { fn } from "storybook/test";
import { Modal } from "@/src/components/Modal/Modal"; import { Modal, ModalProps } from "@/src/components/Modal/Modal";
import { Fieldset, FieldsetFieldProps } from "@/src/components/Form/Fieldset"; import { Fieldset, FieldsetFieldProps } from "@/src/components/Form/Fieldset";
import { TextInput } from "@/src/components/Form/TextInput"; import { TextInput } from "@/src/components/Form/TextInput";
import { TextArea } from "@/src/components/Form/TextArea"; import { TextArea } from "@/src/components/Form/TextArea";
import { Checkbox } from "@/src/components/Form/Checkbox"; import { Checkbox } from "@/src/components/Form/Checkbox";
import { Button } from "../Button/Button"; import { Button } from "../Button/Button";
const meta: Meta<typeof Modal> = { const meta: Meta<ModalProps> = {
title: "Components/Modal", title: "Components/Modal",
component: Modal, component: Modal,
render: (args) => ( render: (args: ModalProps) => (
<Modal <Modal
{...args} {...args}
children={ children={
@@ -67,7 +68,7 @@ const meta: Meta<typeof Modal> = {
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<TagProps>;
export const Default: Story = { export const Default: Story = {
args: { args: {

View File

@@ -14,7 +14,7 @@ import Icon from "../Icon/Icon";
import cx from "classnames"; import cx from "classnames";
import { Dynamic } from "solid-js/web"; import { Dynamic } from "solid-js/web";
interface ModalContextType { export interface ModalContextType {
portalRef: HTMLDivElement; portalRef: HTMLDivElement;
} }

View File

@@ -1,11 +1,14 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { NavSection } from "@/src/components/NavSection/NavSection"; import {
NavSection,
NavSectionProps,
} from "@/src/components/NavSection/NavSection";
const meta: Meta<typeof NavSection> = { const meta: Meta<NavSectionProps> = {
title: "Components/NavSection", title: "Components/NavSection",
component: NavSection, component: NavSection,
decorators: [ decorators: [
(Story) => ( (Story: StoryObj) => (
<div class="w-96"> <div class="w-96">
<Story /> <Story />
</div> </div>
@@ -15,7 +18,7 @@ const meta: Meta<typeof NavSection> = {
export default meta; export default meta;
type Story = StoryObj<typeof NavSection>; type Story = StoryObj<NavSectionProps>;
export const Default: Story = { export const Default: Story = {
args: { args: {

View File

@@ -16,7 +16,7 @@ import { CollectionNode } from "@kobalte/core/*";
import cx from "classnames"; import cx from "classnames";
import { Loader } from "../Loader/Loader"; import { Loader } from "../Loader/Loader";
interface Option { export interface Option {
value: string; value: string;
label: string; label: string;
disabled?: boolean; disabled?: boolean;

View File

@@ -1,20 +1,24 @@
import { Meta, StoryObj } from "storybook-solidjs-vite"; import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Search } from "./Search"; import { Search, SearchProps } from "./Search";
import Icon from "../Icon/Icon"; import Icon from "../Icon/Icon";
import { Combobox } from "@kobalte/core/combobox"; import { Combobox } from "@kobalte/core/combobox";
import { Typography } from "../Typography/Typography"; import { Typography } from "../Typography/Typography";
import { ItemRenderOptions, SearchMultiple } from "./MultipleSearch"; import {
ItemRenderOptions,
SearchMultiple,
SearchMultipleProps,
} from "./MultipleSearch";
import { Show } from "solid-js"; import { Show } from "solid-js";
const meta: Meta<typeof Search> = { const meta = {
title: "Components/Search", title: "Components/Search",
component: Search, component: Search,
}; } satisfies Meta<SearchProps<unknown>>;
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<SearchProps<unknown>>;
// To test the virtualizer, we can generate a list of modules // To test the virtualizer, we can generate a list of modules
function generateModules(count: number): Module[] { function generateModules(count: number): Module[] {
@@ -103,7 +107,7 @@ export const Default: Story = {
); );
}, },
}, },
render: (args) => { render: (args: SearchProps<Module>) => {
return ( return (
<div class="fixed bottom-10 left-1/2 mb-2 w-[30rem] -translate-x-1/2"> <div class="fixed bottom-10 left-1/2 mb-2 w-[30rem] -translate-x-1/2">
<Search<Module> <Search<Module>
@@ -126,7 +130,7 @@ export const Loading: Story = {
options: [], options: [],
renderItem: () => <span></span>, renderItem: () => <span></span>,
}, },
render: (args) => { render: (args: SearchProps<Module>) => {
return ( return (
<div class="absolute bottom-1/3 w-3/4 px-3"> <div class="absolute bottom-1/3 w-3/4 px-3">
<Search<Module> <Search<Module>
@@ -231,7 +235,7 @@ export const Multiple: Story = {
); );
}, },
}, },
render: (args) => { render: (args: SearchMultipleProps<MachineOrTag>) => {
return ( return (
<div class="absolute bottom-1/3 w-3/4 px-3"> <div class="absolute bottom-1/3 w-3/4 px-3">
<SearchMultiple<MachineOrTag> <SearchMultiple<MachineOrTag>

View File

@@ -8,7 +8,7 @@ import { CollectionNode } from "@kobalte/core/*";
import { Loader } from "../Loader/Loader"; import { Loader } from "../Loader/Loader";
import cx from "classnames"; import cx from "classnames";
interface Option { export interface Option {
value: string; value: string;
label: string; label: string;
disabled?: boolean; disabled?: boolean;

View File

@@ -1,14 +1,14 @@
import { Meta, StoryObj } from "storybook-solidjs-vite"; import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { TagSelect } from "./TagSelect"; import { TagSelect, TagSelectProps } from "./TagSelect";
import { Tag } from "../Tag/Tag"; import { Tag } from "../Tag/Tag";
import Icon from "../Icon/Icon"; import Icon from "../Icon/Icon";
import { createSignal } from "solid-js"; import { createSignal } from "solid-js";
const meta: Meta<typeof TagSelect> = { const meta = {
title: "Components/Custom/SelectStepper", title: "Components/Custom/SelectStepper",
component: TagSelect, component: TagSelect,
}; } satisfies Meta<TagSelectProps<string>>;
export default meta; export default meta;
@@ -17,7 +17,7 @@ interface Item {
label: string; label: string;
} }
type Story = StoryObj<typeof meta>; type Story = StoryObj<TagSelectProps<Item>>;
const Item = (item: Item) => ( const Item = (item: Item) => (
<Tag <Tag
@@ -42,8 +42,8 @@ export const Default: Story = {
{ value: "corge", label: "Corge" }, { value: "corge", label: "Corge" },
{ value: "grault", label: "Grault" }, { value: "grault", label: "Grault" },
], ],
}, } satisfies Partial<TagSelectProps<Item>>,
render: (args) => { render: (args: TagSelectProps<Item>) => {
const [state, setState] = createSignal<Item[]>([]); const [state, setState] = createSignal<Item[]>([]);
return ( return (
<TagSelect<Item> <TagSelect<Item>

View File

@@ -1,13 +1,18 @@
import { Meta, StoryObj } from "storybook-solidjs-vite"; import { TagProps } from "@/src/components/Tag/Tag";
import { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
import { Select } from "./Select"; import { Select, SelectProps } from "./Select";
import { Fieldset } from "../Form/Fieldset"; import { Fieldset } from "../Form/Fieldset";
const meta: Meta<typeof Select> = { // const meta: Meta<SelectProps> = {
// title: "Components/Select",
// component: Select,
// };
const meta = {
title: "Components/Form/Select", title: "Components/Form/Select",
component: Select, component: Select,
decorators: [ decorators: [
(Story) => { (Story: StoryObj, context: StoryContext<SelectProps>) => {
return ( return (
<div class={`w-[600px]`}> <div class={`w-[600px]`}>
<Fieldset> <Fieldset>
@@ -17,11 +22,11 @@ const meta: Meta<typeof Select> = {
); );
}, },
], ],
}; } satisfies Meta<SelectProps>;
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<TagProps>;
export const Default: Story = { export const Default: Story = {
args: { args: {

View File

@@ -16,7 +16,7 @@ import cx from "classnames";
import { useModalContext } from "../Modal/Modal"; import { useModalContext } from "../Modal/Modal";
import { keepTruthy } from "@/src/util"; import { keepTruthy } from "@/src/util";
interface Option { export interface Option {
value: string; value: string;
label: string; label: string;
disabled?: boolean; disabled?: boolean;

View File

@@ -1,5 +1,10 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { createMemoryHistory, MemoryRouter, Route } from "@solidjs/router"; import {
createMemoryHistory,
MemoryRouter,
Route,
RouteSectionProps,
} from "@solidjs/router";
import { Sidebar } from "@/src/components/Sidebar/Sidebar"; import { Sidebar } from "@/src/components/Sidebar/Sidebar";
import { Suspense } from "solid-js"; import { Suspense } from "solid-js";
import { addClanURI, resetStore } from "@/src/stores/clan"; import { addClanURI, resetStore } from "@/src/stores/clan";
@@ -101,7 +106,7 @@ const staticSections = [
}, },
]; ];
const meta: Meta<typeof Sidebar> = { const meta: Meta<RouteSectionProps> = {
title: "Components/Sidebar", title: "Components/Sidebar",
component: Sidebar, component: Sidebar,
render: () => { render: () => {
@@ -139,7 +144,7 @@ const meta: Meta<typeof Sidebar> = {
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<RouteSectionProps>;
const mockFetcher = <K extends OperationNames>( const mockFetcher = <K extends OperationNames>(
method: K, method: K,

View File

@@ -4,12 +4,12 @@ import { SidebarBody } from "@/src/components/Sidebar/SidebarBody";
import cx from "classnames"; import cx from "classnames";
import { splitProps } from "solid-js"; import { splitProps } from "solid-js";
interface LinkProps { export interface LinkProps {
path: string; path: string;
label?: string; label?: string;
} }
interface SectionProps { export interface SectionProps {
title: string; title: string;
links: LinkProps[]; links: LinkProps[];
} }

View File

@@ -1,5 +1,8 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { SidebarPane } from "@/src/components/Sidebar/SidebarPane"; import {
SidebarPane,
SidebarPaneProps,
} from "@/src/components/Sidebar/SidebarPane";
import { SidebarSection } from "./SidebarSection"; import { SidebarSection } from "./SidebarSection";
import { Divider } from "@/src/components/Divider/Divider"; import { Divider } from "@/src/components/Divider/Divider";
import { TextInput } from "@/src/components/Form/TextInput"; import { TextInput } from "@/src/components/Form/TextInput";
@@ -11,6 +14,9 @@ import { splitProps } from "solid-js";
import { Typography } from "@/src/components/Typography/Typography"; import { Typography } from "@/src/components/Typography/Typography";
import { MachineTags } from "@/src/components/Form/MachineTags"; import { MachineTags } from "@/src/components/Form/MachineTags";
import { setValue } from "@modular-forms/solid"; import { setValue } from "@modular-forms/solid";
import { StoryContext } from "@kachurun/storybook-solid-vite";
type Story = StoryObj<SidebarPaneProps>;
const profiles = { const profiles = {
ron: { ron: {
@@ -22,13 +28,18 @@ const profiles = {
}, },
}; };
const meta: Meta<typeof SidebarPane> = { const meta: Meta<SidebarPaneProps> = {
title: "Components/SidebarPane", title: "Components/SidebarPane",
component: SidebarPane, component: SidebarPane,
decorators: [
(
Story: StoryObj<SidebarPaneProps>,
context: StoryContext<SidebarPaneProps>,
) =>
() => <Story {...context.args} />,
],
}; };
type Story = StoryObj<typeof meta>;
export default meta; export default meta;
export const Default: Story = { export const Default: Story = {
@@ -40,7 +51,7 @@ export const Default: Story = {
}, },
// We have to provide children within a custom render function to ensure we aren't creating any reactivity outside the // We have to provide children within a custom render function to ensure we aren't creating any reactivity outside the
// solid-js scope. // solid-js scope.
render: (args) => ( render: (args: SidebarPaneProps) => (
<SidebarPane <SidebarPane
{...args} {...args}
children={ children={

View File

@@ -2,7 +2,7 @@ import { JSX, Show } from "solid-js";
import styles from "./SidebarSection.module.css"; import styles from "./SidebarSection.module.css";
import { Typography } from "@/src/components/Typography/Typography"; import { Typography } from "@/src/components/Typography/Typography";
interface SidebarSectionProps { export interface SidebarSectionProps {
title: string; title: string;
controls?: JSX.Element; controls?: JSX.Element;
children: JSX.Element; children: JSX.Element;

View File

@@ -18,7 +18,7 @@ import { Button } from "@/src/components/Button/Button";
import { Loader } from "../../components/Loader/Loader"; import { Loader } from "../../components/Loader/Loader";
import { SidebarSection } from "./SidebarSection"; import { SidebarSection } from "./SidebarSection";
interface SidebarSectionFormProps<FormValues extends FieldValues> { export interface SidebarSectionFormProps<FormValues extends FieldValues> {
title: string; title: string;
schema: GenericSchema<FormValues> | GenericSchemaAsync<FormValues>; schema: GenericSchema<FormValues> | GenericSchemaAsync<FormValues>;
initialValues: PartialValues<FormValues>; initialValues: PartialValues<FormValues>;

View File

@@ -7,7 +7,7 @@ import styles from "./SidebarSectionInstall.module.css";
import { Alert } from "../Alert/Alert"; import { Alert } from "../Alert/Alert";
import { useClanContext } from "@/src/routes/Clan/Clan"; import { useClanContext } from "@/src/routes/Clan/Clan";
interface SidebarSectionInstallProps { export interface SidebarSectionInstallProps {
clanURI: string; clanURI: string;
machineName: string; machineName: string;
} }

View File

@@ -6,7 +6,7 @@ import styles from "./SidebarSectionInstall.module.css";
import { UpdateModal } from "@/src/workflows/InstallMachine/UpdateMachine"; import { UpdateModal } from "@/src/workflows/InstallMachine/UpdateMachine";
import { useClanContext } from "@/src/routes/Clan/Clan"; import { useClanContext } from "@/src/routes/Clan/Clan";
interface SidebarSectionUpdateProps { export interface SidebarSectionUpdateProps {
clanURI: string; clanURI: string;
machineName: string; machineName: string;
} }

View File

@@ -1,16 +1,16 @@
import { Tag } from "@/src/components/Tag/Tag"; import { Tag, TagProps } from "@/src/components/Tag/Tag";
import { Meta, StoryObj } from "storybook-solidjs-vite"; import { Meta, type StoryContext, StoryObj } from "@kachurun/storybook-solid";
import { fn } from "storybook/test"; import { fn } from "storybook/test";
import Icon from "../Icon/Icon"; import Icon from "../Icon/Icon";
const meta: Meta<typeof Tag> = { const meta: Meta<TagProps> = {
title: "Components/Tag", title: "Components/Tag",
component: Tag, component: Tag,
}; };
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<TagProps>;
export const Default: Story = { export const Default: Story = {
args: { args: {
@@ -43,7 +43,7 @@ export const WithAction: Story = {
icon: IconAction, icon: IconAction,
interactive: true, interactive: true,
}, },
play: async ({ canvas, step, userEvent, args }) => { play: async ({ canvas, step, userEvent, args }: StoryContext) => {
await userEvent.click(canvas.getByRole("button")); await userEvent.click(canvas.getByRole("button"));
// await expect(args.icon.onClick).toHaveBeenCalled(); // await expect(args.icon.onClick).toHaveBeenCalled();
}, },

View File

@@ -1,11 +1,11 @@
import { TagGroup } from "@/src/components/TagGroup/TagGroup"; import { TagGroup, TagGroupProps } from "@/src/components/TagGroup/TagGroup";
import { Meta, StoryObj } from "storybook-solidjs-vite"; import { Meta, StoryObj } from "@kachurun/storybook-solid";
const meta: Meta<typeof TagGroup> = { const meta: Meta<TagGroupProps> = {
title: "Components/TagGroup", title: "Components/TagGroup",
component: TagGroup, component: TagGroup,
decorators: [ decorators: [
(Story) => ( (Story: StoryObj) => (
/* for some reason w-x from tailwind was not working */ /* for some reason w-x from tailwind was not working */
<div style="width: 196px"> <div style="width: 196px">
<Story /> <Story />
@@ -16,7 +16,7 @@ const meta: Meta<typeof TagGroup> = {
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<TagGroupProps>;
export const Default: Story = { export const Default: Story = {
args: { args: {

View File

@@ -1,18 +1,19 @@
import { Meta, StoryObj } from "storybook-solidjs-vite"; import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Toolbar } from "@/src/components/Toolbar/Toolbar"; import { Toolbar, ToolbarProps } from "@/src/components/Toolbar/Toolbar";
import { ToolbarButton } from "./ToolbarButton"; import { ToolbarButton } from "./ToolbarButton";
const meta: Meta<typeof Toolbar> = { const meta: Meta<ToolbarProps> = {
title: "Components/Toolbar", title: "Components/Toolbar",
component: Toolbar, component: Toolbar,
}; };
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<ToolbarProps>;
export const Default: Story = { export const Default: Story = {
// We have to specify children inside a render function to avoid issues with reactivity outside a solid-js context. // We have to specify children inside a render function to avoid issues with reactivity outside a solid-js context.
// @ts-expect-error: args in storybook is not typed correctly. This is a storybook issue.
render: (args) => ( render: (args) => (
<div class="flex h-[80vh]"> <div class="flex h-[80vh]">
<div class="mt-auto"> <div class="mt-auto">

View File

@@ -5,7 +5,7 @@ import Icon, { IconVariant } from "@/src/components/Icon/Icon";
import type { JSX } from "solid-js"; import type { JSX } from "solid-js";
import { Tooltip } from "../Tooltip/Tooltip"; import { Tooltip } from "../Tooltip/Tooltip";
interface ToolbarButtonProps export interface ToolbarButtonProps
extends JSX.ButtonHTMLAttributes<HTMLButtonElement> { extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
icon: IconVariant; icon: IconVariant;
description: JSX.Element; description: JSX.Element;

View File

@@ -1,18 +1,18 @@
import { Meta, StoryObj } from "storybook-solidjs-vite"; import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Tooltip } from "@/src/components/Tooltip/Tooltip"; import { Tooltip, TooltipProps } from "@/src/components/Tooltip/Tooltip";
import { Typography } from "@/src/components/Typography/Typography"; import { Typography } from "@/src/components/Typography/Typography";
const meta: Meta<typeof Tooltip> = { const meta: Meta<TooltipProps> = {
title: "Components/Tooltip", title: "Components/Tooltip",
component: Tooltip, component: Tooltip,
decorators: [ decorators: [
(Story) => ( (Story: StoryObj<TooltipProps>) => (
<div class="p-16"> <div class="p-16">
<Story /> <Story />
</div> </div>
), ),
], ],
render: (args) => ( render: (args: TooltipProps) => (
<div class="p-16"> <div class="p-16">
<Tooltip <Tooltip
{...args} {...args}
@@ -33,7 +33,7 @@ const meta: Meta<typeof Tooltip> = {
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<TooltipProps>;
export const Default: Story = { export const Default: Story = {
args: { args: {

View File

@@ -1,4 +1,4 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Family, Hierarchy, Typography, Weight } from "./Typography"; import { Family, Hierarchy, Typography, Weight } from "./Typography";
import { Component, For, Show } from "solid-js"; import { Component, For, Show } from "solid-js";
@@ -73,14 +73,14 @@ const TypographyExamples: Component<TypographyExamplesProps> = (props) => (
</table> </table>
); );
const meta: Meta<typeof TypographyExamples> = { const meta: Meta<TypographyExamplesProps> = {
title: "Components/Typography", title: "Components/Typography",
component: TypographyExamples, component: TypographyExamples,
}; };
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<TypographyExamplesProps>;
export const BodyCondensed: Story = { export const BodyCondensed: Story = {
args: { args: {

View File

@@ -9,15 +9,15 @@ import { getInClasses } from "@/src/util";
export type Hierarchy = "body" | "title" | "headline" | "label" | "teaser"; export type Hierarchy = "body" | "title" | "headline" | "label" | "teaser";
export type Weight = "normal" | "medium" | "bold"; export type Weight = "normal" | "medium" | "bold";
export type Family = "regular" | "mono"; export type Family = "regular" | "mono";
type Transform = "uppercase" | "lowercase" | "capitalize"; export type Transform = "uppercase" | "lowercase" | "capitalize";
interface SizeForHierarchy { export interface SizeForHierarchy {
body: "default" | "s" | "xs" | "xxs"; body: "default" | "s" | "xs" | "xxs";
headline: "default" | "m" | "l" | "xl" | "xxl"; headline: "default" | "m" | "l" | "xl" | "xxl";
title: "default" | "m" | "l"; title: "default" | "m" | "l";
label: "default" | "s" | "xs" | "xxs"; label: "default" | "s" | "xs" | "xxs";
teaser: "default"; teaser: "default";
} }
interface TagForHierarchy { export interface TagForHierarchy {
body: "span" | "p" | "div"; body: "span" | "p" | "div";
headline: "h1" | "h2" | "h3" | "h4"; headline: "h1" | "h2" | "h3" | "h4";
title: "h1" | "h2" | "h3" | "h4"; title: "h1" | "h2" | "h3" | "h4";
@@ -40,7 +40,7 @@ const defaultTagMap = {
label: "span", label: "span",
teaser: "h3", teaser: "h3",
} as const; } as const;
interface TypographyProps<H extends Hierarchy> { export interface TypographyProps<H extends Hierarchy> {
hierarchy: H; hierarchy: H;
children: JSX.Element; children: JSX.Element;
size?: SizeForHierarchy[H]; size?: SizeForHierarchy[H];

View File

@@ -1,7 +1,7 @@
import { createContext, JSX, useContext } from "solid-js"; import { createContext, JSX, useContext } from "solid-js";
import { ApiCall, OperationArgs, OperationNames } from "./api"; import { ApiCall, OperationArgs, OperationNames } from "./api";
interface ApiClient { export interface ApiClient {
fetch: Fetcher; fetch: Fetcher;
} }

View File

@@ -3,7 +3,7 @@ import { addClanURI, setActiveClanURI } from "@/src/stores/clan";
import { Params, Navigator, useParams, useSearchParams } from "@solidjs/router"; import { Params, Navigator, useParams, useSearchParams } from "@solidjs/router";
export const encodeBase64 = (value: string) => window.btoa(value); export const encodeBase64 = (value: string) => window.btoa(value);
const decodeBase64 = (value: string) => window.atob(value); export const decodeBase64 = (value: string) => window.atob(value);
export const selectClanFolder = async () => { export const selectClanFolder = async () => {
const req = callApi("get_clan_folder", {}); const req = callApi("get_clan_folder", {});
@@ -80,7 +80,7 @@ export const navigateToClan = (navigate: Navigator, clanURI: string) => {
export const navigateToOnboarding = (navigate: Navigator, addClan: boolean) => export const navigateToOnboarding = (navigate: Navigator, addClan: boolean) =>
navigate(`/${addClan ? "?addClan=true" : ""}`); navigate(`/${addClan ? "?addClan=true" : ""}`);
const navigateToMachine = ( export const navigateToMachine = (
navigate: Navigator, navigate: Navigator,
clanURI: string, clanURI: string,
name: string, name: string,
@@ -90,7 +90,7 @@ const navigateToMachine = (
navigate(path); navigate(path);
}; };
const clanURIParam = (params: Params) => { export const clanURIParam = (params: Params) => {
try { try {
return decodeBase64(params.clanURI); return decodeBase64(params.clanURI);
} catch (e) { } catch (e) {
@@ -101,19 +101,19 @@ const clanURIParam = (params: Params) => {
export const useClanURI = () => clanURIParam(useParams()); export const useClanURI = () => clanURIParam(useParams());
const machineNameParam = (params: Params) => { export const machineNameParam = (params: Params) => {
return params.machineName; return params.machineName;
}; };
const inputParam = (params: Params) => params.input; export const inputParam = (params: Params) => params.input;
const nameParam = (params: Params) => params.name; export const nameParam = (params: Params) => params.name;
const idParam = (params: Params) => params.id; export const idParam = (params: Params) => params.id;
export const useMachineName = (): string => machineNameParam(useParams()); export const useMachineName = (): string => machineNameParam(useParams());
const useInputParam = (): string => inputParam(useParams()); export const useInputParam = (): string => inputParam(useParams());
const useNameParam = (): string => nameParam(useParams()); export const useNameParam = (): string => nameParam(useParams());
const maybeUseIdParam = (): string | null => { export const maybeUseIdParam = (): string | null => {
const params = useParams(); const params = useParams();
if (params.id === undefined) { if (params.id === undefined) {
return null; return null;

View File

@@ -0,0 +1,25 @@
import { useMutation, useQueryClient } from "@tanstack/solid-query";
import { callApi, OperationArgs } from "@/src/hooks/api";
import { encodeBase64 } from "@/src/hooks/clan";
const queryClient = useQueryClient();
export const updateMachine = useMutation(() => ({
mutationFn: async (args: OperationArgs<"set_machine">) => {
const call = callApi("set_machine", args);
return {
args,
...call,
};
},
onSuccess: async ({ args }) => {
const {
name,
flake: { identifier },
} = args.machine;
await queryClient.invalidateQueries({
queryKey: ["clans", encodeBase64(identifier), "machine", name],
});
},
}));

View File

@@ -44,7 +44,7 @@ window.notifyBus = (msg: ProcessMessage) => {
* *
* consider using useNotify for reactive usage on solidjs * consider using useNotify for reactive usage on solidjs
*/ */
function _subscribeNotify<T extends ProcessMessage>( export function _subscribeNotify<T extends ProcessMessage>(
filter: (msg: T) => boolean, filter: (msg: T) => boolean,
callback: (msg: T) => void, callback: (msg: T) => void,
) { ) {
@@ -65,7 +65,7 @@ function _subscribeNotify<T extends ProcessMessage>(
* The signal has the value of the last message where filter was true * The signal has the value of the last message where filter was true
* null in case no message was recieved yet * null in case no message was recieved yet
*/ */
function useNotify<T extends ProcessMessage = ProcessMessage>( export function useNotify<T extends ProcessMessage = ProcessMessage>(
filter: (msg: T) => boolean = () => true as boolean, filter: (msg: T) => boolean = () => true as boolean,
) { ) {
const [message, setMessage] = createSignal<T | null>(null); const [message, setMessage] = createSignal<T | null>(null);

View File

@@ -16,16 +16,16 @@ export interface ClanDetails {
fieldsSchema: SuccessData<"get_clan_details_schema">; fieldsSchema: SuccessData<"get_clan_details_schema">;
} }
type Tags = SuccessData<"list_tags">; export type Tags = SuccessData<"list_tags">;
export type Machine = SuccessData<"get_machine">; export type Machine = SuccessData<"get_machine">;
type MachineState = SuccessData<"get_machine_state">; export type MachineState = SuccessData<"get_machine_state">;
export type MachineStatus = MachineState["status"]; export type MachineStatus = MachineState["status"];
type ListMachines = SuccessData<"list_machines">; export type ListMachines = SuccessData<"list_machines">;
type MachineDetails = SuccessData<"get_machine_details">; export type MachineDetails = SuccessData<"get_machine_details">;
type ListServiceModules = SuccessData<"list_service_modules">; export type ListServiceModules = SuccessData<"list_service_modules">;
export type ListServiceInstances = SuccessData<"list_service_instances">; export type ListServiceInstances = SuccessData<"list_service_instances">;
export interface MachineDetail { export interface MachineDetail {
@@ -35,7 +35,7 @@ export interface MachineDetail {
} }
export type MachinesQueryResult = UseQueryResult<ListMachines>; export type MachinesQueryResult = UseQueryResult<ListMachines>;
type ClanListQueryResult = UseQueryResult<ClanDetails>[]; export type ClanListQueryResult = UseQueryResult<ClanDetails>[];
export const DefaultQueryClient = new QueryClient({ export const DefaultQueryClient = new QueryClient({
defaultOptions: { defaultOptions: {
@@ -67,7 +67,7 @@ export const useMachinesQuery = (clanURI: string) => {
})); }));
}; };
const machineKey = (clanUri: string, machineName: string) => [ export const machineKey = (clanUri: string, machineName: string) => [
...clanKey(clanUri), ...clanKey(clanUri),
"machine", "machine",
encodeBase64(machineName), encodeBase64(machineName),
@@ -174,7 +174,7 @@ export const useMachineStateQuery = (clanURI: string, machineName: string) => {
})); }));
}; };
const useServiceModulesQuery = (clanURI: string) => { export const useServiceModulesQuery = (clanURI: string) => {
const client = useApiClient(); const client = useApiClient();
return useQuery<ListServiceModules>(() => ({ return useQuery<ListServiceModules>(() => ({
@@ -222,7 +222,10 @@ export const useServiceInstancesQuery = (clanURI: string) => {
})); }));
}; };
const useMachineDetailsQuery = (clanURI: string, machineName: string) => { export const useMachineDetailsQuery = (
clanURI: string,
machineName: string,
) => {
const client = useApiClient(); const client = useApiClient();
return useQuery<MachineDetails>(() => ({ return useQuery<MachineDetails>(() => ({
queryKey: [machineKey(clanURI, machineName), "details"], queryKey: [machineKey(clanURI, machineName), "details"],
@@ -248,7 +251,7 @@ const useMachineDetailsQuery = (clanURI: string, machineName: string) => {
})); }));
}; };
const ClanDetailsPersister = experimental_createQueryPersister({ export const ClanDetailsPersister = experimental_createQueryPersister({
storage: ClanDetailsStore, storage: ClanDetailsStore,
}); });
@@ -370,10 +373,10 @@ export const useClanListQuery = (
})); }));
}; };
type MachineFlashOptions = SuccessData<"get_machine_flash_options">; export type MachineFlashOptions = SuccessData<"get_machine_flash_options">;
type MachineFlashOptionsQuery = UseQueryResult<MachineFlashOptions>; export type MachineFlashOptionsQuery = UseQueryResult<MachineFlashOptions>;
const useMachineFlashOptions = (): MachineFlashOptionsQuery => { export const useMachineFlashOptions = (): MachineFlashOptionsQuery => {
const client = useApiClient(); const client = useApiClient();
return useQuery<MachineFlashOptions>(() => ({ return useQuery<MachineFlashOptions>(() => ({
queryKey: ["flash_options"], queryKey: ["flash_options"],
@@ -392,8 +395,8 @@ const useMachineFlashOptions = (): MachineFlashOptionsQuery => {
})); }));
}; };
type SystemStorageOptions = SuccessData<"list_system_storage_devices">; export type SystemStorageOptions = SuccessData<"list_system_storage_devices">;
type SystemStorageOptionsQuery = UseQueryResult<SystemStorageOptions>; export type SystemStorageOptionsQuery = UseQueryResult<SystemStorageOptions>;
export const useSystemStorageOptions = (): SystemStorageOptionsQuery => { export const useSystemStorageOptions = (): SystemStorageOptionsQuery => {
const client = useApiClient(); const client = useApiClient();
@@ -414,8 +417,10 @@ export const useSystemStorageOptions = (): SystemStorageOptionsQuery => {
})); }));
}; };
type MachineHardwareSummary = SuccessData<"get_machine_hardware_summary">; export type MachineHardwareSummary =
type MachineHardwareSummaryQuery = UseQueryResult<MachineHardwareSummary>; SuccessData<"get_machine_hardware_summary">;
export type MachineHardwareSummaryQuery =
UseQueryResult<MachineHardwareSummary>;
export const useMachineHardwareSummary = ( export const useMachineHardwareSummary = (
clanUri: string, clanUri: string,
@@ -452,8 +457,8 @@ export const useMachineHardwareSummary = (
})); }));
}; };
type MachineDiskSchema = SuccessData<"get_machine_disk_schemas">; export type MachineDiskSchema = SuccessData<"get_machine_disk_schemas">;
type MachineDiskSchemaQuery = UseQueryResult<MachineDiskSchema>; export type MachineDiskSchemaQuery = UseQueryResult<MachineDiskSchema>;
export const useMachineDiskSchemas = ( export const useMachineDiskSchemas = (
clanUri: string, clanUri: string,
@@ -491,7 +496,7 @@ export const useMachineDiskSchemas = (
}; };
export type MachineGenerators = SuccessData<"get_generators">; export type MachineGenerators = SuccessData<"get_generators">;
type MachineGeneratorsQuery = UseQueryResult<MachineGenerators>; export type MachineGeneratorsQuery = UseQueryResult<MachineGenerators>;
export const useMachineGenerators = ( export const useMachineGenerators = (
clanUri: string, clanUri: string,
@@ -533,7 +538,7 @@ export const useMachineGenerators = (
})); }));
}; };
type ServiceModulesQuery = ReturnType<typeof useServiceModules>; export type ServiceModulesQuery = ReturnType<typeof useServiceModules>;
export type ServiceModules = SuccessData<"list_service_modules">; export type ServiceModules = SuccessData<"list_service_modules">;
export const useServiceModules = (clanUri: string) => { export const useServiceModules = (clanUri: string) => {
const client = useApiClient(); const client = useApiClient();
@@ -561,7 +566,7 @@ export const useServiceModules = (clanUri: string) => {
export const clanKey = (clanUri: string) => ["clans", encodeBase64(clanUri)]; export const clanKey = (clanUri: string) => ["clans", encodeBase64(clanUri)];
export type ServiceInstancesQuery = ReturnType<typeof useServiceInstances>; export type ServiceInstancesQuery = ReturnType<typeof useServiceInstances>;
type ServiceInstances = SuccessData<"list_service_instances">; export type ServiceInstances = SuccessData<"list_service_instances">;
export const useServiceInstances = (clanUri: string) => { export const useServiceInstances = (clanUri: string) => {
const client = useApiClient(); const client = useApiClient();
return useQuery(() => ({ return useQuery(() => ({

View File

@@ -7,13 +7,13 @@ import {
} from "solid-js"; } from "solid-js";
import { createStore, SetStoreFunction, Store } from "solid-js/store"; import { createStore, SetStoreFunction, Store } from "solid-js/store";
interface StepBase { export interface StepBase {
id: string; id: string;
} }
type Step<ExtraFields = unknown> = StepBase & ExtraFields; export type Step<ExtraFields = unknown> = StepBase & ExtraFields;
interface StepOptions<Id, StoreType> { export interface StepOptions<Id, StoreType> {
initialStep: Id; initialStep: Id;
initialStoreData?: StoreType; initialStoreData?: StoreType;
} }
@@ -95,7 +95,10 @@ export function createStepper<
} }
type StoreTuple<T> = [get: Store<T>, set: SetStoreFunction<T>]; type StoreTuple<T> = [get: Store<T>, set: SetStoreFunction<T>];
interface StepperReturn<T extends readonly Step[], StepId = T[number]["id"]> { export interface StepperReturn<
T extends readonly Step[],
StepId = T[number]["id"],
> {
_store: never; _store: never;
activeStep: Accessor<StepId>; activeStep: Accessor<StepId>;
setActiveStep: (id: StepId) => void; setActiveStep: (id: StepId) => void;

View File

@@ -1,43 +1,45 @@
import { fn } from "storybook/test"; import { fn } from "storybook/test";
import type { Meta, StoryObj } from "storybook-solidjs-vite"; import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { ClanSettingsModal } from "./ClanSettingsModal"; import { ClanSettingsModal, ClanSettingsModalProps } from "./ClanSettingsModal";
const meta: Meta<typeof ClanSettingsModal> = { const meta: Meta<ClanSettingsModalProps> = {
title: "Modals/ClanSettings", title: "Modals/ClanSettings",
component: ClanSettingsModal, component: ClanSettingsModal,
}; };
export default meta; export default meta;
type Story = StoryObj<typeof meta>; type Story = StoryObj<ClanSettingsModalProps>;
export const Default: Story = { const props: ClanSettingsModalProps = {
args: { onClose: fn(),
onClose: fn(), model: {
model: { uri: "/home/foo/my-clan",
uri: "/home/foo/my-clan", details: {
details: { name: "Sol",
name: "Sol", description: null,
description: null, icon: null,
icon: null, },
fieldsSchema: {
name: {
readonly: true,
reason: null,
readonly_members: [],
}, },
fieldsSchema: { description: {
name: { readonly: false,
readonly: true, reason: null,
reason: null, readonly_members: [],
readonly_members: [], },
}, icon: {
description: { readonly: false,
readonly: false, reason: null,
reason: null, readonly_members: [],
readonly_members: [],
},
icon: {
readonly: false,
reason: null,
readonly_members: [],
},
}, },
}, },
}, },
}; };
export const Default: Story = {
args: props,
};

View File

@@ -8,7 +8,7 @@ import { useClanListQuery } from "@/src/hooks/queries";
import { Alert } from "@/src/components/Alert/Alert"; import { Alert } from "@/src/components/Alert/Alert";
import { NavSection } from "@/src/components/NavSection/NavSection"; import { NavSection } from "@/src/components/NavSection/NavSection";
interface ListClansModalProps { export interface ListClansModalProps {
onClose?: () => void; onClose?: () => void;
error?: { error?: {
title: string; title: string;

View File

@@ -39,7 +39,7 @@ import { ListClansModal } from "@/src/modals/ListClansModal/ListClansModal";
import { AddMachine } from "@/src/workflows/AddMachine/AddMachine"; import { AddMachine } from "@/src/workflows/AddMachine/AddMachine";
import { SelectService } from "@/src/workflows/Service/SelectServiceFlyout"; import { SelectService } from "@/src/workflows/Service/SelectServiceFlyout";
type WorldMode = "default" | "select" | "service" | "create" | "move"; export type WorldMode = "default" | "select" | "service" | "create" | "move";
function createClanContext( function createClanContext(
clanURI: string, clanURI: string,

View File

@@ -22,7 +22,7 @@ type FieldNames = "name" | "description" | "machineClass";
type FormValues = v.InferInput<typeof schema>; type FormValues = v.InferInput<typeof schema>;
interface SectionGeneralProps { export interface SectionGeneralProps {
clanURI: string; clanURI: string;
machineName: string; machineName: string;
onSubmit: (values: FormValues) => Promise<void>; onSubmit: (values: FormValues) => Promise<void>;

View File

@@ -52,7 +52,7 @@ export function createMachineMesh() {
}; };
} }
function createCubeBase( export function createCubeBase(
color: THREE.ColorRepresentation, color: THREE.ColorRepresentation,
emissive: THREE.ColorRepresentation, emissive: THREE.ColorRepresentation,
geometry: THREE.BoxGeometry, geometry: THREE.BoxGeometry,
@@ -70,7 +70,7 @@ function createCubeBase(
} }
// Function to build rounded rect shape // Function to build rounded rect shape
function roundedRectShape(w: number, h: number, r: number) { export function roundedRectShape(w: number, h: number, r: number) {
const shape = new THREE.Shape(); const shape = new THREE.Shape();
const x = -w / 2; const x = -w / 2;
const y = -h / 2; const y = -h / 2;

View File

@@ -80,7 +80,7 @@ const [lastClickedMachine, setLastClickedMachine] = createSignal<string | null>(
// Exported so others could also emit the signal if needed // Exported so others could also emit the signal if needed
// And for testing purposes // And for testing purposes
function emitMachineClick(id: string | null) { export function emitMachineClick(id: string | null) {
setLastClickedMachine(id); setLastClickedMachine(id);
if (id) { if (id) {
// Clear after a short delay to allow re-clicking the same machine // Clear after a short delay to allow re-clicking the same machine

View File

@@ -7,7 +7,7 @@ const [highlightGroups, setHighlightGroups] = createStore<
>({}); >({});
// Add highlight // Add highlight
function highlight(group: string, nodeId: string) { export function highlight(group: string, nodeId: string) {
setHighlightGroups(group, (prev = new Set()) => { setHighlightGroups(group, (prev = new Set()) => {
const next = new Set(prev); const next = new Set(prev);
next.add(nodeId); next.add(nodeId);
@@ -16,7 +16,7 @@ function highlight(group: string, nodeId: string) {
} }
// Remove highlight // Remove highlight
function unhighlight(group: string, nodeId: string) { export function unhighlight(group: string, nodeId: string) {
setHighlightGroups(group, (prev = new Set()) => { setHighlightGroups(group, (prev = new Set()) => {
const next = new Set(prev); const next = new Set(prev);
next.delete(nodeId); next.delete(nodeId);

View File

@@ -1,4 +1,4 @@
import { Meta, StoryObj } from "storybook-solidjs-vite"; import { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Splash } from "./splash"; import { Splash } from "./splash";
const meta: Meta = { const meta: Meta = {

Some files were not shown because too many files have changed in this diff Show More