diff --git a/main.go b/main.go index 4228e84..a2eb004 100644 --- a/main.go +++ b/main.go @@ -41,20 +41,6 @@ func main() { Automigrate: isGoRun, }) - app.OnAfterBootstrap().Add(func(e *core.BootstrapEvent) error { - // update app settings on first run - settings := app.Settings() - if app.Settings().Meta.AppName == "Acme" { - app.Settings().Meta.AppName = "Qoma" - app.Settings().Meta.HideControls = true - err := app.Dao().SaveSettings(settings) - if err != nil { - return err - } - } - return nil - }) - // serve site app.OnBeforeServe().Add(func(e *core.ServeEvent) error { switch isGoRun { @@ -116,7 +102,7 @@ func main() { // api route to return public key e.Router.GET("/api/qoma/getkey", func(c echo.Context) error { requestData := apis.RequestInfo(c) - if requestData.Admin == nil { + if requestData.AuthRecord == nil { return apis.NewForbiddenError("Forbidden", nil) } key, err := os.ReadFile("./pb_data/id_ed25519.pub") diff --git a/migrations/1720568457_collections_snapshot.go b/migrations/1720568457_collections_snapshot.go index 993c8ec..ecb2433 100644 --- a/migrations/1720568457_collections_snapshot.go +++ b/migrations/1720568457_collections_snapshot.go @@ -12,73 +12,10 @@ import ( func init() { m.Register(func(db dbx.Builder) error { jsonData := `[ - { - "id": "_pb_users_auth_", - "created": "2024-07-07 15:59:04.262Z", - "updated": "2024-07-09 23:42:40.542Z", - "name": "users", - "type": "auth", - "system": false, - "schema": [ - { - "system": false, - "id": "users_name", - "name": "name", - "type": "text", - "required": false, - "presentable": false, - "unique": false, - "options": { - "min": null, - "max": null, - "pattern": "" - } - }, - { - "system": false, - "id": "users_avatar", - "name": "avatar", - "type": "file", - "required": false, - "presentable": false, - "unique": false, - "options": { - "mimeTypes": [ - "image/jpeg", - "image/png", - "image/svg+xml", - "image/gif", - "image/webp" - ], - "thumbs": null, - "maxSelect": 1, - "maxSize": 5242880, - "protected": false - } - } - ], - "indexes": [], - "listRule": "id = @request.auth.id", - "viewRule": "id = @request.auth.id", - "createRule": "", - "updateRule": "id = @request.auth.id", - "deleteRule": "id = @request.auth.id", - "options": { - "allowEmailAuth": true, - "allowOAuth2Auth": true, - "allowUsernameAuth": true, - "exceptEmailDomains": null, - "manageRule": null, - "minPasswordLength": 8, - "onlyEmailDomains": null, - "onlyVerified": false, - "requireEmail": false - } - }, { "id": "2hz5ncl8tizk5nx", "created": "2024-07-07 16:08:20.979Z", - "updated": "2024-07-13 23:20:50.678Z", + "updated": "2024-07-14 03:36:23.090Z", "name": "systems", "type": "base", "system": false, @@ -156,17 +93,17 @@ func init() { } ], "indexes": [], - "listRule": null, - "viewRule": null, - "createRule": null, - "updateRule": null, - "deleteRule": null, + "listRule": "", + "viewRule": "@request.auth.id != \"\"", + "createRule": "@request.auth.id != \"\" && @request.auth.admin = true", + "updateRule": "", + "deleteRule": "@request.auth.id != \"\" && @request.auth.admin = true", "options": {} }, { "id": "ej9oowivz8b2mht", "created": "2024-07-07 16:09:09.179Z", - "updated": "2024-07-09 23:42:40.542Z", + "updated": "2024-07-14 03:36:23.089Z", "name": "system_stats", "type": "base", "system": false, @@ -203,7 +140,7 @@ func init() { "indexes": [ "CREATE INDEX ` + "`" + `idx_GxIee0j` + "`" + ` ON ` + "`" + `system_stats` + "`" + ` (` + "`" + `system` + "`" + `)" ], - "listRule": null, + "listRule": "@request.auth.id != \"\"", "viewRule": null, "createRule": null, "updateRule": null, @@ -213,7 +150,7 @@ func init() { { "id": "juohu4jipgc13v7", "created": "2024-07-07 16:09:57.976Z", - "updated": "2024-07-09 23:42:40.542Z", + "updated": "2024-07-14 03:36:23.090Z", "name": "container_stats", "type": "base", "system": false, @@ -248,12 +185,71 @@ func init() { } ], "indexes": [], - "listRule": null, + "listRule": "@request.auth.id != \"\"", "viewRule": null, "createRule": null, "updateRule": null, "deleteRule": null, "options": {} + }, + { + "id": "_pb_users_auth_", + "created": "2024-07-14 03:36:23.076Z", + "updated": "2024-07-14 03:36:23.087Z", + "name": "users", + "type": "auth", + "system": false, + "schema": [ + { + "system": false, + "id": "users_avatar", + "name": "avatar", + "type": "file", + "required": false, + "presentable": false, + "unique": false, + "options": { + "mimeTypes": [ + "image/jpeg", + "image/png", + "image/svg+xml", + "image/gif", + "image/webp" + ], + "thumbs": null, + "maxSelect": 1, + "maxSize": 5242880, + "protected": false + } + }, + { + "system": false, + "id": "ebyl7gfs", + "name": "admin", + "type": "bool", + "required": false, + "presentable": false, + "unique": false, + "options": {} + } + ], + "indexes": [], + "listRule": "id = @request.auth.id", + "viewRule": "id = @request.auth.id", + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": { + "allowEmailAuth": true, + "allowOAuth2Auth": true, + "allowUsernameAuth": true, + "exceptEmailDomains": null, + "manageRule": null, + "minPasswordLength": 8, + "onlyEmailDomains": null, + "onlyVerified": false, + "requireEmail": false + } } ]` diff --git a/migrations/initial-settings.go b/migrations/initial-settings.go new file mode 100644 index 0000000..dcb0a37 --- /dev/null +++ b/migrations/initial-settings.go @@ -0,0 +1,19 @@ +package migrations + +import ( + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db) + + settings, _ := dao.FindSettings() + settings.Meta.AppName = "Qoma" + settings.Meta.HideControls = true + + return dao.SaveSettings(settings) + }, nil) +} diff --git a/site/public/favicon-green.svg b/site/public/favicon-green.svg index 5efca21..cf1bbee 100644 --- a/site/public/favicon-green.svg +++ b/site/public/favicon-green.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/site/src/components/add-server.tsx b/site/src/components/add-server.tsx index 51d38d4..bf4dbb7 100644 --- a/site/src/components/add-server.tsx +++ b/site/src/components/add-server.tsx @@ -79,17 +79,17 @@ export function AddServerButton() { Add System - + - Add New System + Add New System The agent must be running on the server to connect. Copy the{' '} docker-compose.yml for the agent below. -
-
+ +
-
- +
diff --git a/site/src/components/mode-toggle.tsx b/site/src/components/mode-toggle.tsx index 8ce1f1b..c0b404f 100644 --- a/site/src/components/mode-toggle.tsx +++ b/site/src/components/mode-toggle.tsx @@ -21,7 +21,7 @@ export function ModeToggle() { Toggle theme - + setTheme('light')}>Light setTheme('dark')}>Dark setTheme('system')}>System diff --git a/site/src/components/routes/home.tsx b/site/src/components/routes/home.tsx index d6eb676..21325e6 100644 --- a/site/src/components/routes/home.tsx +++ b/site/src/components/routes/home.tsx @@ -1,5 +1,4 @@ import { Suspense, lazy, useEffect } from 'react' -// import { DataTable } from '../server-table/data-table' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card' const SystemsTable = lazy(() => import('../server-table/systems-table')) diff --git a/site/src/components/routes/server.tsx b/site/src/components/routes/server.tsx index bd6dc8a..7345bcd 100644 --- a/site/src/components/routes/server.tsx +++ b/site/src/components/routes/server.tsx @@ -4,14 +4,18 @@ import { Suspense, lazy, useEffect, useState } from 'react' import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '../ui/card' import { useStore } from '@nanostores/react' import Spinner from '../spinner' -import CpuChart from '../charts/cpu-chart' -import MemChart from '../charts/mem-chart' -import DiskChart from '../charts/disk-chart' -import ContainerCpuChart from '../charts/container-cpu-chart' +// import CpuChart from '../charts/cpu-chart' +// import MemChart from '../charts/mem-chart' +// import DiskChart from '../charts/disk-chart' +// import ContainerCpuChart from '../charts/container-cpu-chart' +// import ContainerMemChart from '../charts/container-mem-chart' import { CpuIcon, MemoryStickIcon } from 'lucide-react' -import ContainerMemChart from '../charts/container-mem-chart' -// const CpuChart = lazy(() => import('../cpu-chart')) +const CpuChart = lazy(() => import('../charts/cpu-chart')) +const ContainerCpuChart = lazy(() => import('../charts/container-cpu-chart')) +const MemChart = lazy(() => import('../charts/mem-chart')) +const ContainerMemChart = lazy(() => import('../charts/container-mem-chart')) +const DiskChart = lazy(() => import('../charts/disk-chart')) function timestampToBrowserTime(timestamp: string) { const date = new Date(timestamp) @@ -138,6 +142,9 @@ export default function ServerDetail({ name }: { name: string }) { return ( <>
+
+

{name}

+
@@ -203,9 +210,9 @@ export default function ServerDetail({ name }: { name: string }) { Precise usage at the recorded time - {/* }> */} - - {/* */} + }> + +
diff --git a/site/src/components/server-table/systems-table.tsx b/site/src/components/server-table/systems-table.tsx index 33dea94..7bd04de 100644 --- a/site/src/components/server-table/systems-table.tsx +++ b/site/src/components/server-table/systems-table.tsx @@ -61,7 +61,7 @@ import { useMemo, useState } from 'react' import { $servers, pb, navigate } from '@/lib/stores' import { useStore } from '@nanostores/react' import { AddServerButton } from '../add-server' -import { cn, copyToClipboard } from '@/lib/utils' +import { cn, copyToClipboard, isAdmin } from '@/lib/utils' import { Dialog, DialogContent, @@ -75,7 +75,7 @@ function CellFormatter(info: CellContext) { const val = info.getValue() as number return (
- + table.getColumn('name')?.setFilterValue(event.target.value)} className="max-w-sm" /> -
- -
+ {isAdmin() && ( +
+ +
+ )}
diff --git a/site/src/components/user-auth-form.tsx b/site/src/components/user-auth-form.tsx index 0847dac..6efc8ed 100644 --- a/site/src/components/user-auth-form.tsx +++ b/site/src/components/user-auth-form.tsx @@ -3,8 +3,8 @@ import { cn } from '@/lib/utils' import { buttonVariants } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' -import { Github, LoaderCircle, LogInIcon } from 'lucide-react' -import { pb } from '@/lib/stores' +import { Github, LoaderCircle, LockIcon, LogInIcon, MailIcon, UserIcon } from 'lucide-react' +import { $authenticated, pb } from '@/lib/stores' import * as v from 'valibot' import { toast } from './ui/use-toast' import { @@ -24,13 +24,21 @@ const passwordSchema = v.pipe( ) const LoginSchema = v.looseObject({ - username: honeypot, + name: honeypot, email: emailSchema, password: passwordSchema, }) const RegisterSchema = v.looseObject({ - username: honeypot, + name: honeypot, + username: v.pipe( + v.string(), + v.regex( + /^(?=.*[a-zA-Z])[a-zA-Z0-9_-]+$/, + 'Invalid username. You may use alphanumeric characters, underscores, and hyphens.' + ), + v.minLength(3, 'Username must be at least 3 characters long.') + ), email: emailSchema, password: passwordSchema, passwordConfirm: passwordSchema, @@ -68,7 +76,7 @@ export function UserAuthForm({ setErrors(errors) return } - const { email, password, passwordConfirm } = result.output + const { email, password, passwordConfirm, username } = result.output if (isFirstRun) { // check that passwords match if (password !== passwordConfirm) { @@ -82,9 +90,19 @@ export function UserAuthForm({ passwordConfirm: password, }) await pb.admins.authWithPassword(email, password) + await pb.collection('users').create({ + username, + email, + password, + passwordConfirm: password, + admin: true, + verified: true, + }) + await pb.collection('users').authWithPassword(email, password) } else { - await pb.admins.authWithPassword(email, password) + await pb.collection('users').authWithPassword(email, password) } + $authenticated.set(true) } catch (e) { return toast({ title: 'Login attempt failed', @@ -100,30 +118,49 @@ export function UserAuthForm({
setErrors({})}>
-
- {/* honeypot */} - - -
-
+ {isFirstRun && ( +
+ + + + {errors?.username &&

{errors.username}

} +
+ )} +
+ {errors?.email &&

{errors.email}

}
-
+
+ @@ -135,11 +172,13 @@ export function UserAuthForm({ type="password" autoComplete="current-password" disabled={isLoading || isGitHubLoading} + className="pl-9" /> {errors?.password &&

{errors.password}

}
{isFirstRun && ( -
+
+ @@ -151,12 +190,18 @@ export function UserAuthForm({ type="password" autoComplete="current-password" disabled={isLoading || isGitHubLoading} + className="pl-9" /> {errors?.passwordConfirm && (

{errors.passwordConfirm}

)}
)} +
+ {/* honeypot */} + + +