Compare commits
3 Commits
ke-docs-te
...
update-nix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c3b3ffd8e | ||
|
|
ae5712229c | ||
|
|
0ea561f998 |
@@ -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'"
|
||||
print("Running tests with command: " + cmd)
|
||||
|
||||
|
||||
# Run tests as text-user (environment variables are set automatically)
|
||||
peer1.succeed(cmd)
|
||||
'';
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
diff --git a/src/main.rs b/src/main.rs
|
||||
index 8baf5924a7db..1234567890ab 100644
|
||||
--- a/src/main.rs
|
||||
+++ b/src/main.rs
|
||||
@@ -1295,6 +1295,12 @@ won't take effect until you reboot the system.
|
||||
From 46ec73cdeac58807a49b738c44d2e8d5dfbc5fc8 Mon Sep 17 00:00:00 2001
|
||||
From: pinpox <git@pablo.tools>
|
||||
Date: Mon, 20 Oct 2025 16:30:22 +0200
|
||||
Subject: [PATCH] patch switch-to-configuration-ng for container tests
|
||||
|
||||
---
|
||||
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 {
|
||||
// Use current version of systemctl binary before daemon is reexeced.
|
||||
@@ -15,3 +24,6 @@ index 8baf5924a7db..1234567890ab 100644
|
||||
let unit = path_to_unit_name(¤t_system_bin, &mountpoint);
|
||||
if let Some(new_filesystem) = new_filesystems.get(&mountpoint) {
|
||||
if current_filesystem.fs_type != new_filesystem.fs_type
|
||||
--
|
||||
2.51.0
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
options = {
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
ip address or hostname (domain) of the machine
|
||||
'';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"publickey": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
|
||||
"publickey": "age1yr5dfj6sx0lrcyegc5jentmwsf779wsc24lkd65dhrpkney0m59q5j060j",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
@@ -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": {
|
||||
"age": [
|
||||
{
|
||||
"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",
|
||||
"mac": "ENC[AES256_GCM,data:4631iJmioJ2vZ2PTFbdEJu7UqtyQbp43XBlgEbFAviGZdugb3weVI24rJ8m1Rdnxq8uciEeiX6YHBhURdWQY4JNm2wTGnjz7e2PwQ8FCwOmxCcIQPpdKKsziq/M4HArgD66eUxIWfTt1yJfHgBcUuuANbrbH8MirllT+hJTBhqE=,iv:rM8a/MpKbK7DlqjuR4BG77XDHLK11Q+E2rzZLDJalhk=,tag:bbGMn4anXrLHg4eLA0/CXA==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
"lastmodified": "2025-10-23T09:40:31Z",
|
||||
"mac": "ENC[AES256_GCM,data:rjoogj7WVVxOJYMGvZpFkrYcttme3YEZFdHiBy6FY/wbW6vJhxtMVsMlmtg3Ak/fQwxFQOKlXRVSxo9qGk1b05iYxZdZrzvdpH4wVD3xjzDWhIurDsuSHFqHyrx6TqfZzpKKijPTZESe6C/CCvV3uWo/BgBlOM0CrH3QHig4spw=,iv:mKKxmzQPcw0m3qPOTvyjFNO84phyjnALpnqhZFf/vQ0=,tag:yXSrmSLipwDrUQQs/lS+0A==,type:str]",
|
||||
"version": "3.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
@@ -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": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNQS96MUFubVdOR2VCc2xO\ncTc5QnNHNTFpdURnSnF3dVhBQXQ3bnBuRW1RCngzSVlhSW9rNUxoSWdKcEtKVXc3\nQitLZ2NDUXBSUmxtVWpYRUlvOHVXcW8KLS0tIGZaWlRVak9NYmt2elpwYStYenRE\nanlkT3BET1FjQ2lFZkp3SXFMSkJSaVkKKkr+MNNqs6Ve3K5OrZfBEGlnc7OAthqf\nOZrP9NYOTMgkvhFsZTVpUS0zskry0iwmTNt+KeluYf0Tko8K53Kx2A==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMMndqMFM2b3JxZDlSMUdM\nejJyWFZLU0lHN1ZYdFJJMmp2NExuY2tnYUZFCkVLbVNjQ0hYb2tld2xvQU4rNE1J\nRzFQMjV2eHhMc3AyUzlvSGV6cUFOQ1UKLS0tIEZpWVN3WHBEZ0Vjc2lkQXdJRVRB\nd296b2VZNmVQbUt6WENWNFYzb1p0NVkK/idbBu9CRAEmY2fIx3X9GJqOkpdkmdWP\nUVeNB+SAOxNK2a6ys6d1iGsnVvRmQuZ5V4GZN6xDvnpvibWdKCGKgQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXd2dVYmJIbUVVeXk5Nk1E\nekFiUldVVUhRTmE4dHRiTHNDdEMyS1pRV1RrCkNScGdXVSs4UU5id29DV0pZWDQr\nenV1QmpnOFk5aFpTTUxmb0hDVHZDdFkKLS0tIHpmalJtRC94bEhaUStmeUlHT21w\nd3o3UzJHZklxK0RCYUUxc2c3aG1XclkKEPq1ZgyGiAK/Hy4zT7wfdDfPEE3vMHpR\nzwQV5y3M3DmlnKQEvJu0DpQ334CyAcubZC7cswQdUrM8TPqJhb/TuA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
"recipient": "age1yr5dfj6sx0lrcyegc5jentmwsf779wsc24lkd65dhrpkney0m59q5j060j",
|
||||
"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",
|
||||
"mac": "ENC[AES256_GCM,data:wdAFURkJZvclbz3UFPSPV9fma7zrZVEhMhsRqylGQMLepX/WohEAr8nJgeHl05be1Q8M8biPXCCoL0vfwg4BRZOkhD8PusJh8iBI3+STNQe/S1qoIK1ByfBFhJD+tIsVsgduLp6G32e6SRNvkuX3UpJqyViuRUavfQd3b8LRU4I=,iv:S3sMNTz5Kg4TxHj1tnk/ayiFuO74dR4aPnnomtkGByo=,tag:uive2bYe42s6VtPd03jTMw==,type:str]",
|
||||
"lastmodified": "2025-10-23T09:40:31Z",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQWWo5OEJ5N1RTR0xMaDhL\nQnlUV2RrRXIzM01OemhQWjVkd3FNZjRhR2dzCi9IeE56b3VZTkNkdW9DMzVia3Zx\nbklxWmFpenRjdEIrc0ZDTGdmSTAxRTQKLS0tIHZJdjdYUzhhY0YzQjRqS0psZmpI\nVHJpUjNZNHRpc2ZWSml1TVNNejhiT28K8TTP/J+XspXZ7TVYj9YaBhEodPIXjojB\nRLqAIgJXRaK4NCLukC6l0IMii6w5J/512RnO2ZBTGhKfbdLfyLOFqg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBR2pTN1Z2czJOelM1SjQ4\nQ1BxK09iT0JlNjBEMThoRnRXTXBRejk3ZVVRClJMeVlHTklwTzRubldvdmdmUzRJ\ndDR1M3pEMHROUktyckxoODBxTXozMUUKLS0tIHBUTFFseTJSaSsyWU5PdThNQ2hG\nTXpjZlhIaG1CM2E3R2JmdWlndTZzZHcKYbFqPfQ5s157FBj2Xs8Q5lXgi+FUX9aZ\ne6nOnOHvmgq6MdDK2z6WjtqP4HM/WU9iFSrGSQCvhPFweY1ILmU9tQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrZVc5b0FhbzNXcG1zUDlD\neEVWcWpSRkRCMkxBTHdBM3dCbjVpR3FBa0VjCitlTmx4eUJOMHlaU0dFZEhpK3ZD\nZzlMQXVuZWpnaUNmQW9kOGtOaGVDMU0KLS0tIFNlUi9LSzF0UEJCSVBiRlRSNFQz\nNHhMbmNlRXd4ZEJQWVcvTWdCRWEzMUkKls7RbmNOdPDx8z15F+7qay9qIWx6jNsN\nTahT+GgbG29t1aGQCb0yEzKuUyAp39maxxSWToPsfCgJSYJ8RYiUng==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
"recipient": "age1yr5dfj6sx0lrcyegc5jentmwsf779wsc24lkd65dhrpkney0m59q5j060j",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2dGVGd1A5dkt6QzAyL2Ja\nSUNoSEc0K0NQc04zREZSN3JORUdMRlpCdkI4CktSSS9NTjhKTUt1YUhTZERmK1Fi\nSjhmTG1CckNTYVNnL1MrR3d1VnRLMkkKLS0tIFNBS2Z0N3BFWjRUcGRDMm5mM2FR\nNlp3VkRYN1dtMGdRSFVjNXVZb2pjbXcKYaWNCnqIe85ldUWh5RWcbxA28k/1EJV9\ntoD5y6Qu0qZqm7vhgHPSZ8fCmuiafQTUj77XhjJbfw1exac1wP0m8w==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-09-18T14:33:39Z",
|
||||
"mac": "ENC[AES256_GCM,data:g+9/fRiqom2+W28ZpiF+oBj9V6ieq5Xz3sRz3GyzvHoLr6yw51JvpG2QuYNYANW0WCiUjFDkU0qPj/9gLHcuX52nc+gNaTzznb1QGPg7WCGSQI7xaMzyYsPxHpg/BOdj5CL8GyLiOWstD1ch0kc3bJmyu68sJUs04uGtHAADzsE=,iv:oASrYaZarEPDu0R3hd/jMazLgwG5r//hIdMyU/tN15o=,tag:o1fgf5oy+rlWXg88FN5Nfw==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
"lastmodified": "2025-10-23T09:40:34Z",
|
||||
"mac": "ENC[AES256_GCM,data:y4bNxCWif4JL/AfkfV2wQb22xooHQUgr6//ajzgyBJ5Z9xB8WboVhyTn0nKnA6G+1PpJmoaXUWFQcv0kq6RPFw3teATw5pdKK29NS9rxqnIht5v7mrqP0CnVoFQVDz1yGtY0rotGd7hUNAXsrjb6Ewfs6N3k8QPwpTX4V7STT48=,iv:+BMrr/Vjnd8DT7aC1/ckp5LZ7UuQkffsMeGbBFxjhGA=,tag:vuUWd2UjJaVY/20QrIzC+g==,type:str]",
|
||||
"version": "3.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQeVh2M2tqSGlOVkpzNlhU\nd0pMd1R0c0tQWnZzdXViWmtxcjl1Wk1Ka0FNCnBUUWJVbjlyR1hSNGpXNWlPRHJB\nNnMzN3BMQ2NDamFBMlhHbVdJUEZ6cjQKLS0tIEJjWmI0ZDl1NXgrSW9uc0R0LzAr\neEwwOC9DdDg2RTJHQ0M3QTFlcVBaSE0K2Du4NguefdEyY1gS6OuVdO3gHga4omcR\n8B+K1wUfIQbArxZLawPxrj7WNDoW5d4mF9fA3MeV1DFyc4KwtYZmUw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQb3hNVW5BK05tRllUTFRR\nUzM3aFJYamVzZEZELzRieDMwNzBtMTY2ZnpBCjdFU25uSllRYjloTjV2Zjc3RjAz\nTTl4M1pRL0g0QklhUlNjK2oveHZQWVUKLS0tIGlLdHFWMHZ6QXltdEF1YmlicXgy\ndzZVUGRmQ1JKUFN6WEhIbEMvZy9xUU0KurnoOMO9YEbsqg8zYw7vqL1hjKGnq65B\nxI+uV0aGVS79zu6Sqtm62XmgRNdySdz+seYxtISZviGRvhKsvHWi0g==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvWkdBakVrMVR4RU8xdDlF\nRDkvL0Mrb3ltazhIMjRLZDVlSTVlaFY2ODBBCnlQM2s0SGEvZjFDN3dGWDhIN0dK\nenhQbjZ1ZS9QZzg5SE5XazZXS3dFSkkKLS0tIHJhKzhadGpjTXd4L3hOQkhpR0Fy\nYzhTN2dxVSt3OE5uZFpuWmVlYW4vd1kKwHOxP0C5mLcm4oIT/sGQtUsdsmu3LSN0\nSola5+N+IrAZ+HKnuZlDLZ5JmJSc5j/YhGNn7KR1xhkhfGSS1e3UZw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
"recipient": "age1yr5dfj6sx0lrcyegc5jentmwsf779wsc24lkd65dhrpkney0m59q5j060j",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPSEJWanVSNEdkTDd1T3ho\nUFE2UklxRmp4L0M4MHlnM3o3Q0FkR00rRjNBCkk2SEVpQjdCeUpxN21mbG5LdUZh\nUXhwRkpwVDZuTzhUb2g3WmFnR3c4NWMKLS0tIGcrbkIrYVYyVVlBN0NyYlI4cGx1\nTitVR083K3pwL1Zlc1I3VVdVakJCVkUKCcDSfeu4Md5iPcZOcCktDvwXh0G3wdFb\nGr73xyIY9lcP2+NupMBgKTBxck4beRcJxxyFhYf9ZSkmIK3e4pFQNg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-09-18T14:33:39Z",
|
||||
"mac": "ENC[AES256_GCM,data:ehbrYqTJcsBKGHUB25JHFnKXrJ6z3LkcElZ89xVr4XxLet+odbhsjIoP2FCcxex7PlXcegMduhHBpXwNGUbX+IUNAXTxlWA9CLDmYhWuS2WLiEVXrS11NE03/zUyHdVx/C38dbIPrWD9iaYSrAiuOyfqDTh9k/Bn7vehLTtadoE=,iv:Nk2WVuJydi5tfsb1Mib4A6NocBCDp9QoIbSadq3bIDI=,tag:IaoyfCv3SkmtemXMR9XnkA==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
"lastmodified": "2025-10-23T09:40:34Z",
|
||||
"mac": "ENC[AES256_GCM,data:Qh2VQ43UqUhQtMBEhjBfky2hUjTNxJcKrIm6zGteJ/X+z0FxZ9LoUew48ml+HPHvPN+vLRtgVeOkP2d3x0kq/ag4RDNFuK7IgkzrVt8vzT0IpDHJmyw1ehGYy/MhMaDR5zv0ADZV/GrXrn9O+Wj74cgGqYg15TQJ3I/a68ViYhk=,iv:aJZCcd25xaOhmrfkJZuGUAdAGb7lFrP3D5kZtZnP/v4=,tag:se43YOxOEZHawE1SnnvTzA==,type:str]",
|
||||
"version": "3.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1ntpf7lqqw4zrk8swjvwtyfak7f2wg04uf7ggu6vk2yyt9qt74qkswn25ck",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4Tk1INGtybUVlejlNNlZE\nVms3TkdRVVF1T0E4TmV3NmxvYWVEL2U3WVhNCjJIaHhBcWVlMEYxRjg5bzJpTWdJ\neUhaRTNRTmtlTW0zUXQxTVZEMkQ2MFEKLS0tIFNGWDI4b2FXTE8xQ2xqb0cyK3FI\ncktHWnE5c1ZSVFpmQU1HZmU2VVB1QmcK/s1fVmwpMMg4BYkkAJzSY7hVQWae1F7g\nmfH8EGlr74mifWUNEbd49/K13nl8atQx6bcau83JIEQR+yyihuY4Jw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDRmExdUNwWGRnQ2FLWXpU\nWW40V09mNTJwMEk1d0xGRjRLZ2NGRk9uQVg4CmE5cWpZVE81TzJxZm04ZWowVW1y\naUVldkc4MHJsbW1vOTh6KzFNM3lGUncKLS0tIDlDdW90dzlJTVRwN2JlYWFTZHpC\nV1pnOStuanV3MmJwWXJVbitqV0dUK28KEp0VUW7F1kRB2VqINUu1yXLbDwvvLzJn\noFd9WnA6HXxfyZvwk45DSwnb6VskuQryy9cqNYgvfiVOAaPuK0rsHg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
},
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsL2FXVytUUVZnVU90bG5L\nYURiYjgwN3RuTldWMGl4clpUWmxkeUsrVzM0CkhKZFgwWHl4dWhNSWRQRXVPNDR6\na3hHNmp2RG9YNDhNM2MyV2FuOGY2UlUKLS0tIFpNU2tNOHdhRDRTdHhYWVh2NGZa\nU3J3S0hpclZzWGIwTlFyczdNZkZSZTAKXCZrLaIOVq90ejoKMaRiK0xNw8WOPcnm\nz2uxProEYvQhY8k29mhCFX5HCN0tGn1XTtHeDL7uHuKuFsnSG/fgYQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
"recipient": "age1yr5dfj6sx0lrcyegc5jentmwsf779wsc24lkd65dhrpkney0m59q5j060j",
|
||||
"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",
|
||||
"mac": "ENC[AES256_GCM,data:QkGJKj/H+MI9Mr9Up5NDUToSddY5eTz47egc2+IatfxR8RebKJ2/mYaeLV26vPdmY60bIac4N/nZkoa6IVBhkHHMvsEHsx3nD6Lro9Wf/pWP8Zddzr90LF5p2+wusq25JutKQiPKOb2gmrcagmSsH/7V/UqI/my3PMeKmw6irhw=,iv:hOtHF/cDFdNfvqCKRhJsOwAHEiQmCPjENzsg23sKG+Q=,tag:K7qG9b4fQD0VbAV8OYp3vw==,type:str]",
|
||||
"unencrypted_suffix": "_unencrypted",
|
||||
"version": "3.10.2"
|
||||
"lastmodified": "2025-10-23T09:40:34Z",
|
||||
"mac": "ENC[AES256_GCM,data:k60YIgs+HdcrGkaW4XI3iu2O6JMwlX0ToV8o/Eix27M1xJ2ipcnJI7gghWGBG3GWlzuVHAl9QlkFPu4SRv6KtP62iGzbnzXlbIe+Z1eQgkn+GGaM39SZpsMlW8T1OtV3mW6oe+NRCP7zmPgc2Yr3U08wYSooDOn4XOze5qZNbGk=,iv:q8siSK4a8CHkXcJxO4QTsX12zZxKTwHdw6qirF8j/v0=,tag:aWrxb1x+7BJQ9vIrttc+Hw==,type:str]",
|
||||
"version": "3.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
|
||||
# Collect searchDomains from all servers in this instance
|
||||
allServerSearchDomains = lib.flatten (
|
||||
lib.mapAttrsToList (_name: machineConfig: machineConfig.settings.certificate.searchDomains or [ ]) (
|
||||
@@ -46,7 +47,7 @@
|
||||
)
|
||||
);
|
||||
# Merge client's searchDomains with all servers' searchDomains
|
||||
searchDomains = lib.uniqueStrings (settings.certificate.searchDomains ++ allServerSearchDomains);
|
||||
searchDomains = uniqueStrings (settings.certificate.searchDomains ++ allServerSearchDomains);
|
||||
in
|
||||
{
|
||||
clan.core.vars.generators.openssh-ca = lib.mkIf (searchDomains != [ ]) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Yggdrasil is designed to be a future-proof and decentralised alternative to the
|
||||
structured routing protocols commonly used today on the internet. Inside your
|
||||
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.
|
||||
Yggdrasil is designed to be a future-proof and decentralised alternative to
|
||||
the structured routing protocols commonly used today on the internet. Inside your clan, it will allow you to reach all of your machines.
|
||||
|
||||
## Example Usage
|
||||
|
||||
|
||||
@@ -29,13 +29,12 @@
|
||||
];
|
||||
};
|
||||
|
||||
options.extraPeers = lib.mkOption {
|
||||
options.peers = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
Additional static peers to configure for this host. If you use a
|
||||
VPN clan service, it will automatically be added as peers to other hosts.
|
||||
Local peers are also auto-discovered and don't need to be added.
|
||||
Static peers to configure for this host.
|
||||
If not set, local peers will be auto-discovered
|
||||
'';
|
||||
example = [
|
||||
"tcp://192.168.1.1:6443"
|
||||
@@ -46,67 +45,16 @@
|
||||
};
|
||||
};
|
||||
perInstance =
|
||||
{
|
||||
settings,
|
||||
roles,
|
||||
exports,
|
||||
...
|
||||
}:
|
||||
{ settings, ... }:
|
||||
{
|
||||
nixosModule =
|
||||
{
|
||||
config,
|
||||
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 = {
|
||||
|
||||
files.privateKey = { };
|
||||
@@ -151,7 +99,7 @@
|
||||
settings = {
|
||||
PrivateKeyPath = "/key";
|
||||
IfName = "ygg";
|
||||
Peers = lib.lists.unique (exportedPeers ++ settings.extraPeers);
|
||||
Peers = settings.peers;
|
||||
MulticastInterfaces = [
|
||||
# Ethernet is preferred over WIFI
|
||||
{
|
||||
|
||||
@@ -17,13 +17,6 @@
|
||||
roles.default.machines.peer1 = { };
|
||||
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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -140,6 +140,9 @@
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(import ./shared.nix {
|
||||
@@ -156,7 +159,7 @@
|
||||
config = {
|
||||
systemd.services.zerotier-inventory-autoaccept =
|
||||
let
|
||||
machines = lib.uniqueStrings (
|
||||
machines = uniqueStrings (
|
||||
(lib.optionals (roles ? moon) (lib.attrNames roles.moon.machines))
|
||||
++ (lib.optionals (roles ? controller) (lib.attrNames roles.controller.machines))
|
||||
++ (lib.optionals (roles ? peer) (lib.attrNames roles.peer.machines))
|
||||
|
||||
18
devFlake/flake.lock
generated
18
devFlake/flake.lock
generated
@@ -3,10 +3,10 @@
|
||||
"clan-core-for-checks": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1761204206,
|
||||
"narHash": "sha256-A4KDudGblln1yh8c95OVow2NRlHtbGZXr/pgNenyrNc=",
|
||||
"lastModified": 1760361585,
|
||||
"narHash": "sha256-v4PnSmt1hXW4dSgVWxcd1ZeEBlhO7NksNRC5cX7L5iw=",
|
||||
"ref": "main",
|
||||
"rev": "aabbe0dfac47b7cfbe2210bcb27fb7ecce93350f",
|
||||
"rev": "7e7e58eb64ef61beb0a938a6622ec0122382131b",
|
||||
"shallow": true,
|
||||
"type": "git",
|
||||
"url": "https://git.clan.lol/clan/clan-core"
|
||||
@@ -105,11 +105,11 @@
|
||||
},
|
||||
"nixpkgs-dev": {
|
||||
"locked": {
|
||||
"lastModified": 1761544814,
|
||||
"narHash": "sha256-t5f0A+2MtSWTfA6hzMNiotpIMGLlSQF2JnK9m6nkzIY=",
|
||||
"lastModified": 1761164809,
|
||||
"narHash": "sha256-3uM91Lx9WZomE6MMEBorJyEyBNiHWRIxza/GganDxew=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e5aa45ed6c45058ec109658b2b7352a9a062cdf3",
|
||||
"rev": "3d2db9755e7815937fb7b8f089fad9b44bc416d8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -208,11 +208,11 @@
|
||||
"nixpkgs": []
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1761311587,
|
||||
"narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=",
|
||||
"lastModified": 1760945191,
|
||||
"narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc",
|
||||
"rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -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.
|
||||
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.
|
||||
They offer significant performance advantages over VM tests:
|
||||
|
||||
- **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.
|
||||
- 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.
|
||||
- setuid binaries don't work
|
||||
|
||||
### Where to find examples for NixOS container tests
|
||||
|
||||
|
||||
18
flake.lock
generated
18
flake.lock
generated
@@ -71,11 +71,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1761339987,
|
||||
"narHash": "sha256-IUaawVwItZKi64IA6kF6wQCLCzpXbk2R46dHn8sHkig=",
|
||||
"lastModified": 1760721282,
|
||||
"narHash": "sha256-aAHphQbU9t/b2RRy2Eb8oMv+I08isXv2KUGFAFn7nCo=",
|
||||
"owner": "nix-darwin",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "7cd9aac79ee2924a85c211d21fafd394b06a38de",
|
||||
"rev": "c3211fcd0c56c11ff110d346d4487b18f7365168",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -115,10 +115,10 @@
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 315532800,
|
||||
"narHash": "sha256-yDxtm0PESdgNetiJN5+MFxgubBcLDTiuSjjrJiyvsvM=",
|
||||
"rev": "d7f52a7a640bc54c7bb414cca603835bf8dd4b10",
|
||||
"narHash": "sha256-Zk4ebRwxwsWjR20i15LsNk13uVhgubF44pJQddcCt4w=",
|
||||
"rev": "cb82756ecc37fa623f8cf3e88854f9bf7f64af93",
|
||||
"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": {
|
||||
"type": "tarball",
|
||||
@@ -181,11 +181,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1761311587,
|
||||
"narHash": "sha256-Msq86cR5SjozQGCnC6H8C+0cD4rnx91BPltZ9KK613Y=",
|
||||
"lastModified": 1760945191,
|
||||
"narHash": "sha256-ZRVs8UqikBa4Ki3X4KCnMBtBW0ux1DaT35tgsnB1jM4=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "2eddae033e4e74bf581c2d1dfa101f9033dbd2dc",
|
||||
"rev": "f56b1934f5f8fcab8deb5d38d42fd692632b47c2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -137,12 +137,6 @@ in
|
||||
default = { };
|
||||
type = types.submoduleWith {
|
||||
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)
|
||||
clan-core
|
||||
nixpkgs
|
||||
|
||||
@@ -7,10 +7,14 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib) mkOption types uniqueStrings;
|
||||
inherit (lib) mkOption types;
|
||||
inherit (types) attrsWith submoduleWith;
|
||||
|
||||
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
|
||||
|
||||
@@ -77,7 +81,6 @@ let
|
||||
applySettings =
|
||||
instanceName: instance:
|
||||
lib.mapAttrs (roleName: role: {
|
||||
settings = config.instances.${instanceName}.roles.${roleName}.finalSettings.config;
|
||||
machines = lib.mapAttrs (machineName: _v: {
|
||||
settings =
|
||||
config.instances.${instanceName}.roles.${roleName}.machines.${machineName}.finalSettings.config;
|
||||
@@ -155,29 +158,6 @@ in
|
||||
(
|
||||
{ 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
|
||||
options.machines = mkOption {
|
||||
description = ''
|
||||
@@ -236,7 +216,7 @@ in
|
||||
|
||||
options.extraModules = lib.mkOption {
|
||||
default = [ ];
|
||||
type = types.listOf types.deferredModule;
|
||||
type = types.listOf (types.either types.deferredModule types.str);
|
||||
};
|
||||
}
|
||||
)
|
||||
@@ -503,9 +483,6 @@ in
|
||||
type = types.deferredModule;
|
||||
default = { };
|
||||
description = ''
|
||||
!!! Danger "Experimental Feature"
|
||||
This feature is experimental and will change in the future.
|
||||
|
||||
export modules defined in 'perInstance'
|
||||
mapped to their instance name
|
||||
|
||||
@@ -634,9 +611,6 @@ in
|
||||
type = types.deferredModule;
|
||||
default = { };
|
||||
description = ''
|
||||
!!! Danger "Experimental Feature"
|
||||
This feature is experimental and will change in the future.
|
||||
|
||||
export modules defined in 'perMachine'
|
||||
mapped to their machine name
|
||||
|
||||
@@ -738,9 +712,6 @@ in
|
||||
|
||||
exports = mkOption {
|
||||
description = ''
|
||||
!!! Danger "Experimental Feature"
|
||||
This feature is experimental and will change in the future.
|
||||
|
||||
This services exports.
|
||||
Gets merged with all other services exports.
|
||||
|
||||
@@ -879,11 +850,7 @@ in
|
||||
instanceRes.nixosModule
|
||||
]
|
||||
++ (map (
|
||||
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
|
||||
s: if builtins.typeOf s == "string" then "${directory}/${s}" else s
|
||||
) instanceCfg.roles.${roleName}.extraModules);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -137,7 +137,6 @@ in
|
||||
settings = { };
|
||||
};
|
||||
};
|
||||
settings = { };
|
||||
};
|
||||
peer = {
|
||||
machines = {
|
||||
@@ -147,9 +146,6 @@ in
|
||||
};
|
||||
};
|
||||
};
|
||||
settings = {
|
||||
timeout = "foo-peer";
|
||||
};
|
||||
};
|
||||
};
|
||||
settings = {
|
||||
|
||||
@@ -102,23 +102,18 @@ in
|
||||
specificRoleSettings =
|
||||
res.importedModulesEvaluated.self-A.result.allMachines.jon.passthru.instances.instance_foo.roles.peer;
|
||||
};
|
||||
expected = {
|
||||
expected = rec {
|
||||
hasMachineSettings = true;
|
||||
hasRoleSettings = true;
|
||||
hasRoleSettings = false;
|
||||
specificMachineSettings = {
|
||||
timeout = "foo-peer-jon";
|
||||
};
|
||||
specificRoleSettings = {
|
||||
machines = {
|
||||
jon = {
|
||||
settings = {
|
||||
timeout = "foo-peer-jon";
|
||||
};
|
||||
settings = specificMachineSettings;
|
||||
};
|
||||
};
|
||||
settings = {
|
||||
timeout = "foo-peer";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -501,7 +501,7 @@ def setup_filesystems(container: ContainerInfo) -> None:
|
||||
if file.is_symlink():
|
||||
target = file.readlink()
|
||||
sym = container.nix_store_dir / file.name
|
||||
os.symlink(target, sym)
|
||||
sym.symlink_to(target)
|
||||
|
||||
# Read /proc/mounts and replicate every bind mount
|
||||
with Path("/proc/self/mounts").open() as f:
|
||||
|
||||
@@ -1,26 +1,4 @@
|
||||
{ self, lib, ... }:
|
||||
let
|
||||
clanModule = lib.modules.importApply ./default.nix { clan-core = self; };
|
||||
in
|
||||
{
|
||||
flake.modules.clan.default = clanModule;
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
jsonDocs = import ./eval-docs.nix {
|
||||
inherit
|
||||
pkgs
|
||||
lib
|
||||
clanModule
|
||||
;
|
||||
clanLib = self.clanLib;
|
||||
};
|
||||
in
|
||||
{
|
||||
legacyPackages.clan-options = jsonDocs.optionsJSON;
|
||||
};
|
||||
flake.modules.clan.default = lib.modules.importApply ./default.nix { clan-core = self; };
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ in
|
||||
Global information about the clan.
|
||||
'';
|
||||
type = types.deferredModuleWith {
|
||||
staticModules = [ ../inventoryClass/meta.nix ];
|
||||
staticModules = [ ../inventoryClass/meta-interface.nix ];
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
|
||||
@@ -222,18 +222,6 @@ in
|
||||
inventoryClass =
|
||||
let
|
||||
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
|
||||
{
|
||||
_module.args = {
|
||||
@@ -242,12 +230,7 @@ in
|
||||
imports = [
|
||||
../inventoryClass/default.nix
|
||||
{
|
||||
inherit
|
||||
inventory
|
||||
directory
|
||||
flakeInputs
|
||||
relativeDirectory
|
||||
;
|
||||
inherit inventory directory flakeInputs;
|
||||
exportsModule = config.exportsModule;
|
||||
}
|
||||
(
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
{
|
||||
pkgs,
|
||||
lib,
|
||||
clanModule,
|
||||
clanLib,
|
||||
clan-core,
|
||||
}:
|
||||
let
|
||||
eval = lib.evalModules {
|
||||
modules = [
|
||||
clanModule
|
||||
clan-core.modules.clan.default
|
||||
];
|
||||
};
|
||||
|
||||
evalDocs = pkgs.nixosOptionsDoc {
|
||||
options = eval.options;
|
||||
warningsAreErrors = false;
|
||||
transformOptions = clanLib.docs.stripStorePathsFromDeclarations;
|
||||
transformOptions = clan-core.clanLib.docs.stripStorePathsFromDeclarations;
|
||||
};
|
||||
in
|
||||
{
|
||||
@@ -1,5 +1,24 @@
|
||||
{
|
||||
self,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
./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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,11 +40,12 @@ let
|
||||
name:
|
||||
let
|
||||
v = set.${name};
|
||||
loc = path ++ [ name ];
|
||||
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
|
||||
[ ]
|
||||
@@ -55,7 +56,8 @@ let
|
||||
# Remove extraModules from serialization,
|
||||
# identified by: prefix + pathLength + name
|
||||
# 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;
|
||||
in
|
||||
{
|
||||
@@ -81,14 +83,6 @@ in
|
||||
directory = mkOption {
|
||||
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 {
|
||||
type = types.attrsOf (submodule ({
|
||||
options = {
|
||||
|
||||
@@ -115,7 +115,7 @@ in
|
||||
meta = lib.mkOption {
|
||||
type = lib.types.submoduleWith {
|
||||
modules = [
|
||||
./meta.nix
|
||||
./meta-interface.nix
|
||||
];
|
||||
};
|
||||
};
|
||||
@@ -359,7 +359,7 @@ in
|
||||
inherit clanLib;
|
||||
};
|
||||
}
|
||||
(import ./role.nix { })
|
||||
(import ./roles-interface.nix { })
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
@@ -44,6 +44,12 @@ in
|
||||
description = ''
|
||||
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
|
||||
**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 = [ ];
|
||||
type = types.listOf types.raw;
|
||||
type = types.listOf (
|
||||
types.oneOf [
|
||||
types.str
|
||||
types.path
|
||||
(types.attrsOf types.anything)
|
||||
]
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
70
modules/inventoryClass/service-list-from-inputs.nix
Normal file
70
modules/inventoryClass/service-list-from-inputs.nix
Normal 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;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -107,7 +107,8 @@ in
|
||||
readOnly = true;
|
||||
};
|
||||
tld = lib.mkOption {
|
||||
type = types.strMatching "[a-z]+";
|
||||
default = "clan";
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
the TLD for the clan
|
||||
'';
|
||||
|
||||
@@ -94,7 +94,7 @@ class TestHttpBridge:
|
||||
|
||||
def test_http_bridge_middleware_setup(self, http_bridge: tuple) -> None:
|
||||
"""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
|
||||
# The actual HTTP handling will be tested through the server integration tests
|
||||
|
||||
1
pkgs/clan-app/ui/.gitignore
vendored
1
pkgs/clan-app/ui/.gitignore
vendored
@@ -2,4 +2,5 @@ app/api
|
||||
app/.fonts
|
||||
|
||||
.vite
|
||||
storybook-static
|
||||
*.css.d.ts
|
||||
@@ -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 {
|
||||
framework: "storybook-solidjs-vite",
|
||||
const config: StorybookConfig = {
|
||||
framework: "@kachurun/storybook-solid-vite",
|
||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.tsx"],
|
||||
addons: [
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-docs",
|
||||
"@storybook/addon-a11y",
|
||||
{
|
||||
name: "@storybook/addon-vitest",
|
||||
options: {
|
||||
cli: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
async viteFinal(config) {
|
||||
return mergeConfig(config, {
|
||||
define: { "process.env": {} },
|
||||
});
|
||||
},
|
||||
core: {
|
||||
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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Preview } from "storybook-solidjs-vite";
|
||||
import type { Preview } from "@kachurun/storybook-solid-vite";
|
||||
|
||||
import "../src/index.css";
|
||||
import "./preview.css";
|
||||
|
||||
@@ -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 projectAnnotations from "./preview";
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
{
|
||||
"ignore": [
|
||||
"gtk.webview.js",
|
||||
"src/api/clan/client-fetch.ts",
|
||||
"stylelint.config.js",
|
||||
"util.ts",
|
||||
"src/components/v2/**",
|
||||
"api/**",
|
||||
"tailwind/**"
|
||||
],
|
||||
"ignoreDependencies": [
|
||||
"@babel/plugin-syntax-import-attributes",
|
||||
"@storybook/addon-viewport",
|
||||
"@typescript-eslint/parser",
|
||||
"@vitest/coverage-v8",
|
||||
"http-server",
|
||||
"playwright",
|
||||
"wait-on"
|
||||
]
|
||||
}
|
||||
|
||||
1582
pkgs/clan-app/ui/package-lock.json
generated
1582
pkgs/clan-app/ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,35 +12,48 @@
|
||||
"check": "tsc --noEmit --skipLibCheck && eslint ./src --fix",
|
||||
"test": "vitest run --project unit --typecheck",
|
||||
"vite": "vite",
|
||||
"storybook": "storybook",
|
||||
"knip": "knip --fix",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook-dev": "storybook dev -p 6006",
|
||||
"test-storybook": "vitest run --project storybook",
|
||||
"test-storybook-update-snapshots": "vitest run --project storybook --update"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-import-attributes": "^7.27.1",
|
||||
"@eslint/js": "^9.3.0",
|
||||
"@kachurun/storybook-solid-vite": "^9.0.11",
|
||||
"@linaria/core": "^6.3.0",
|
||||
"@storybook/addon-a11y": "^9.1.13",
|
||||
"@storybook/addon-docs": "^9.1.13",
|
||||
"@storybook/addon-links": "^9.1.13",
|
||||
"@storybook/addon-vitest": "^9.1.13",
|
||||
"@sinonjs/fake-timers": "^14.0.0",
|
||||
"@storybook/addon-a11y": "^9.0.8",
|
||||
"@storybook/addon-docs": "^9.0.8",
|
||||
"@storybook/addon-links": "^9.0.8",
|
||||
"@storybook/addon-viewport": "^9.0.8",
|
||||
"@storybook/addon-vitest": "^9.0.8",
|
||||
"@types/node": "^22.15.19",
|
||||
"@types/sinonjs__fake-timers": "^8.1.5",
|
||||
"@types/three": "^0.176.0",
|
||||
"@typescript-eslint/parser": "^8.32.1",
|
||||
"@vitest/browser": "^3.2.3",
|
||||
"@vitest/coverage-v8": "^3.2.3",
|
||||
"@wyw-in-js/vite": "^0.7.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"classnames": "^2.5.1",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-tailwindcss": "^3.17.0",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"extend": "^3.0.2",
|
||||
"http-server": "^14.1.1",
|
||||
"jsdom": "^26.1.0",
|
||||
"knip": "^5.61.2",
|
||||
"markdown-to-jsx": "^7.7.10",
|
||||
"playwright": "1.54.1",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-url": "^10.1.3",
|
||||
"prettier": "^3.2.5",
|
||||
"storybook": "^9.1.13",
|
||||
"storybook-solidjs-vite": "^9.0.3",
|
||||
"storybook": "^9.0.8",
|
||||
"swagger-ui-dist": "^5.26.2",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-eslint": "^8.32.1",
|
||||
@@ -49,9 +62,11 @@
|
||||
"vite-css-modules": "^1.10.0",
|
||||
"vite-plugin-solid": "^2.8.2",
|
||||
"vite-plugin-solid-svg": "^0.8.1",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^3.2.3",
|
||||
"wait-on": "^8.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.8",
|
||||
"@kobalte/core": "^0.13.10",
|
||||
"@kobalte/tailwindcss": "^0.9.0",
|
||||
"@modular-forms/solid": "^0.25.1",
|
||||
@@ -65,6 +80,22 @@
|
||||
"solid-js": "^1.9.7",
|
||||
"solid-toast": "^0.5.0",
|
||||
"three": "^0.176.0",
|
||||
"troika-three-text": "^0.52.4",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { expect, fn } from "storybook/test";
|
||||
import { StoryContext } from "@kachurun/storybook-solid-vite";
|
||||
|
||||
const AlertExamples = (props: AlertProps) => (
|
||||
<div class="grid w-fit grid-cols-2 gap-8">
|
||||
@@ -19,14 +20,14 @@ const AlertExamples = (props: AlertProps) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const meta: Meta<typeof AlertExamples> = {
|
||||
const meta: Meta<AlertProps> = {
|
||||
title: "Components/Alert",
|
||||
component: AlertExamples,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<AlertProps>;
|
||||
|
||||
export const Info: Story = {
|
||||
args: {
|
||||
@@ -91,13 +92,10 @@ export const InfoDismiss: Story = {
|
||||
args: {
|
||||
...Info.args,
|
||||
onDismiss: fn(),
|
||||
},
|
||||
render(args) {
|
||||
return <Alert {...args} />;
|
||||
},
|
||||
play: async ({ canvas, userEvent, args }) => {
|
||||
await userEvent.click(canvas.getByRole("button"));
|
||||
await expect(args.onDismiss).toHaveBeenCalled();
|
||||
play: async ({ canvas, step, userEvent, args }: StoryContext) => {
|
||||
await userEvent.click(canvas.getByRole("button"));
|
||||
await expect(args.onDismiss).toHaveBeenCalled();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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 { Component } from "solid-js";
|
||||
import { expect, fn, within } from "storybook/test";
|
||||
import { StoryContext } from "@kachurun/storybook-solid-vite";
|
||||
|
||||
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",
|
||||
component: ButtonExamples,
|
||||
};
|
||||
@@ -210,13 +211,15 @@ export default meta;
|
||||
|
||||
type Story = StoryObj<ButtonProps>;
|
||||
|
||||
const timeout = process.env.NODE_ENV === "test" ? 500 : 2000;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
hierarchy: "primary",
|
||||
onClick: fn(),
|
||||
},
|
||||
|
||||
play: async ({ canvasElement, step, userEvent, args }) => {
|
||||
play: async ({ canvasElement, step, userEvent, args }: StoryContext) => {
|
||||
const canvas = within(canvasElement);
|
||||
const buttons = await canvas.findAllByRole("button");
|
||||
|
||||
@@ -261,7 +264,7 @@ export const GhostPrimary: Story = {
|
||||
},
|
||||
play: Primary.play,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
(Story: StoryObj) => (
|
||||
<div class="p-10 bg-def-3">
|
||||
<Story />
|
||||
</div>
|
||||
|
||||
@@ -8,11 +8,11 @@ import Icon, { IconVariant } from "@/src/components/Icon/Icon";
|
||||
import { Loader } from "@/src/components/Loader/Loader";
|
||||
import { getInClasses, joinByDash, keepTruthy } from "@/src/util";
|
||||
|
||||
type Size = "default" | "s" | "xs";
|
||||
type Hierarchy = "primary" | "secondary";
|
||||
type Elasticity = "default" | "fit";
|
||||
export type Size = "default" | "s" | "xs";
|
||||
export type Hierarchy = "primary" | "secondary";
|
||||
export type Elasticity = "default" | "fit";
|
||||
|
||||
type Action = () => Promise<void>;
|
||||
export type Action = () => Promise<void>;
|
||||
|
||||
export interface ButtonProps
|
||||
extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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",
|
||||
component: CubeConstruction,
|
||||
globals: {
|
||||
@@ -12,7 +12,7 @@ const meta: Meta<typeof CubeConstruction> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { Divider } from "@/src/components/Divider/Divider";
|
||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { Divider, DividerProps } from "@/src/components/Divider/Divider";
|
||||
|
||||
const meta: Meta<typeof Divider> = {
|
||||
const meta: Meta<DividerProps> = {
|
||||
title: "Components/Divider",
|
||||
component: Divider,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<DividerProps>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
@@ -30,7 +30,7 @@ export const Vertical: Story = {
|
||||
orientation: "vertical",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
(Story: Story) => (
|
||||
<div class="h-32 w-full">
|
||||
<Story />
|
||||
</div>
|
||||
@@ -43,5 +43,5 @@ export const VerticalInverted: Story = {
|
||||
inverted: true,
|
||||
...Vertical.args,
|
||||
},
|
||||
decorators: Vertical.decorators,
|
||||
decorators: [...Vertical.decorators],
|
||||
};
|
||||
|
||||
@@ -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 { Checkbox, CheckboxProps } from "@/src/components/Form/Checkbox";
|
||||
|
||||
@@ -23,17 +23,17 @@ const Examples = (props: CheckboxProps) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const meta: Meta<typeof Examples> = {
|
||||
const meta = {
|
||||
title: "Components/Form/Checkbox",
|
||||
component: Examples,
|
||||
decorators: [
|
||||
(Story, { args }) => {
|
||||
(Story: StoryObj, context: StoryContext<CheckboxProps>) => {
|
||||
return (
|
||||
<div
|
||||
class={cx({
|
||||
"w-[600px]": (args.orientation || "vertical") == "vertical",
|
||||
"w-[1024px]": args.orientation == "horizontal",
|
||||
"bg-inv-acc-3": args.inverted,
|
||||
"w-[600px]": (context.args.orientation || "vertical") == "vertical",
|
||||
"w-[1024px]": context.args.orientation == "horizontal",
|
||||
"bg-inv-acc-3": context.args.inverted,
|
||||
})}
|
||||
>
|
||||
<Story />
|
||||
@@ -41,7 +41,7 @@ const meta: Meta<typeof Examples> = {
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
} satisfies Meta<CheckboxProps>;
|
||||
|
||||
export default meta;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
|
||||
import {
|
||||
Fieldset,
|
||||
FieldsetFieldProps,
|
||||
@@ -18,17 +18,17 @@ const FieldsetExamples = (props: FieldsetProps) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const meta: Meta<typeof FieldsetExamples> = {
|
||||
const meta = {
|
||||
title: "Components/Form/Fieldset",
|
||||
component: FieldsetExamples,
|
||||
decorators: [
|
||||
(Story, { args }) => {
|
||||
(Story: StoryObj, context: StoryContext<FieldsetProps>) => {
|
||||
return (
|
||||
<div
|
||||
class={cx({
|
||||
"w-[600px]": (args.orientation || "vertical") == "vertical",
|
||||
"w-[512px]": args.orientation == "horizontal",
|
||||
"bg-inv-acc-3": args.inverted,
|
||||
"w-[600px]": (context.args.orientation || "vertical") == "vertical",
|
||||
"w-[512px]": context.args.orientation == "horizontal",
|
||||
"bg-inv-acc-3": context.args.inverted,
|
||||
})}
|
||||
>
|
||||
<Story />
|
||||
@@ -36,7 +36,7 @@ const meta: Meta<typeof FieldsetExamples> = {
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
} satisfies Meta<FieldsetProps>;
|
||||
|
||||
export default meta;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
HostFileInput,
|
||||
@@ -31,17 +31,17 @@ const Examples = (props: HostFileInputProps) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const meta: Meta<typeof Examples> = {
|
||||
const meta = {
|
||||
title: "Components/Form/HostFileInput",
|
||||
component: Examples,
|
||||
decorators: [
|
||||
(Story, { args }) => {
|
||||
(Story: StoryObj, context: StoryContext<HostFileInputProps>) => {
|
||||
return (
|
||||
<div
|
||||
class={cx({
|
||||
"w-[600px]": (args.orientation || "vertical") == "vertical",
|
||||
"w-[1024px]": args.orientation == "horizontal",
|
||||
"bg-inv-acc-3": args.inverted,
|
||||
"w-[600px]": (context.args.orientation || "vertical") == "vertical",
|
||||
"w-[1024px]": context.args.orientation == "horizontal",
|
||||
"bg-inv-acc-3": context.args.inverted,
|
||||
})}
|
||||
>
|
||||
<Story />
|
||||
@@ -49,7 +49,7 @@ const meta: Meta<typeof Examples> = {
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
} satisfies Meta<HostFileInputProps>;
|
||||
|
||||
export default meta;
|
||||
|
||||
|
||||
@@ -10,15 +10,15 @@ import styles from "./Label.module.css";
|
||||
import cx from "classnames";
|
||||
import { getInClasses } from "@/src/util";
|
||||
|
||||
type Size = "default" | "s";
|
||||
export type Size = "default" | "s";
|
||||
|
||||
type LabelComponent =
|
||||
export type LabelComponent =
|
||||
| typeof TextField.Label
|
||||
| typeof Checkbox.Label
|
||||
| typeof Combobox.Label
|
||||
| typeof Select.Label;
|
||||
|
||||
type DescriptionComponent =
|
||||
export type DescriptionComponent =
|
||||
| typeof TextField.Description
|
||||
| typeof Checkbox.Description
|
||||
| typeof Combobox.Description
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { MachineTags } from "./MachineTags";
|
||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { MachineTags, MachineTagsProps } from "./MachineTags";
|
||||
import { createForm, setValue } from "@modular-forms/solid";
|
||||
import { Button } from "../Button/Button";
|
||||
|
||||
const meta: Meta<typeof MachineTags> = {
|
||||
const meta = {
|
||||
title: "Components/MachineTags",
|
||||
component: MachineTags,
|
||||
};
|
||||
} satisfies Meta<MachineTagsProps>;
|
||||
|
||||
export default meta;
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { CollectionNode } from "@kobalte/core";
|
||||
import styles from "./MachineTags.module.css";
|
||||
import { keepTruthy } from "@/src/util";
|
||||
|
||||
interface MachineTag {
|
||||
export interface MachineTag {
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { JSX, mergeProps } from "solid-js";
|
||||
|
||||
import styles from "./Orienter.module.css";
|
||||
|
||||
interface OrienterProps {
|
||||
export interface OrienterProps {
|
||||
orientation?: "vertical" | "horizontal";
|
||||
align?: "center" | "start";
|
||||
children: JSX.Element;
|
||||
|
||||
@@ -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 { TextArea, TextAreaProps } from "./TextArea";
|
||||
|
||||
@@ -23,17 +23,17 @@ const Examples = (props: TextAreaProps) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const meta: Meta<typeof Examples> = {
|
||||
const meta = {
|
||||
title: "Components/Form/TextArea",
|
||||
component: Examples,
|
||||
decorators: [
|
||||
(Story, { args }) => {
|
||||
(Story: StoryObj, context: StoryContext<TextAreaProps>) => {
|
||||
return (
|
||||
<div
|
||||
class={cx({
|
||||
"w-[600px]": (args.orientation || "vertical") == "vertical",
|
||||
"w-[1024px]": args.orientation == "horizontal",
|
||||
"bg-inv-acc-3": args.inverted,
|
||||
"w-[600px]": (context.args.orientation || "vertical") == "vertical",
|
||||
"w-[1024px]": context.args.orientation == "horizontal",
|
||||
"bg-inv-acc-3": context.args.inverted,
|
||||
})}
|
||||
>
|
||||
<Story />
|
||||
@@ -41,7 +41,7 @@ const meta: Meta<typeof Examples> = {
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
} satisfies Meta<TextAreaProps>;
|
||||
|
||||
export default meta;
|
||||
|
||||
|
||||
@@ -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 { TextInput, TextInputProps } from "@/src/components/Form/TextInput";
|
||||
import Icon from "../Icon/Icon";
|
||||
@@ -25,17 +25,17 @@ const Examples = (props: TextInputProps) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const meta: Meta<typeof Examples> = {
|
||||
const meta = {
|
||||
title: "Components/Form/TextInput",
|
||||
component: Examples,
|
||||
decorators: [
|
||||
(Story, { args }) => {
|
||||
(Story: StoryObj, context: StoryContext<TextInputProps>) => {
|
||||
return (
|
||||
<div
|
||||
class={cx({
|
||||
"w-[600px]": (args.orientation || "vertical") == "vertical",
|
||||
"w-[1024px]": args.orientation == "horizontal",
|
||||
"bg-inv-acc-3": args.inverted,
|
||||
"w-[600px]": (context.args.orientation || "vertical") == "vertical",
|
||||
"w-[1024px]": context.args.orientation == "horizontal",
|
||||
"bg-inv-acc-3": context.args.inverted,
|
||||
})}
|
||||
>
|
||||
<Story />
|
||||
@@ -43,7 +43,7 @@ const meta: Meta<typeof Examples> = {
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
} satisfies Meta<TextInputProps>;
|
||||
|
||||
export default meta;
|
||||
|
||||
|
||||
@@ -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 Icon, { IconProps, IconVariant } from "./Icon";
|
||||
import cx from "classnames";
|
||||
@@ -57,12 +57,12 @@ const IconExamples: Component<IconProps> = (props) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const meta: Meta<typeof IconExamples> = {
|
||||
const meta: Meta<IconProps> = {
|
||||
title: "Components/Icon",
|
||||
component: IconExamples,
|
||||
decorators: [
|
||||
(Story, { args }) => (
|
||||
<div class={cx(args.inverted || false ? "bg-inv-acc-3" : "")}>
|
||||
(Story: StoryObj, context: StoryContext<IconProps>) => (
|
||||
<div class={cx(context.args.inverted || false ? "bg-inv-acc-3" : "")}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
@@ -71,7 +71,7 @@ const meta: Meta<typeof IconExamples> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<IconProps>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
const LoaderExamples = (props: LoaderProps) => (
|
||||
@@ -9,14 +9,14 @@ const LoaderExamples = (props: LoaderProps) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const meta: Meta<typeof LoaderExamples> = {
|
||||
const meta: Meta<LoaderProps> = {
|
||||
title: "Components/Loader",
|
||||
component: LoaderExamples,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<LoaderProps>;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mergeProps } from "solid-js";
|
||||
import styles from "./Loader.module.css";
|
||||
import cx from "classnames";
|
||||
|
||||
type Hierarchy = "primary" | "secondary";
|
||||
export type Hierarchy = "primary" | "secondary";
|
||||
|
||||
export interface LoaderProps {
|
||||
hierarchy?: Hierarchy;
|
||||
|
||||
@@ -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";
|
||||
|
||||
const meta: Meta<typeof LoadingBar> = {
|
||||
const meta: Meta = {
|
||||
title: "Components/LoadingBar",
|
||||
component: LoadingBar,
|
||||
decorators: [
|
||||
(Story) => {
|
||||
(Story: StoryObj, context: StoryContext<unknown>) => {
|
||||
return (
|
||||
<div class={"flex w-fit items-center justify-center bg-slate-500 p-10"}>
|
||||
<Story />
|
||||
@@ -17,6 +17,6 @@ const meta: Meta<typeof LoadingBar> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { JSX } from "solid-js";
|
||||
import styles from "./LoadingBar.module.css";
|
||||
import cx from "classnames";
|
||||
|
||||
type LoadingBarProps = JSX.HTMLAttributes<HTMLDivElement> & {};
|
||||
export type LoadingBarProps = JSX.HTMLAttributes<HTMLDivElement> & {};
|
||||
export const LoadingBar = (props: LoadingBarProps) => (
|
||||
<div {...props} class={cx(styles.loading_bar, props.class)} />
|
||||
);
|
||||
|
||||
@@ -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 { Logo, LogoProps, LogoVariant } from "./Logo";
|
||||
import cx from "classnames";
|
||||
@@ -11,12 +11,12 @@ const LogoExamples: Component<LogoProps> = (props) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
const meta: Meta<typeof LogoExamples> = {
|
||||
const meta: Meta<LogoProps> = {
|
||||
title: "Components/Logo",
|
||||
component: LogoExamples,
|
||||
decorators: [
|
||||
(Story, { args }) => (
|
||||
<div class={cx(args.inverted || false ? "bg-inv-acc-3" : "")}>
|
||||
(Story: StoryObj, context: StoryContext<LogoProps>) => (
|
||||
<div class={cx(context.args.inverted || false ? "bg-inv-acc-3" : "")}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
@@ -25,7 +25,7 @@ const meta: Meta<typeof LogoExamples> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<LogoProps>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { MachineStatus } from "@/src/components/MachineStatus/MachineStatus";
|
||||
import { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import {
|
||||
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",
|
||||
component: MachineStatus,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
(Story: StoryObj) => (
|
||||
<div class="p-5 bg-inv-1">
|
||||
<Story />
|
||||
</div>
|
||||
@@ -15,7 +18,7 @@ const meta: Meta<typeof MachineStatus> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<MachineStatusProps>;
|
||||
|
||||
export const Loading: Story = {
|
||||
args: {},
|
||||
|
||||
@@ -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 { Modal } from "@/src/components/Modal/Modal";
|
||||
import { Modal, ModalProps } from "@/src/components/Modal/Modal";
|
||||
import { Fieldset, FieldsetFieldProps } from "@/src/components/Form/Fieldset";
|
||||
import { TextInput } from "@/src/components/Form/TextInput";
|
||||
import { TextArea } from "@/src/components/Form/TextArea";
|
||||
import { Checkbox } from "@/src/components/Form/Checkbox";
|
||||
import { Button } from "../Button/Button";
|
||||
|
||||
const meta: Meta<typeof Modal> = {
|
||||
const meta: Meta<ModalProps> = {
|
||||
title: "Components/Modal",
|
||||
component: Modal,
|
||||
render: (args) => (
|
||||
render: (args: ModalProps) => (
|
||||
<Modal
|
||||
{...args}
|
||||
children={
|
||||
@@ -67,7 +68,7 @@ const meta: Meta<typeof Modal> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<TagProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
|
||||
@@ -14,7 +14,7 @@ import Icon from "../Icon/Icon";
|
||||
import cx from "classnames";
|
||||
import { Dynamic } from "solid-js/web";
|
||||
|
||||
interface ModalContextType {
|
||||
export interface ModalContextType {
|
||||
portalRef: HTMLDivElement;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { NavSection } from "@/src/components/NavSection/NavSection";
|
||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import {
|
||||
NavSection,
|
||||
NavSectionProps,
|
||||
} from "@/src/components/NavSection/NavSection";
|
||||
|
||||
const meta: Meta<typeof NavSection> = {
|
||||
const meta: Meta<NavSectionProps> = {
|
||||
title: "Components/NavSection",
|
||||
component: NavSection,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
(Story: StoryObj) => (
|
||||
<div class="w-96">
|
||||
<Story />
|
||||
</div>
|
||||
@@ -15,7 +18,7 @@ const meta: Meta<typeof NavSection> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof NavSection>;
|
||||
type Story = StoryObj<NavSectionProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { CollectionNode } from "@kobalte/core/*";
|
||||
import cx from "classnames";
|
||||
import { Loader } from "../Loader/Loader";
|
||||
|
||||
interface Option {
|
||||
export interface Option {
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -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 { Combobox } from "@kobalte/core/combobox";
|
||||
import { Typography } from "../Typography/Typography";
|
||||
import { ItemRenderOptions, SearchMultiple } from "./MultipleSearch";
|
||||
import {
|
||||
ItemRenderOptions,
|
||||
SearchMultiple,
|
||||
SearchMultipleProps,
|
||||
} from "./MultipleSearch";
|
||||
import { Show } from "solid-js";
|
||||
|
||||
const meta: Meta<typeof Search> = {
|
||||
const meta = {
|
||||
title: "Components/Search",
|
||||
component: Search,
|
||||
};
|
||||
} satisfies Meta<SearchProps<unknown>>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<SearchProps<unknown>>;
|
||||
|
||||
// To test the virtualizer, we can generate a list of modules
|
||||
function generateModules(count: number): Module[] {
|
||||
@@ -103,7 +107,7 @@ export const Default: Story = {
|
||||
);
|
||||
},
|
||||
},
|
||||
render: (args) => {
|
||||
render: (args: SearchProps<Module>) => {
|
||||
return (
|
||||
<div class="fixed bottom-10 left-1/2 mb-2 w-[30rem] -translate-x-1/2">
|
||||
<Search<Module>
|
||||
@@ -126,7 +130,7 @@ export const Loading: Story = {
|
||||
options: [],
|
||||
renderItem: () => <span></span>,
|
||||
},
|
||||
render: (args) => {
|
||||
render: (args: SearchProps<Module>) => {
|
||||
return (
|
||||
<div class="absolute bottom-1/3 w-3/4 px-3">
|
||||
<Search<Module>
|
||||
@@ -231,7 +235,7 @@ export const Multiple: Story = {
|
||||
);
|
||||
},
|
||||
},
|
||||
render: (args) => {
|
||||
render: (args: SearchMultipleProps<MachineOrTag>) => {
|
||||
return (
|
||||
<div class="absolute bottom-1/3 w-3/4 px-3">
|
||||
<SearchMultiple<MachineOrTag>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { CollectionNode } from "@kobalte/core/*";
|
||||
import { Loader } from "../Loader/Loader";
|
||||
import cx from "classnames";
|
||||
|
||||
interface Option {
|
||||
export interface Option {
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -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 Icon from "../Icon/Icon";
|
||||
import { createSignal } from "solid-js";
|
||||
|
||||
const meta: Meta<typeof TagSelect> = {
|
||||
const meta = {
|
||||
title: "Components/Custom/SelectStepper",
|
||||
component: TagSelect,
|
||||
};
|
||||
} satisfies Meta<TagSelectProps<string>>;
|
||||
|
||||
export default meta;
|
||||
|
||||
@@ -17,7 +17,7 @@ interface Item {
|
||||
label: string;
|
||||
}
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<TagSelectProps<Item>>;
|
||||
|
||||
const Item = (item: Item) => (
|
||||
<Tag
|
||||
@@ -42,8 +42,8 @@ export const Default: Story = {
|
||||
{ value: "corge", label: "Corge" },
|
||||
{ value: "grault", label: "Grault" },
|
||||
],
|
||||
},
|
||||
render: (args) => {
|
||||
} satisfies Partial<TagSelectProps<Item>>,
|
||||
render: (args: TagSelectProps<Item>) => {
|
||||
const [state, setState] = createSignal<Item[]>([]);
|
||||
return (
|
||||
<TagSelect<Item>
|
||||
|
||||
@@ -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";
|
||||
|
||||
const meta: Meta<typeof Select> = {
|
||||
// const meta: Meta<SelectProps> = {
|
||||
// title: "Components/Select",
|
||||
// component: Select,
|
||||
// };
|
||||
const meta = {
|
||||
title: "Components/Form/Select",
|
||||
component: Select,
|
||||
decorators: [
|
||||
(Story) => {
|
||||
(Story: StoryObj, context: StoryContext<SelectProps>) => {
|
||||
return (
|
||||
<div class={`w-[600px]`}>
|
||||
<Fieldset>
|
||||
@@ -17,11 +22,11 @@ const meta: Meta<typeof Select> = {
|
||||
);
|
||||
},
|
||||
],
|
||||
};
|
||||
} satisfies Meta<SelectProps>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<TagProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
|
||||
@@ -16,7 +16,7 @@ import cx from "classnames";
|
||||
import { useModalContext } from "../Modal/Modal";
|
||||
import { keepTruthy } from "@/src/util";
|
||||
|
||||
interface Option {
|
||||
export interface Option {
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { createMemoryHistory, MemoryRouter, Route } from "@solidjs/router";
|
||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import {
|
||||
createMemoryHistory,
|
||||
MemoryRouter,
|
||||
Route,
|
||||
RouteSectionProps,
|
||||
} from "@solidjs/router";
|
||||
import { Sidebar } from "@/src/components/Sidebar/Sidebar";
|
||||
import { Suspense } from "solid-js";
|
||||
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",
|
||||
component: Sidebar,
|
||||
render: () => {
|
||||
@@ -139,7 +144,7 @@ const meta: Meta<typeof Sidebar> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<RouteSectionProps>;
|
||||
|
||||
const mockFetcher = <K extends OperationNames>(
|
||||
method: K,
|
||||
|
||||
@@ -4,12 +4,12 @@ import { SidebarBody } from "@/src/components/Sidebar/SidebarBody";
|
||||
import cx from "classnames";
|
||||
import { splitProps } from "solid-js";
|
||||
|
||||
interface LinkProps {
|
||||
export interface LinkProps {
|
||||
path: string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
interface SectionProps {
|
||||
export interface SectionProps {
|
||||
title: string;
|
||||
links: LinkProps[];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { SidebarPane } from "@/src/components/Sidebar/SidebarPane";
|
||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import {
|
||||
SidebarPane,
|
||||
SidebarPaneProps,
|
||||
} from "@/src/components/Sidebar/SidebarPane";
|
||||
import { SidebarSection } from "./SidebarSection";
|
||||
import { Divider } from "@/src/components/Divider/Divider";
|
||||
import { TextInput } from "@/src/components/Form/TextInput";
|
||||
@@ -11,6 +14,9 @@ import { splitProps } from "solid-js";
|
||||
import { Typography } from "@/src/components/Typography/Typography";
|
||||
import { MachineTags } from "@/src/components/Form/MachineTags";
|
||||
import { setValue } from "@modular-forms/solid";
|
||||
import { StoryContext } from "@kachurun/storybook-solid-vite";
|
||||
|
||||
type Story = StoryObj<SidebarPaneProps>;
|
||||
|
||||
const profiles = {
|
||||
ron: {
|
||||
@@ -22,13 +28,18 @@ const profiles = {
|
||||
},
|
||||
};
|
||||
|
||||
const meta: Meta<typeof SidebarPane> = {
|
||||
const meta: Meta<SidebarPaneProps> = {
|
||||
title: "Components/SidebarPane",
|
||||
component: SidebarPane,
|
||||
decorators: [
|
||||
(
|
||||
Story: StoryObj<SidebarPaneProps>,
|
||||
context: StoryContext<SidebarPaneProps>,
|
||||
) =>
|
||||
() => <Story {...context.args} />,
|
||||
],
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export default meta;
|
||||
|
||||
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
|
||||
// solid-js scope.
|
||||
render: (args) => (
|
||||
render: (args: SidebarPaneProps) => (
|
||||
<SidebarPane
|
||||
{...args}
|
||||
children={
|
||||
|
||||
@@ -2,7 +2,7 @@ import { JSX, Show } from "solid-js";
|
||||
import styles from "./SidebarSection.module.css";
|
||||
import { Typography } from "@/src/components/Typography/Typography";
|
||||
|
||||
interface SidebarSectionProps {
|
||||
export interface SidebarSectionProps {
|
||||
title: string;
|
||||
controls?: JSX.Element;
|
||||
children: JSX.Element;
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Button } from "@/src/components/Button/Button";
|
||||
import { Loader } from "../../components/Loader/Loader";
|
||||
import { SidebarSection } from "./SidebarSection";
|
||||
|
||||
interface SidebarSectionFormProps<FormValues extends FieldValues> {
|
||||
export interface SidebarSectionFormProps<FormValues extends FieldValues> {
|
||||
title: string;
|
||||
schema: GenericSchema<FormValues> | GenericSchemaAsync<FormValues>;
|
||||
initialValues: PartialValues<FormValues>;
|
||||
|
||||
@@ -7,7 +7,7 @@ import styles from "./SidebarSectionInstall.module.css";
|
||||
import { Alert } from "../Alert/Alert";
|
||||
import { useClanContext } from "@/src/routes/Clan/Clan";
|
||||
|
||||
interface SidebarSectionInstallProps {
|
||||
export interface SidebarSectionInstallProps {
|
||||
clanURI: string;
|
||||
machineName: string;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import styles from "./SidebarSectionInstall.module.css";
|
||||
import { UpdateModal } from "@/src/workflows/InstallMachine/UpdateMachine";
|
||||
import { useClanContext } from "@/src/routes/Clan/Clan";
|
||||
|
||||
interface SidebarSectionUpdateProps {
|
||||
export interface SidebarSectionUpdateProps {
|
||||
clanURI: string;
|
||||
machineName: string;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { Tag } from "@/src/components/Tag/Tag";
|
||||
import { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { Tag, TagProps } from "@/src/components/Tag/Tag";
|
||||
import { Meta, type StoryContext, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { fn } from "storybook/test";
|
||||
import Icon from "../Icon/Icon";
|
||||
|
||||
const meta: Meta<typeof Tag> = {
|
||||
const meta: Meta<TagProps> = {
|
||||
title: "Components/Tag",
|
||||
component: Tag,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<TagProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
@@ -43,7 +43,7 @@ export const WithAction: Story = {
|
||||
icon: IconAction,
|
||||
interactive: true,
|
||||
},
|
||||
play: async ({ canvas, step, userEvent, args }) => {
|
||||
play: async ({ canvas, step, userEvent, args }: StoryContext) => {
|
||||
await userEvent.click(canvas.getByRole("button"));
|
||||
// await expect(args.icon.onClick).toHaveBeenCalled();
|
||||
},
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { TagGroup } from "@/src/components/TagGroup/TagGroup";
|
||||
import { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { TagGroup, TagGroupProps } from "@/src/components/TagGroup/TagGroup";
|
||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
|
||||
const meta: Meta<typeof TagGroup> = {
|
||||
const meta: Meta<TagGroupProps> = {
|
||||
title: "Components/TagGroup",
|
||||
component: TagGroup,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
(Story: StoryObj) => (
|
||||
/* for some reason w-x from tailwind was not working */
|
||||
<div style="width: 196px">
|
||||
<Story />
|
||||
@@ -16,7 +16,7 @@ const meta: Meta<typeof TagGroup> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<TagGroupProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { Toolbar } from "@/src/components/Toolbar/Toolbar";
|
||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { Toolbar, ToolbarProps } from "@/src/components/Toolbar/Toolbar";
|
||||
import { ToolbarButton } from "./ToolbarButton";
|
||||
|
||||
const meta: Meta<typeof Toolbar> = {
|
||||
const meta: Meta<ToolbarProps> = {
|
||||
title: "Components/Toolbar",
|
||||
component: Toolbar,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<ToolbarProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
// 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) => (
|
||||
<div class="flex h-[80vh]">
|
||||
<div class="mt-auto">
|
||||
|
||||
@@ -5,7 +5,7 @@ import Icon, { IconVariant } from "@/src/components/Icon/Icon";
|
||||
import type { JSX } from "solid-js";
|
||||
import { Tooltip } from "../Tooltip/Tooltip";
|
||||
|
||||
interface ToolbarButtonProps
|
||||
export interface ToolbarButtonProps
|
||||
extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
icon: IconVariant;
|
||||
description: JSX.Element;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { Tooltip } from "@/src/components/Tooltip/Tooltip";
|
||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { Tooltip, TooltipProps } from "@/src/components/Tooltip/Tooltip";
|
||||
import { Typography } from "@/src/components/Typography/Typography";
|
||||
|
||||
const meta: Meta<typeof Tooltip> = {
|
||||
const meta: Meta<TooltipProps> = {
|
||||
title: "Components/Tooltip",
|
||||
component: Tooltip,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
(Story: StoryObj<TooltipProps>) => (
|
||||
<div class="p-16">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
render: (args) => (
|
||||
render: (args: TooltipProps) => (
|
||||
<div class="p-16">
|
||||
<Tooltip
|
||||
{...args}
|
||||
@@ -33,7 +33,7 @@ const meta: Meta<typeof Tooltip> = {
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<TooltipProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
|
||||
@@ -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 { Component, For, Show } from "solid-js";
|
||||
@@ -73,14 +73,14 @@ const TypographyExamples: Component<TypographyExamplesProps> = (props) => (
|
||||
</table>
|
||||
);
|
||||
|
||||
const meta: Meta<typeof TypographyExamples> = {
|
||||
const meta: Meta<TypographyExamplesProps> = {
|
||||
title: "Components/Typography",
|
||||
component: TypographyExamples,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<TypographyExamplesProps>;
|
||||
|
||||
export const BodyCondensed: Story = {
|
||||
args: {
|
||||
|
||||
@@ -9,15 +9,15 @@ import { getInClasses } from "@/src/util";
|
||||
export type Hierarchy = "body" | "title" | "headline" | "label" | "teaser";
|
||||
export type Weight = "normal" | "medium" | "bold";
|
||||
export type Family = "regular" | "mono";
|
||||
type Transform = "uppercase" | "lowercase" | "capitalize";
|
||||
interface SizeForHierarchy {
|
||||
export type Transform = "uppercase" | "lowercase" | "capitalize";
|
||||
export interface SizeForHierarchy {
|
||||
body: "default" | "s" | "xs" | "xxs";
|
||||
headline: "default" | "m" | "l" | "xl" | "xxl";
|
||||
title: "default" | "m" | "l";
|
||||
label: "default" | "s" | "xs" | "xxs";
|
||||
teaser: "default";
|
||||
}
|
||||
interface TagForHierarchy {
|
||||
export interface TagForHierarchy {
|
||||
body: "span" | "p" | "div";
|
||||
headline: "h1" | "h2" | "h3" | "h4";
|
||||
title: "h1" | "h2" | "h3" | "h4";
|
||||
@@ -40,7 +40,7 @@ const defaultTagMap = {
|
||||
label: "span",
|
||||
teaser: "h3",
|
||||
} as const;
|
||||
interface TypographyProps<H extends Hierarchy> {
|
||||
export interface TypographyProps<H extends Hierarchy> {
|
||||
hierarchy: H;
|
||||
children: JSX.Element;
|
||||
size?: SizeForHierarchy[H];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createContext, JSX, useContext } from "solid-js";
|
||||
import { ApiCall, OperationArgs, OperationNames } from "./api";
|
||||
|
||||
interface ApiClient {
|
||||
export interface ApiClient {
|
||||
fetch: Fetcher;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { addClanURI, setActiveClanURI } from "@/src/stores/clan";
|
||||
import { Params, Navigator, useParams, useSearchParams } from "@solidjs/router";
|
||||
|
||||
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 () => {
|
||||
const req = callApi("get_clan_folder", {});
|
||||
@@ -80,7 +80,7 @@ export const navigateToClan = (navigate: Navigator, clanURI: string) => {
|
||||
export const navigateToOnboarding = (navigate: Navigator, addClan: boolean) =>
|
||||
navigate(`/${addClan ? "?addClan=true" : ""}`);
|
||||
|
||||
const navigateToMachine = (
|
||||
export const navigateToMachine = (
|
||||
navigate: Navigator,
|
||||
clanURI: string,
|
||||
name: string,
|
||||
@@ -90,7 +90,7 @@ const navigateToMachine = (
|
||||
navigate(path);
|
||||
};
|
||||
|
||||
const clanURIParam = (params: Params) => {
|
||||
export const clanURIParam = (params: Params) => {
|
||||
try {
|
||||
return decodeBase64(params.clanURI);
|
||||
} catch (e) {
|
||||
@@ -101,19 +101,19 @@ const clanURIParam = (params: Params) => {
|
||||
|
||||
export const useClanURI = () => clanURIParam(useParams());
|
||||
|
||||
const machineNameParam = (params: Params) => {
|
||||
export const machineNameParam = (params: Params) => {
|
||||
return params.machineName;
|
||||
};
|
||||
|
||||
const inputParam = (params: Params) => params.input;
|
||||
const nameParam = (params: Params) => params.name;
|
||||
const idParam = (params: Params) => params.id;
|
||||
export const inputParam = (params: Params) => params.input;
|
||||
export const nameParam = (params: Params) => params.name;
|
||||
export const idParam = (params: Params) => params.id;
|
||||
|
||||
export const useMachineName = (): string => machineNameParam(useParams());
|
||||
const useInputParam = (): string => inputParam(useParams());
|
||||
const useNameParam = (): string => nameParam(useParams());
|
||||
export const useInputParam = (): string => inputParam(useParams());
|
||||
export const useNameParam = (): string => nameParam(useParams());
|
||||
|
||||
const maybeUseIdParam = (): string | null => {
|
||||
export const maybeUseIdParam = (): string | null => {
|
||||
const params = useParams();
|
||||
if (params.id === undefined) {
|
||||
return null;
|
||||
|
||||
25
pkgs/clan-app/ui/src/hooks/mutations.ts
Normal file
25
pkgs/clan-app/ui/src/hooks/mutations.ts
Normal 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],
|
||||
});
|
||||
},
|
||||
}));
|
||||
@@ -44,7 +44,7 @@ window.notifyBus = (msg: ProcessMessage) => {
|
||||
*
|
||||
* consider using useNotify for reactive usage on solidjs
|
||||
*/
|
||||
function _subscribeNotify<T extends ProcessMessage>(
|
||||
export function _subscribeNotify<T extends ProcessMessage>(
|
||||
filter: (msg: T) => boolean,
|
||||
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
|
||||
* 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,
|
||||
) {
|
||||
const [message, setMessage] = createSignal<T | null>(null);
|
||||
|
||||
@@ -16,16 +16,16 @@ export interface ClanDetails {
|
||||
fieldsSchema: SuccessData<"get_clan_details_schema">;
|
||||
}
|
||||
|
||||
type Tags = SuccessData<"list_tags">;
|
||||
export type Tags = SuccessData<"list_tags">;
|
||||
export type Machine = SuccessData<"get_machine">;
|
||||
|
||||
type MachineState = SuccessData<"get_machine_state">;
|
||||
export type MachineState = SuccessData<"get_machine_state">;
|
||||
export type MachineStatus = MachineState["status"];
|
||||
|
||||
type ListMachines = SuccessData<"list_machines">;
|
||||
type MachineDetails = SuccessData<"get_machine_details">;
|
||||
export type ListMachines = SuccessData<"list_machines">;
|
||||
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 interface MachineDetail {
|
||||
@@ -35,7 +35,7 @@ export interface MachineDetail {
|
||||
}
|
||||
|
||||
export type MachinesQueryResult = UseQueryResult<ListMachines>;
|
||||
type ClanListQueryResult = UseQueryResult<ClanDetails>[];
|
||||
export type ClanListQueryResult = UseQueryResult<ClanDetails>[];
|
||||
|
||||
export const DefaultQueryClient = new QueryClient({
|
||||
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),
|
||||
"machine",
|
||||
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();
|
||||
|
||||
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();
|
||||
return useQuery<MachineDetails>(() => ({
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -370,10 +373,10 @@ export const useClanListQuery = (
|
||||
}));
|
||||
};
|
||||
|
||||
type MachineFlashOptions = SuccessData<"get_machine_flash_options">;
|
||||
type MachineFlashOptionsQuery = UseQueryResult<MachineFlashOptions>;
|
||||
export type MachineFlashOptions = SuccessData<"get_machine_flash_options">;
|
||||
export type MachineFlashOptionsQuery = UseQueryResult<MachineFlashOptions>;
|
||||
|
||||
const useMachineFlashOptions = (): MachineFlashOptionsQuery => {
|
||||
export const useMachineFlashOptions = (): MachineFlashOptionsQuery => {
|
||||
const client = useApiClient();
|
||||
return useQuery<MachineFlashOptions>(() => ({
|
||||
queryKey: ["flash_options"],
|
||||
@@ -392,8 +395,8 @@ const useMachineFlashOptions = (): MachineFlashOptionsQuery => {
|
||||
}));
|
||||
};
|
||||
|
||||
type SystemStorageOptions = SuccessData<"list_system_storage_devices">;
|
||||
type SystemStorageOptionsQuery = UseQueryResult<SystemStorageOptions>;
|
||||
export type SystemStorageOptions = SuccessData<"list_system_storage_devices">;
|
||||
export type SystemStorageOptionsQuery = UseQueryResult<SystemStorageOptions>;
|
||||
|
||||
export const useSystemStorageOptions = (): SystemStorageOptionsQuery => {
|
||||
const client = useApiClient();
|
||||
@@ -414,8 +417,10 @@ export const useSystemStorageOptions = (): SystemStorageOptionsQuery => {
|
||||
}));
|
||||
};
|
||||
|
||||
type MachineHardwareSummary = SuccessData<"get_machine_hardware_summary">;
|
||||
type MachineHardwareSummaryQuery = UseQueryResult<MachineHardwareSummary>;
|
||||
export type MachineHardwareSummary =
|
||||
SuccessData<"get_machine_hardware_summary">;
|
||||
export type MachineHardwareSummaryQuery =
|
||||
UseQueryResult<MachineHardwareSummary>;
|
||||
|
||||
export const useMachineHardwareSummary = (
|
||||
clanUri: string,
|
||||
@@ -452,8 +457,8 @@ export const useMachineHardwareSummary = (
|
||||
}));
|
||||
};
|
||||
|
||||
type MachineDiskSchema = SuccessData<"get_machine_disk_schemas">;
|
||||
type MachineDiskSchemaQuery = UseQueryResult<MachineDiskSchema>;
|
||||
export type MachineDiskSchema = SuccessData<"get_machine_disk_schemas">;
|
||||
export type MachineDiskSchemaQuery = UseQueryResult<MachineDiskSchema>;
|
||||
|
||||
export const useMachineDiskSchemas = (
|
||||
clanUri: string,
|
||||
@@ -491,7 +496,7 @@ export const useMachineDiskSchemas = (
|
||||
};
|
||||
|
||||
export type MachineGenerators = SuccessData<"get_generators">;
|
||||
type MachineGeneratorsQuery = UseQueryResult<MachineGenerators>;
|
||||
export type MachineGeneratorsQuery = UseQueryResult<MachineGenerators>;
|
||||
|
||||
export const useMachineGenerators = (
|
||||
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 const useServiceModules = (clanUri: string) => {
|
||||
const client = useApiClient();
|
||||
@@ -561,7 +566,7 @@ export const useServiceModules = (clanUri: string) => {
|
||||
export const clanKey = (clanUri: string) => ["clans", encodeBase64(clanUri)];
|
||||
|
||||
export type ServiceInstancesQuery = ReturnType<typeof useServiceInstances>;
|
||||
type ServiceInstances = SuccessData<"list_service_instances">;
|
||||
export type ServiceInstances = SuccessData<"list_service_instances">;
|
||||
export const useServiceInstances = (clanUri: string) => {
|
||||
const client = useApiClient();
|
||||
return useQuery(() => ({
|
||||
|
||||
@@ -7,13 +7,13 @@ import {
|
||||
} from "solid-js";
|
||||
import { createStore, SetStoreFunction, Store } from "solid-js/store";
|
||||
|
||||
interface StepBase {
|
||||
export interface StepBase {
|
||||
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;
|
||||
initialStoreData?: StoreType;
|
||||
}
|
||||
@@ -95,7 +95,10 @@ export function createStepper<
|
||||
}
|
||||
|
||||
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;
|
||||
activeStep: Accessor<StepId>;
|
||||
setActiveStep: (id: StepId) => void;
|
||||
|
||||
@@ -1,43 +1,45 @@
|
||||
import { fn } from "storybook/test";
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { ClanSettingsModal } from "./ClanSettingsModal";
|
||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { ClanSettingsModal, ClanSettingsModalProps } from "./ClanSettingsModal";
|
||||
|
||||
const meta: Meta<typeof ClanSettingsModal> = {
|
||||
const meta: Meta<ClanSettingsModalProps> = {
|
||||
title: "Modals/ClanSettings",
|
||||
component: ClanSettingsModal,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
type Story = StoryObj<ClanSettingsModalProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
onClose: fn(),
|
||||
model: {
|
||||
uri: "/home/foo/my-clan",
|
||||
details: {
|
||||
name: "Sol",
|
||||
description: null,
|
||||
icon: null,
|
||||
const props: ClanSettingsModalProps = {
|
||||
onClose: fn(),
|
||||
model: {
|
||||
uri: "/home/foo/my-clan",
|
||||
details: {
|
||||
name: "Sol",
|
||||
description: null,
|
||||
icon: null,
|
||||
},
|
||||
fieldsSchema: {
|
||||
name: {
|
||||
readonly: true,
|
||||
reason: null,
|
||||
readonly_members: [],
|
||||
},
|
||||
fieldsSchema: {
|
||||
name: {
|
||||
readonly: true,
|
||||
reason: null,
|
||||
readonly_members: [],
|
||||
},
|
||||
description: {
|
||||
readonly: false,
|
||||
reason: null,
|
||||
readonly_members: [],
|
||||
},
|
||||
icon: {
|
||||
readonly: false,
|
||||
reason: null,
|
||||
readonly_members: [],
|
||||
},
|
||||
description: {
|
||||
readonly: false,
|
||||
reason: null,
|
||||
readonly_members: [],
|
||||
},
|
||||
icon: {
|
||||
readonly: false,
|
||||
reason: null,
|
||||
readonly_members: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
args: props,
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useClanListQuery } from "@/src/hooks/queries";
|
||||
import { Alert } from "@/src/components/Alert/Alert";
|
||||
import { NavSection } from "@/src/components/NavSection/NavSection";
|
||||
|
||||
interface ListClansModalProps {
|
||||
export interface ListClansModalProps {
|
||||
onClose?: () => void;
|
||||
error?: {
|
||||
title: string;
|
||||
|
||||
@@ -39,7 +39,7 @@ import { ListClansModal } from "@/src/modals/ListClansModal/ListClansModal";
|
||||
import { AddMachine } from "@/src/workflows/AddMachine/AddMachine";
|
||||
import { SelectService } from "@/src/workflows/Service/SelectServiceFlyout";
|
||||
|
||||
type WorldMode = "default" | "select" | "service" | "create" | "move";
|
||||
export type WorldMode = "default" | "select" | "service" | "create" | "move";
|
||||
|
||||
function createClanContext(
|
||||
clanURI: string,
|
||||
|
||||
@@ -22,7 +22,7 @@ type FieldNames = "name" | "description" | "machineClass";
|
||||
|
||||
type FormValues = v.InferInput<typeof schema>;
|
||||
|
||||
interface SectionGeneralProps {
|
||||
export interface SectionGeneralProps {
|
||||
clanURI: string;
|
||||
machineName: string;
|
||||
onSubmit: (values: FormValues) => Promise<void>;
|
||||
|
||||
@@ -52,7 +52,7 @@ export function createMachineMesh() {
|
||||
};
|
||||
}
|
||||
|
||||
function createCubeBase(
|
||||
export function createCubeBase(
|
||||
color: THREE.ColorRepresentation,
|
||||
emissive: THREE.ColorRepresentation,
|
||||
geometry: THREE.BoxGeometry,
|
||||
@@ -70,7 +70,7 @@ function createCubeBase(
|
||||
}
|
||||
|
||||
// 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 x = -w / 2;
|
||||
const y = -h / 2;
|
||||
|
||||
@@ -80,7 +80,7 @@ const [lastClickedMachine, setLastClickedMachine] = createSignal<string | null>(
|
||||
|
||||
// Exported so others could also emit the signal if needed
|
||||
// And for testing purposes
|
||||
function emitMachineClick(id: string | null) {
|
||||
export function emitMachineClick(id: string | null) {
|
||||
setLastClickedMachine(id);
|
||||
if (id) {
|
||||
// Clear after a short delay to allow re-clicking the same machine
|
||||
|
||||
@@ -7,7 +7,7 @@ const [highlightGroups, setHighlightGroups] = createStore<
|
||||
>({});
|
||||
|
||||
// Add highlight
|
||||
function highlight(group: string, nodeId: string) {
|
||||
export function highlight(group: string, nodeId: string) {
|
||||
setHighlightGroups(group, (prev = new Set()) => {
|
||||
const next = new Set(prev);
|
||||
next.add(nodeId);
|
||||
@@ -16,7 +16,7 @@ function highlight(group: string, nodeId: string) {
|
||||
}
|
||||
|
||||
// Remove highlight
|
||||
function unhighlight(group: string, nodeId: string) {
|
||||
export function unhighlight(group: string, nodeId: string) {
|
||||
setHighlightGroups(group, (prev = new Set()) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(nodeId);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { Splash } from "./splash";
|
||||
|
||||
const meta: Meta = {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user