diff --git a/latest b/latest index 99a4aef..c641220 100644 --- a/latest +++ b/latest @@ -1 +1 @@ -v1.1.3 +v1.1.4 diff --git a/pkg/rest/setup.go b/pkg/rest/setup.go index 454796a..1792609 100644 --- a/pkg/rest/setup.go +++ b/pkg/rest/setup.go @@ -127,7 +127,7 @@ func (c *Context) contextHandler(w http.ResponseWriter, r *http.Request) { } } - out, err := json.Marshal(ContextSetupResponse{SetupCompleted: c.SetupCompleted}) + out, err := json.Marshal(ContextSetupResponse{SetupCompleted: c.SetupCompleted, CloudType: c.CloudType}) if err != nil { c.returnError(w, err, http.StatusBadRequest) return diff --git a/pkg/rest/types.go b/pkg/rest/types.go index 66af301..133b2fc 100644 --- a/pkg/rest/types.go +++ b/pkg/rest/types.go @@ -68,7 +68,8 @@ type ContextRequest struct { Protocol string `json:"protocol"` } type ContextSetupResponse struct { - SetupCompleted bool `json:"setupCompleted"` + SetupCompleted bool `json:"setupCompleted"` + CloudType string `json:"cloudType"` } type AuthMethodsResponse struct { diff --git a/webapp/src/AppInit/AppInit.tsx b/webapp/src/AppInit/AppInit.tsx index e1ac419..3c86f40 100644 --- a/webapp/src/AppInit/AppInit.tsx +++ b/webapp/src/AppInit/AppInit.tsx @@ -27,7 +27,7 @@ import { AppSettings } from '../Constants/Constants'; } if (!setupCompleted) { - return + return } else { return children } diff --git a/webapp/src/AppInit/SetAdminPassword.tsx b/webapp/src/AppInit/SetAdminPassword.tsx index a9ef519..9d3872d 100644 --- a/webapp/src/AppInit/SetAdminPassword.tsx +++ b/webapp/src/AppInit/SetAdminPassword.tsx @@ -43,6 +43,13 @@ export function SetAdminPassword({onChangeStep, secret}: Props) { } passwordMutation.mutate(password) } + const captureEnter = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + if(password !== "" && password2 !== "") { + changePassword() + } + } + } return (
@@ -51,16 +58,20 @@ export function SetAdminPassword({onChangeStep, secret}: Props) { Set a password for the admin user. At the next screen you'll be able to login with the username "admin" and the password you'll set now. {passwordMutation.isPending ? ( -
Setting Password...
+
Setting Password for user 'admin'...
) : (
Your password - setPassword(event.currentTarget.value)} value={password} error={passwordError} + onKeyDown={(e) => captureEnter(e)} /> Repeat password @@ -68,9 +79,11 @@ export function SetAdminPassword({onChangeStep, secret}: Props) { setPassword2(event.currentTarget.value)} value={password2} - error={password2Error} + error={password2Error} + onKeyDown={(e) => captureEnter(e)} />
diff --git a/webapp/src/AppInit/SetSecret.tsx b/webapp/src/AppInit/SetSecret.tsx index a726e74..327f76a 100644 --- a/webapp/src/AppInit/SetSecret.tsx +++ b/webapp/src/AppInit/SetSecret.tsx @@ -1,44 +1,97 @@ -import { Text, Title, TextInput, Button } from '@mantine/core'; +import { Text, Title, TextInput, Button, Card, Grid, Container, Center, Alert, ActionIcon } from '@mantine/core'; +import { useClipboard } from '@mantine/hooks'; +import { TbCheck, TbCopy } from 'react-icons/tb'; import classes from './SetupBanner.module.css'; import {useState} from 'react'; -import axios from 'axios'; +import axios, { AxiosError } from 'axios'; import { AppSettings } from '../Constants/Constants'; import { useQueryClient, useMutation, } from '@tanstack/react-query' +import { TbInfoCircle } from 'react-icons/tb'; type Props = { onChangeStep: (newType: number) => void; onChangeSecret: (newType: string) => void; - }; + cloudType: string; +}; -export function SetSecret({onChangeStep, onChangeSecret}: Props) { +type SetupResponse = { + secret: string; + tagHash: string; + instanceID: string; +} +type SetupResponseError = { + error: string; +} + +const randomHex = (length:number) => { + const bytes = window.crypto.getRandomValues(new Uint8Array(length)) + var hexstring='', h; + for(var i=0; i(""); + const [setupResponse, setSetupResponse] = useState({secret: "", tagHash: "", instanceID: ""}); const [secretError, setSecretError] = useState(""); + const [randomHexValue] = useState(randomHex(16)) const secretMutation = useMutation({ - mutationFn: (newSecret: string) => { + mutationFn: (setupResponse: SetupResponse) => { setSecretError("") - return axios.post(AppSettings.url + '/context', {secret: newSecret}) + return axios.post(AppSettings.url + '/context', setupResponse) }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['context'] }) - onChangeSecret(secret) + onChangeSecret(setupResponse.secret) onChangeStep(1) }, - onError: (error) => { - if(error.message.includes("status code 403")) { - setSecretError("Invalid secret") - } else { + onError: (error:AxiosError) => { + const errorMessage = error.response?.data as SetupResponseError + if(errorMessage?.error === undefined) { setSecretError("Error: "+ error.message) + } else { + setSecretError(errorMessage.error) } - } + }, }) + const captureEnter = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + secretMutation.mutate(setupResponse) + } + } + const alertIcon = + const hasMoreOptions = cloudType === "aws" || cloudType === "digitalocean" ? true : false + const colSpanWithSSH = hasMoreOptions ? 3 : 6 + return ( -
-
- Start Setup... + +
+ Start Setup +
+ {secretError !== "" ? + + + + {secretError} + + + : + null + } + + + + + {hasMoreOptions ? "Option 1: " : ""}With SSH Access Enter the secret to start the setup. @@ -57,14 +110,69 @@ export function SetSecret({onChangeStep, onChangeSecret}: Props) { setSecret(event.currentTarget.value)} - value={secret} - error={secretError} + onChange={(event) => setSetupResponse({ ...setupResponse, secret: event.currentTarget.value})} + value={setupResponse.secret} + onKeyDown={(e) => captureEnter(e)} /> - +
)} -
-
+ + + {cloudType === "aws" ? + + + {hasMoreOptions ? "Option 2: " : ""}Without SSH Access + + + Enter the EC2 Instance ID of the VPN Server + + {secretMutation.isPending ? ( +
Checking Instance ID...
+ ) : ( +
+ setSetupResponse({ ...setupResponse, instanceID: event.currentTarget.value})} + value={setupResponse.instanceID} + onKeyDown={(e) => captureEnter(e)} + /> + +
+ )} +
+
+ : null } + {cloudType === "digitalocean" ? + + + {hasMoreOptions ? "Option 2: " : ""}Without SSH Access + + + Add the following tag to the droplet by going to the droplet settings and opening the Tags page. + + {secretMutation.isPending ? ( +
Checking tag...
+ ) : ( +
+ clipboard.copy(randomHexValue)}> + { clipboard.copied ? : } + + } + /> + +
+ )} +
+
+ : null } + + ); } \ No newline at end of file diff --git a/webapp/src/AppInit/SetupBanner.module.css b/webapp/src/AppInit/SetupBanner.module.css index fc2debc..37e8dfd 100644 --- a/webapp/src/AppInit/SetupBanner.module.css +++ b/webapp/src/AppInit/SetupBanner.module.css @@ -2,9 +2,6 @@ display: flex; align-items: center; padding: calc(var(--mantine-spacing-xl) * 2); - border-radius: var(--mantine-radius-md); - background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-8)); - border: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-8)); @media (max-width: $mantine-breakpoint-sm) { flex-direction: column-reverse; @@ -58,4 +55,7 @@ border-top-left-radius: 0; border-bottom-left-radius: 0; } - \ No newline at end of file + + .error:first-letter { + text-transform: capitalize + } \ No newline at end of file diff --git a/webapp/src/AppInit/SetupBanner.tsx b/webapp/src/AppInit/SetupBanner.tsx index 76b99f6..2360d6a 100644 --- a/webapp/src/AppInit/SetupBanner.tsx +++ b/webapp/src/AppInit/SetupBanner.tsx @@ -5,9 +5,10 @@ import React from 'react'; type Props = { onCompleted: (newType: boolean) => void; + cloudType: string; }; -export function SetupBanner({onCompleted}:Props) { +export function SetupBanner({onCompleted, cloudType}:Props) { const [step, setStep] = useState(0); const [secret, setSecret] = useState(""); @@ -18,7 +19,7 @@ export function SetupBanner({onCompleted}:Props) { }, [step]); if(step === 0) { - return + return } else if(step === 1) { return }