Compare commits
47 Commits
pr-3785
...
ui-version
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f9ab3de19 | ||
|
|
9739a5ae2b | ||
|
|
54446d751f | ||
|
|
7bc8e091a5 | ||
|
|
3462d458ac | ||
|
|
bd42d67b0c | ||
|
|
d99ca36f9f | ||
|
|
57f9cd9eee | ||
|
|
a9ec94b0df | ||
|
|
c64dbceceb | ||
|
|
5d924e0c98 | ||
|
|
6a6688019b | ||
|
|
f33172fa73 | ||
|
|
00914311a4 | ||
|
|
ceeb40d9ac | ||
|
|
afab33056e | ||
|
|
a5183f4b4c | ||
|
|
a686d7523b | ||
|
|
56b784992d | ||
|
|
5f723dc376 | ||
|
|
1609989734 | ||
|
|
0c07d5cfe0 | ||
|
|
9c37ef4cbe | ||
|
|
783b6a8b06 | ||
|
|
4f13049ee2 | ||
|
|
2f4f303048 | ||
|
|
d02868b950 | ||
|
|
4f7d82671f | ||
|
|
0dce3fc7ec | ||
|
|
a635f9c6fe | ||
|
|
a8ed1c30e4 | ||
|
|
c0c41d52bd | ||
|
|
bb236bb543 | ||
|
|
d7cf79faa7 | ||
|
|
dab11cb020 | ||
|
|
f2cb6fef41 | ||
|
|
655b87ad04 | ||
|
|
d462ae501e | ||
|
|
59a8c402ba | ||
|
|
3b309ea74b | ||
|
|
508cd3c784 | ||
|
|
2bff7403df | ||
|
|
b5a6e809d0 | ||
|
|
ec28c5c307 | ||
|
|
b8ba8b79ca | ||
|
|
eb6460fb40 | ||
|
|
e1796e19e4 |
75
.gitea/workflows/create-pr.sh
Executable file
75
.gitea/workflows/create-pr.sh
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shared script for creating pull requests in Gitea workflows
|
||||
set -euo pipefail
|
||||
|
||||
# Required environment variables:
|
||||
# - CI_BOT_TOKEN: Gitea bot token for authentication
|
||||
# - PR_BRANCH: Branch name for the pull request
|
||||
# - PR_TITLE: Title of the pull request
|
||||
# - PR_BODY: Body/description of the pull request
|
||||
|
||||
if [[ -z "${CI_BOT_TOKEN:-}" ]]; then
|
||||
echo "Error: CI_BOT_TOKEN is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${PR_BRANCH:-}" ]]; then
|
||||
echo "Error: PR_BRANCH is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${PR_TITLE:-}" ]]; then
|
||||
echo "Error: PR_TITLE is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${PR_BODY:-}" ]]; then
|
||||
echo "Error: PR_BODY is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Push the branch
|
||||
git push origin "+HEAD:${PR_BRANCH}"
|
||||
|
||||
# Create pull request
|
||||
resp=$(nix run --inputs-from . nixpkgs#curl -- -X POST \
|
||||
-H "Authorization: token $CI_BOT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"head\": \"${PR_BRANCH}\",
|
||||
\"base\": \"main\",
|
||||
\"title\": \"${PR_TITLE}\",
|
||||
\"body\": \"${PR_BODY}\"
|
||||
}" \
|
||||
"https://git.clan.lol/api/v1/repos/clan/clan-core/pulls")
|
||||
|
||||
pr_number=$(echo "$resp" | jq -r '.number')
|
||||
|
||||
if [[ "$pr_number" == "null" ]]; then
|
||||
echo "Error creating pull request:" >&2
|
||||
echo "$resp" | jq . >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Created pull request #$pr_number"
|
||||
|
||||
# Merge when checks succeed
|
||||
while true; do
|
||||
resp=$(nix run --inputs-from . nixpkgs#curl -- -X POST \
|
||||
-H "Authorization: token $CI_BOT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"Do": "merge",
|
||||
"merge_when_checks_succeed": true,
|
||||
"delete_branch_after_merge": true
|
||||
}' \
|
||||
"https://git.clan.lol/api/v1/repos/clan/clan-core/pulls/$pr_number/merge")
|
||||
msg=$(echo "$resp" | jq -r '.message')
|
||||
if [[ "$msg" != "Please try again later" ]]; then
|
||||
break
|
||||
fi
|
||||
echo "Retrying in 2 seconds..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Pull request #$pr_number merge initiated"
|
||||
@@ -19,35 +19,10 @@ jobs:
|
||||
run: |
|
||||
export GIT_AUTHOR_NAME=clan-bot GIT_AUTHOR_EMAIL=clan-bot@clan.lol GIT_COMMITTER_NAME=clan-bot GIT_COMMITTER_EMAIL=clan-bot@clan.lol
|
||||
git commit -am "Update pinned clan-core for checks"
|
||||
git push origin +HEAD:update-clan-core-for-checks
|
||||
set -x
|
||||
resp=$(nix run --inputs-from . nixpkgs#curl -- -X POST \
|
||||
-H "Authorization: token $CI_BOT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"head": "update-clan-core-for-checks",
|
||||
"base": "main",
|
||||
"title": "Update Clan Core for Checks",
|
||||
"body": "This PR updates the pinned clan-core flake input that is used for checks."
|
||||
}' \
|
||||
"https://git.clan.lol/api/v1/repos/clan/clan-core/pulls")
|
||||
pr_number=$(echo "$resp" | jq -r '.number')
|
||||
|
||||
# Merge when succeed
|
||||
while true; do
|
||||
resp=$(nix run --inputs-from . nixpkgs#curl -- -X POST \
|
||||
-H "Authorization: token $CI_BOT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"Do": "merge",
|
||||
"merge_when_checks_succeed": true,
|
||||
"delete_branch_after_merge": true
|
||||
}' \
|
||||
"https://git.clan.lol/api/v1/repos/clan/clan-core/pulls/$pr_number/merge")
|
||||
msg=$(echo $resp | jq -r '.message')
|
||||
if [[ "$msg" != "Please try again later" ]]; then
|
||||
break
|
||||
fi
|
||||
echo "Retrying in 2 seconds..."
|
||||
sleep 2
|
||||
done
|
||||
# Use shared PR creation script
|
||||
export PR_BRANCH="update-clan-core-for-checks"
|
||||
export PR_TITLE="Update Clan Core for Checks"
|
||||
export PR_BODY="This PR updates the pinned clan-core flake input that is used for checks."
|
||||
|
||||
./.gitea/workflows/create-pr.sh
|
||||
|
||||
40
.gitea/workflows/update-private-flake-inputs.yml
Normal file
40
.gitea/workflows/update-private-flake-inputs.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
name: "Update private flake inputs"
|
||||
on:
|
||||
repository_dispatch:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 3 * * *" # Run daily at 3 AM
|
||||
jobs:
|
||||
update-private-flake:
|
||||
runs-on: nix
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Update private flake inputs
|
||||
run: |
|
||||
# Update the private flake lock file
|
||||
cd devFlake/private
|
||||
nix flake update
|
||||
cd ../..
|
||||
|
||||
# Update the narHash
|
||||
bash ./devFlake/update-private-narhash
|
||||
- name: Create pull request
|
||||
env:
|
||||
CI_BOT_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
|
||||
run: |
|
||||
export GIT_AUTHOR_NAME=clan-bot GIT_AUTHOR_EMAIL=clan-bot@clan.lol GIT_COMMITTER_NAME=clan-bot GIT_COMMITTER_EMAIL=clan-bot@clan.lol
|
||||
|
||||
# Check if there are any changes
|
||||
if ! git diff --quiet; then
|
||||
git add devFlake/private/flake.lock devFlake/private.narHash
|
||||
git commit -m "Update dev flake"
|
||||
|
||||
# Use shared PR creation script
|
||||
export PR_BRANCH="update-dev-flake"
|
||||
export PR_TITLE="Update dev flake"
|
||||
export PR_BODY="This PR updates the dev flake inputs and corresponding narHash."
|
||||
else
|
||||
echo "No changes detected in dev flake inputs"
|
||||
fi
|
||||
@@ -19,10 +19,11 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
dependencies = [
|
||||
self
|
||||
pkgs.stdenv.drvPath
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
dependencies =
|
||||
[
|
||||
pkgs.stdenv.drvPath
|
||||
]
|
||||
++ builtins.map (i: i.outPath) (builtins.attrValues (builtins.removeAttrs self.inputs [ "self" ]));
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
in
|
||||
{
|
||||
|
||||
@@ -47,14 +47,6 @@ nixosLib.runTest (
|
||||
|
||||
clientone =
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
dependencies = [
|
||||
clan-core
|
||||
pkgs.stdenv.drvPath
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues clan-core.inputs);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
services.openssh.enable = true;
|
||||
@@ -65,15 +57,6 @@ nixosLib.runTest (
|
||||
|
||||
environment.systemPackages = [ clan-core.packages.${pkgs.system}.clan-cli ];
|
||||
|
||||
environment.etc.install-closure.source = "${closureInfo}/store-paths";
|
||||
nix.settings = {
|
||||
substituters = pkgs.lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = pkgs.lib.mkForce 3;
|
||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
};
|
||||
system.extraDependencies = dependencies;
|
||||
|
||||
clan.core.state.test-backups.folders = [ "/var/test-backups" ];
|
||||
};
|
||||
|
||||
|
||||
@@ -41,14 +41,6 @@
|
||||
clan-core,
|
||||
...
|
||||
}:
|
||||
let
|
||||
dependencies = [
|
||||
clan-core
|
||||
pkgs.stdenv.drvPath
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues clan-core.inputs);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
services.openssh.enable = true;
|
||||
@@ -59,15 +51,6 @@
|
||||
|
||||
environment.systemPackages = [ clan-core.packages.${pkgs.system}.clan-cli ];
|
||||
|
||||
environment.etc.install-closure.source = "${closureInfo}/store-paths";
|
||||
nix.settings = {
|
||||
substituters = pkgs.lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = pkgs.lib.mkForce 3;
|
||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
};
|
||||
system.extraDependencies = dependencies;
|
||||
|
||||
clan.core.state.test-backups.folders = [ "/var/test-backups" ];
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,13 @@ in
|
||||
unit-test-module = (
|
||||
self.clanLib.test.flakeModules.makeEvalChecks {
|
||||
inherit module;
|
||||
inherit self inputs;
|
||||
inherit inputs;
|
||||
fileset = lib.fileset.unions [
|
||||
# The hello-world service being tested
|
||||
../../clanServices/hello-world
|
||||
# Required modules
|
||||
../../nixosModules/clanCore
|
||||
];
|
||||
testName = "hello-world";
|
||||
tests = ./tests/eval-tests.nix;
|
||||
# Optional arguments passed to the test
|
||||
|
||||
@@ -15,7 +15,15 @@ in
|
||||
unit-test-module = (
|
||||
self.clanLib.test.flakeModules.makeEvalChecks {
|
||||
inherit module;
|
||||
inherit self inputs;
|
||||
inherit inputs;
|
||||
fileset = lib.fileset.unions [
|
||||
# The zerotier service being tested
|
||||
../../clanServices/zerotier
|
||||
# Required modules
|
||||
../../nixosModules/clanCore
|
||||
# Dependencies like clan-cli
|
||||
../../pkgs/clan-cli
|
||||
];
|
||||
testName = "zerotier";
|
||||
tests = ./tests/eval-tests.nix;
|
||||
testArgs = { };
|
||||
|
||||
1
devFlake/private.narHash
Normal file
1
devFlake/private.narHash
Normal file
@@ -0,0 +1 @@
|
||||
sha256-pFUj3KhQ4FkzZT19t+FHBru8u8Lspax0rS2cv7nXIgM=
|
||||
165
devFlake/private/flake.lock
generated
Normal file
165
devFlake/private/flake.lock
generated
Normal file
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": [
|
||||
"systems"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"ixx": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"nuschtos",
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nuschtos",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1748294338,
|
||||
"narHash": "sha256-FVO01jdmUNArzBS7NmaktLdGA5qA3lUMJ4B7a05Iynw=",
|
||||
"owner": "NuschtOS",
|
||||
"repo": "ixx",
|
||||
"rev": "cc5f390f7caf265461d4aab37e98d2292ebbdb85",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NuschtOS",
|
||||
"ref": "v0.0.8",
|
||||
"repo": "ixx",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-dev": {
|
||||
"locked": {
|
||||
"lastModified": 1751867001,
|
||||
"narHash": "sha256-3I49W0s3WVEDBO5S1RxYr74E2LLG7X8Wuvj9AmU0RDk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "73feb5e20ec7259e280ca6f424ba165059b3bb6b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable-small",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nuschtos": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"ixx": "ixx",
|
||||
"nixpkgs": [
|
||||
"nixpkgs-dev"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1749730855,
|
||||
"narHash": "sha256-L3x2nSlFkXkM6tQPLJP3oCBMIsRifhIDPMQQdHO5xWo=",
|
||||
"owner": "NuschtOS",
|
||||
"repo": "search",
|
||||
"rev": "8dfe5879dd009ff4742b668d9c699bc4b9761742",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NuschtOS",
|
||||
"repo": "search",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs-dev": "nixpkgs-dev",
|
||||
"nuschtos": "nuschtos",
|
||||
"systems": "systems_2",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": []
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1750931469,
|
||||
"narHash": "sha256-0IEdQB1nS+uViQw4k3VGUXntjkDp7aAlqcxdewb/hAc=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "ac8e6f32e11e9c7f153823abc3ab007f2a65d3e1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
19
devFlake/private/flake.nix
Normal file
19
devFlake/private/flake.nix
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
description = "private dev inputs";
|
||||
|
||||
# Dev dependencies
|
||||
inputs.nixpkgs-dev.url = "github:NixOS/nixpkgs/nixos-unstable-small";
|
||||
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.flake-utils.inputs.systems.follows = "systems";
|
||||
|
||||
inputs.nuschtos.url = "github:NuschtOS/search";
|
||||
inputs.nuschtos.inputs.nixpkgs.follows = "nixpkgs-dev";
|
||||
|
||||
inputs.treefmt-nix.url = "github:numtide/treefmt-nix";
|
||||
inputs.treefmt-nix.inputs.nixpkgs.follows = "";
|
||||
|
||||
inputs.systems.url = "github:nix-systems/default";
|
||||
|
||||
outputs = _: { };
|
||||
}
|
||||
12
devFlake/update-private-narhash
Executable file
12
devFlake/update-private-narhash
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Used to update the private dev flake hash reference.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "Updating $PWD/private.narHash" >&2
|
||||
|
||||
nix --extra-experimental-features 'flakes nix-command' flake lock ./private
|
||||
nix --extra-experimental-features 'flakes nix-command' hash path ./private >./private.narHash
|
||||
|
||||
echo OK
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
clan-core,
|
||||
pkgs,
|
||||
module-docs,
|
||||
clan-cli-docs,
|
||||
@@ -19,7 +18,17 @@ pkgs.stdenv.mkDerivation {
|
||||
|
||||
# Points to repository root.
|
||||
# so that we can access directories outside of docs to include code snippets
|
||||
src = clan-core;
|
||||
src = pkgs.lib.fileset.toSource {
|
||||
root = ../..;
|
||||
fileset = pkgs.lib.fileset.unions [
|
||||
# Docs directory
|
||||
../../docs
|
||||
# Icons needed for the build
|
||||
../../pkgs/clan-app/ui/icons
|
||||
# Any other directories that might be referenced for code snippets
|
||||
# Add them here as needed based on what mkdocs actually uses
|
||||
];
|
||||
};
|
||||
|
||||
nativeBuildInputs =
|
||||
[
|
||||
|
||||
@@ -82,10 +82,9 @@
|
||||
}
|
||||
''
|
||||
export CLAN_CORE_PATH=${
|
||||
self.filter {
|
||||
include = [
|
||||
"clanModules"
|
||||
];
|
||||
inputs.nixpkgs.lib.fileset.toSource {
|
||||
root = ../..;
|
||||
fileset = ../../clanModules;
|
||||
}
|
||||
}
|
||||
export CLAN_CORE_DOCS=${jsonDocs.clanCore}/share/doc/nixos/options.json
|
||||
@@ -126,7 +125,6 @@
|
||||
});
|
||||
packages = {
|
||||
docs = pkgs.python3.pkgs.callPackage ./default.nix {
|
||||
clan-core = self;
|
||||
inherit (self'.packages)
|
||||
clan-cli-docs
|
||||
docs-options
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
{ self, config, ... }:
|
||||
{
|
||||
self,
|
||||
config,
|
||||
inputs,
|
||||
privateInputs ? { },
|
||||
...
|
||||
}:
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
inputs',
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
@@ -157,11 +163,16 @@
|
||||
};
|
||||
in
|
||||
{
|
||||
packages.docs-options = inputs'.nuschtos.packages.mkMultiSearch {
|
||||
inherit baseHref;
|
||||
title = "Clan Options";
|
||||
# scopes = mapAttrsToList mkScope serviceModules;
|
||||
scopes = [ (mkScope "Clan Inventory" serviceModules) ];
|
||||
packages = lib.optionalAttrs ((privateInputs ? nuschtos) || (inputs ? nuschtos)) {
|
||||
docs-options =
|
||||
(privateInputs.nuschtos or inputs.nuschtos)
|
||||
.packages.${pkgs.stdenv.hostPlatform.system}.mkMultiSearch
|
||||
{
|
||||
inherit baseHref;
|
||||
title = "Clan Options";
|
||||
# scopes = mapAttrsToList mkScope serviceModules;
|
||||
scopes = [ (mkScope "Clan Inventory" serviceModules) ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ git+file:///home/lhebendanz/Projects/clan-core
|
||||
│ ├───editor omitted (use '--all-systems' to show)
|
||||
└───templates
|
||||
├───default: template: Initialize a new clan flake
|
||||
└───new-clan: template: Initialize a new clan flake
|
||||
└───default: template: Initialize a new clan flake
|
||||
```
|
||||
|
||||
You can execute every test separately by following the tree path `nix run .#checks.x86_64-linux.clan-pytest -L` for example.
|
||||
|
||||
72
flake.lock
generated
72
flake.lock
generated
@@ -67,52 +67,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": [
|
||||
"systems"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"ixx": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"nuschtos",
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nuschtos",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1748294338,
|
||||
"narHash": "sha256-FVO01jdmUNArzBS7NmaktLdGA5qA3lUMJ4B7a05Iynw=",
|
||||
"owner": "NuschtOS",
|
||||
"repo": "ixx",
|
||||
"rev": "cc5f390f7caf265461d4aab37e98d2292ebbdb85",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NuschtOS",
|
||||
"ref": "v0.0.8",
|
||||
"repo": "ixx",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-darwin": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -174,41 +128,15 @@
|
||||
"url": "https://nixos.org/channels/nixpkgs-unstable/nixexprs.tar.xz"
|
||||
}
|
||||
},
|
||||
"nuschtos": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"ixx": "ixx",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1749730855,
|
||||
"narHash": "sha256-L3x2nSlFkXkM6tQPLJP3oCBMIsRifhIDPMQQdHO5xWo=",
|
||||
"owner": "NuschtOS",
|
||||
"repo": "search",
|
||||
"rev": "8dfe5879dd009ff4742b668d9c699bc4b9761742",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NuschtOS",
|
||||
"repo": "search",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"data-mesher": "data-mesher",
|
||||
"disko": "disko",
|
||||
"flake-parts": "flake-parts",
|
||||
"flake-utils": "flake-utils",
|
||||
"nix-darwin": "nix-darwin",
|
||||
"nix-select": "nix-select",
|
||||
"nixos-facter-modules": "nixos-facter-modules",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nuschtos": "nuschtos",
|
||||
"sops-nix": "sops-nix",
|
||||
"systems": "systems",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
|
||||
23
flake.nix
23
flake.nix
@@ -35,19 +35,13 @@
|
||||
};
|
||||
};
|
||||
|
||||
# dependencies needed for nuschtos
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
flake-utils.inputs.systems.follows = "systems";
|
||||
nuschtos.url = "github:NuschtOS/search";
|
||||
nuschtos.inputs.nixpkgs.follows = "nixpkgs";
|
||||
nuschtos.inputs.flake-utils.follows = "flake-utils";
|
||||
};
|
||||
|
||||
outputs =
|
||||
inputs@{
|
||||
flake-parts,
|
||||
nixpkgs,
|
||||
systems,
|
||||
flake-parts,
|
||||
...
|
||||
}:
|
||||
let
|
||||
@@ -56,10 +50,25 @@
|
||||
optional
|
||||
pathExists
|
||||
;
|
||||
|
||||
loadDevFlake =
|
||||
path:
|
||||
let
|
||||
flakeHash = nixpkgs.lib.fileContents "${toString path}.narHash";
|
||||
flakePath = "path:${toString path}?narHash=${flakeHash}";
|
||||
in
|
||||
builtins.getFlake (builtins.unsafeDiscardStringContext flakePath);
|
||||
|
||||
devFlake = builtins.tryEval (loadDevFlake ./devFlake/private);
|
||||
|
||||
privateInputs = if devFlake.success then devFlake.value.inputs else { };
|
||||
in
|
||||
flake-parts.lib.mkFlake { inherit inputs; } (
|
||||
{ ... }:
|
||||
{
|
||||
_module.args = {
|
||||
inherit privateInputs;
|
||||
};
|
||||
clan = {
|
||||
meta.name = "clan-core";
|
||||
inventory = {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
perSystem =
|
||||
{ self', pkgs, ... }:
|
||||
{
|
||||
treefmt.projectRootFile = ".git/config";
|
||||
treefmt.projectRootFile = "LICENSE.md";
|
||||
treefmt.programs.shellcheck.enable = true;
|
||||
|
||||
treefmt.programs.mypy.enable = true;
|
||||
|
||||
@@ -37,6 +37,7 @@ lib.fix (
|
||||
inventory = clanLib.callLib ./modules/inventory { };
|
||||
modules = clanLib.callLib ./modules/inventory/frontmatter { };
|
||||
test = clanLib.callLib ./test { };
|
||||
flake-inputs = clanLib.callLib ./flake-inputs.nix { };
|
||||
# Custom types
|
||||
types = clanLib.callLib ./types { };
|
||||
|
||||
|
||||
18
lib/flake-inputs.nix
Normal file
18
lib/flake-inputs.nix
Normal file
@@ -0,0 +1,18 @@
|
||||
{ ... }:
|
||||
{
|
||||
/**
|
||||
Generate nix-unit input overrides for tests
|
||||
|
||||
# Example
|
||||
```nix
|
||||
inputOverrides = clanLib.flake-inputs.getOverrides inputs;
|
||||
```
|
||||
*/
|
||||
getOverrides =
|
||||
inputs:
|
||||
builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (
|
||||
builtins.filter (name: name != "self") (builtins.attrNames inputs)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
{ self, inputs, ... }:
|
||||
let
|
||||
inputOverrides = builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||
);
|
||||
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
|
||||
in
|
||||
{
|
||||
perSystem =
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
inputOverrides = builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||
);
|
||||
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{ self, inputs, ... }:
|
||||
let
|
||||
inputOverrides = builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||
);
|
||||
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
|
||||
in
|
||||
{
|
||||
perSystem =
|
||||
@@ -12,6 +10,23 @@ in
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
# Common filtered source for inventory tests
|
||||
inventoryTestsSrc = lib.fileset.toSource {
|
||||
root = ../../../..;
|
||||
fileset = lib.fileset.unions [
|
||||
../../../../flake.nix
|
||||
../../../../flake.lock
|
||||
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../../..)
|
||||
../../../../flakeModules
|
||||
../../../../lib
|
||||
../../../../nixosModules/clanCore
|
||||
../../../../clanModules/borgbackup
|
||||
../../../../machines
|
||||
../../../../inventory.json
|
||||
];
|
||||
};
|
||||
in
|
||||
{
|
||||
# Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux.<attrName>
|
||||
legacyPackages.evalTests-distributedServices = import ./tests {
|
||||
@@ -29,7 +44,7 @@ in
|
||||
--extra-experimental-features flakes \
|
||||
--show-trace \
|
||||
${inputOverrides} \
|
||||
--flake ${self}#legacyPackages.${system}.evalTests-distributedServices
|
||||
--flake ${inventoryTestsSrc}#legacyPackages.${system}.evalTests-distributedServices
|
||||
|
||||
touch $out
|
||||
'';
|
||||
@@ -39,7 +54,7 @@ in
|
||||
--extra-experimental-features flakes \
|
||||
--show-trace \
|
||||
${inputOverrides} \
|
||||
--flake ${self}#legacyPackages.${system}.eval-tests-resolve-module
|
||||
--flake ${inventoryTestsSrc}#legacyPackages.${system}.eval-tests-resolve-module
|
||||
|
||||
touch $out
|
||||
'';
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
inputOverrides = builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||
);
|
||||
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
@@ -70,12 +68,18 @@ in
|
||||
--show-trace \
|
||||
${inputOverrides} \
|
||||
--flake ${
|
||||
self.filter {
|
||||
include = [
|
||||
"flakeModules"
|
||||
"lib"
|
||||
"clanModules/flake-module.nix"
|
||||
"clanModules/borgbackup"
|
||||
lib.fileset.toSource {
|
||||
root = ../../..;
|
||||
fileset = lib.fileset.unions [
|
||||
../../../flake.nix
|
||||
../../../flake.lock
|
||||
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../..)
|
||||
../../../flakeModules
|
||||
../../../lib
|
||||
../../../nixosModules/clanCore
|
||||
../../../clanModules/borgbackup
|
||||
../../../machines
|
||||
../../../inventory.json
|
||||
];
|
||||
}
|
||||
}#legacyPackages.${system}.evalTests-inventory
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
makeEvalChecks =
|
||||
{
|
||||
self,
|
||||
fileset,
|
||||
inputs,
|
||||
testName,
|
||||
tests,
|
||||
@@ -24,9 +24,7 @@
|
||||
testArgs ? { },
|
||||
}:
|
||||
let
|
||||
inputOverrides = builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||
);
|
||||
inputOverrides = clanLib.flake-inputs.getOverrides inputs;
|
||||
attrName = "eval-tests-${testName}";
|
||||
in
|
||||
{
|
||||
@@ -41,16 +39,44 @@
|
||||
}
|
||||
// testArgs
|
||||
);
|
||||
checks.${attrName} = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
||||
export HOME="$(realpath .)"
|
||||
checks.${attrName} =
|
||||
let
|
||||
# The root is two directories up from where this file is located
|
||||
root = ../..;
|
||||
|
||||
nix-unit --eval-store "$HOME" \
|
||||
--extra-experimental-features flakes \
|
||||
--show-trace \
|
||||
${inputOverrides} \
|
||||
--flake ${self}#legacyPackages.${system}.${attrName}
|
||||
touch $out
|
||||
'';
|
||||
# Combine the user-provided fileset with all flake-module.nix files
|
||||
# and other essential files
|
||||
src = lib.fileset.toSource {
|
||||
inherit root;
|
||||
fileset = lib.fileset.unions [
|
||||
# Core flake files
|
||||
(root + "/flake.nix")
|
||||
(root + "/flake.lock")
|
||||
|
||||
# All flake-module.nix files anywhere in the tree
|
||||
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") root)
|
||||
|
||||
# The flakeModules/clan.nix if it exists
|
||||
(lib.fileset.maybeMissing (root + "/flakeModules/clan.nix"))
|
||||
|
||||
# Core libraries
|
||||
(root + "/lib")
|
||||
|
||||
# User-provided fileset
|
||||
fileset
|
||||
];
|
||||
};
|
||||
in
|
||||
pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
||||
export HOME="$(realpath .)"
|
||||
|
||||
nix-unit --eval-store "$HOME" \
|
||||
--extra-experimental-features flakes \
|
||||
--show-trace \
|
||||
${inputOverrides} \
|
||||
--flake ${src}#legacyPackages.${system}.${attrName}
|
||||
touch $out
|
||||
'';
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{ self, inputs, ... }:
|
||||
{
|
||||
self,
|
||||
inputs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{
|
||||
perSystem =
|
||||
{ ... }:
|
||||
@@ -10,7 +15,11 @@
|
||||
test-types-module = (
|
||||
self.clanLib.test.flakeModules.makeEvalChecks {
|
||||
module = throw "";
|
||||
inherit self inputs;
|
||||
inherit inputs;
|
||||
fileset = lib.fileset.unions [
|
||||
# Only lib is needed for type tests
|
||||
../../lib
|
||||
];
|
||||
testName = "types";
|
||||
tests = ./tests.nix;
|
||||
# Optional arguments passed to the test
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ lib, pkgs, ... }:
|
||||
{ lib, pkgs }:
|
||||
let
|
||||
eval =
|
||||
module:
|
||||
|
||||
@@ -5,18 +5,14 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
inputOverrides = builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||
);
|
||||
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
|
||||
in
|
||||
{
|
||||
perSystem =
|
||||
{ system, pkgs, ... }:
|
||||
{
|
||||
legacyPackages.evalTests-module-clan-vars = import ./eval-tests {
|
||||
inherit lib;
|
||||
clan-core = self;
|
||||
pkgs = inputs.nixpkgs.legacyPackages.${system};
|
||||
inherit lib pkgs;
|
||||
};
|
||||
checks.eval-module-clan-vars = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
||||
export HOME="$(realpath .)"
|
||||
@@ -26,11 +22,15 @@ in
|
||||
--show-trace \
|
||||
${inputOverrides} \
|
||||
--flake ${
|
||||
self.filter {
|
||||
include = [
|
||||
"flakeModules"
|
||||
"nixosModules"
|
||||
"lib"
|
||||
lib.fileset.toSource {
|
||||
root = ../../..;
|
||||
fileset = lib.fileset.unions [
|
||||
../../../flake.nix
|
||||
../../../flake.lock
|
||||
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../..)
|
||||
../../../flakeModules/clan.nix
|
||||
../../../lib
|
||||
../../../nixosModules/clanCore/vars
|
||||
];
|
||||
}
|
||||
}#legacyPackages.${system}.evalTests-module-clan-vars
|
||||
|
||||
16
pkgs/clan-app/ui/package-lock.json
generated
16
pkgs/clan-app/ui/package-lock.json
generated
@@ -48,7 +48,7 @@
|
||||
"jsdom": "^26.1.0",
|
||||
"knip": "^5.61.2",
|
||||
"markdown-to-jsx": "^7.7.10",
|
||||
"playwright": "~1.52.0",
|
||||
"playwright": "~1.53.2",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-url": "^10.1.3",
|
||||
"prettier": "^3.2.5",
|
||||
@@ -6189,13 +6189,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz",
|
||||
"integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==",
|
||||
"version": "1.53.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz",
|
||||
"integrity": "sha512-6K/qQxVFuVQhRQhFsVZ9fGeatxirtrpPgxzBYWyZLEXJzqYwuL4fuNmfOfD5et1tJE4GScKyPNeLhZeRwuTU3A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.52.0"
|
||||
"playwright-core": "1.53.2"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -6208,9 +6208,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz",
|
||||
"integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==",
|
||||
"version": "1.53.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.2.tgz",
|
||||
"integrity": "sha512-ox/OytMy+2w1jcYEYlOo1Hhp8hZkLCximMTUTMBXjGUA1KoFfiSZ+DU+3a739jsPY0yoKH2TFy9S2fsJas8yAw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"jsdom": "^26.1.0",
|
||||
"knip": "^5.61.2",
|
||||
"markdown-to-jsx": "^7.7.10",
|
||||
"playwright": "~1.52.0",
|
||||
"playwright": "~1.53.2",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-url": "^10.1.3",
|
||||
"prettier": "^3.2.5",
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
div.divider {
|
||||
@apply bg-inv-2;
|
||||
hr {
|
||||
@apply border-none outline-none bg-inv-2;
|
||||
|
||||
&.inverted {
|
||||
@apply bg-def-3;
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
&[data-orientation="horizontal"] {
|
||||
@apply w-full h-px;
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
&[data-orientation="vertical"] {
|
||||
@apply h-full w-px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import "./Divider.css";
|
||||
import cx from "classnames";
|
||||
import { Separator, SeparatorRootProps } from "@kobalte/core/separator";
|
||||
|
||||
export interface DividerProps {
|
||||
export interface DividerProps extends Pick<SeparatorRootProps, "orientation"> {
|
||||
inverted?: boolean;
|
||||
orientation?: "horizontal" | "vertical";
|
||||
}
|
||||
|
||||
export const Divider = (props: DividerProps) => {
|
||||
const inverted = props.inverted || false;
|
||||
const orientation = () => props.orientation || "horizontal";
|
||||
|
||||
return <div class={cx("divider", orientation(), { inverted: inverted })} />;
|
||||
return (
|
||||
<Separator
|
||||
class={cx({ inverted: inverted })}
|
||||
orientation={props.orientation}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,6 +16,10 @@ div.form-field {
|
||||
&[data-invalid] {
|
||||
@apply border-semantic-error-4;
|
||||
}
|
||||
|
||||
&[data-readonly] {
|
||||
@apply cursor-default bg-inherit border-none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +36,10 @@ div.form-field {
|
||||
&[data-disabled] {
|
||||
@apply bg-def-4 border-none;
|
||||
}
|
||||
|
||||
&[data-readonly] {
|
||||
@apply bg-inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,15 @@ export const Disabled: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const ReadOnly: Story = {
|
||||
export const ReadOnlyUnchecked: Story = {
|
||||
args: {
|
||||
...Tooltip.args,
|
||||
readOnly: true,
|
||||
defaultChecked: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const ReadOnlyChecked: Story = {
|
||||
args: {
|
||||
...Tooltip.args,
|
||||
readOnly: true,
|
||||
|
||||
@@ -11,37 +11,64 @@ import { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import "./Checkbox.css";
|
||||
import { FieldProps } from "./Field";
|
||||
import { Orienter } from "./Orienter";
|
||||
import { Show } from "solid-js";
|
||||
|
||||
export type CheckboxProps = FieldProps &
|
||||
KCheckboxRootProps & {
|
||||
input?: PolymorphicProps<"input", KCheckboxInputProps<"input">>;
|
||||
};
|
||||
|
||||
export const Checkbox = (props: CheckboxProps) => (
|
||||
<KCheckbox
|
||||
class={cx("form-field", "checkbox", props.size, props.orientation, {
|
||||
inverted: props.inverted,
|
||||
ghost: props.ghost,
|
||||
})}
|
||||
{...props}
|
||||
>
|
||||
<Orienter orientation={props.orientation} align={"start"}>
|
||||
<Label
|
||||
labelComponent={KCheckbox.Label}
|
||||
descriptionComponent={KCheckbox.Description}
|
||||
{...props}
|
||||
/>
|
||||
<KCheckbox.Input {...props.input} />
|
||||
<KCheckbox.Control class="checkbox-control">
|
||||
<KCheckbox.Indicator>
|
||||
<Icon
|
||||
icon="Checkmark"
|
||||
inverted={props.inverted}
|
||||
color="secondary"
|
||||
size="100%"
|
||||
/>
|
||||
</KCheckbox.Indicator>
|
||||
</KCheckbox.Control>
|
||||
</Orienter>
|
||||
</KCheckbox>
|
||||
);
|
||||
export const Checkbox = (props: CheckboxProps) => {
|
||||
const alignment = () =>
|
||||
(props.orientation || "vertical") == "vertical" ? "start" : "center";
|
||||
|
||||
const iconChecked = (
|
||||
<Icon
|
||||
icon="Checkmark"
|
||||
inverted={props.inverted}
|
||||
color="secondary"
|
||||
size="100%"
|
||||
/>
|
||||
);
|
||||
|
||||
const iconUnchecked = (
|
||||
<Icon
|
||||
icon="Close"
|
||||
inverted={props.inverted}
|
||||
color="secondary"
|
||||
size="100%"
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<KCheckbox
|
||||
class={cx("form-field", "checkbox", props.size, props.orientation, {
|
||||
inverted: props.inverted,
|
||||
ghost: props.ghost,
|
||||
})}
|
||||
{...props}
|
||||
>
|
||||
<Orienter orientation={props.orientation} align={alignment()}>
|
||||
<Label
|
||||
labelComponent={KCheckbox.Label}
|
||||
descriptionComponent={KCheckbox.Description}
|
||||
{...props}
|
||||
/>
|
||||
<KCheckbox.Input {...props.input} />
|
||||
<KCheckbox.Control class="checkbox-control">
|
||||
{props.readOnly && (
|
||||
<Show
|
||||
when={props.checked || props.defaultChecked}
|
||||
fallback={iconUnchecked}
|
||||
>
|
||||
{iconChecked}
|
||||
</Show>
|
||||
)}
|
||||
{!props.readOnly && (
|
||||
<KCheckbox.Indicator>{iconChecked}</KCheckbox.Indicator>
|
||||
)}
|
||||
</KCheckbox.Control>
|
||||
</Orienter>
|
||||
</KCheckbox>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
div.form-field.combobox {
|
||||
div.control {
|
||||
@apply flex flex-col w-full gap-2;
|
||||
@apply flex flex-col size-full gap-2;
|
||||
|
||||
div.selected-options {
|
||||
@apply flex flex-wrap gap-1 w-full min-h-5;
|
||||
@apply flex flex-wrap gap-1 size-full min-h-5;
|
||||
}
|
||||
|
||||
div.input-container {
|
||||
@@ -44,7 +44,8 @@ div.form-field.combobox {
|
||||
}
|
||||
|
||||
&[data-readonly] {
|
||||
@apply outline-def-2 cursor-not-allowed;
|
||||
@apply outline-none border-none bg-inherit;
|
||||
@apply p-0 resize-none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +77,10 @@ div.form-field.combobox {
|
||||
& > input {
|
||||
@apply px-1.5 py-1;
|
||||
font-size: 0.75rem;
|
||||
|
||||
&[data-readonly] {
|
||||
@apply p-0;
|
||||
}
|
||||
}
|
||||
|
||||
& > button.trigger {
|
||||
@@ -111,6 +116,10 @@ div.form-field.combobox {
|
||||
&[data-invalid] {
|
||||
@apply outline-semantic-error-4;
|
||||
}
|
||||
|
||||
&[data-readonly] {
|
||||
@apply outline-none border-none bg-inherit cursor-auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@ export const ReadOnly: Story = {
|
||||
args: {
|
||||
...Tooltip.args,
|
||||
readOnly: true,
|
||||
defaultValue: "foo",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -83,14 +83,18 @@ export const DefaultItemControl = <Option,>(
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="input-container">
|
||||
<KCombobox.Input />
|
||||
<KCombobox.Trigger class="trigger">
|
||||
<KCombobox.Icon class="icon">
|
||||
<Icon icon="Expand" inverted={props.inverted} size="100%" />
|
||||
</KCombobox.Icon>
|
||||
</KCombobox.Trigger>
|
||||
</div>
|
||||
{!(props.readOnly && props.multiple) && (
|
||||
<div class="input-container">
|
||||
<KCombobox.Input />
|
||||
{!props.readOnly && (
|
||||
<KCombobox.Trigger class="trigger">
|
||||
<KCombobox.Icon class="icon">
|
||||
<Icon icon="Expand" inverted={props.inverted} size="100%" />
|
||||
</KCombobox.Icon>
|
||||
</KCombobox.Trigger>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -101,7 +105,13 @@ export const Combobox = <Option, OptGroup = never>(
|
||||
const itemControl = () => props.itemControl || DefaultItemControl;
|
||||
const itemComponent = () => props.itemComponent || DefaultItemComponent;
|
||||
|
||||
const align = () => (props.orientation === "horizontal" ? "start" : "center");
|
||||
const align = () => {
|
||||
if (props.readOnly) {
|
||||
return "center";
|
||||
} else {
|
||||
return props.orientation === "horizontal" ? "start" : "center";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<KCombobox
|
||||
|
||||
@@ -40,7 +40,7 @@ export type Story = StoryObj<typeof meta>;
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
legend: "Signup",
|
||||
fields: (props: FieldProps) => (
|
||||
children: (props: FieldProps) => (
|
||||
<>
|
||||
<TextInput
|
||||
{...props}
|
||||
@@ -90,7 +90,7 @@ export const Error: Story = {
|
||||
args: {
|
||||
legend: "Signup",
|
||||
error: "You must enter a First Name",
|
||||
fields: (props: FieldProps) => (
|
||||
children: (props: FieldProps) => (
|
||||
<>
|
||||
<TextInput
|
||||
{...props}
|
||||
|
||||
@@ -1,41 +1,57 @@
|
||||
import "./Fieldset.css";
|
||||
import { JSX } from "solid-js";
|
||||
import { JSX, splitProps } from "solid-js";
|
||||
import cx from "classnames";
|
||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
||||
import { FieldProps } from "./Field";
|
||||
|
||||
export interface FieldsetProps extends FieldProps {
|
||||
legend: string;
|
||||
disabled: boolean;
|
||||
export type FieldsetFieldProps = Pick<
|
||||
FieldProps,
|
||||
"orientation" | "inverted"
|
||||
> & {
|
||||
error?: string;
|
||||
fields: (props: FieldProps) => JSX.Element;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export interface FieldsetProps
|
||||
extends Pick<FieldProps, "orientation" | "inverted"> {
|
||||
legend?: string;
|
||||
disabled?: boolean;
|
||||
error?: string;
|
||||
children: (props: FieldsetFieldProps) => JSX.Element;
|
||||
}
|
||||
|
||||
export const Fieldset = (props: FieldsetProps) => {
|
||||
const orientation = () => props.orientation || "vertical";
|
||||
|
||||
const [fieldProps] = splitProps(props, [
|
||||
"orientation",
|
||||
"inverted",
|
||||
"disabled",
|
||||
"error",
|
||||
]);
|
||||
|
||||
return (
|
||||
<fieldset
|
||||
role="group"
|
||||
class={cx(orientation(), { inverted: props.inverted })}
|
||||
disabled={props.disabled}
|
||||
class={cx({ inverted: props.inverted })}
|
||||
disabled={props.disabled || false}
|
||||
>
|
||||
<legend>
|
||||
<Typography
|
||||
hierarchy="label"
|
||||
family="mono"
|
||||
size="default"
|
||||
weight="normal"
|
||||
color="tertiary"
|
||||
transform="uppercase"
|
||||
inverted={props.inverted}
|
||||
>
|
||||
{props.legend}
|
||||
</Typography>
|
||||
</legend>
|
||||
<div class="fields">
|
||||
{props.fields({ ...props, orientation: orientation() })}
|
||||
</div>
|
||||
{props.legend && (
|
||||
<legend>
|
||||
<Typography
|
||||
hierarchy="label"
|
||||
family="mono"
|
||||
size="default"
|
||||
weight="normal"
|
||||
color="tertiary"
|
||||
transform="uppercase"
|
||||
inverted={props.inverted}
|
||||
>
|
||||
{props.legend}
|
||||
</Typography>
|
||||
</legend>
|
||||
)}
|
||||
<div class="fields">{props.children(fieldProps)}</div>
|
||||
{props.error && (
|
||||
<div class="error" role="alert">
|
||||
<Typography
|
||||
|
||||
@@ -12,7 +12,7 @@ div.form-label {
|
||||
@apply flex items-center gap-1;
|
||||
}
|
||||
|
||||
& > label[data-required] {
|
||||
& > label[data-required] & not(label[data-readonly]) {
|
||||
span.typography::after {
|
||||
@apply fg-def-4 ml-1;
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface LabelProps {
|
||||
tooltip?: string;
|
||||
icon?: string;
|
||||
inverted?: boolean;
|
||||
readOnly?: boolean;
|
||||
validationState?: "valid" | "invalid";
|
||||
}
|
||||
|
||||
@@ -42,7 +43,7 @@ export const Label = (props: LabelProps) => {
|
||||
hierarchy="label"
|
||||
size={props.size || "default"}
|
||||
color={props.validationState == "invalid" ? "error" : "primary"}
|
||||
weight="bold"
|
||||
weight={props.readOnly ? "normal" : "bold"}
|
||||
inverted={props.inverted}
|
||||
>
|
||||
{props.label}
|
||||
|
||||
@@ -5,7 +5,7 @@ div.orienter {
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
@apply flex-row gap-2 justify-between;
|
||||
@apply flex-row justify-start;
|
||||
|
||||
& > div.form-label {
|
||||
@apply w-1/2 shrink;
|
||||
|
||||
@@ -34,7 +34,13 @@ div.form-field {
|
||||
}
|
||||
|
||||
&[data-readonly] {
|
||||
@apply outline-def-2 cursor-not-allowed;
|
||||
@apply outline-none border-none bg-inherit p-0 cursor-auto resize-none;
|
||||
}
|
||||
}
|
||||
|
||||
&.textarea textarea {
|
||||
&[data-readonly] {
|
||||
@apply overflow-y-hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +78,10 @@ div.form-field {
|
||||
&.textarea textarea {
|
||||
@apply px-1.5 py-1;
|
||||
font-size: 0.75rem;
|
||||
|
||||
&[data-readonly] {
|
||||
@apply p-0;
|
||||
}
|
||||
}
|
||||
|
||||
&.text div.input-container {
|
||||
@@ -114,6 +124,11 @@ div.form-field {
|
||||
&[data-invalid] {
|
||||
@apply outline-semantic-error-4;
|
||||
}
|
||||
|
||||
&[data-readonly] {
|
||||
@apply outline-def-2 cursor-auto;
|
||||
@apply outline-none border-none bg-inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export const TextInput = (props: TextInputProps) => (
|
||||
{...props}
|
||||
/>
|
||||
<div class="input-container">
|
||||
{props.icon && (
|
||||
{props.icon && !props.readOnly && (
|
||||
<Icon
|
||||
icon={props.icon}
|
||||
inverted={props.inverted}
|
||||
@@ -42,7 +42,7 @@ export const TextInput = (props: TextInputProps) => (
|
||||
)}
|
||||
<TextField.Input
|
||||
{...props.input}
|
||||
classList={{ "has-icon": props.icon }}
|
||||
classList={{ "has-icon": props.icon && !props.readOnly }}
|
||||
/>
|
||||
</div>
|
||||
</Orienter>
|
||||
|
||||
24
pkgs/clan-app/ui/src/components/v2/Modal/Modal.css
Normal file
24
pkgs/clan-app/ui/src/components/v2/Modal/Modal.css
Normal file
@@ -0,0 +1,24 @@
|
||||
div.modal-content {
|
||||
@apply max-w-[512px];
|
||||
@apply rounded-md;
|
||||
|
||||
/* todo replace with a theme() color */
|
||||
box-shadow: 0.1875rem 0.1875rem 0 0 rgba(145, 172, 175, 0.32);
|
||||
|
||||
& > div.header {
|
||||
@apply flex items-center justify-center;
|
||||
@apply w-full px-2 py-1.5;
|
||||
@apply bg-def-3;
|
||||
@apply border border-def-2 rounded-tl-md rounded-tr-md;
|
||||
@apply border-b-def-3;
|
||||
|
||||
& > .title {
|
||||
@apply mx-auto;
|
||||
}
|
||||
}
|
||||
|
||||
& > div.body {
|
||||
@apply p-6 bg-def-1;
|
||||
@apply border border-def-2 rounded-bl-md rounded-br-md;
|
||||
}
|
||||
}
|
||||
74
pkgs/clan-app/ui/src/components/v2/Modal/Modal.stories.tsx
Normal file
74
pkgs/clan-app/ui/src/components/v2/Modal/Modal.stories.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { TagProps } from "@/src/components/v2/Tag/Tag";
|
||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { fn } from "storybook/test";
|
||||
import {
|
||||
Modal,
|
||||
ModalContext,
|
||||
ModalProps,
|
||||
} from "@/src/components/v2/Modal/Modal";
|
||||
import { Fieldset } from "@/src/components/v2/Form/Fieldset";
|
||||
import { TextInput } from "@/src/components/v2/Form/TextInput";
|
||||
import { TextArea } from "@/src/components/v2/Form/TextArea";
|
||||
import { Checkbox } from "@/src/components/v2/Form/Checkbox";
|
||||
import { Button } from "../Button/Button";
|
||||
|
||||
const meta: Meta<ModalProps> = {
|
||||
title: "Components/Modal",
|
||||
component: Modal,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<TagProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
title: "Example Modal",
|
||||
onClose: fn(),
|
||||
children: ({ close }: ModalContext) => (
|
||||
<form class="flex flex-col gap-5">
|
||||
<Fieldset legend="General">
|
||||
{(props) => (
|
||||
<>
|
||||
<TextInput
|
||||
{...props}
|
||||
label="First Name"
|
||||
size="s"
|
||||
required={true}
|
||||
input={{ placeholder: "Ron" }}
|
||||
/>
|
||||
<TextInput
|
||||
{...props}
|
||||
label="Last Name"
|
||||
size="s"
|
||||
required={true}
|
||||
input={{ placeholder: "Burgundy" }}
|
||||
/>
|
||||
<TextArea
|
||||
{...props}
|
||||
label="Bio"
|
||||
size="s"
|
||||
input={{ placeholder: "Tell us a bit about yourself", rows: 8 }}
|
||||
/>
|
||||
<Checkbox
|
||||
{...props}
|
||||
size="s"
|
||||
label="Accept Terms"
|
||||
required={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Fieldset>
|
||||
|
||||
<div class="flex w-full items-center justify-end gap-4">
|
||||
<Button size="s" hierarchy="secondary" onClick={close}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="s" type="submit" hierarchy="primary" onClick={close}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
),
|
||||
},
|
||||
};
|
||||
40
pkgs/clan-app/ui/src/components/v2/Modal/Modal.tsx
Normal file
40
pkgs/clan-app/ui/src/components/v2/Modal/Modal.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { createSignal, JSX } from "solid-js";
|
||||
import { Dialog as KDialog } from "@kobalte/core/dialog";
|
||||
import "./Modal.css";
|
||||
import { Typography } from "../Typography/Typography";
|
||||
import Icon from "../Icon/Icon";
|
||||
|
||||
export interface ModalContext {
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export interface ModalProps {
|
||||
id?: string;
|
||||
title: string;
|
||||
onClose: () => void;
|
||||
children: (ctx: ModalContext) => JSX.Element;
|
||||
}
|
||||
|
||||
export const Modal = (props: ModalProps) => {
|
||||
const [open, setOpen] = createSignal(true);
|
||||
|
||||
return (
|
||||
<KDialog id={props.id} open={open()} modal={true}>
|
||||
<KDialog.Portal>
|
||||
<KDialog.Content class="modal-content">
|
||||
<div class="header">
|
||||
<Typography class="title" hierarchy="label" family="mono" size="xs">
|
||||
{props.title}
|
||||
</Typography>
|
||||
<KDialog.CloseButton onClick={() => setOpen(false)}>
|
||||
<Icon icon="Close" size="0.75rem" />
|
||||
</KDialog.CloseButton>
|
||||
</div>
|
||||
<div class="body">
|
||||
{props.children({ close: () => setOpen(false) })}
|
||||
</div>
|
||||
</KDialog.Content>
|
||||
</KDialog.Portal>
|
||||
</KDialog>
|
||||
);
|
||||
};
|
||||
@@ -4,8 +4,8 @@ div.sidebar-header {
|
||||
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
theme(colors.bg.inv.3) 0%,
|
||||
theme(colors.bg.inv.4) 0%
|
||||
theme(colors.bg.inv.2) 0%,
|
||||
theme(colors.bg.inv.3) 0%
|
||||
);
|
||||
|
||||
& > .dropdown-trigger {
|
||||
|
||||
33
pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarPane.css
Normal file
33
pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarPane.css
Normal file
@@ -0,0 +1,33 @@
|
||||
div.sidebar-pane {
|
||||
@apply h-full w-auto max-w-60 border-none;
|
||||
|
||||
& > div.header {
|
||||
@apply flex items-center justify-between px-3 py-2 rounded-t-[0.5rem];
|
||||
@apply border-t-[1px] border-t-bg-inv-3
|
||||
border-r-[1px] border-r-bg-inv-3
|
||||
border-b-2 border-b-bg-inv-4
|
||||
border-l-[1px] border-l-bg-inv-3;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
theme(colors.bg.inv.3) 0%,
|
||||
theme(colors.bg.inv.4) 100%
|
||||
);
|
||||
}
|
||||
|
||||
& > div.body {
|
||||
@apply flex flex-col gap-4 px-2 pt-4 pb-3 w-full h-full;
|
||||
@apply backdrop-blur-md;
|
||||
@apply rounded-b-[0.5rem]
|
||||
border-r-[1px] border-r-bg-inv-3
|
||||
border-b-2 border-b-bg-inv-4
|
||||
border-l-[1px] border-l-bg-inv-3;
|
||||
|
||||
background:
|
||||
linear-gradient(0deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.2) 100%),
|
||||
linear-gradient(
|
||||
180deg,
|
||||
theme(colors.bg.inv.2) 0%,
|
||||
theme(colors.bg.inv.3) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import {
|
||||
SidebarPane,
|
||||
SidebarPaneProps,
|
||||
} from "@/src/components/v2/Sidebar/SidebarPane";
|
||||
import { SidebarSection } from "./SidebarSection";
|
||||
import { Divider } from "@/src/components/v2/Divider/Divider";
|
||||
import { TextInput } from "@/src/components/v2/Form/TextInput";
|
||||
import { TextArea } from "@/src/components/v2/Form/TextArea";
|
||||
import { Checkbox } from "@/src/components/v2/Form/Checkbox";
|
||||
import { Combobox } from "../Form/Combobox";
|
||||
|
||||
const meta: Meta<SidebarPaneProps> = {
|
||||
title: "Components/Sidebar/Pane",
|
||||
component: SidebarPane,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<SidebarPaneProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
title: "Neptune",
|
||||
onClose: () => {
|
||||
console.log("closing");
|
||||
},
|
||||
children: (
|
||||
<>
|
||||
<SidebarSection
|
||||
title="General"
|
||||
onSave={async () => {
|
||||
console.log("saving general");
|
||||
}}
|
||||
>
|
||||
{(editing) => (
|
||||
<form class="flex flex-col gap-3">
|
||||
<TextInput
|
||||
label="First Name"
|
||||
size="s"
|
||||
inverted={true}
|
||||
required={true}
|
||||
readOnly={!editing}
|
||||
orientation="horizontal"
|
||||
input={{ value: "Ron" }}
|
||||
/>
|
||||
<Divider />
|
||||
<TextInput
|
||||
label="Last Name"
|
||||
size="s"
|
||||
inverted={true}
|
||||
required={true}
|
||||
readOnly={!editing}
|
||||
orientation="horizontal"
|
||||
input={{ value: "Burgundy" }}
|
||||
/>
|
||||
<Divider />
|
||||
<TextArea
|
||||
label="Bio"
|
||||
size="s"
|
||||
inverted={true}
|
||||
readOnly={!editing}
|
||||
orientation="horizontal"
|
||||
input={{
|
||||
value:
|
||||
"It's actually an optical illusion, it's the pattern on the pants.",
|
||||
rows: 4,
|
||||
}}
|
||||
/>
|
||||
<Divider />
|
||||
<Checkbox
|
||||
size="s"
|
||||
label="Share Profile"
|
||||
required={true}
|
||||
inverted={true}
|
||||
readOnly={!editing}
|
||||
checked={true}
|
||||
orientation="horizontal"
|
||||
/>
|
||||
</form>
|
||||
)}
|
||||
</SidebarSection>
|
||||
<SidebarSection
|
||||
title="Tags"
|
||||
onSave={async () => {
|
||||
console.log("saving general");
|
||||
}}
|
||||
>
|
||||
{(editing) => (
|
||||
<form class="flex flex-col gap-3">
|
||||
<Combobox
|
||||
size="s"
|
||||
inverted={true}
|
||||
required={true}
|
||||
readOnly={!editing}
|
||||
orientation="horizontal"
|
||||
multiple={true}
|
||||
options={["All", "Home Server", "Backup", "Random"]}
|
||||
defaultValue={["All", "Home Server", "Backup", "Random"]}
|
||||
/>
|
||||
</form>
|
||||
)}
|
||||
</SidebarSection>
|
||||
<SidebarSection
|
||||
title="Advanced Settings"
|
||||
onSave={async () => {
|
||||
console.log("saving general");
|
||||
}}
|
||||
>
|
||||
{(editing) => <></>}
|
||||
</SidebarSection>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
27
pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarPane.tsx
Normal file
27
pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarPane.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { JSX } from "solid-js";
|
||||
import "./SidebarPane.css";
|
||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
||||
import Icon from "../Icon/Icon";
|
||||
import { Button as KButton } from "@kobalte/core/button";
|
||||
|
||||
export interface SidebarPaneProps {
|
||||
title: string;
|
||||
onClose: () => void;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
export const SidebarPane = (props: SidebarPaneProps) => {
|
||||
return (
|
||||
<div class="sidebar-pane">
|
||||
<div class="header">
|
||||
<Typography hierarchy="body" size="s" weight="bold" inverted={true}>
|
||||
{props.title}
|
||||
</Typography>
|
||||
<KButton onClick={props.onClose}>
|
||||
<Icon icon="Close" color="primary" size="0.75rem" inverted={true} />
|
||||
</KButton>
|
||||
</div>
|
||||
<div class="body">{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
div.sidebar-section {
|
||||
@apply flex flex-col gap-2 w-full h-full;
|
||||
|
||||
& > div.header {
|
||||
@apply flex items-center justify-between px-1.5;
|
||||
|
||||
& > div.controls {
|
||||
@apply flex justify-end gap-2;
|
||||
}
|
||||
}
|
||||
|
||||
& > div.content {
|
||||
@apply w-full h-full px-1.5 py-3 rounded-md bg-inv-4;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { createSignal, JSX } from "solid-js";
|
||||
import "./SidebarSection.css";
|
||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
||||
import { Button as KButton } from "@kobalte/core/button";
|
||||
import Icon from "../Icon/Icon";
|
||||
|
||||
export interface SidebarSectionProps {
|
||||
title: string;
|
||||
onSave: () => Promise<void>;
|
||||
children: (editing: boolean) => JSX.Element;
|
||||
}
|
||||
|
||||
export const SidebarSection = (props: SidebarSectionProps) => {
|
||||
const [editing, setEditing] = createSignal(false);
|
||||
|
||||
const save = async () => {
|
||||
// todo how do we surface errors?
|
||||
await props.onSave();
|
||||
setEditing(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="sidebar-section">
|
||||
<div class="header">
|
||||
<Typography
|
||||
hierarchy="label"
|
||||
size="xs"
|
||||
family="mono"
|
||||
weight="light"
|
||||
transform="uppercase"
|
||||
color="tertiary"
|
||||
inverted={true}
|
||||
>
|
||||
{props.title}
|
||||
</Typography>
|
||||
<div class="controls">
|
||||
{editing() && (
|
||||
<KButton>
|
||||
<Icon
|
||||
icon="Checkmark"
|
||||
color="tertiary"
|
||||
size="0.75rem"
|
||||
inverted={true}
|
||||
onClick={save}
|
||||
/>
|
||||
</KButton>
|
||||
)}
|
||||
<KButton onClick={() => setEditing(!editing())}>
|
||||
<Icon
|
||||
icon={editing() ? "Close" : "Edit"}
|
||||
color="tertiary"
|
||||
size="0.75rem"
|
||||
inverted={true}
|
||||
/>
|
||||
</KButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">{props.children(editing())}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,9 @@
|
||||
/* Body */
|
||||
.typography {
|
||||
&.weight-light {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
&.weight-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Color, fgClass } from "@/src/components/v2/colors";
|
||||
|
||||
export type Tag = "span" | "p" | "h1" | "h2" | "h3" | "h4" | "div";
|
||||
export type Hierarchy = "body" | "title" | "headline" | "label" | "teaser";
|
||||
export type Weight = "normal" | "medium" | "bold";
|
||||
export type Weight = "normal" | "medium" | "bold" | "light";
|
||||
export type Family = "regular" | "condensed" | "mono";
|
||||
export type Transform = "uppercase" | "lowercase" | "capitalize";
|
||||
|
||||
@@ -80,9 +80,10 @@ const defaultFamilyMap: Record<Hierarchy, Family> = {
|
||||
};
|
||||
|
||||
const weightMap: Record<Weight, string> = {
|
||||
normal: cx("weight-normal"),
|
||||
medium: cx("weight-medium"),
|
||||
bold: cx("weight-bold"),
|
||||
normal: "weight-normal",
|
||||
medium: "weight-medium",
|
||||
bold: "weight-bold",
|
||||
light: "weight-light",
|
||||
};
|
||||
|
||||
interface _TypographyProps<H extends Hierarchy> {
|
||||
|
||||
@@ -49,6 +49,21 @@ def run_machine_flash(
|
||||
extra_args: list[str] | None = None,
|
||||
graphical: bool = False,
|
||||
) -> None:
|
||||
"""Flash a machine with the given configuration.
|
||||
Args:
|
||||
machine: The Machine instance to flash.
|
||||
mode: The mode to use for flashing (e.g., "install", "reinstall
|
||||
disks: List of Disk instances representing the disks to flash.
|
||||
system_config: SystemConfig instance containing language, keymap, and SSH keys.
|
||||
dry_run: If True, perform a dry run without making changes.
|
||||
write_efi_boot_entries: If True, write EFI boot entries.
|
||||
debug: If True, enable debug mode.
|
||||
extra_args: Additional arguments to pass to the disko-install command.
|
||||
graphical: If True, run the command in graphical mode.
|
||||
Raises:
|
||||
ClanError: If the language or keymap is invalid, or if there are issues with
|
||||
reading SSH keys, or if disko-install fails.
|
||||
"""
|
||||
devices = [Path(disk.device) for disk in disks]
|
||||
with pause_automounting(devices, machine, request_graphical=graphical):
|
||||
if extra_args is None:
|
||||
|
||||
@@ -19,6 +19,12 @@ class FlashOptions(TypedDict):
|
||||
|
||||
@API.register
|
||||
def get_flash_options() -> FlashOptions:
|
||||
"""Retrieve available languages and keymaps for flash configuration.
|
||||
Returns:
|
||||
FlashOptions: A dictionary containing lists of available languages and keymaps.
|
||||
Raises:
|
||||
ClanError: If the locale file or keymaps directory does not exist.
|
||||
"""
|
||||
return {"languages": list_languages(), "keymaps": list_keymaps()}
|
||||
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ def test_clan_core_templates(
|
||||
"default",
|
||||
"flake-parts",
|
||||
"minimal",
|
||||
"minimal-flake-parts",
|
||||
]
|
||||
|
||||
# clan.default
|
||||
@@ -55,18 +54,18 @@ def test_clan_core_templates(
|
||||
template_path = default_template.get("path", None)
|
||||
assert template_path is not None
|
||||
|
||||
new_clan = temporary_home / "new_clan"
|
||||
my_clan = temporary_home / "my_clan"
|
||||
|
||||
copy_from_nixstore(
|
||||
Path(template_path),
|
||||
new_clan,
|
||||
my_clan,
|
||||
)
|
||||
|
||||
flake_nix = new_clan / "flake.nix"
|
||||
flake_nix = my_clan / "flake.nix"
|
||||
assert (flake_nix).exists()
|
||||
assert (flake_nix).is_file()
|
||||
|
||||
assert (new_clan / "machines").is_dir()
|
||||
assert (my_clan / "machines").is_dir()
|
||||
|
||||
# Test if we can write to the flake.nix file
|
||||
with flake_nix.open("r+") as f:
|
||||
|
||||
@@ -429,6 +429,21 @@ def get_generators(
|
||||
full_closure: bool = False,
|
||||
include_previous_values: bool = False,
|
||||
) -> list[Generator]:
|
||||
"""
|
||||
Get the list of generators for a machine, optionally with previous values.
|
||||
If `full_closure` is True, it returns the full closure of generators.
|
||||
If `include_previous_values` is True, it includes the previous values for prompts.
|
||||
|
||||
Args:
|
||||
machine_name (str): The name of the machine.
|
||||
base_dir (Path): The base directory of the flake.
|
||||
full_closure (bool): Whether to return the full closure of generators. If False,
|
||||
it returns only the generators that are missing or need to be regenerated.
|
||||
include_previous_values (bool): Whether to include previous values for prompts.
|
||||
Returns:
|
||||
list[Generator]: A list of generators for the machine.
|
||||
|
||||
"""
|
||||
from clan_lib.machines.machines import Machine
|
||||
|
||||
return get_closure(
|
||||
@@ -468,6 +483,20 @@ def run_generators(
|
||||
base_dir: Path,
|
||||
no_sandbox: bool = False,
|
||||
) -> bool:
|
||||
"""Run the specified generators for a machine.
|
||||
Args:
|
||||
machine_name (str): The name of the machine.
|
||||
generators (list[str]): The list of generator names to run.
|
||||
all_prompt_values (dict[str, dict[str, str]]): A dictionary mapping generator names
|
||||
to their prompt values.
|
||||
base_dir (Path): The base directory of the flake.
|
||||
no_sandbox (bool): Whether to disable sandboxing when executing the generator.
|
||||
Returns:
|
||||
bool: True if any variables were generated, False otherwise.
|
||||
Raises:
|
||||
ClanError: If the machine or generator is not found, or if there are issues with
|
||||
executing the generator.
|
||||
"""
|
||||
from clan_lib.machines.machines import Machine
|
||||
|
||||
machine = Machine(name=machine_name, flake=Flake(str(base_dir)))
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import json
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
from clan_lib.cmd import RunOpts, run
|
||||
from clan_lib.errors import ClanError
|
||||
from clan_lib.flake import Flake
|
||||
from clan_lib.nix import nix_shell
|
||||
|
||||
from . import API
|
||||
@@ -42,55 +38,6 @@ def open_file(file_request: FileRequest) -> list[str] | None:
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
|
||||
@dataclass
|
||||
class File:
|
||||
path: str
|
||||
file_type: Literal["file", "directory", "symlink"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Directory:
|
||||
path: str
|
||||
files: list[File] = field(default_factory=list)
|
||||
|
||||
|
||||
@API.register
|
||||
def get_directory(flake: Flake) -> Directory:
|
||||
curr_dir = flake.path
|
||||
directory = Directory(path=str(curr_dir))
|
||||
|
||||
if not curr_dir.is_dir():
|
||||
msg = f"Path {curr_dir} is not a directory"
|
||||
raise ClanError(msg)
|
||||
|
||||
with os.scandir(curr_dir.resolve()) as it:
|
||||
for entry in it:
|
||||
if entry.is_symlink():
|
||||
directory.files.append(
|
||||
File(
|
||||
path=str(curr_dir / Path(entry.name)),
|
||||
file_type="symlink",
|
||||
)
|
||||
)
|
||||
elif entry.is_file():
|
||||
directory.files.append(
|
||||
File(
|
||||
path=str(curr_dir / Path(entry.name)),
|
||||
file_type="file",
|
||||
)
|
||||
)
|
||||
|
||||
elif entry.is_dir():
|
||||
directory.files.append(
|
||||
File(
|
||||
path=str(curr_dir / Path(entry.name)),
|
||||
file_type="directory",
|
||||
)
|
||||
)
|
||||
|
||||
return directory
|
||||
|
||||
|
||||
@dataclass
|
||||
class BlkInfo:
|
||||
name: str
|
||||
|
||||
@@ -89,6 +89,10 @@ def parse_avahi_output(output: str) -> DNSInfo:
|
||||
|
||||
@API.register
|
||||
def list_mdns_services() -> DNSInfo:
|
||||
"""List mDNS/DNS-SD services on the local network.
|
||||
Returns:
|
||||
DNSInfo: A dictionary containing discovered mDNS/DNS-SD services.
|
||||
"""
|
||||
cmd = nix_shell(
|
||||
["avahi"],
|
||||
[
|
||||
|
||||
@@ -31,6 +31,15 @@ def git_command(directory: Path, *args: str) -> list[str]:
|
||||
|
||||
@API.register
|
||||
def create_clan(opts: CreateOptions) -> None:
|
||||
"""Create a new clan repository with the specified template.
|
||||
Args:
|
||||
opts: CreateOptions containing the destination path, template name,
|
||||
source flake, and other options.
|
||||
Raises:
|
||||
ClanError: If the source flake is not a valid flake or if the destination
|
||||
directory already exists.
|
||||
"""
|
||||
|
||||
dest = opts.dest.resolve()
|
||||
|
||||
if opts.src_flake is not None:
|
||||
@@ -69,6 +78,10 @@ def create_clan(opts: CreateOptions) -> None:
|
||||
if opts.update_clan:
|
||||
run(nix_command(["flake", "update"]), RunOpts(cwd=dest))
|
||||
|
||||
if opts.setup_git:
|
||||
run(git_command(dest, "add", "."))
|
||||
run(git_command(dest, "commit", "-m", "Initial commit"))
|
||||
|
||||
if opts.initial:
|
||||
inventory_store = InventoryStore(flake=Flake(str(opts.dest)))
|
||||
inventory_store.write(opts.initial, message="Init inventory")
|
||||
|
||||
@@ -7,6 +7,14 @@ from clan_lib.persist.inventory_store import InventoryStore
|
||||
|
||||
@API.register
|
||||
def get_clan_details(flake: Flake) -> InventoryMeta:
|
||||
"""Retrieve the clan details from the inventory of a given flake.
|
||||
Args:
|
||||
flake: The Flake instance representing the clan.
|
||||
Returns:
|
||||
InventoryMeta: The meta information from the clan's inventory.
|
||||
Raises:
|
||||
ClanError: If the flake does not exist, or if the inventory is invalid (missing the meta attribute).
|
||||
"""
|
||||
if flake.is_local and not flake.path.exists():
|
||||
msg = f"Path {flake} does not exist"
|
||||
raise ClanError(msg, description="clan directory does not exist")
|
||||
|
||||
@@ -15,6 +15,14 @@ class UpdateOptions:
|
||||
|
||||
@API.register
|
||||
def set_clan_details(options: UpdateOptions) -> InventorySnapshot:
|
||||
"""Update the clan metadata in the inventory of a given flake.
|
||||
Args:
|
||||
options: UpdateOptions containing the flake and the new metadata.
|
||||
Returns:
|
||||
InventorySnapshot: The updated inventory snapshot after modifying the metadata.
|
||||
Raises:
|
||||
ClanError: If the flake does not exist or if the inventory is invalid (missing the meta attribute).
|
||||
"""
|
||||
inventory_store = InventoryStore(options.flake)
|
||||
inventory = inventory_store.read()
|
||||
set_value_by_path(inventory, "meta", options.meta)
|
||||
|
||||
@@ -54,6 +54,19 @@ def list_machines(
|
||||
|
||||
@API.register
|
||||
def get_machine(flake: Flake, name: str) -> InventoryMachine:
|
||||
"""
|
||||
Retrieve a machine's inventory details by name from the given flake.
|
||||
|
||||
Args:
|
||||
flake (Flake): The flake object representing the configuration source.
|
||||
name (str): The name of the machine to retrieve from the inventory.
|
||||
|
||||
Returns:
|
||||
InventoryMachine: An instance representing the machine's inventory details.
|
||||
|
||||
Raises:
|
||||
ClanError: If the machine with the specified name is not found in the inventory.
|
||||
"""
|
||||
inventory_store = InventoryStore(flake=flake)
|
||||
inventory = inventory_store.read()
|
||||
|
||||
|
||||
@@ -19,6 +19,13 @@ log = logging.getLogger(__name__)
|
||||
|
||||
@API.register
|
||||
def delete_machine(machine: Machine) -> None:
|
||||
"""Delete a machine from the clan's inventory and remove its associated files.
|
||||
Args:
|
||||
machine: The Machine instance to be deleted.
|
||||
Raises:
|
||||
ClanError: If the machine does not exist in the inventory or if there are issues with
|
||||
removing its files.
|
||||
"""
|
||||
inventory_store = InventoryStore(machine.flake)
|
||||
try:
|
||||
inventory_store.delete(
|
||||
|
||||
@@ -40,6 +40,16 @@ class InstallOptions:
|
||||
|
||||
@API.register
|
||||
def run_machine_install(opts: InstallOptions, target_host: Remote) -> None:
|
||||
"""Install a machine using nixos-anywhere.
|
||||
Args:
|
||||
opts: InstallOptions containing the machine to install, kexec option, debug mode,
|
||||
no-reboot option, phases, build-on option, hardware config update, password,
|
||||
identity file, and use_tor flag.
|
||||
target_host: Remote object representing the target host for installation.
|
||||
Raises:
|
||||
ClanError: If the machine is not found in the inventory or if there are issues with
|
||||
generating facts or variables.
|
||||
"""
|
||||
machine = opts.machine
|
||||
|
||||
machine.debug(f"installing {machine.name}")
|
||||
|
||||
@@ -52,8 +52,22 @@ def extract_header(c: str) -> str:
|
||||
return "\n".join(header_lines)
|
||||
|
||||
|
||||
# TODO: Remove this function
|
||||
# Split out the disko schema extraction into a separate function
|
||||
# get machine returns the machine already
|
||||
@API.register
|
||||
def get_machine_details(machine: Machine) -> MachineDetails:
|
||||
"""Retrieve detailed information about a machine, including its inventory,
|
||||
hardware configuration, and disk schema if available.
|
||||
Args:
|
||||
machine (Machine): The machine instance for which details are to be retrieved.
|
||||
Returns:
|
||||
MachineDetails: An instance containing the machine's inventory, hardware configuration,
|
||||
and disk schema.
|
||||
Raises:
|
||||
ClanError: If the machine's inventory cannot be found or if there are issues with the
|
||||
hardware configuration or disk schema extraction.
|
||||
"""
|
||||
machine_inv = get_machine(machine.flake, machine.name)
|
||||
hw_config = HardwareConfig.detect_type(machine)
|
||||
|
||||
|
||||
@@ -106,6 +106,16 @@ def upload_sources(machine: Machine, ssh: Remote) -> str:
|
||||
def run_machine_deploy(
|
||||
machine: Machine, target_host: Remote, build_host: Remote | None
|
||||
) -> None:
|
||||
"""Update an existing machine using nixos-rebuild or darwin-rebuild.
|
||||
Args:
|
||||
machine: The Machine instance to deploy.
|
||||
target_host: Remote object representing the target host for deployment.
|
||||
build_host: Optional Remote object representing the build host.
|
||||
Raises:
|
||||
ClanError: If the machine is not found in the inventory or if there are issues with
|
||||
generating facts or variables.
|
||||
"""
|
||||
|
||||
with ExitStack() as stack:
|
||||
target_host = stack.enter_context(target_host.ssh_control_master())
|
||||
|
||||
|
||||
@@ -444,6 +444,21 @@ class CheckResult:
|
||||
def check_machine_ssh_login(
|
||||
remote: Remote, opts: ConnectionOptions | None = None
|
||||
) -> CheckResult:
|
||||
"""Checks if a remote machine is reachable via SSH by attempting to run a simple command.
|
||||
Args:
|
||||
remote (Remote): The remote host to check for SSH login.
|
||||
opts (ConnectionOptions, optional): Connection options such as timeout and number of retries.
|
||||
If not provided, default values are used.
|
||||
Returns:
|
||||
CheckResult: An object indicating whether the SSH login is successful (`ok=True`) or not (`ok=False`),
|
||||
and a reason if the check failed.
|
||||
Usage:
|
||||
result = check_machine_ssh_login(remote)
|
||||
if result.ok:
|
||||
print("SSH login successful")
|
||||
else:
|
||||
print(f"SSH login failed: {result.reason}")
|
||||
"""
|
||||
if opts is None:
|
||||
opts = ConnectionOptions()
|
||||
|
||||
@@ -470,6 +485,22 @@ def check_machine_ssh_login(
|
||||
def check_machine_ssh_reachable(
|
||||
remote: Remote, opts: ConnectionOptions | None = None
|
||||
) -> CheckResult:
|
||||
"""
|
||||
Checks if a remote machine is reachable via SSH by attempting to open a TCP connection
|
||||
to the specified address and port.
|
||||
Args:
|
||||
remote (Remote): The remote host to check for SSH reachability.
|
||||
opts (ConnectionOptions, optional): Connection options such as timeout and number of retries.
|
||||
If not provided, default values are used.
|
||||
Returns:
|
||||
CheckResult: An object indicating whether the SSH port is reachable (`ok=True`) or not (`ok=False`),
|
||||
and a reason if the check failed.
|
||||
Usage:
|
||||
result = check_machine_ssh_reachable(remote)
|
||||
if result.ok:
|
||||
print("SSH port is reachable")
|
||||
print(f"SSH port is not reachable: {result.reason}")
|
||||
"""
|
||||
if opts is None:
|
||||
opts = ConnectionOptions()
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ def test_clan_create_api(
|
||||
assert public_key.exists()
|
||||
assert public_key.is_file()
|
||||
|
||||
dest_clan_dir = Path("~/new-clan").expanduser()
|
||||
dest_clan_dir = Path("~/default").expanduser()
|
||||
|
||||
# ===== CREATE CLAN ======
|
||||
# TODO: We need to generate a lock file for the templates
|
||||
|
||||
@@ -150,7 +150,7 @@ def main() -> None:
|
||||
errors.extend(check_res)
|
||||
|
||||
if not func_schema.get("description"):
|
||||
warnings.append(
|
||||
errors.append(
|
||||
f"{func_name} doesn't have a description. Python docstring is required for an API function."
|
||||
)
|
||||
|
||||
|
||||
2
templates/clan/default/.gitignore
vendored
Normal file
2
templates/clan/default/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
result
|
||||
.direnv/
|
||||
2
templates/clan/flake-parts/.gitignore
vendored
Normal file
2
templates/clan/flake-parts/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
result
|
||||
.direnv/
|
||||
@@ -1,2 +0,0 @@
|
||||
result
|
||||
.direnv/
|
||||
@@ -1,22 +0,0 @@
|
||||
{ self, ... }:
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
self',
|
||||
lib,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
{
|
||||
checks =
|
||||
let
|
||||
nixosMachines = lib.mapAttrs' (
|
||||
name: config: lib.nameValuePair "nixos-${name}" config.config.system.build.toplevel
|
||||
) ((lib.filterAttrs (_: config: config.pkgs.system == system)) self.nixosConfigurations);
|
||||
|
||||
packages = lib.mapAttrs' (n: lib.nameValuePair "package-${n}") self'.packages;
|
||||
devShells = lib.mapAttrs' (n: lib.nameValuePair "devShell-${n}") self'.devShells;
|
||||
in
|
||||
nixosMachines // packages // devShells;
|
||||
};
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{ self, inputs, ... }:
|
||||
{
|
||||
imports = [
|
||||
inputs.clan.flakeModules.default
|
||||
];
|
||||
clan = {
|
||||
meta.name = "__CHANGE_ME__";
|
||||
inherit self;
|
||||
specialArgs = {
|
||||
inherit inputs;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
_: {
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
inputs',
|
||||
...
|
||||
}:
|
||||
{
|
||||
devShells = {
|
||||
default = pkgs.mkShellNoCC {
|
||||
packages = [
|
||||
inputs'.clan.packages.default
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
|
||||
inputs = {
|
||||
clan.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz";
|
||||
nixpkgs.follows = "clan/nixpkgs";
|
||||
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
flake-parts.inputs.nixpkgs-lib.follows = "clan/nixpkgs";
|
||||
};
|
||||
|
||||
outputs =
|
||||
inputs@{ flake-parts, ... }:
|
||||
flake-parts.lib.mkFlake { inherit inputs; } (
|
||||
{ ... }:
|
||||
{
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
|
||||
imports = [
|
||||
./checks.nix
|
||||
./clan.nix
|
||||
./devshells.nix
|
||||
./formatter.nix
|
||||
];
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
_: {
|
||||
perSystem =
|
||||
{
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
formatter = pkgs.nixfmt;
|
||||
};
|
||||
}
|
||||
2
templates/clan/minimal/.gitignore
vendored
Normal file
2
templates/clan/minimal/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
result
|
||||
.direnv/
|
||||
@@ -25,7 +25,7 @@
|
||||
clan = {
|
||||
default = {
|
||||
description = "Initialize a new clan flake";
|
||||
path = ./clan/new-clan;
|
||||
path = ./clan/default;
|
||||
};
|
||||
minimal = {
|
||||
description = "for clans managed via (G)UI";
|
||||
@@ -35,10 +35,6 @@
|
||||
description = "Flake-parts";
|
||||
path = ./clan/flake-parts;
|
||||
};
|
||||
minimal-flake-parts = {
|
||||
description = "Minimal flake-parts clan template";
|
||||
path = ./clan/minimal-flake-parts;
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
|
||||
Reference in New Issue
Block a user