From 357e3ad5d71324a64f2bc5d7d37d54059a213278 Mon Sep 17 00:00:00 2001 From: Henry Dollman Date: Sat, 13 Jul 2024 16:25:27 -0400 Subject: [PATCH] login and other updates --- .../components/charts/container-cpu-chart.tsx | 1 - site/src/components/charts/cpu-chart.tsx | 3 +- site/src/components/login.tsx | 26 +-- site/src/components/routes/home.tsx | 38 +--- site/src/components/routes/server.tsx | 11 +- .../components/server-table/data-table.tsx | 37 +++- site/src/components/user-auth-form.tsx | 177 +++++++++++------- site/src/main.tsx | 45 ++++- 8 files changed, 193 insertions(+), 145 deletions(-) diff --git a/site/src/components/charts/container-cpu-chart.tsx b/site/src/components/charts/container-cpu-chart.tsx index 1f6878e..57e14a1 100644 --- a/site/src/components/charts/container-cpu-chart.tsx +++ b/site/src/components/charts/container-cpu-chart.tsx @@ -88,7 +88,6 @@ export default function ({ chartData }: { chartData: Record ( diff --git a/site/src/components/login.tsx b/site/src/components/login.tsx index 692cf78..62af7ff 100644 --- a/site/src/components/login.tsx +++ b/site/src/components/login.tsx @@ -1,22 +1,24 @@ import { UserAuthForm } from '@/components/user-auth-form' import { Logo } from './logo' +import { useEffect } from 'react' export default function () { + useEffect(() => { + document.title = 'Login / Qoma' + }, []) return ( -
-
-
+
+
+
-

- -
Qoma
+

+ + Qoma

-

- Enter your email to sign in to your account -

+

Please sign in to your account

-

+

{/* todo: add forgot password section to readme and link to section reset w/ command or link to pb reset */}

-
+ {/*
-
+
*/}
) } diff --git a/site/src/components/routes/home.tsx b/site/src/components/routes/home.tsx index 0f9bdb3..ea7524c 100644 --- a/site/src/components/routes/home.tsx +++ b/site/src/components/routes/home.tsx @@ -1,48 +1,12 @@ import { Suspense, lazy, useEffect } from 'react' -import { $servers, pb } from '@/lib/stores' // import { DataTable } from '../server-table/data-table' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card' -import { SystemRecord } from '@/types' -import { updateServerList } from '@/lib/utils' const DataTable = lazy(() => import('../server-table/data-table')) export default function () { useEffect(() => { - document.title = 'Qoma Dashboard' - }, []) - - useEffect(updateServerList, []) - - useEffect(() => { - pb.collection('systems').subscribe('*', (e) => { - const curServers = $servers.get() - const newServers = [] - console.log('e', e) - if (e.action === 'delete') { - for (const server of curServers) { - if (server.id !== e.record.id) { - newServers.push(server) - } - } - } else { - let found = 0 - for (const server of curServers) { - if (server.id === e.record.id) { - found = newServers.push(e.record) - } else { - newServers.push(server) - } - } - if (!found) { - newServers.push(e.record) - } - } - $servers.set(newServers) - }) - return () => { - pb.collection('systems').unsubscribe('*') - } + document.title = 'Dashboard / Qoma' }, []) return ( diff --git a/site/src/components/routes/server.tsx b/site/src/components/routes/server.tsx index 5c3fe78..bd6dc8a 100644 --- a/site/src/components/routes/server.tsx +++ b/site/src/components/routes/server.tsx @@ -71,7 +71,7 @@ export default function ServerDetail({ name }: { name: string }) { // console.log('sctats', records) setServerStats(records.items) }) - }, [server]) + }, [server, servers]) // get cpu data useEffect(() => { @@ -100,16 +100,9 @@ export default function ServerDetail({ name }: { name: string }) { } // console.log('running') const matchingServer = servers.find((s) => s.name === name) as SystemRecord - + // console.log('found server', matchingServer) setServer(matchingServer) - console.log('found server', matchingServer) - // pb.collection('systems') - // .getOne(serverId) - // .then((record) => { - // setServer(record) - // }) - pb.collection('container_stats') .getList(1, 60, { filter: `system="${matchingServer.id}"`, diff --git a/site/src/components/server-table/data-table.tsx b/site/src/components/server-table/data-table.tsx index 3084d4e..909af02 100644 --- a/site/src/components/server-table/data-table.tsx +++ b/site/src/components/server-table/data-table.tsx @@ -62,6 +62,14 @@ import { $servers, pb, navigate } from '@/lib/stores' import { useStore } from '@nanostores/react' import { AddServerButton } from '../add-server' import { cn, copyToClipboard } from '@/lib/utils' +import { + Dialog, + DialogContent, + DialogTrigger, + DialogDescription, + DialogTitle, + DialogHeader, +} from '@/components/ui/dialog' function CellFormatter(info: CellContext) { const val = info.getValue() as number @@ -121,6 +129,7 @@ export default function () { style={{ marginBottom: '-1px' }} > + + + + + + + Notifications + + + The agent must be running on the server to connect. Copy the{' '} + docker-compose.yml for the + agent below. + + + - @@ -301,7 +320,7 @@ export default function () { })} onClick={(e) => { const target = e.target as HTMLElement - if (target.tagName !== 'BUTTON' && !target.hasAttribute('role')) { + if (!target.closest('[data-nolink]') && e.currentTarget.contains(target)) { navigate(`/server/${row.original.name}`) } }} diff --git a/site/src/components/user-auth-form.tsx b/site/src/components/user-auth-form.tsx index bee950f..8d20bc5 100644 --- a/site/src/components/user-auth-form.tsx +++ b/site/src/components/user-auth-form.tsx @@ -8,78 +8,88 @@ import * as React from 'react' // import * as z from 'zod' import { cn } from '@/lib/utils' -import { userAuthSchema } from '@/lib/validations/auth' import { buttonVariants } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' // import { toast } from '@/components/ui/use-toast' -import { Github, LoaderCircle } from 'lucide-react' +import { Github, LoaderCircle, LogInIcon } from 'lucide-react' +import { pb } from '@/lib/stores' +import * as v from 'valibot' +import { toast } from './ui/use-toast' +import { + Dialog, + DialogContent, + DialogTrigger, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' -interface UserAuthFormProps extends React.HTMLAttributes {} +const LoginSchema = v.object({ + email: v.pipe(v.string(), v.email('Invalid email address.')), + password: v.pipe(v.string(), v.minLength(10, 'Password must be at least 10 characters long.')), +}) -type FormData = z.infer +// type LoginData = v.InferOutput // { email: string; password: string } -export function UserAuthForm({ className, ...props }: UserAuthFormProps) { - const signIn = (s: string) => console.log(s) - const handleSubmit = (e: React.FormEvent) => { - // e.preventDefault() - signIn('github') - } - - const errors = { - email: 'This field is required', - password: 'This field is required', - } - - // const { - // register, - // handleSubmit, - // formState: { errors }, - // } = useForm({ - // resolver: zodResolver(userAuthSchema), - // }) +export function UserAuthForm({ className, ...props }: { className?: string }) { const [isLoading, setIsLoading] = React.useState(false) const [isGitHubLoading, setIsGitHubLoading] = React.useState(false) + const [errors, setErrors] = React.useState>({}) + // const searchParams = useSearchParams() - async function onSubmit(data: FormData) { + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() setIsLoading(true) - - alert('do pb stuff') - - // const signInResult = await signIn('email', { - // email: data.email.toLowerCase(), - // redirect: false, - // callbackUrl: searchParams?.get('from') || '/dashboard', - // }) - - setIsLoading(false) - - if (!signInResult?.ok) { - alert('Your sign in request failed. Please try again.') - // return toast({ - // title: 'Something went wrong.', - // description: 'Your sign in request failed. Please try again.', - // variant: 'destructive', - // }) + try { + const formData = new FormData(e.target as HTMLFormElement) + const data = Object.fromEntries(formData) as Record + const result = v.safeParse(LoginSchema, data) + if (!result.success) { + let errors = {} + for (const issue of result.issues) { + // @ts-ignore + errors[issue.path[0].key] = issue.message + } + setErrors(errors) + return + } + const { email, password } = result.output + let firstRun = true + if (firstRun) { + await pb.admins.create({ + email, + password, + passwordConfirm: password, + }) + await pb.admins.authWithPassword(email, password) + } else { + await pb.admins.authWithPassword(email, password) + } + } catch (e) { + return toast({ + title: 'Login attempt failed', + description: 'Please check your credentials and try again', + variant: 'destructive', + }) + } finally { + setIsLoading(false) } - - // return toast({ - // title: 'Check your email', - // description: 'We sent you a login link. Be sure to check your spam too.', - // }) } return (
-
+
- {errors?.email &&

{errors.email.message}

} + {errors?.email &&

{errors.email}

} +
+
+ + + {errors?.password &&

{errors.password}

}
@@ -103,23 +132,35 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) { Or continue with
- + + + + + + + OAuth support coming soon + + OAuth / OpenID with all major providers should be available at 1.0.0. + + + +
) } diff --git a/site/src/main.tsx b/site/src/main.tsx index 3f76e37..99ce9b7 100644 --- a/site/src/main.tsx +++ b/site/src/main.tsx @@ -24,6 +24,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from './components/ui/dropdown-menu.tsx' +import { SystemRecord } from './types' const ServerDetail = lazy(() => import('./components/routes/server.tsx')) const CommandPalette = lazy(() => import('./components/command-palette.tsx')) @@ -37,27 +38,53 @@ const App = () => { // get servers useEffect(updateServerList, []) + useEffect(() => { + pb.collection('systems').subscribe('*', (e) => { + const curServers = $servers.get() + const newServers = [] + // console.log('e', e) + if (e.action === 'delete') { + for (const server of curServers) { + if (server.id !== e.record.id) { + newServers.push(server) + } + } + } else { + let found = 0 + for (const server of curServers) { + if (server.id === e.record.id) { + found = newServers.push(e.record) + } else { + newServers.push(server) + } + } + if (!found) { + newServers.push(e.record) + } + } + $servers.set(newServers) + }) + return () => { + pb.collection('systems').unsubscribe('*') + } + }, []) + // update favicon useEffect(() => { if (!authenticated || !servers.length) { - console.log('no auth favicon') updateFavicon('/favicon.svg') } else { - const cleanup = () => { - updateFavicon('/favicon.svg') - } let up = false for (const server of servers) { if (server.status === 'down') { - console.log('down', server) updateFavicon('/favicon-red.svg') - return cleanup + return () => updateFavicon('/favicon.svg') } else if (server.status === 'up') { up = true } } updateFavicon(up ? '/favicon-green.svg' : '/favicon.svg') - return cleanup + return () => updateFavicon('/favicon.svg') } return () => { updateFavicon('/favicon.svg') @@ -155,7 +182,6 @@ const Layout = () => {
-
) @@ -167,6 +193,9 @@ ReactDOM.createRoot(document.getElementById('app')!).render( + + + )