This commit is contained in:
Eugene 2024-12-18 00:07:46 +01:00 committed by GitHub
parent efcb2205ff
commit 409b382e8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 1180 additions and 728 deletions

76
.github/readme/brand-dark.svg vendored Normal file
View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="60mm"
height="8mm"
viewBox="0 0 60 8"
version="1.1"
id="svg5"
sodipodi:docname="brand-dark.svg"
inkscape:version="1.1 (c4e8f9e, 2021-05-24)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview15"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="2.0998592"
inkscape:cx="98.578038"
inkscape:cy="10.476893"
inkscape:window-width="1440"
inkscape:window-height="772"
inkscape:window-x="0"
inkscape:window-y="204"
inkscape:window-maximized="0"
inkscape:current-layer="svg5" />
<defs
id="defs2">
<rect
x="180.24561"
y="396.18341"
width="458.64478"
height="221.29164"
id="rect3774" />
</defs>
<path
d="m 11.538149,0.26599 -3.0395654,7.4295 h -2.19075 L 5.8482877,2.80599 3.8948336,7.69549 h -2.19075 L 0.92306998,0.26599 H 2.85982 L 3.1259729,5.67407 5.251653,0.26599 H 7.24132 L 7.7085559,5.67407 9.5908202,0.26599 Z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583;fill:#d2f0fd;fill-opacity:1"
id="path11089" />
<path
d="M 14.900136,6.38316 H 12.127303 L 11.591175,7.69549 H 9.696758 l 3.206895,-7.4295 h 2.0955 l 2.169438,7.4295 h -1.915583 z m -0.368131,-1.397 -0.730842,-2.71992 -1.100074,2.71992 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583;fill:#d2f0fd;fill-opacity:1"
id="path11091" />
<path
d="M 21.621427,7.69549 20.076261,4.89091 h -0.433917 l 4e-6,2.80458 h -1.80975 v -7.4295 h 3.037417 q 0.878416,0 1.49225,0.30692 0.624416,0.30691 0.931333,0.84666 0.306917,0.52917 0.306917,1.18534 10e-7,0.74083 -0.423334,1.32291 -0.41275,0.58209 -1.227666,0.8255 l 1.714496,2.94217 z M 19.642344,3.61032 h 1.121833 q 0.497417,0 0.740834,-0.24341 0.254,-0.24342 0.254,-0.68792 1.1e-5,-0.42333 -0.254,-0.66675 -0.243417,-0.24342 -0.740834,-0.24342 h -1.121833 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583;fill:#d2f0fd;fill-opacity:1"
id="path11093" />
<path
d="m 29.90879,2.65782 q 0,0.64559 -0.296333,1.18534 -0.296333,0.52916 -0.910167,0.85725 -0.613833,0.32808 -1.524,0.32808 h -1.121833 v 2.667 h -1.80975 v -7.4295 h 2.931583 q 0.889,0 1.502834,0.30692 0.613833,0.30691 0.92075,0.84666 0.306916,0.53975 0.306916,1.23825 z m -2.868083,0.93134 q 0.518583,0 0.772583,-0.24342 0.254,-0.24342 0.254,-0.68792 0,-0.4445 -0.254,-0.68791 -0.254,-0.24342 -0.772583,-0.24342 h -0.98425 v 1.86267 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583;fill:#d2f0fd;fill-opacity:1"
id="path11095" />
<path
d="M 35.563571,2.61549 Q 35.362488,2.24507 34.981488,2.05457 34.611071,1.85349 34.103071,1.85349 q -0.878416,0 -1.407583,0.58208 -0.529166,0.5715 -0.529166,1.53459 0,1.02658 0.550333,1.60866 0.560916,0.5715 1.534583,0.5715 0.66675,0 1.121833,-0.33866 0.465667,-0.33867 0.677334,-0.97367 h -2.296584 v -1.3335 h 3.937 v 1.68275 q -0.201083,0.67733 -0.687916,1.25942 -0.47625,0.58208 -1.217084,0.94191 -0.740833,0.35984 -1.672166,0.35984 -1.100667,0 -1.9685,-0.47625 -0.85725,-0.48684 -1.344083,-1.34409 -0.47625,-0.85725 -0.47625,-1.95791 0,-1.10067 0.47625,-1.95792 0.486833,-0.86783 1.344083,-1.34408 0.85725,-0.48684 1.957916,-0.48684 1.3335,0 2.243667,0.64559 0.92075,0.64558 1.217083,1.78858 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583;fill:#d2f0fd;fill-opacity:1"
id="path11097" />
<path
d="m 42.606476,6.38316 h -2.772833 l -0.467407,1.31233 h -1.894417 l 2.817849,-7.4295 h 2.0955 l 2.558484,7.4295 H 43.028069 Z M 42.165193,4.98616 41.29192,2.26624 40.334277,4.98616 Z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583;fill:#d2f0fd;fill-opacity:1"
id="path11099" />
<path
d="m 50.129387,0.26599 -2e-6,1.44992 h -1.968499 l 0,5.97958 h -1.809752 l 0,-5.97958 h -1.9685 l 2e-6,-1.44992 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583;fill:#d2f0fd;fill-opacity:1"
id="path11101" />
<path
d="m 52.634481,1.71591 2e-6,1.50283 h 2.42358 l 1.5e-5,1.397 h -2.42358 l -10e-7,1.62983 h 2.74108 l -2e-6,1.44992 h -4.55083 l 0,-7.4295 h 4.55083 l -2e-6,1.44992 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583;fill:#d2f0fd;fill-opacity:1"
id="path11103" />
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

View file

@ -1,11 +1,17 @@
<br/>
<p align="center">
<img src="warpgate-web/public/assets/logo.svg" width="100" />
<picture>
<source media="(prefers-color-scheme: dark)" srcset=".github/readme/brand-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="warpgate-web/public/assets/brand.svg">
<img alt="Shows a black logo in light color mode and a white one in dark color mode." src=".github/readme/brand-dark.svg">
</picture>
</p>
<br/>
<p align="center">
<a href="https://github.com/warp-tech/warpgate/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/warp-tech/warpgate/total.svg?label=DOWNLOADS&logo=github&style=for-the-badge"></a> &nbsp; <a href="https://nightly.link/warp-tech/warpgate/workflows/build/main"><img src="https://shields.io/badge/-Nightly%20Builds-orange?logo=hackthebox&logoColor=fff&style=for-the-badge"/></a> &nbsp; <a href="https://discord.gg/Vn7BjmzhtF"><img alt="Discord" src="https://img.shields.io/discord/1280890060195233934?style=for-the-badge&color=blue&logo=discord&logoColor=white&label=Discord"></a>
<a href="https://github.com/warp-tech/warpgate/releases/latest"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/warp-tech/warpgate/total.svg?label=DOWNLOADS&logo=github&style=for-the-badge&color=8f8"></a> &nbsp; <a href="https://nightly.link/warp-tech/warpgate/workflows/build/main"><img src="https://shields.io/badge/-Nightly%20Builds-fa5?logo=hackthebox&logoColor=444&style=for-the-badge"/></a> &nbsp; <a href="https://discord.gg/Vn7BjmzhtF"><img alt="Discord" src="https://img.shields.io/discord/1280890060195233934?style=for-the-badge&color=acc&logo=discord&logoColor=white&label=Discord"></a>
</p>

View file

@ -21,7 +21,7 @@ pub fn error_page(e: poem::Error) -> impl IntoResponse {
}}
</style>
<main>
<img src="/@warpgate/assets/logo.svg" />
<img src="/@warpgate/assets/brand.svg" />
<h1>Request failed</h1>
<p>{e}</p>
</main>

View file

@ -18,6 +18,7 @@
"openapi": "yarn run openapi:schema:admin && yarn run openapi:schema:gateway && yarn run openapi:client:admin && yarn run openapi:client:gateway"
},
"devDependencies": {
"@fontsource/poppins": "^5.1.0",
"@fontsource/work-sans": "^4.5.12",
"@fortawesome/free-brands-svg-icons": "^6.7.1",
"@fortawesome/free-regular-svg-icons": "^6.7.1",
@ -46,7 +47,7 @@
"format-duration": "^3.0.2",
"otpauth": "^9.3.5",
"qrcode": "^1.5.4",
"sass": "~1.82",
"sass": "1.78",
"svelte": "^5.11.0",
"svelte-check": "^4.1.1",
"svelte-fa": "^4.0.3",

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="60mm"
height="8mm"
viewBox="0 0 60 8"
version="1.1"
id="svg5"
sodipodi:docname="brand.svg"
inkscape:version="1.1 (c4e8f9e, 2021-05-24)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview15"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="2.0998592"
inkscape:cx="98.578038"
inkscape:cy="10.476893"
inkscape:window-width="1440"
inkscape:window-height="772"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
inkscape:current-layer="svg5" />
<defs
id="defs2">
<rect
x="180.24561"
y="396.18341"
width="458.64478"
height="221.29164"
id="rect3774" />
</defs>
<path
d="m 11.538149,0.26599 -3.0395654,7.4295 h -2.19075 L 5.8482877,2.80599 3.8948336,7.69549 h -2.19075 L 0.92306998,0.26599 H 2.85982 L 3.1259729,5.67407 5.251653,0.26599 H 7.24132 L 7.7085559,5.67407 9.5908202,0.26599 Z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583"
id="path11089" />
<path
d="M 14.900136,6.38316 H 12.127303 L 11.591175,7.69549 H 9.696758 l 3.206895,-7.4295 h 2.0955 l 2.169438,7.4295 h -1.915583 z m -0.368131,-1.397 -0.730842,-2.71992 -1.100074,2.71992 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583"
id="path11091" />
<path
d="M 21.621427,7.69549 20.076261,4.89091 h -0.433917 l 4e-6,2.80458 h -1.80975 v -7.4295 h 3.037417 q 0.878416,0 1.49225,0.30692 0.624416,0.30691 0.931333,0.84666 0.306917,0.52917 0.306917,1.18534 10e-7,0.74083 -0.423334,1.32291 -0.41275,0.58209 -1.227666,0.8255 l 1.714496,2.94217 z M 19.642344,3.61032 h 1.121833 q 0.497417,0 0.740834,-0.24341 0.254,-0.24342 0.254,-0.68792 1.1e-5,-0.42333 -0.254,-0.66675 -0.243417,-0.24342 -0.740834,-0.24342 h -1.121833 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583"
id="path11093" />
<path
d="m 29.90879,2.65782 q 0,0.64559 -0.296333,1.18534 -0.296333,0.52916 -0.910167,0.85725 -0.613833,0.32808 -1.524,0.32808 h -1.121833 v 2.667 h -1.80975 v -7.4295 h 2.931583 q 0.889,0 1.502834,0.30692 0.613833,0.30691 0.92075,0.84666 0.306916,0.53975 0.306916,1.23825 z m -2.868083,0.93134 q 0.518583,0 0.772583,-0.24342 0.254,-0.24342 0.254,-0.68792 0,-0.4445 -0.254,-0.68791 -0.254,-0.24342 -0.772583,-0.24342 h -0.98425 v 1.86267 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583"
id="path11095" />
<path
d="M 35.563571,2.61549 Q 35.362488,2.24507 34.981488,2.05457 34.611071,1.85349 34.103071,1.85349 q -0.878416,0 -1.407583,0.58208 -0.529166,0.5715 -0.529166,1.53459 0,1.02658 0.550333,1.60866 0.560916,0.5715 1.534583,0.5715 0.66675,0 1.121833,-0.33866 0.465667,-0.33867 0.677334,-0.97367 h -2.296584 v -1.3335 h 3.937 v 1.68275 q -0.201083,0.67733 -0.687916,1.25942 -0.47625,0.58208 -1.217084,0.94191 -0.740833,0.35984 -1.672166,0.35984 -1.100667,0 -1.9685,-0.47625 -0.85725,-0.48684 -1.344083,-1.34409 -0.47625,-0.85725 -0.47625,-1.95791 0,-1.10067 0.47625,-1.95792 0.486833,-0.86783 1.344083,-1.34408 0.85725,-0.48684 1.957916,-0.48684 1.3335,0 2.243667,0.64559 0.92075,0.64558 1.217083,1.78858 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583"
id="path11097" />
<path
d="m 42.606476,6.38316 h -2.772833 l -0.467407,1.31233 h -1.894417 l 2.817849,-7.4295 h 2.0955 l 2.558484,7.4295 H 43.028069 Z M 42.165193,4.98616 41.29192,2.26624 40.334277,4.98616 Z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583"
id="path11099" />
<path
d="m 50.129387,0.26599 -2e-6,1.44992 h -1.968499 l 0,5.97958 h -1.809752 l 0,-5.97958 h -1.9685 l 2e-6,-1.44992 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583"
id="path11101" />
<path
d="m 52.634481,1.71591 2e-6,1.50283 h 2.42358 l 1.5e-5,1.397 h -2.42358 l -10e-7,1.62983 h 2.74108 l -2e-6,1.44992 h -4.55083 l 0,-7.4295 h 4.55083 l -2e-6,1.44992 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;stroke-width:0.264583"
id="path11103" />
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="24mm"
height="24mm"
viewBox="0 0 24 24"
version="1.1"
id="svg914"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs911" />
<g
id="layer1">
<path
style="fill:#14141c;fill-opacity:1;fill-rule:evenodd;stroke:#311a1a;stroke-width:1.41432;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0;paint-order:markers fill stroke"
id="rect1026"
width="22.627413"
height="22.529339"
x="0.71679527"
y="0.74948686"
d="M 4.9501286,0.74948686 H 19.110875 A 4.2333333,4.2333333 45 0 1 23.344208,4.9828202 V 19.045492 a 4.2333333,4.2333333 135 0 1 -4.233333,4.233334 l -14.1607464,0 A 4.2333333,4.2333333 45 0 1 0.71679527,19.045492 V 4.9828202 A 4.2333333,4.2333333 135 0 1 4.9501286,0.74948686 Z" />
<g
id="g2449"
transform="matrix(1.1046419,0,0,1.1046419,-1.0038918,1.1276169)">
<path
d="M 14.272807,6.1286052 11.233242,13.558105 H 9.0424929 L 8.5829469,8.6686052 6.6294929,13.558105 h -2.19075 L 3.6577289,6.1286052 h 1.93675 l 0.266153,5.4080798 2.12568,-5.4080798 h 1.9896666 l 0.4672355,5.4080798 1.882264,-5.4080798 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;fill:#bfcce7;fill-opacity:1;stroke-width:0.264583"
id="path11089" />
<path
d="m 17.634794,12.245775 h -2.772833 l -0.536128,1.31233 h -1.894417 l 3.206895,-7.4294998 h 2.0955 l 2.169438,7.4294998 h -1.915583 z m -0.368131,-1.397 -0.730842,-2.7199198 -1.100074,2.7199198 z"
style="font-weight:bold;font-size:40px;line-height:1.25;font-family:Poppins;-inkscape-font-specification:'Poppins Bold';white-space:pre;fill:#bfcce7;fill-opacity:1;stroke-width:0.264583"
id="path11091" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -1,62 +0,0 @@
<svg viewBox="0 0 80 47"
id="warpgate-logo"
xmlns="http://www.w3.org/2000/svg">
<style>
#warpgate-logo path {
fill: rgb(64, 64, 236);
}
@media ( prefers-color-scheme: dark ) {
#warpgate-logo path {
fill: rgb(11, 207, 215);
}
}
</style>
<path d="m9.2 4.7c-1 .3-2 .8-2.9 1.4l-1.1 1.2c-.6.8-1.1 1.7-1.3 2.7z"/>
<path d="m4 13.3 8.3-8.7c-.4-.1-.8-.1-1.2-.1l-7.3 7.6c0 .4.1.8.2 1.2z"/>
<path d="m13.4 5-8.9 9.8c.1.1.1.3.2.4 0 0 .1.2.3.5l9.2-10.2c-.2-.2-.5-.3-.8-.5z"/>
<path d="m16 6.9c-.2-.3-.5-.5-.7-.7l-9.4 10.8.6.9z"/>
<path d="m17.2 8.7c-.3-.5-.5-.9-.6-.9l-9.3 11.4c.2.3.4.6.6.9z"/>
<path d="m18.4 10.6c-.2-.3-.4-.6-.6-.9l-9.1 11.7c.2.3.4.6.6.9z"/>
<path d="m19.7 12.5c-.2-.3-.4-.6-.6-.9l-8.9 12.1c.2.3.4.6.6.9z"/>
<path d="m20.9 14.5c-.2-.3-.4-.6-.6-.9l-8.7 12.4.6.9z"/>
<path d="m22.2 16.5c-.2-.3-.4-.6-.6-.9l-8.5 12.7c.2.3.4.6.6.9z"/>
<path d="m23.5 18.6c-.2-.3-.4-.6-.6-.9l-8.3 13.1.6.9z"/>
<path d="m24.8 20.7c-.2-.3-.4-.6-.6-.9l-8.1 13.5c.2.3.4.6.6.9z"/>
<path d="m35.5 6.5c-.4.4-.8.8-1.1 1.3l-8.1 12.8-8.6 15.2c.2.3.4.7.6 1z"/>
<path d="m37.1 5.3-17.7 33c.1.2.2.4.2.4.1.2.3.4.4.6l18.5-34.5c-.5.1-1 .3-1.4.5z"/>
<path d="m40.6 4.5c-.1 0-.2 0-.2 0-.3 0-.6 0-.9.1l-18.1 36c.3.2.6.4.8.5z"/>
<path d="m41.4 4.6-17.6 37.1c.3.1.7.1 1 .2l17.6-37.1c-.4-.1-.7-.2-1-.2z"/>
<path d="m43.9 5.5c-.3-.2-.6-.3-.9-.5l-16.3 36.9c.4-.1.8-.2 1.2-.3z"/>
<path d="m33.2 36.1 12.1-29.5c-.2-.3-.5-.5-.8-.7l-14.1 34.2c.4-.4.8-.8 1.1-1.3z"/>
<path d="m40.1 25.1 6.5-16.8-.3-.5c-.1-.2-.3-.4-.4-.6l-8.6 22.2z"/>
<path d="m47.8 10.1-.7-1.1-6 16.7c.2.3.4.7.7 1.1z"/>
<path d="m49 12.1-.7-1.1-5.7 17.2c.2.4.5.7.7 1.1z"/>
<path d="m50.3 14.1-.7-1.1-5.4 17.7c.2.4.5.7.7 1.1z"/>
<path d="m51.6 16.2-.7-1.1-5 18.2c.2.4.5.8.7 1.1z"/>
<path d="m53 18.5-.7-1.2-4.7 18.8c.3.4.5.8.7 1.2z"/>
<path d="m54.5 20.8-.8-1.2-4.3 19.4c.2.3.5.7.8 1z"/>
<path d="m56.9 19.2-1.5 2.4-3.8 19.4c.3.2.6.3.9.5z"/>
<path d="m59.6 14.9-1.4 2.2-4.3 24.8c.3.1.7.1 1 .1z"/>
<path d="m62 11.1-1.3 2.1-4.3 28.7c.4-.1.7-.2 1.1-.3z"/>
<path d="m64.1 7.8-1.2 1.8-3.8 31.2c.4-.3.8-.6 1.1-.9l4-32.2c-.1 0-.1 0-.1.1z"/>
<path d="m66 5.8c-.4.3-.8.6-1.1 1l-3 30.7c.3-.5.7-1.1 1.2-1.9z"/>
<path d="m67.7 4.9c-.4.1-.7.3-1 .5l-2.1 27.9c.4-.6.7-1.2 1.1-1.8z"/>
<path d="m69.2 4.5c-.3 0-.7.1-1 .2l-1.2 24.6c.4-.6.7-1.1 1.1-1.7z"/>
<path d="m70.8 4.6c-.3 0-.7-.1-1-.1l-.5 21.3c.4-.6.7-1.1 1-1.6z"/>
<path d="m72.4 5c-.3-.1-.7-.2-1-.3v17.8c.3-.5.7-1.1 1-1.6z"/>
<path d="m73.9 5.8c-.3-.2-.7-.4-1-.6l.3 14.2c.3-.6.7-1.1 1-1.5z"/>
<path d="m75.6 7.3c-.3-.4-.7-.8-1.1-1.1l.5 10.4c.5-.9.9-1.4.9-1.4v-.1z"/>
<path d="m76.7 13.6c.6-1.9.4-3.7-.4-5.1z"/>
<path d="m3.5 11.8c.1 1.1.4 2.3 1.1 3.4l14.8 23.5c.3.4.6.8.9 1.2z"/>
<path d="m3.7 9.8c-.1.5-.2 1-.2 1.6l19 30c.5.2 1 .4 1.5.5z"/>
<path d="m4.2 8.5c-.2.4-.4.7-.5 1.1l21.6 32.4c.4 0 .8 0 1.1-.1z"/>
<path d="m4.8 7.5c-.2.2-.4.5-.6.9l23.4 33.2c.3-.1.6-.3.9-.4z"/>
<path d="m5.6 6.6c-.3.2-.5.5-.7.7l24.6 33.2c.3-.2.5-.4.7-.7z"/>
<path d="m6.4 5.9c-.2.2-.5.4-.7.6l25.3 32.5c.2-.3.4-.6.5-.9z"/>
<path d="m7.4 5.3c-.3.1-.6.3-.9.5l25.5 31.1c.1-.4.2-.8.3-1.3z"/>
<path d="m7.5 5.2 24.8 28.7c-.1-.9-.5-1.8-1-2.7 0 0 0-.1-.1-.2l-22.7-26.2c-.4.1-.7.3-1 .4z"/>
<path d="m9.7 4.5c-.3.1-.7.2-1.1.3l19.5 21.5c-.9-1.5-2-3.2-3.1-4.9z"/>
<path d="m11.3 4.5c-.5 0-.9 0-1.4 0l13.2 13.8c-.9-1.5-1.9-3-2.7-4.3z"/>
<path d="m11.6 4.6 7.6 7.6c-1.1-1.7-2-3.1-2.4-3.8l-3.1-3.1c-.7-.4-1.4-.6-2.1-.7z"/>
<path d="m14.7 5.9 1 .9c-.3-.3-.6-.6-1-.9z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -5,9 +5,9 @@ import Router, { link } from 'svelte-spa-router'
import active from 'svelte-spa-router/active'
import { wrap } from 'svelte-spa-router/wrap'
import ThemeSwitcher from 'common/ThemeSwitcher.svelte'
import Logo from 'common/Logo.svelte'
import DelayedSpinner from 'common/DelayedSpinner.svelte'
import AuthBar from 'common/AuthBar.svelte'
import Brand from 'common/Brand.svelte'
async function init () {
await reloadServerInfo()
@ -25,15 +25,6 @@ const routes = {
'/recordings/:id': wrap({
asyncComponent: () => import('./Recording.svelte') as any,
}),
'/tickets': wrap({
asyncComponent: () => import('./Tickets.svelte') as any,
}),
'/tickets/create': wrap({
asyncComponent: () => import('./CreateTicket.svelte') as any,
}),
'/config': wrap({
asyncComponent: () => import('./Config.svelte') as any,
}),
'/targets/create': wrap({
asyncComponent: () => import('./CreateTarget.svelte') as any,
}),
@ -52,14 +43,32 @@ const routes = {
'/users/:id': wrap({
asyncComponent: () => import('./User.svelte') as any,
}),
'/ssh': wrap({
asyncComponent: () => import('./SSH.svelte') as any,
}),
'/log': wrap({
asyncComponent: () => import('./Log.svelte') as any,
}),
'/parameters': wrap({
asyncComponent: () => import('./Parameters.svelte') as any,
'/config': wrap({
asyncComponent: () => import('./config/Config.svelte') as any,
}),
'/config/parameters': wrap({
asyncComponent: () => import('./config/Parameters.svelte') as any,
}),
'/config/users': wrap({
asyncComponent: () => import('./config/Users.svelte') as any,
}),
'/config/roles': wrap({
asyncComponent: () => import('./config/Roles.svelte') as any,
}),
'/config/targets': wrap({
asyncComponent: () => import('./config/Targets.svelte') as any,
}),
'/config/ssh': wrap({
asyncComponent: () => import('./config/SSHKeys.svelte') as any,
}),
'/config/tickets': wrap({
asyncComponent: () => import('./config/Tickets.svelte') as any,
}),
'/config/tickets/create': wrap({
asyncComponent: () => import('./CreateTicket.svelte') as any,
}),
}
</script>
@ -69,18 +78,15 @@ const routes = {
{:then}
<div class="app container">
<header>
<a href="/@warpgate" class="d-flex">
<div class="logo">
<Logo />
</div>
<a href="/@warpgate" class="d-flex logo-link me-4">
<Brand />
</a>
{#if $serverInfo?.username}
<a use:link use:active href="/">Sessions</a>
<a use:link use:active href="/config">Config</a>
<a use:link use:active href="/tickets">Tickets</a>
<a use:link use:active href="/ssh">SSH</a>
<a use:link use:active href="/log">Log</a>
{/if}
<span class="ms-3"></span>
<AuthBar />
</header>
<main>
@ -88,7 +94,7 @@ const routes = {
</main>
<footer class="mt-5">
<span class="me-auto">
<span class="me-auto ms-3">
v{$serverInfo?.version}
</span>
<ThemeSwitcher />
@ -97,18 +103,18 @@ const routes = {
{/await}
<style lang="scss">
@media (max-width: 767px) {
.logo-link {
display: none !important;
}
}
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.logo {
width: 40px;
padding-top: 2px;
display: flex;
}
header, footer {
flex: none;
}
@ -120,15 +126,12 @@ const routes = {
header {
display: flex;
align-items: center;
padding: 10px 0;
padding: 7px 0;
margin: 10px 0 20px;
a, .logo {
a {
font-size: 1.5rem;
}
a:not(:first-child) {
margin-left: 15px;
margin-right: 15px;
}
}
</style>

View file

@ -1,147 +0,0 @@
<script lang="ts">
import { Observable, from, map } from 'rxjs'
import { type Role, type Target, type User, api } from 'admin/lib/api'
import ItemList, { type LoadOptions, type PaginatedResponse } from 'common/ItemList.svelte'
import { link } from 'svelte-spa-router'
function getTargets (options: LoadOptions): Observable<PaginatedResponse<Target>> {
return from(api.getTargets({
search: options.search,
})).pipe(map(targets => ({
items: targets,
offset: 0,
total: targets.length,
})))
}
function getUsers (options: LoadOptions): Observable<PaginatedResponse<User>> {
return from(api.getUsers({
search: options.search,
})).pipe(map(targets => ({
items: targets,
offset: 0,
total: targets.length,
})))
}
function getRoles (options: LoadOptions): Observable<PaginatedResponse<Role>> {
return from(api.getRoles({
search: options.search,
})).pipe(map(targets => ({
items: targets,
offset: 0,
total: targets.length,
})))
}
</script>
<div class="row">
<div class="col-12 col-lg-6 mb-4 pe-4">
<div class="page-summary-bar">
<h1>Targets</h1>
<a
class="btn btn-outline-secondary ms-auto"
href="/targets/create"
use:link>
Add a target
</a>
</div>
<ItemList load={getTargets} showSearch={true}>
{#snippet item({ item: target })}
<a
class="list-group-item list-group-item-action"
href="/targets/{target.id}"
use:link>
<strong class="me-auto">
{target.name}
</strong>
<small class="text-muted ms-auto">
{#if target.options.kind === 'Http'}
HTTP
{/if}
{#if target.options.kind === 'MySql'}
MySQL
{/if}
{#if target.options.kind === 'Postgres'}
PostgreSQL
{/if}
{#if target.options.kind === 'Ssh'}
SSH
{/if}
{#if target.options.kind === 'WebAdmin'}
This web admin interface
{/if}
</small>
</a>
{/snippet}
</ItemList>
</div>
<div class="col-12 col-lg-6 pe-4">
<div class="page-summary-bar">
<h1>Users</h1>
<a
class="btn btn-outline-secondary ms-auto"
href="/users/create"
use:link>
Add a user
</a>
</div>
<ItemList load={getUsers} showSearch={true}>
{#snippet item({ item: user })}
<a
class="list-group-item list-group-item-action"
href="/users/{user.id}"
use:link>
<strong class="me-auto">
{user.username}
</strong>
</a>
{/snippet}
</ItemList>
<div class="page-summary-bar mt-4">
<h1>Roles</h1>
<a
class="btn btn-outline-secondary ms-auto"
href="/roles/create"
use:link>
Add a role
</a>
</div>
<ItemList load={getRoles} showSearch={true}>
{#snippet item({ item: role })}
<a
class="list-group-item list-group-item-action"
href="/roles/{role.id}"
use:link>
<strong class="me-auto">
{role.name}
</strong>
</a>
{/snippet}
</ItemList>
<h1>Misc.</h1>
<div class="list-group list-group-flush mb-3">
<a
class="list-group-item list-group-item-action"
href="/parameters"
use:link
>
<strong>Global parameters</strong>
</a>
</div>
</div>
</div>
<style lang="scss">
.list-group-item {
display: flex;
align-items: center;
}
</style>

View file

@ -9,7 +9,7 @@
ModalFooter,
} from '@sveltestrap/sveltestrap'
import ModalHeader from 'common/ModalHeader.svelte'
import ModalHeader from 'common/sveltestrap-s5-ports/ModalHeader.svelte'
import QRCode from 'qrcode'
import * as OTPAuth from 'otpauth'
import base32Encode from 'base32-encode'
@ -120,11 +120,11 @@
<img class="qr mb-3" bind:this={qrImage} alt="OTP QR code" />
<div class="d-flex justify-content-center mb-4">
<Button outline class="d-flex align-items-center" color="link" on:click={generateNewTotpKey}>
<Button class="d-flex align-items-center me-3" on:click={generateNewTotpKey}>
<Fa class="me-2" fw icon={faRefresh} />
Regenerate
</Button>
<CopyButton outline class="d-flex align-items-center" color="link" text={totpUri!} label={'Copy URI'} />
<CopyButton class="d-flex align-items-center" color="secondary" text={totpUri!} label={'Copy URI'} />
</div>
<FormGroup floating label="Paste the generated OTP code" class="mt-3">
<Input
@ -144,13 +144,11 @@
<Button
class="ms-auto"
disabled={!totpValid}
outline
on:click={() => validated = true}
>Create</Button>
<Button
class="ms-2"
outline
color="danger"
on:click={_cancel}
>Cancel</Button>

View file

@ -9,7 +9,7 @@
ModalFooter,
} from '@sveltestrap/sveltestrap'
import ModalHeader from 'common/ModalHeader.svelte'
import ModalHeader from 'common/sveltestrap-s5-ports/ModalHeader.svelte'
interface Props {
isOpen: boolean
@ -48,7 +48,7 @@
Password
</ModalHeader>
<ModalBody>
<FormGroup floating class="mt-3" label="Enter a new password">
<FormGroup floating label="Enter a new password">
<Input
bind:inner={field}
type="password"
@ -61,13 +61,11 @@
<div class="d-flex">
<Button
class="ms-auto"
outline
on:click={() => validated = true}
>Create</Button>
<Button
class="ms-2"
outline
color="danger"
on:click={_cancel}
>Cancel</Button>

View file

@ -2,17 +2,14 @@
import { api } from 'admin/lib/api'
import AsyncButton from 'common/AsyncButton.svelte'
import { replace } from 'svelte-spa-router'
import { FormGroup } from '@sveltestrap/sveltestrap'
import { Form, FormGroup } from '@sveltestrap/sveltestrap'
import { stringifyError } from 'common/errors'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
let error: string|null = $state(null)
let name = $state('')
async function create () {
if (!name) {
return
}
try {
const role = await api.createRole({
roleDataRequest: {
@ -33,14 +30,18 @@ async function create () {
<div class="page-summary-bar">
<h1>Add a role</h1>
<h1>add a role</h1>
</div>
<FormGroup floating label="Name">
<input class="form-control" bind:value={name} required />
</FormGroup>
<div class="narrow-page">
<Form>
<FormGroup floating label="Name">
<input class="form-control" bind:value={name} required />
</FormGroup>
<AsyncButton
outline
click={create}
>Create role</AsyncButton>
<AsyncButton
color="primary"
click={create}
>Create role</AsyncButton>
</Form>
</div>

View file

@ -1,75 +1,74 @@
<script lang="ts">
import { api, type TargetOptions, TlsMode } from 'admin/lib/api'
import AsyncButton from 'common/AsyncButton.svelte'
import { replace } from 'svelte-spa-router'
import { FormGroup } from '@sveltestrap/sveltestrap'
import { stringifyError } from 'common/errors'
import Alert from 'common/Alert.svelte'
import { api, type TargetOptions, TlsMode } from 'admin/lib/api'
import AsyncButton from 'common/AsyncButton.svelte'
import { replace } from 'svelte-spa-router'
import { Button, ButtonGroup, Form, FormGroup } from '@sveltestrap/sveltestrap'
import { stringifyError } from 'common/errors'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
import { TargetKind } from 'gateway/lib/api'
let error: string|null = $state(null)
let name = $state('')
let type: 'Http' | 'MySql' | 'Ssh' | 'Postgres' = $state('Ssh')
let error: string|null = $state(null)
let name = $state('')
let type: TargetKind = $state(TargetKind.Ssh)
async function create () {
if (!name || !type) {
return
}
try {
const options: TargetOptions|undefined = {
Ssh: {
kind: 'Ssh' as const,
host: '192.168.0.1',
port: 22,
username: 'root',
auth: {
kind: 'PublicKey' as const,
async function create () {
try {
const options: TargetOptions|undefined = {
[TargetKind.Ssh]: {
kind: TargetKind.Ssh,
host: '192.168.0.1',
port: 22,
username: 'root',
auth: {
kind: 'PublicKey' as const,
},
},
},
Http: {
kind: 'Http' as const,
url: 'http://192.168.0.1',
tls: {
mode: TlsMode.Preferred,
verify: true,
[TargetKind.Http]: {
kind: TargetKind.Http,
url: 'http://192.168.0.1',
tls: {
mode: TlsMode.Preferred,
verify: true,
},
},
},
MySql: {
kind: 'MySql' as const,
host: '192.168.0.1',
port: 3306,
tls: {
mode: TlsMode.Preferred,
verify: true,
[TargetKind.MySql]: {
kind: TargetKind.MySql,
host: '192.168.0.1',
port: 3306,
tls: {
mode: TlsMode.Preferred,
verify: true,
},
username: 'root',
password: '',
},
username: 'root',
password: '',
},
Postgres: {
kind: 'Postgres' as const,
host: '192.168.0.1',
port: 5432,
tls: {
mode: TlsMode.Preferred,
verify: true,
[TargetKind.Postgres]: {
kind: TargetKind.Postgres,
host: '192.168.0.1',
port: 5432,
tls: {
mode: TlsMode.Preferred,
verify: true,
},
username: 'postgres',
password: '',
},
username: 'postgres',
password: '',
},
}[type]
if (!options) {
return
[TargetKind.WebAdmin]: null as any,
}[type]
if (!options) {
return
}
const target = await api.createTarget({
targetDataRequest: {
name,
options,
},
})
replace(`/targets/${target.id}`)
} catch (err) {
error = await stringifyError(err)
}
const target = await api.createTarget({
targetDataRequest: {
name,
options,
},
})
replace(`/targets/${target.id}`)
} catch (err) {
error = await stringifyError(err)
}
}
</script>
@ -79,23 +78,40 @@ async function create () {
<div class="page-summary-bar">
<h1>Add a target</h1>
<h1>add a target</h1>
</div>
<FormGroup floating label="Name">
<input class="form-control" bind:value={name} />
</FormGroup>
<div class="narrow-page">
<Form>
<!-- svelte-ignore a11y_label_has_associated_control -->
<label class="mb-2">Type</label>
<ButtonGroup class="w-100 mb-3">
<Button
active={type === TargetKind.Ssh}
on:click={() => type = TargetKind.Ssh}
>SSH</Button>
<Button
active={type === TargetKind.Http}
on:click={() => type = TargetKind.Http}
>HTTP</Button>
<Button
active={type === TargetKind.MySql}
on:click={() => type = TargetKind.MySql}
>MySQL</Button>
<Button
active={type === TargetKind.Postgres}
on:click={() => type = TargetKind.Postgres}
>PostgreSQL</Button>
</ButtonGroup>
<FormGroup floating label="Type">
<select bind:value={type} class="form-control">
<option value={'Ssh'}>SSH</option>
<option value={'Http'}>HTTP</option>
<option value={'MySql'}>MySQL</option>
<option value={'Postgres'}>PostgreSQL</option>
</select>
</FormGroup>
<FormGroup floating label="Name">
<input class="form-control" required bind:value={name} />
</FormGroup>
<AsyncButton
outline
click={create}
>Create target</AsyncButton>
<AsyncButton
color="primary"
click={create}
>Create target</AsyncButton>
</Form>
</div>

View file

@ -7,7 +7,7 @@ import { link } from 'svelte-spa-router'
import { FormGroup } from '@sveltestrap/sveltestrap'
import { firstBy } from 'thenby'
import { stringifyError } from 'common/errors'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
let error: string|null = $state(null)
let targets: Target[]|undefined = $state()
@ -58,7 +58,7 @@ async function create () {
{#if result}
<div class="page-summary-bar">
<h1>Ticket created</h1>
<h1>ticket created</h1>
</div>
<Alert color="warning" fade={false}>
@ -77,13 +77,13 @@ async function create () {
<a
class="btn btn-secondary"
href="/tickets"
href="/config/tickets"
use:link
>Done</a>
{:else}
<div class="narrow-page">
<div class="page-summary-bar">
<h1>Create an access ticket</h1>
<h1>create an access ticket</h1>
</div>
{#if users}
@ -119,7 +119,7 @@ async function create () {
</FormGroup>
<AsyncButton
outline
color="primary"
click={create}
>Create ticket</AsyncButton>
</div>

View file

@ -2,17 +2,14 @@
import { api } from 'admin/lib/api'
import AsyncButton from 'common/AsyncButton.svelte'
import { replace } from 'svelte-spa-router'
import { FormGroup } from '@sveltestrap/sveltestrap'
import { Form, FormGroup } from '@sveltestrap/sveltestrap'
import { stringifyError } from 'common/errors'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
let error: string|null = $state(null)
let username = $state('')
async function create () {
if (!username) {
return
}
try {
const user = await api.createUser({
createUserRequest: {
@ -33,14 +30,17 @@ async function create () {
<div class="page-summary-bar">
<h1>Add a user</h1>
<h1>add a user</h1>
</div>
<div class="narrow-page">
<Form>
<FormGroup floating label="Username">
<input class="form-control" required bind:value={username} />
</FormGroup>
<FormGroup floating label="Username">
<input class="form-control" bind:value={username} />
</FormGroup>
<AsyncButton
outline
click={create}
>Create user</AsyncButton>
<AsyncButton
color="primary"
click={create}
>Create user</AsyncButton>
</Form>
</div>

View file

@ -13,7 +13,7 @@
import Fa from 'svelte-fa'
import { Button } from '@sveltestrap/sveltestrap'
import { stringifyError } from 'common/errors'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
import CreatePasswordModal from './CreatePasswordModal.svelte'
import SsoCredentialModal from './SsoCredentialModal.svelte'
import PublicKeyCredentialModal from './PublicKeyCredentialModal.svelte'

View file

@ -61,20 +61,21 @@
_reloadSessions()
const interval = setInterval(_reloadSessions, 1000000)
onDestroy(() => clearInterval(interval))
</script>
{#if activeSessionCount !== undefined}
<div class="page-summary-bar">
{#if activeSessionCount }
<h1>Sessions right now: {activeSessionCount}</h1>
<h1>
<span>active sessions:</span> <span class="counter">{activeSessionCount}</span>
</h1>
<div class="ms-auto">
<AsyncButton outline click={closeAllSesssions}>
Close all sessions
<AsyncButton color="warning" click={closeAllSesssions}>
Close all
</AsyncButton>
</div>
{:else}
<h1>No active sessions</h1>
<h1>no active sessions</h1>
{/if}
</div>
{/if}

View file

@ -3,7 +3,7 @@ import LogViewer from './LogViewer.svelte'
</script>
<div class="page-summary-bar">
<h1>Log</h1>
<h1>log</h1>
</div>
<LogViewer filters={{}} />

View file

@ -5,7 +5,7 @@ import IntersectionObserver from 'svelte-intersection-observer'
import { link } from 'svelte-spa-router'
import { onDestroy, onMount } from 'svelte'
import { stringifyError } from 'common/errors'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
interface Props {
filters: {

View file

@ -9,7 +9,7 @@
ModalFooter,
} from '@sveltestrap/sveltestrap'
import ModalHeader from 'common/ModalHeader.svelte'
import ModalHeader from 'common/sveltestrap-s5-ports/ModalHeader.svelte'
import { type ExistingPublicKeyCredential } from './lib/api'
interface Props {
@ -74,13 +74,11 @@
<Button
type="submit"
class="ms-auto"
outline
on:click={() => validated = true}
>Save</Button>
<Button
class="ms-2"
outline
color="danger"
on:click={_cancel}
>Cancel</Button>

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { api, type Recording } from 'admin/lib/api'
import TerminalRecordingPlayer from 'admin/player/TerminalRecordingPlayer.svelte'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
import DelayedSpinner from 'common/DelayedSpinner.svelte'
import { stringifyError } from 'common/errors'
@ -30,7 +30,7 @@ load().catch(async e => {
<div class="page-summary-bar">
<h1>Session recording</h1>
<h1>session recording</h1>
</div>
{#if !recording && !error}

View file

@ -5,7 +5,7 @@ import DelayedSpinner from 'common/DelayedSpinner.svelte'
import { replace } from 'svelte-spa-router'
import { FormGroup } from '@sveltestrap/sveltestrap'
import { stringifyError } from 'common/errors'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
interface Props {
params: { id: string };
@ -38,7 +38,7 @@ async function update () {
async function remove () {
if (confirm(`Delete role ${role!.name}?`)) {
await api.deleteRole(role!)
replace('/config')
replace('/config/roles')
}
}
</script>
@ -49,7 +49,7 @@ async function remove () {
<div class="page-summary-bar">
<div>
<h1>{role!.name}</h1>
<div class="text-muted">Role</div>
<div class="text-muted">role</div>
</div>
</div>
@ -64,14 +64,13 @@ async function remove () {
<div class="d-flex">
<AsyncButton
color="primary"
class="ms-auto"
outline
click={update}
>Update</AsyncButton>
<AsyncButton
class="ms-2"
outline
color="danger"
click={remove}
>Remove</AsyncButton>

View file

@ -1,66 +1,71 @@
<script lang="ts">
import { api, type SessionSnapshot, type Recording, type TargetSSHOptions, type TargetHTTPOptions, type TargetMySqlOptions, type TargetPostgresOptions } from 'admin/lib/api'
import { timeAgo } from 'admin/lib/time'
import AsyncButton from 'common/AsyncButton.svelte'
import DelayedSpinner from 'common/DelayedSpinner.svelte'
import { formatDistance, formatDistanceToNow } from 'date-fns'
import { onDestroy } from 'svelte'
import { link } from 'svelte-spa-router'
import LogViewer from './LogViewer.svelte'
import RelativeDate from './RelativeDate.svelte'
import { stringifyError } from 'common/errors'
import Alert from 'common/Alert.svelte'
import { api, type SessionSnapshot, type Recording, type TargetSSHOptions, type TargetHTTPOptions, type TargetMySqlOptions, type TargetPostgresOptions } from 'admin/lib/api'
import { timeAgo } from 'admin/lib/time'
import AsyncButton from 'common/AsyncButton.svelte'
import DelayedSpinner from 'common/DelayedSpinner.svelte'
import { formatDistance, formatDistanceToNow } from 'date-fns'
import { onDestroy } from 'svelte'
import { link } from 'svelte-spa-router'
import LogViewer from './LogViewer.svelte'
import RelativeDate from './RelativeDate.svelte'
import { stringifyError } from 'common/errors'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
import Fa from 'svelte-fa'
import { faUser } from '@fortawesome/free-regular-svg-icons'
import Badge from 'common/sveltestrap-s5-ports/Badge.svelte'
import { faArrowRight } from '@fortawesome/free-solid-svg-icons'
import Tooltip from 'common/sveltestrap-s5-ports/Tooltip.svelte'
interface Props {
params: { id: string }
}
let { params = { id: '' } }: Props = $props()
let error: string|null = $state(null)
let session: SessionSnapshot|null = $state(null)
let recordings: Recording[]|null = $state(null)
async function load () {
session = await api.getSession(params)
recordings = await api.getSessionRecordings(params)
}
async function close () {
api.closeSession(session!)
}
function getTargetDescription () {
if (session?.target) {
let address = '<unknown>'
if (session.target.options.kind === 'Ssh') {
const options = session.target.options as TargetSSHOptions
address = `${options.host}:${options?.port}`
}
if (session.target.options.kind === 'MySql') {
const options = session.target.options as TargetMySqlOptions
address = `${options.host}:${options?.port}`
}
if (session.target.options.kind === 'Postgres') {
const options = session.target.options as TargetPostgresOptions
address = `${options.host}:${options?.port}`
}
if (session.target.options.kind === 'Http') {
const options = session.target.options as unknown as TargetHTTPOptions
address = options.url
}
return `${session.target.name} (${address})`
} else {
return 'Not selected yet'
interface Props {
params: { id: string }
}
}
load().catch(async e => {
error = await stringifyError(e)
})
let { params = { id: '' } }: Props = $props()
const interval = setInterval(load, 1000)
onDestroy(() => clearInterval(interval))
let error: string|null = $state(null)
let session: SessionSnapshot|null = $state(null)
let recordings: Recording[]|null = $state(null)
async function load () {
session = await api.getSession(params)
recordings = await api.getSessionRecordings(params)
}
async function close () {
api.closeSession(session!)
}
function getTargetDescription () {
if (session?.target) {
let address = '<unknown>'
if (session.target.options.kind === 'Ssh') {
const options = session.target.options as TargetSSHOptions
address = `${options.host}:${options?.port}`
}
if (session.target.options.kind === 'MySql') {
const options = session.target.options as TargetMySqlOptions
address = `${options.host}:${options?.port}`
}
if (session.target.options.kind === 'Postgres') {
const options = session.target.options as TargetPostgresOptions
address = `${options.host}:${options?.port}`
}
if (session.target.options.kind === 'Http') {
const options = session.target.options as unknown as TargetHTTPOptions
address = options.url
}
return `${session.target.name} (${address})`
} else {
return 'Not selected yet'
}
}
load().catch(async e => {
error = await stringifyError(e)
})
const interval = setInterval(load, 1000)
onDestroy(() => clearInterval(interval))
</script>
@ -75,17 +80,25 @@ onDestroy(() => clearInterval(interval))
{#if session}
<div class="page-summary-bar">
<div>
<h1>Session</h1>
<div>
<strong class="me-2">
<h1>session</h1>
<div class="d-flex align-items-center mt-1">
<Tooltip delay="500" target="usernameBadge" animation>Authenticated user</Tooltip>
<Tooltip delay="500" target="targetBadge" animation>Selected target</Tooltip>
<Badge id="usernameBadge" color="success" class="me-2 d-flex align-items-center">
{#if session.username}
<Fa icon={faUser} class="me-2" />
{session.username}
{:else}
Logging in
{/if}
{getTargetDescription()}
</strong>
</Badge>
{#if session.target}
<Badge id="targetBadge" color="info" class="me-2 d-flex align-items-center">
<Fa icon={faArrowRight} class="me-2" />
{getTargetDescription()}
</Badge>
{/if}
<span class="text-muted">
{#if session.ended}
{formatDistance(new Date(session.started), new Date(session.ended))} long, <RelativeDate date={session.started} />
@ -97,7 +110,7 @@ onDestroy(() => clearInterval(interval))
</div>
{#if !session.ended}
<div class="ms-auto">
<AsyncButton outline click={close}>
<AsyncButton color="warning" click={close}>
Close now
</AsyncButton>
</div>

View file

@ -9,10 +9,10 @@
ModalFooter,
} from '@sveltestrap/sveltestrap'
import ModalHeader from 'common/ModalHeader.svelte'
import ModalHeader from 'common/sveltestrap-s5-ports/ModalHeader.svelte'
import { type ExistingSsoCredential } from './lib/api'
import { api } from 'gateway/lib/api'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
interface Props {
isOpen: boolean
@ -88,13 +88,11 @@
<Button
type="submit"
class="ms-auto"
outline
on:click={() => validated = true}
>Save</Button>
<Button
class="ms-2"
outline
color="danger"
on:click={_cancel}
>Cancel</Button>

View file

@ -11,7 +11,7 @@ import { replace } from 'svelte-spa-router'
import { FormGroup, Input } from '@sveltestrap/sveltestrap'
import TlsConfiguration from './TlsConfiguration.svelte'
import { stringifyError } from 'common/errors'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
interface Props {
params: { id: string };
@ -56,7 +56,7 @@ async function update () {
async function remove () {
if (confirm(`Delete target ${target!.name}?`)) {
await api.deleteTarget(target!)
replace('/config')
replace('/config/targets')
}
}
@ -190,7 +190,7 @@ async function toggleRole (role: Role) {
<Input
class="mb-0 me-2"
type="switch"
label="Allow insecure SSH algorithms (e.g. for older networks devices)"
label="Allow insecure SSH algorithms (e.g. for older network devices)"
bind:checked={target.options.allowInsecureAlgos} />
</div>
@ -268,14 +268,13 @@ async function toggleRole (role: Role) {
<div class="d-flex">
<AsyncButton
color="primary"
class="ms-auto"
outline
click={update}
>Update configuration</AsyncButton>
<AsyncButton
class="ms-2"
outline
color="danger"
click={remove}
>Remove</AsyncButton>

View file

@ -5,7 +5,7 @@
import { replace } from 'svelte-spa-router'
import { FormGroup, Input } from '@sveltestrap/sveltestrap'
import { stringifyError } from 'common/errors'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
import CredentialEditor from './CredentialEditor.svelte'
interface Props {
@ -46,7 +46,7 @@
async function remove () {
if (confirm(`Delete user ${user!.username}?`)) {
await api.deleteUser(user!)
replace('/config')
replace('/config/users')
}
}
@ -114,14 +114,13 @@
<div class="d-flex">
<AsyncButton
color="primary"
class="ms-auto"
outline
click={update}
>Update</AsyncButton>
<AsyncButton
class="ms-2"
outline
color="danger"
click={remove}
>Remove</AsyncButton>

View file

@ -0,0 +1,39 @@
<script lang="ts">
import NavListItem from 'common/NavListItem.svelte'
</script>
<NavListItem
title="Targets"
description="Destinations for users to connect to"
href="/config/targets"
/>
<NavListItem
title="Users"
description="Manage accounts and credentials"
href="/config/users"
/>
<NavListItem
title="Roles"
description="Group users together"
href="/config/roles"
/>
<NavListItem
title="Tickets"
description="Temporary access credentials"
href="/config/tickets"
/>
<NavListItem
title="SSH keys"
description="Own keys and known hosts"
href="/config/ssh"
/>
<NavListItem
title="Global parameters"
description="Change instance-wide settings"
href="/config/parameters"
/>

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { Input } from '@sveltestrap/sveltestrap'
import { api, type ParameterValues } from 'admin/lib/api'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
import DelayedSpinner from 'common/DelayedSpinner.svelte'
import { stringifyError } from 'common/errors'
@ -19,7 +19,7 @@
</script>
<div class="page-summary-bar">
<h1>Global parameters</h1>
<h1>global parameters</h1>
</div>
{#await load()}

View file

@ -0,0 +1,47 @@
<script lang="ts">
import { Observable, from, map } from 'rxjs'
import { type Role, api } from 'admin/lib/api'
import ItemList, { type LoadOptions, type PaginatedResponse } from 'common/ItemList.svelte'
import { link } from 'svelte-spa-router'
function getRoles (options: LoadOptions): Observable<PaginatedResponse<Role>> {
return from(api.getRoles({
search: options.search,
})).pipe(map(targets => ({
items: targets,
offset: 0,
total: targets.length,
})))
}
</script>
<div class="page-summary-bar mt-4">
<h1>roles</h1>
<a
class="btn btn-primary ms-auto"
href="/roles/create"
use:link>
Add a role
</a>
</div>
<ItemList load={getRoles} showSearch={true}>
{#snippet item({ item: role })}
<a
class="list-group-item list-group-item-action"
href="/roles/{role.id}"
use:link>
<strong class="me-auto">
{role.name}
</strong>
</a>
{/snippet}
</ItemList>
<style lang="scss">
.list-group-item {
display: flex;
align-items: center;
}
</style>

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { api, type SSHKey, type SSHKnownHost } from 'admin/lib/api'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
import CopyButton from 'common/CopyButton.svelte'
import { stringifyError } from 'common/errors'

View file

@ -0,0 +1,65 @@
<script lang="ts">
import { Observable, from, map } from 'rxjs'
import { type Target, api } from 'admin/lib/api'
import ItemList, { type LoadOptions, type PaginatedResponse } from 'common/ItemList.svelte'
import { link } from 'svelte-spa-router'
import { TargetKind } from 'gateway/lib/api'
function getTargets (options: LoadOptions): Observable<PaginatedResponse<Target>> {
return from(api.getTargets({
search: options.search,
})).pipe(map(targets => ({
items: targets,
offset: 0,
total: targets.length,
})))
}
</script>
<div class="page-summary-bar">
<h1>targets</h1>
<a
class="btn btn-primary ms-auto"
href="/targets/create"
use:link>
Add a target
</a>
</div>
<ItemList load={getTargets} showSearch={true}>
{#snippet item({ item: target })}
<a
class="list-group-item list-group-item-action"
class:disabled={target.options.kind === TargetKind.WebAdmin}
href="/targets/{target.id}"
use:link>
<strong class="me-auto">
{target.name}
</strong>
<small class="text-muted ms-auto">
{#if target.options.kind === TargetKind.Http}
HTTP
{/if}
{#if target.options.kind === TargetKind.MySql}
MySQL
{/if}
{#if target.options.kind === TargetKind.Postgres}
PostgreSQL
{/if}
{#if target.options.kind === TargetKind.Ssh}
SSH
{/if}
{#if target.options.kind === TargetKind.WebAdmin}
This web admin interface
{/if}
</small>
</a>
{/snippet}
</ItemList>
<style lang="scss">
.list-group-item {
display: flex;
align-items: center;
}
</style>

View file

@ -1,11 +1,11 @@
<script lang="ts">
import { api, type Ticket } from 'admin/lib/api'
import { link } from 'svelte-spa-router'
import RelativeDate from './RelativeDate.svelte'
import RelativeDate from '../RelativeDate.svelte'
import Fa from 'svelte-fa'
import { faCalendarXmark, faCalendarCheck, faSquareXmark, faSquareCheck } from '@fortawesome/free-solid-svg-icons'
import { stringifyError } from 'common/errors'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
let error: string|undefined = $state()
let tickets: Ticket[]|undefined = $state()
@ -32,13 +32,13 @@ async function deleteTicket (ticket: Ticket) {
{#if tickets }
<div class="page-summary-bar">
{#if tickets.length }
<h1>Access tickets: {tickets.length}</h1>
<h1>access tickets: <span class="counter">{tickets.length}</span></h1>
{:else}
<h1>No tickets created yet</h1>
{/if}
<a
class="btn btn-outline-secondary ms-auto"
href="/tickets/create"
class="btn btn-primary ms-auto"
href="/config/tickets/create"
use:link>
Create a ticket
</a>

View file

@ -0,0 +1,46 @@
<script lang="ts">
import { Observable, from, map } from 'rxjs'
import { type User, api } from 'admin/lib/api'
import ItemList, { type LoadOptions, type PaginatedResponse } from 'common/ItemList.svelte'
import { link } from 'svelte-spa-router'
function getUsers (options: LoadOptions): Observable<PaginatedResponse<User>> {
return from(api.getUsers({
search: options.search,
})).pipe(map(targets => ({
items: targets,
offset: 0,
total: targets.length,
})))
}
</script>
<div class="page-summary-bar">
<h1>users</h1>
<a
class="btn btn-primary ms-auto"
href="/users/create"
use:link>
Add a user
</a>
</div>
<ItemList load={getUsers} showSearch={true}>
{#snippet item({ item: user })}
<a
class="list-group-item list-group-item-action"
href="/users/{user.id}"
use:link>
<strong class="me-auto">
{user.username}
</strong>
</a>
{/snippet}
</ItemList>
<style lang="scss">
.list-group-item {
display: flex;
align-items: center;
}
</style>

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/assets/logo.svg" />
<link rel="icon" href="/@warpgate/assets/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Warpgate</title>
<style>#app { display: none }</style>

View file

@ -1,5 +1,5 @@
<script lang="ts">
import { faCheck } from '@fortawesome/free-solid-svg-icons'
import { faCheck, faTimes } from '@fortawesome/free-solid-svg-icons'
import Fa from 'svelte-fa'
import { Button, Spinner, type Color } from '@sveltestrap/sveltestrap'
@ -8,7 +8,8 @@ enum State {
Normal = 'n',
Progress = 'p',
ProgressWithSpinner = 'ps',
Done = 'd'
Done = 'd',
Failed = 'f'
}
interface Props {
@ -31,6 +32,15 @@ async function _click () {
if (!button) {
return
}
const parentForm = button.closest<HTMLFormElement>('form')
if (parentForm) {
parentForm.classList.add('was-validated')
if (!parentForm.checkValidity()) {
return
}
}
lastWidth = button.offsetWidth
st = State.Progress
setTimeout(() => {
@ -40,10 +50,12 @@ async function _click () {
}, 500)
try {
await click()
} finally {
st = State.Done
} catch {
st = State.Failed
} finally {
setTimeout(() => {
if (st === State.Done) {
if (st === State.Done || st === State.Failed) {
st = State.Normal
lastWidth = 0
}
@ -56,7 +68,7 @@ async function _click () {
<Button
on:click={_click}
bind:inner={button}
style="min-width: {lastWidth}px"
style="min-width: {lastWidth}px; min-height: 40px;"
class={cls}
outline={outline}
color={color}
@ -73,5 +85,18 @@ async function _click () {
{#if st === State.Done}
<Fa icon={faCheck} fw />
{/if}
{#if st === State.Failed}
<Fa icon={faTimes} fw />
{/if}
</div>
</Button>
<style lang="scss">
.overlay {
margin: auto;
:global(svg) {
margin: auto;
}
}
</style>

View file

@ -45,7 +45,7 @@ async function singleLogout () {
</DropdownMenu>
</Dropdown>
{:else}
<Button color="link" on:click={logout} title="Log out">
<Button color="link" on:click={logout} title="Log out" class="p-0 ms-2">
<Fa icon={faSignOut} fw />
</Button>
{/if}

View file

@ -0,0 +1,50 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte'
import logo from '../../public/assets/brand.svg?raw'
import { currentThemeFile } from 'theme'
import { get } from 'svelte/store'
let element: HTMLElement|undefined = $state()
let s = currentThemeFile.subscribe(colorizeByTheme)
function colorize (r: number, g: number, b: number, dr: number, dg: number, db: number) {
element?.querySelectorAll('path').forEach((p, idx) => {
let d = idx
p.style.fill = `rgb(${r + d * dr}, ${g + d * dg}, ${b + d * db})`
})
}
function colorizeByTheme () {
if (get(currentThemeFile) === 'light') {
colorize(49, 57, 72, -1, 1, 3)
} else {
colorize(203, 212, 235, -3, -2, -1)
}
}
onMount(() => {
colorizeByTheme()
})
onDestroy(s)
</script>
<div bind:this={element} class="brand">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html logo}
</div>
<style lang="scss">
:global(svg) {
width: auto;
display: block;
max-height: 100%;
}
.brand {
height: 22px;
}
</style>

View file

@ -4,7 +4,7 @@
import { serverInfo } from 'gateway/lib/store'
import { makeExampleSSHCommand, makeSSHUsername, makeExampleMySQLCommand, makeExampleMySQLURI, makeMySQLUsername, makeTargetURL, makeExamplePostgreSQLCommand, makePostgreSQLUsername, makeExamplePostgreSQLURI } from 'common/protocols'
import CopyButton from 'common/CopyButton.svelte'
import Alert from './Alert.svelte'
import Alert from './sveltestrap-s5-ports/Alert.svelte'
interface Props {
targetName?: string;

View file

@ -1,43 +0,0 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte'
import { get } from 'svelte/store'
import { currentThemeFile } from 'theme'
import logo from '../../public/assets/logo.svg?raw'
let element: HTMLElement|undefined = $state()
function colorize (r: number, g: number, b: number, dr: number, dg: number, db: number) {
element?.querySelectorAll('path').forEach((p, idx) => {
let d = idx
p.style.fill = `rgb(${r + d * dr}, ${g + d * dg}, ${b + d * db})`
})
}
function colorizeByTheme () {
if (get(currentThemeFile) === 'light') {
colorize(81, 47, 185, -1, 1, 3)
} else {
colorize(131, 167, 255, -3, 1, -1)
}
}
let s = currentThemeFile.subscribe(colorizeByTheme)
onMount(() => {
colorizeByTheme()
})
onDestroy(s)
</script>
<div bind:this={element} class="d-flex">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html logo}
</div>
<style>
:global(svg) {
width: 100%;
}
</style>

View file

@ -0,0 +1,80 @@
<script lang="ts">
import { faArrowRight } from '@fortawesome/free-solid-svg-icons'
import Fa from 'svelte-fa'
import { link } from 'svelte-spa-router'
interface Props {
title: string
description?: string
href: string
}
let {
title,
description,
href,
}: Props = $props()
</script>
<a
class="link"
href={href}
use:link
>
<div class="text">
<h5 class="title">{title}</h5>
{#if description}
<div class="description text-muted">{description}</div>
{/if}
</div>
<div class="icon">
<Fa class="icon" icon={faArrowRight} />
</div>
</a>
<style lang="scss">
a {
cursor: pointer;
display: flex;
width: 100%;
text-decoration: none;
padding: 1rem 1.5rem;
border-radius: var(--bs-border-radius);
align-items: center;
.text {
flex-grow: 1;
}
&:hover {
background: var(--bs-list-group-action-hover-bg);
h5 {
color: var(--bs-list-group-action-hover-color);
}
}
&:active {
background: var(--bs-list-group-action-active-bg);
h5 {
color: var(--bs-list-group-action-active-color);
}
}
}
h5 {
margin-bottom: 0.25rem;
text-decoration: underline;
text-decoration-color: var(--wg-link-underline-color);
text-underline-offset: 2px;
}
.link:hover h5 {
text-decoration-color: var(--wg-link-hover-underline-color);
}
.description {
text-decoration: none;
}
</style>

View file

@ -5,7 +5,7 @@
import { get } from 'svelte/store'
import { currentTheme, setCurrentTheme } from 'theme'
import Tooltip from './Tooltip.svelte'
import Tooltip from './sveltestrap-s5-ports/Tooltip.svelte'
function toggle () {
if (get(currentTheme) === 'auto') {

View file

@ -0,0 +1,72 @@
<script>
import { classnames } from './_sveltestrapUtils'
/**
* Additional CSS classes for container element.
* @type {string}
* @default ''
*/
/**
* @typedef {Object} Props
* @property {string} [ariaLabel] - Text to be read by screen readers.
* @property {boolean | string} [border] - Determines if the badge should have a border
* @property {string} [class]
* @property {string} [children] - The content to be displayed within the badge.
* @property {string} [color] - The color theme for the badge.
* @property {string} [href] - The href attribute for the badge, which turns it into a link if provided.
* @property {boolean} [indicator] - Create a circular indicator for absolute positioned badge.
* @property {boolean} [pill] - Flag to indicate if the badge should have a pill shape.
* @property {boolean} [positioned] - Flag to indicate if the badge should be absolutely positioned.
* @property {string} [placement] - Classes determining where the badge should be absolutely positioned.
* @property {boolean | string} [shadow] - Determines if the badge should have a shadow
* @property {string | undefined} [theme] - The theme name override to apply to this component instance.
* @property {CallableFunction} [children]
*/
let {
ariaLabel = '',
border = false,
'class': className = '',
color = 'secondary',
href = '',
indicator = false,
pill = false,
positioned = false,
placement = 'top-0 start-100',
shadow = false,
theme = undefined,
children,
...rest
} = $props()
let classes = $derived(classnames(
'badge',
`text-bg-${color}`,
pill ? 'rounded-pill' : false,
positioned ? 'position-absolute translate-middle' : false,
positioned ? placement : false,
indicator ? 'p-2' : false,
border ? (typeof border === 'string' ? border : 'border') : false,
shadow ? (typeof shadow === 'string' ? shadow : 'shadow') : false,
className
))
</script>
{#if href}
<a {...rest} {href} class={classes} data-bs-theme={theme}>
{@render children?.()}
{#if positioned || indicator}
<span class="visually-hidden">{ariaLabel}</span>
{/if}
</a>
{:else}
<span {...rest} class={classes} data-bs-theme={theme}>
{@render children?.()}
{#if positioned || indicator}
<span class="visually-hidden">{ariaLabel}</span>
{/if}
</span>
{/if}

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { api } from 'gateway/lib/api'
import { onMount } from 'svelte'
import logo from '../../public/assets/logo.svg'
import logo from '../../public/assets/favicon.svg'
let ready = false
let menuVisible = false
@ -67,8 +67,10 @@ async function logout () {
class:wg-hidden={!ready}
style="left: {position.x * 100}%; top: {position.y * 100}%"
>
<button
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<img
class="menu-toggle"
src={logo} alt="Warpgate"
on:mouseup|stopPropagation|preventDefault={() => {
if (!dragging) {
menuVisible = !menuVisible
@ -76,15 +78,13 @@ async function logout () {
stopDragging()
}
}}
on:mousemove={e => {
on:mousedown|preventDefault
on:mousemove|preventDefault={e => {
if (e.buttons && !dragging) {
startDragging(e)
}
}}
>
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<img class="logo" src={logo} alt="Warpgate" on:mousedown|preventDefault />
</button>
{#if menuVisible}
<div class="menu">
@ -103,21 +103,20 @@ async function logout () {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
&.wg-hidden button {
&.wg-hidden > img.menu-toggle {
opacity: 0;
}
> button.menu-toggle {
> img.menu-toggle {
transition: 0.5s ease-out opacity;
opacity: 1;
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 7px;
border: 1px solid rgba(128, 128, 128, .25);
background: rgba(255, 255, 255, .5);
backdrop-filter: blur(4px);
border: none;
padding: 0;
}
.menu {

View file

@ -4,10 +4,10 @@ import { wrap } from 'svelte-spa-router/wrap'
import { get } from 'svelte/store'
import { reloadServerInfo, serverInfo } from 'gateway/lib/store'
import ThemeSwitcher from 'common/ThemeSwitcher.svelte'
import Logo from 'common/Logo.svelte'
import DelayedSpinner from 'common/DelayedSpinner.svelte'
import AuthBar from 'common/AuthBar.svelte'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
import Brand from 'common/Brand.svelte'
let redirecting = false
let serverInfoPromise = reloadServerInfo()
@ -69,7 +69,7 @@ init()
{:else}
<div class="d-flex align-items-center mt-5 mb-5">
<a class="logo" href="/@warpgate">
<Logo />
<Brand />
</a>
<AuthBar />
@ -80,7 +80,7 @@ init()
</main>
<footer class="mt-5">
<span class="me-auto">
<span class="me-auto ms-3">
v{$serverInfo?.version}
</span>
<ThemeSwitcher />
@ -96,9 +96,4 @@ init()
width: 500px;
max-width: 100vw;
}
.logo {
width: 5rem;
margin: 0 -0.5rem;
}
</style>

View file

@ -2,7 +2,7 @@
import { api, CredentialKind, PasswordState, type CredentialsState, type ExistingOtpCredential, type ExistingPublicKeyCredential } from 'gateway/lib/api'
import { serverInfo } from 'gateway/lib/store'
import DelayedSpinner from 'common/DelayedSpinner.svelte'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
import { stringifyError } from 'common/errors'
import { faIdBadge, faKey, faKeyboard, faMobilePhone } from '@fortawesome/free-solid-svg-icons'
import Fa from 'svelte-fa'
@ -148,7 +148,7 @@
<span class="ms-auto"></span>
<Button size="sm" color="link" on:click={() => {
creatingPublicKeyCredential = true
}}>Add new</Button>
}}>Add key</Button>
</div>
<div class="list-group list-group-flush mb-3">

View file

@ -11,7 +11,7 @@ import { reloadServerInfo } from 'gateway/lib/store'
import AsyncButton from 'common/AsyncButton.svelte'
import DelayedSpinner from 'common/DelayedSpinner.svelte'
import { stringifyError } from 'common/errors'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
let error: string|null = $state(null)
let username = $state('')
@ -184,8 +184,8 @@ async function startSSO (provider: SsoProviderDescription) {
</FormGroup>
<AsyncButton
outline
class="d-flex align-items-center"
color="primary"
type="submit"
disabled={busy}
click={login}
@ -208,25 +208,23 @@ async function startSSO (provider: SsoProviderDescription) {
{#await ssoProvidersPromise then ssoProviders}
{#if authState === ApiAuthState.SsoNeeded || authState === ApiAuthState.NotStarted || authState === ApiAuthState.Failed}
<div class="mt-5">
<div class="mt-5 sso-buttons">
{#each ssoProviders as ssoProvider}
<button
class="btn d-flex align-items-center w-100 mb-2 btn-outline-primary"
class="btn btn-secondary"
disabled={busy}
onclick={() => startSSO(ssoProvider)}
>
<span class="m-auto">
{#if ssoProvider.kind === SsoProviderKind.Google}
<Fa fw class="me-2" icon={faGoogle} />
{/if}
{#if ssoProvider.kind === SsoProviderKind.Azure}
<Fa fw class="me-2" icon={faMicrosoft} />
{/if}
{#if ssoProvider.kind === SsoProviderKind.Apple}
<Fa fw class="me-2" icon={faApple} />
{/if}
{ssoProvider.label}
</span>
{#if ssoProvider.kind === SsoProviderKind.Google}
<Fa fw class="me-2" icon={faGoogle} />
{/if}
{#if ssoProvider.kind === SsoProviderKind.Azure}
<Fa fw class="me-2" icon={faMicrosoft} />
{/if}
{#if ssoProvider.kind === SsoProviderKind.Apple}
<Fa fw class="me-2" icon={faApple} />
{/if}
{ssoProvider.label}
</button>
{/each}
</div>
@ -235,10 +233,30 @@ async function startSSO (provider: SsoProviderDescription) {
{#if authState !== ApiAuthState.NotStarted && authState !== ApiAuthState.Failed}
<button
class="btn w-100 mt-3 btn-outline-secondary"
class="btn w-100 mt-3 btn-secondary"
onclick={cancel}
>
Cancel
</button>
{/if}
{/await}
<style lang="scss">
h1 {
font-size: 3rem;
}
.sso-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.85rem 1rem;
button {
flex: 1 0 0;
display: flex;
align-items: center;
justify-content: center;
text-wrap: nowrap;
}
}
</style>

View file

@ -3,7 +3,7 @@ import { api, ApiAuthState, type AuthStateResponseInternal } from 'gateway/lib/a
import AsyncButton from 'common/AsyncButton.svelte'
import DelayedSpinner from 'common/DelayedSpinner.svelte'
import RelativeDate from 'admin/RelativeDate.svelte'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
interface Props {
params: { stateId: string };
@ -52,7 +52,7 @@ async function reject () {
{:then}
{#if authState}
<div class="page-summary-bar">
<h1>Authorization request</h1>
<h1>authorization request</h1>
</div>
<div class="mb-5">
@ -93,7 +93,6 @@ async function reject () {
Authorize
</AsyncButton>
<AsyncButton
outline
color="secondary"
class="d-flex align-items-center ms-2"
click={reject}

View file

@ -1,14 +1,11 @@
<script lang="ts">
import { serverInfo } from 'gateway/lib/store'
import Alert from 'common/Alert.svelte'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
import CredentialManager from './CredentialManager.svelte'
</script>
<div class="page-summary-bar">
<div>
<h1>{$serverInfo!.username}</h1>
<div class="text-muted">User</div>
</div>
<h1>{$serverInfo!.username}</h1>
</div>
{#if $serverInfo}

View file

@ -8,7 +8,7 @@ import Fa from 'svelte-fa'
import { Modal, ModalBody } from '@sveltestrap/sveltestrap'
import { serverInfo } from './lib/store'
import { firstBy } from 'thenby'
import ModalHeader from 'common/ModalHeader.svelte'
import ModalHeader from 'common/sveltestrap-s5-ports/ModalHeader.svelte'
let selectedTarget: TargetSnapshot|undefined = $state()

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<base href="/@warpgate" />
<link rel="icon" href="/assets/logo.svg" />
<link rel="icon" href="/@warpgate/assets/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Warpgate</title>
<style>#app { display: none }</style>

View file

@ -1,5 +1,37 @@
@use "sass:map";
@mixin button-variant(
$background,
$border,
$color: color-contrast($background),
$hover-background: if($color == $color-contrast-dark, shade-color($background, $btn-hover-bg-shade-amount), tint-color($background, $btn-hover-bg-tint-amount)),
$hover-border: if($color == $color-contrast-dark, shade-color($border, $btn-hover-border-shade-amount), tint-color($border, $btn-hover-border-tint-amount)),
$hover-color: color-contrast($hover-background),
$active-background: if($color == $color-contrast-dark, shade-color($background, $btn-active-bg-shade-amount), tint-color($background, $btn-active-bg-tint-amount)),
$active-border: if($color == $color-contrast-dark, shade-color($border, $btn-active-border-shade-amount), tint-color($border, $btn-active-border-tint-amount)),
$active-color: color-contrast($active-background),
$disabled-background: $background,
$disabled-border: $border,
$disabled-color: $btn-disabled-color,
$text-color: if($color == $color-contrast-light, shade-color($background, $btn-color-shade-amount), tint-color($background, $btn-color-tint-amount))
) {
$real-background: if($color == $color-contrast-dark, shade-color($background, $btn-bg-shade-amount), tint-color($background, $btn-bg-tint-amount));
$real-color: if($color == $color-contrast-light, shade-color($background, $btn-color-shade-amount), tint-color($background, $btn-color-tint-amount));
--#{$prefix}btn-color: #{$real-color};
--#{$prefix}btn-bg: #{$real-background};
--#{$prefix}btn-border-color: #{if($color == $color-contrast-dark, shade-color($background, $btn-border-shade-amount), tint-color($background, $btn-border-tint-amount))};
--#{$prefix}btn-hover-color: #{$hover-color};
--#{$prefix}btn-hover-bg: #{$hover-background};
--#{$prefix}btn-hover-border-color: #{$hover-border};
--#{$prefix}btn-focus-shadow-rgb: #{to-rgb(mix($color, $border, 15%))};
--#{$prefix}btn-active-color: #{$active-color};
--#{$prefix}btn-active-bg: #{$active-background};
--#{$prefix}btn-active-border-color: #{$active-border};
--#{$prefix}btn-active-shadow: #{$btn-active-box-shadow};
--#{$prefix}btn-disabled-color: #{$disabled-color};
--#{$prefix}btn-disabled-bg: #{$real-background};
}
// Layout & components
@import "bootstrap/scss/root";
@import "bootstrap/scss/reboot";
@ -12,7 +44,7 @@
@import "bootstrap/scss/buttons";
@import "bootstrap/scss/transitions";
@import "bootstrap/scss/dropdown";
// @import "bootstrap/scss/button-group";
@import "bootstrap/scss/button-group";
// @import "bootstrap/scss/nav";
// @import "bootstrap/scss/navbar";
// @import "bootstrap/scss/card";
@ -52,8 +84,19 @@ $font-family-os: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
margin: 0.25rem 0 1.5rem;
h1 {
font-family: 'Poppins';
font-weight: 700;
font-size: 2.5rem;
margin: 0;
}
.counter {
display: inline-block;
padding: 0px 10px;
background: var(--bs-body-color);
color: var(--bs-body-bg);
border-radius: 5px;
}
}
.alert {
@ -72,7 +115,7 @@ footer {
align-items: center;
padding: .5rem 0;
margin: 2rem 0 1rem;
border-top: 1px solid rgba($body-color, .5);
border-top: 1px solid rgba($body-color, .2);
}
input:-webkit-autofill {
@ -91,6 +134,11 @@ input:-webkit-autofill:focus {
font-family: $font-family-os;
}
.btn {
text-decoration: underline;
text-decoration-color: var(--#{$prefix}btn-active-bg);
}
.page-item.active .page-link {
text-decoration: underline;
}
@ -109,3 +157,48 @@ input:-webkit-autofill:focus {
width: map.get($grid-breakpoints, "md");
}
}
a {
text-decoration-color: var(--wg-link-underline-color);
text-underline-offset: 2px;
&:hover, &.active {
text-decoration-color: var(--wg-link-hover-underline-color);
}
}
// Make these vars reusable from everywhere
body {
--bs-list-group-color: #{$list-group-color};
--bs-list-group-bg: #{$list-group-bg};
--bs-list-group-border-color: #{$list-group-border-color};
--bs-list-group-border-width: #{$list-group-border-width};
--bs-list-group-border-radius: #{$list-group-border-radius};
--bs-list-group-item-padding-x: #{$list-group-item-padding-x};
--bs-list-group-item-padding-y: #{$list-group-item-padding-y};
--bs-list-group-action-color: #{$list-group-action-color};
--bs-list-group-action-hover-color: #{$list-group-action-hover-color};
--bs-list-group-action-hover-bg: #{$list-group-hover-bg};
--bs-list-group-action-active-color: #{$list-group-action-active-color};
--bs-list-group-action-active-bg: #{$list-group-action-active-bg};
--bs-list-group-disabled-color: #{$list-group-disabled-color};
--bs-list-group-disabled-bg: #{$list-group-disabled-bg};
--bs-list-group-active-color: #{$list-group-active-color};
--bs-list-group-active-bg: #{$list-group-active-bg};
--bs-list-group-active-border-color: #{$list-group-active-border-color};
--wg-link-underline-color: #{$link-underline-color};
--wg-link-hover-underline-color: #{$link-hover-underline-color};
}
@each $theme-color, $value in $theme-colors {
.text-bg-#{$theme-color} {
$color: color-contrast($value);
$real-background: if($color == $color-contrast-dark, shade-color($value, $btn-bg-shade-amount), tint-color($value, $btn-bg-tint-amount));
$real-color: if($color == $color-contrast-light, shade-color($value, $btn-color-shade-amount), tint-color($value, $btn-color-tint-amount));
color: $real-color if($enable-important-utilities, !important, null);
background-color: $real-background if($enable-important-utilities, !important, null);
}
}

View file

@ -0,0 +1,7 @@
@font-face {
font-family: 'Poppins';
font-style: normal;
font-display: block; /* changed */
font-weight: 700;
src: url(./../../node_modules/@fontsource/poppins/files/poppins-latin-700-normal.woff2) format('woff2'), url(./../../node_modules/@fontsource/poppins/files/poppins-latin-700-normal.woff) format('woff');
}

View file

@ -1,4 +1,5 @@
import '@fontsource/work-sans'
import './fonts.css'
import { get, writable } from 'svelte/store'

View file

@ -29,18 +29,8 @@
@import "theme";
a {
text-decoration-color: rgba($link-color, .5);
text-underline-offset: 2px;
&:hover, &.active {
color: $link-hover-color;
text-decoration-color: rgba($link-hover-color, .75);
}
}
header {
border-bottom: 1px solid rgba($body-color, .5);
border-bottom: 1px solid rgba($body-color, .2);
}
.list-group-item-action {

View file

@ -5,15 +5,6 @@
@import "bootstrap/scss/utilities";
@import "theme";
a {
text-decoration-color: rgba($body-color, 0.25);
text-underline-offset: 2px;
&:hover, &.active {
text-decoration-color: $body-color;
}
}
header {
border-bottom: 1px solid rgba($body-color, .75);
}

View file

@ -8,3 +8,29 @@ $pagination-hover-bg: transparent;
$pagination-focus-bg: transparent;
$modal-header-border-color: transparent;
$dropdown-link-hover-bg: transparent;
$btn-padding-x: 1.5rem;
$btn-bg-shade-amount: 75%;
$btn-bg-tint-amount: 60%;
$btn-border-shade-amount: 75%;
$btn-border-tint-amount: 65%;
$btn-color-shade-amount: 10%;
$btn-color-tint-amount: 50%;
$btn-hover-bg-shade-amount: 60%;
$btn-hover-bg-tint-amount: 50%;
$btn-hover-border-shade-amount: 60%;
$btn-hover-border-tint-amount: 10%;
$btn-active-bg-shade-amount: 50%;
// $btn-active-bg-tint-amount
$btn-active-border-shade-amount: 40%;
// $btn-active-border-tint-amount
$tooltip-color: #c1c9e4;
$badge-font-size: .8em;
$badge-font-weight: 400;
$badge-padding-y: .55em;
$badge-padding-x: .85em;

View file

@ -1,6 +1,6 @@
@import "bootstrap/scss/functions";
$body-bg: #1d1d20;
$body-bg: #14141a;
$body-color: #c1c9e4;
$font-family-sans-serif: "Work Sans", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
@ -21,22 +21,19 @@ $teal: #20c997;
$cyan: #0dcaf0;
$primary: $blue;
$secondary: #627c84;
$secondary: #748d95;
$light: transparent;
$info: $blue;
$component-active-bg: $primary;
$input-bg: #ffffff08;
$input-bg: #1c1c24;
$input-border-color: #ced4da40;
$input-color: #ccc;
$input-focus-bg: #ffffff08;
$input-focus-bg: #1b1b22;
$input-focus-border-color: tint-color($component-active-bg, 25%) ;
$input-disabled-bg: $input-bg;
$form-check-input-border: 2px solid $input-border-color;
$form-switch-color: $input-color;
$input-btn-focus-color-opacity: .25;
$input-btn-border-width: 2px;
@ -60,6 +57,7 @@ $modal-content-bg: $body-bg;
$btn-close-color: $secondary;
$btn-link-disabled-color: rgba(255, 255, 255, .5);
$btn-disabled-color: #969696;
$alert-bg-scale: 100%;
$alert-border-scale: 50%;
@ -67,5 +65,21 @@ $alert-color-scale: 0%;
$code-color: #84f1fe;
$form-check-input-border: 2px solid $input-border-color;
$form-switch-color: $secondary;
$form-check-color: $primary;
@import "bootstrap/scss/variables";
@import "./vars.common.scss";
$link-underline-color: rgba($link-color, .5);
$link-hover-underline-color: rgba($link-hover-color, .75);
$form-check-input-width: 1.3em;
$form-switch-width: 2.5em;
$form-switch-padding-start: 3em;
$form-check-input-checked-border-color: shade-color($form-check-color, $btn-active-border-shade-amount);
$form-check-input-checked-bg-color: shade-color($form-check-color, $btn-active-bg-shade-amount);

View file

@ -9,20 +9,40 @@ $alert-border-radius: 0;
$alert-border-scale: -30%;
$blue: #306F84 !default;
$blue: #3573ac !default;
$purple: #5C398F !default;
$pink: #B53D6D !default;
$red: #D35D47 !default;
$orange: #fd7e14 !default;
$yellow: #D38F47 !default;
$green: #87C041 !default;
$red: #842716 !default;
$orange: #a84c01 !default;
$yellow: #916101 !default;
$green: #417106 !default;
$teal: #20c997 !default;
$cyan: #0dcaf0 !default;
$cyan: #0e6b7e !default;
// $primary: $blue;
$secondary: #506579;
$btn-disabled-color: #969696;
$form-switch-color: $secondary;
// $form-check-color: $primary;
$form-check-input-border: 2px solid rgba(#000, .25);
@import "bootstrap/scss/variables";
@import "./vars.common.scss";
$form-check-color: $primary;
$text-muted: $gray-500;
$link-underline-color: rgba($body-color, 0.25);
$link-hover-underline-color: $body-color;
$form-check-input-width: 1.3em;
$form-switch-width: 2.5em;
$form-switch-padding-start: 3em;
$form-check-input-checked-border-color: tint-color($form-check-color, $btn-active-border-tint-amount);
$form-check-input-checked-bg-color: tint-color($form-check-color, $btn-active-bg-tint-amount);

View file

@ -213,6 +213,11 @@
dependencies:
levn "^0.4.1"
"@fontsource/poppins@^5.1.0":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@fontsource/poppins/-/poppins-5.1.0.tgz#919205f53d99ec4751a945057ef525f06681df8a"
integrity sha512-tpLXlnNi2fwQjiipvuj4uNFHCdoLA8izRsKdoexZuEzjx0r/g1aKLf4ta6lFgF7L+/+AFdmaXFlUwwvmDzYH+g==
"@fontsource/work-sans@^4.5.12":
version "4.5.14"
resolved "https://registry.yarnpkg.com/@fontsource/work-sans/-/work-sans-4.5.14.tgz#965bf103baa9f812d042b5f678a34f632fa76f2e"
@ -426,95 +431,6 @@
resolved "https://registry.yarnpkg.com/@otplib/preset-browser/-/preset-browser-12.0.1.tgz#3f7f4dec25f8fcb5eed533c90df76784f219f088"
integrity sha512-64Eb6JLRRcER2NuIIVQIVNb3yn4mJLUwN1i3icmmNpTS+r4izwdM3eQs9wbeRjjX46kgfMZqPFYsC9JuGM0TKw==
"@parcel/watcher-android-arm64@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz#e32d3dda6647791ee930556aee206fcd5ea0fb7a"
integrity sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==
"@parcel/watcher-darwin-arm64@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz#0d9e680b7e9ec1c8f54944f1b945aa8755afb12f"
integrity sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==
"@parcel/watcher-darwin-x64@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz#f9f1d5ce9d5878d344f14ef1856b7a830c59d1bb"
integrity sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==
"@parcel/watcher-freebsd-x64@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz#2b77f0c82d19e84ff4c21de6da7f7d096b1a7e82"
integrity sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==
"@parcel/watcher-linux-arm-glibc@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz#92ed322c56dbafa3d2545dcf2803334aee131e42"
integrity sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==
"@parcel/watcher-linux-arm-musl@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz#cd48e9bfde0cdbbd2ecd9accfc52967e22f849a4"
integrity sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==
"@parcel/watcher-linux-arm64-glibc@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz#7b81f6d5a442bb89fbabaf6c13573e94a46feb03"
integrity sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==
"@parcel/watcher-linux-arm64-musl@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz#dcb8ff01077cdf59a18d9e0a4dff7a0cfe5fd732"
integrity sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==
"@parcel/watcher-linux-x64-glibc@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz#2e254600fda4e32d83942384d1106e1eed84494d"
integrity sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==
"@parcel/watcher-linux-x64-musl@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz#01fcea60fedbb3225af808d3f0a7b11229792eef"
integrity sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==
"@parcel/watcher-win32-arm64@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz#87cdb16e0783e770197e52fb1dc027bb0c847154"
integrity sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==
"@parcel/watcher-win32-ia32@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz#778c39b56da33e045ba21c678c31a9f9d7c6b220"
integrity sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==
"@parcel/watcher-win32-x64@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz#33873876d0bbc588aacce38e90d1d7480ce81cb7"
integrity sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==
"@parcel/watcher@^2.4.1":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.5.0.tgz#5c88818b12b8de4307a9d3e6dc3e28eba0dfbd10"
integrity sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==
dependencies:
detect-libc "^1.0.3"
is-glob "^4.0.3"
micromatch "^4.0.5"
node-addon-api "^7.0.0"
optionalDependencies:
"@parcel/watcher-android-arm64" "2.5.0"
"@parcel/watcher-darwin-arm64" "2.5.0"
"@parcel/watcher-darwin-x64" "2.5.0"
"@parcel/watcher-freebsd-x64" "2.5.0"
"@parcel/watcher-linux-arm-glibc" "2.5.0"
"@parcel/watcher-linux-arm-musl" "2.5.0"
"@parcel/watcher-linux-arm64-glibc" "2.5.0"
"@parcel/watcher-linux-arm64-musl" "2.5.0"
"@parcel/watcher-linux-x64-glibc" "2.5.0"
"@parcel/watcher-linux-x64-musl" "2.5.0"
"@parcel/watcher-win32-arm64" "2.5.0"
"@parcel/watcher-win32-ia32" "2.5.0"
"@parcel/watcher-win32-x64" "2.5.0"
"@popperjs/core@^2.11.8":
version "2.11.8"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
@ -1076,7 +992,7 @@ chardet@^0.7.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
chokidar@^3.5.1:
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
@ -1091,7 +1007,7 @@ chokidar@^3.5.1:
optionalDependencies:
fsevents "~2.3.2"
chokidar@^4.0.0, chokidar@^4.0.1:
chokidar@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.1.tgz#4a6dff66798fb0f72a94f616abbd7e1a19f31d41"
integrity sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==
@ -1351,11 +1267,6 @@ detect-europe-js@^0.1.2:
resolved "https://registry.yarnpkg.com/detect-europe-js/-/detect-europe-js-0.1.2.tgz#aa76642e05dae786efc2e01a23d4792cd24c7b88"
integrity sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
dijkstrajs@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23"
@ -2144,10 +2055,10 @@ ignore@^5.1.1, ignore@^5.2.0, ignore@^5.3.1:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
immutable@^5.0.2:
version "5.0.3"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1"
integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==
immutable@^4.0.0:
version "4.3.7"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381"
integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==
import-fresh@^3.2.1:
version "3.3.0"
@ -2519,7 +2430,7 @@ merge2@^1.3.0:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micromatch@^4.0.4, micromatch@^4.0.5:
micromatch@^4.0.4:
version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
@ -2610,11 +2521,6 @@ netmask@^2.0.2:
resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7"
integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==
node-addon-api@^7.0.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558"
integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==
node-fetch@^2.6.1:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
@ -3101,16 +3007,14 @@ safe-regex-test@^1.0.3:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass@~1.82:
version "1.82.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.82.0.tgz#30da277af3d0fa6042e9ceabd0d984ed6d07df70"
integrity sha512-j4GMCTa8elGyN9A7x7bEglx0VgSpNUG4W4wNedQ33wSMdnkqQCT8HTwOaVSV4e6yQovcu/3Oc4coJP/l0xhL2Q==
sass@1.78:
version "1.78.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.78.0.tgz#cef369b2f9dc21ea1d2cf22c979f52365da60841"
integrity sha512-AaIqGSrjo5lA2Yg7RvFZrlXDBCp3nV4XP73GrLGvdRWWwk+8H3l0SDvq/5bA4eF+0RFPLuWUk3E+P1U/YqnpsQ==
dependencies:
chokidar "^4.0.0"
immutable "^5.0.2"
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
optionalDependencies:
"@parcel/watcher" "^2.4.1"
semver@^6.1.0, semver@^6.3.1:
version "6.3.1"

View file

@ -27,7 +27,14 @@ pub(crate) async fn command(cli: &crate::Cli, enable_admin_token: bool) -> Resul
})
});
let config = load_config(&cli.config, true)?;
let config = match load_config(&cli.config, true) {
Ok(config) => config,
Err(error) => {
error!(?error, "Failed to load config file");
std::process::exit(1);
}
};
let services = Services::new(config.clone(), admin_token).await?;
install_database_logger(services.db.clone());

View file

@ -10,10 +10,6 @@ use warpgate_common::helpers::fs::secure_file;
use warpgate_common::{WarpgateConfig, WarpgateConfigStore};
pub fn load_config(path: &Path, secure: bool) -> Result<WarpgateConfig> {
if secure {
secure_file(path).context("Could not secure config")?;
}
let mut store: serde_yaml::Value = Config::builder()
.add_source(File::from(path))
.add_source(Environment::with_prefix("WARPGATE"))
@ -22,6 +18,10 @@ pub fn load_config(path: &Path, secure: bool) -> Result<WarpgateConfig> {
.try_deserialize()
.context("Could not parse YAML")?;
if secure {
secure_file(path).context("Could not secure config")?;
}
check_and_migrate_config(&mut store);
let store: WarpgateConfigStore =