This commit is contained in:
Henry Dollman
2024-07-13 12:41:05 -04:00
parent 7e834429cc
commit 86cfa5079e
8 changed files with 74 additions and 62 deletions

10
main.go
View File

@@ -131,7 +131,7 @@ func main() {
}
func serverUpdateTicker() {
ticker := time.NewTicker(30 * time.Second)
ticker := time.NewTicker(60 * time.Second)
for range ticker.C {
updateServers()
}
@@ -220,9 +220,11 @@ func setInactive(record *models.Record) {
delete(serverConnections, record.Id)
}
// set inactive
record.Set("status", "down")
if err := app.Dao().SaveRecord(record); err != nil {
app.Logger().Error("Failed to update record: ", "err", err.Error())
if record.Get("status") != "down" {
record.Set("status", "down")
if err := app.Dao().SaveRecord(record); err != nil {
app.Logger().Error("Failed to update record: ", "err", err.Error())
}
}
}

View File

@@ -44,7 +44,6 @@ export function AddServerButton() {
}
// get public key
pb.send('/getkey', {}).then(({ key }) => {
console.log('key', key)
$publicKey.set(key)
})
}, [open])
@@ -77,12 +76,12 @@ export function AddServerButton() {
<DialogTrigger asChild>
<Button variant="outline" className="flex gap-1">
<Plus className="h-4 w-4 mr-auto" />
Add <span className="hidden sm:inline">Server</span>
Add <span className="hidden sm:inline">System</span>
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Add New Server</DialogTitle>
<DialogTitle>Add New System</DialogTitle>
<DialogDescription>
The agent must be running on the server to connect. Copy the{' '}
<code className="bg-muted px-1 rounded-sm">docker-compose.yml</code> for the agent
@@ -153,7 +152,7 @@ export function AddServerButton() {
>
Copy docker compose
</Button>
<Button>Add server</Button>
<Button>Add system</Button>
</DialogFooter>
</form>
</DialogContent>

View File

@@ -1,56 +1,38 @@
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button'
import { UserAuthForm } from '@/components/user-auth-form'
import { ChevronLeft } from 'lucide-react'
import { Logo } from './logo'
export default function () {
return (
<div className="container relative hidden h-screen flex-col items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0">
<div className="lg:p-8">
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
<div className="flex flex-col space-y-2 text-center">
{/* <img
className="mx-auto h-10 w-10 mb-2"
src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/Numelon_Logo.png/240px-Numelon_Logo.png"
alt=""
/> */}
<h1 className="text-2xl font-semibold tracking-tight">Welcome back</h1>
<div className="relative h-screen grid lg:max-w-none lg:grid-cols-2 lg:px-0">
<div className="grid items-center">
<div className="flex flex-col justify-center space-y-6 w-full px-4 max-w-[22em] mx-auto">
<div className="text-center">
<h1 className="mb-4">
<Logo className="h-6 fill-foreground mx-auto" />
<div className="sr-only">Qoma</div>
</h1>
<p className="text-sm text-muted-foreground">
Enter your email to sign in to your account
</p>
</div>
<UserAuthForm />
<p className="px-8 text-center text-sm text-muted-foreground">
<a href="/register" className="hover:text-brand underline underline-offset-4">
Don&apos;t have an account? Sign Up
{/* todo: add forgot password section to readme and link to section
reset w/ command or link to pb reset */}
<a
href="https://github.com/henrygd/qoma"
className="hover:text-brand underline underline-offset-4"
>
Forgot password?
</a>
</p>
</div>
</div>
<div className="relative hidden h-full flex-col bg-muted p-10 text-white dark:border-r lg:flex">
<div
className="absolute inset-0 bg-background bg-cover opacity-80"
style={{
backgroundImage: `url(https://directus.cloud/assets/waves.2b156907.svg)`,
}}
></div>
<div className="relative z-20 flex gap-2 items-center text-lg font-medium ml-auto">
placeholder
<img
className={'w-6 h-6'}
src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/df/Numelon_Logo.png/240px-Numelon_Logo.png"
alt=""
/>
</div>
{/* <div className="relative z-20 mt-auto">
<blockquote className="space-y-2">
<p className="text-lg">
“This library has saved me countless hours of work and helped me deliver stunning
designs to my clients faster than ever before.”
</p>
<footer className="text-sm">Sofia Davis</footer>
</blockquote>
</div> */}
<div className="relative hidden h-full bg-primary lg:block">
<img
className="absolute inset-0 h-full w-full object-cover bg-primary"
src="/penguin-and-egg.avif"
></img>
</div>
</div>
)

View File

@@ -1,7 +1,7 @@
export function Logo({ className }: { className?: string }) {
return (
// audiowide
<svg viewBox="0 0 299 82" className={className}>
<svg viewBox="0 0 300 82" className={className}>
<path d="M202.1 36v35h-12.6V36a7.5 7.5 0 0 0-.2-1.5q-.1-.8-.5-1.4a3.9 3.9 0 0 0-.7-.9q-1-1-3-1.3a9.2 9.2 0 0 0-.9 0h-16.5V71H155V24.5a6.3 6.3 0 0 1 .4-2.2 6 6 0 0 1 .1-.3q.5-1.1 1.4-2 .9-.8 2-1.3a6.4 6.4 0 0 1 2.5-.5h23a17.2 17.2 0 0 1 3 .3 22.8 22.8 0 0 1 2.6.6q3 1 5.8 3v-3.9h17.1a17.7 17.7 0 0 1 2.6.2 21.3 21.3 0 0 1 1.2.2 19.5 19.5 0 0 1 3.9 1.3 21.5 21.5 0 0 1 .1 0q2 .9 3.8 2.3a15 15 0 0 1 3.2 3.3 16.3 16.3 0 0 1 0 0 16.3 16.3 0 0 1 1.6 3 19.7 19.7 0 0 1 .6 1.6q.8 2.6.8 5.9v35H218V36a8 8 0 0 0-.1-1.5q-.3-1.4-1.1-2.3-1.3-1.3-3.9-1.3h-11.4q.6 2.4.6 5Zm94.8-.3v17.8a17.3 17.3 0 0 1-.3 3 23.1 23.1 0 0 1-.7 2.7 17 17 0 0 1-3 5.7 15.9 15.9 0 0 1-3.6 3.3 19.6 19.6 0 0 1-1.8 1Q284.4 71 280 71a25.4 25.4 0 0 1-.6 0h-22.9a17.3 17.3 0 0 1-3-.3 23.1 23.1 0 0 1-2.7-.6 17 17 0 0 1-5.7-3 15.9 15.9 0 0 1-3.3-3.7 19.6 19.6 0 0 1-1-1.8q-1.7-3.1-1.8-7.5a25.4 25.4 0 0 1 0-.6 17.3 17.3 0 0 1 .3-3 23.4 23.4 0 0 1 .6-2.7q1-3 3-5.7a15.9 15.9 0 0 1 3.7-3.3 19.6 19.6 0 0 1 1.8-1q3.1-1.7 7.5-1.8a25.4 25.4 0 0 1 .6 0h22.9V48h-22.9a6.8 6.8 0 0 0-1.6.2 4.6 4.6 0 0 0-2.4 1.4A5.5 5.5 0 0 0 251 53a7.3 7.3 0 0 0 0 .6 6 6 0 0 0 .2 1.7 4.4 4.4 0 0 0 1.4 2.2 5.8 5.8 0 0 0 3.9 1.4h22.8a6.9 6.9 0 0 0 1.6-.2 4.6 4.6 0 0 0 2.4-1.4 5.4 5.4 0 0 0 1.4-3.2 7.2 7.2 0 0 0 0-.7V35.7a6.5 6.5 0 0 0-.2-1.7 4.8 4.8 0 0 0-1.3-2.3q-1.6-1.4-4-1.4h-27.8v-12h27.9a17.3 17.3 0 0 1 3 .2 23.1 23.1 0 0 1 2.7.6 17 17 0 0 1 5.6 3 15.9 15.9 0 0 1 3.4 3.7 19.6 19.6 0 0 1 1 1.8q1.7 3.1 1.8 7.5a25.4 25.4 0 0 1 0 .6ZM75.7 29.3v13.4a32.8 32.8 0 0 1-.8 7.1 29.5 29.5 0 0 1-.5 2 29 29 0 0 1-3.1 7 26.9 26.9 0 0 1-.6 1 27 27 0 0 1-5.7 6 27 27 0 0 1-7.4 4.2l14.8 11.8H54L41.8 72H29.3a32.1 32.1 0 0 1-8.2-1 28.7 28.7 0 0 1-3.5-1.2q-5.3-2.2-9.3-6a28.2 28.2 0 0 1-6-9.4A29.6 29.6 0 0 1 0 45.2a35.1 35.1 0 0 1-.1-2.5V29.3A31.8 31.8 0 0 1 1 21a28.5 28.5 0 0 1 1.2-3.4 28.1 28.1 0 0 1 5.2-8.3 26.7 26.7 0 0 1 1-1 28 28 0 0 1 9.1-6 31.8 31.8 0 0 1 .1-.1A29.9 29.9 0 0 1 27.4.1a35 35 0 0 1 1.9-.1h17.2a31.6 31.6 0 0 1 8.2 1 28.2 28.2 0 0 1 3.4 1.2 28.1 28.1 0 0 1 9.3 6 27.5 27.5 0 0 1 6 9 31.3 31.3 0 0 1 0 .4 30.1 30.1 0 0 1 2.2 9.6 35.4 35.4 0 0 1 0 2.1Zm68.5 6.7v17.2a22.2 22.2 0 0 1-.2 3 17 17 0 0 1-.6 2.9 18.6 18.6 0 0 1-1.2 2.8 15.3 15.3 0 0 1-1 1.7 15 15 0 0 1-3.1 3.4 14.2 14.2 0 0 1 0 0 18.5 18.5 0 0 1-3.8 2.3 19.5 19.5 0 0 1-4 1.3 20.8 20.8 0 0 1-2.4.3 17 17 0 0 1-1.5.1h-22.9q-2.6 0-5.7-1-3.2-.9-5.8-3a16.3 16.3 0 0 1-3.4-3.7 20 20 0 0 1-1-1.8 14.7 14.7 0 0 1-1.4-3.8q-.4-1.7-.4-3.6a26.1 26.1 0 0 1 0-1V36q0-4.4 1.4-7.6a13.2 13.2 0 0 1 .3-.7 18.6 18.6 0 0 1 2.4-3.5 15.6 15.6 0 0 1 2-2q2.7-2.1 5.9-3a24.2 24.2 0 0 1 2.8-.7q1.5-.3 3-.3h22.8a22.7 22.7 0 0 1 3.7.3q2.2.4 4 1.2a13.5 13.5 0 0 1 .6.3 18 18 0 0 1 3.4 2.2 15 15 0 0 1 2.1 2.2q2.1 2.7 3 5.8a23.3 23.3 0 0 1 .8 3 17.3 17.3 0 0 1 .2 2.8ZM63 42.7V29.3a20.4 20.4 0 0 0-.4-4 16.6 16.6 0 0 0-.8-2.8 15.4 15.4 0 0 0-2.5-4.2 14.3 14.3 0 0 0-.9-1 15 15 0 0 0-4.8-3.2 17.2 17.2 0 0 0-.4-.2 17.5 17.5 0 0 0-4.9-1.1 21 21 0 0 0-1.8-.1H29.3a20 20 0 0 0-4 .4 16.6 16.6 0 0 0-2.8.8q-3 1.2-5.2 3.4a14.8 14.8 0 0 0-3.3 5 17 17 0 0 0-.1.2 17.3 17.3 0 0 0-1 4.4 21.6 21.6 0 0 0-.2 2.4v13.4a20.4 20.4 0 0 0 .4 4 16.6 16.6 0 0 0 .8 2.8 15 15 0 0 0 2.7 4.5 14.3 14.3 0 0 0 .7.7 15.2 15.2 0 0 0 5 3.3 17.5 17.5 0 0 0 .2 0 17.4 17.4 0 0 0 4.7 1.2 21.3 21.3 0 0 0 2.1 0h17a20 20 0 0 0 4.2-.3A16.6 16.6 0 0 0 53 58q3.1-1.2 5.3-3.4a14.8 14.8 0 0 0 3.3-5 17 17 0 0 0 0-.2A17.3 17.3 0 0 0 63 45a21.6 21.6 0 0 0 0-2.4Zm68.5 10.5V36a8.4 8.4 0 0 0 0-1.5l-.5-1.4a3.7 3.7 0 0 0-.8-1 4.2 4.2 0 0 0-1.7-1l-1.4-.3a8.9 8.9 0 0 0-.7 0h-22.8q-1.8 0-3 .6a4 4 0 0 0-.8.7q-1.2 1.1-1.3 3.2a8.7 8.7 0 0 0 0 .6v17.2q0 1.8.7 3a4 4 0 0 0 .6.8 4.2 4.2 0 0 0 1.7 1l1.5.3a8.9 8.9 0 0 0 .6 0h22.8a8 8 0 0 0 1.5-.1l1.4-.5a3.7 3.7 0 0 0 1-.7 4.2 4.2 0 0 0 1-1.7q.2-.7.2-1.5a8.9 8.9 0 0 0 0-.6Z" />
</svg>
)

View File

@@ -55,6 +55,7 @@ import {
PauseCircleIcon,
PlayCircleIcon,
Trash2Icon,
BellIcon,
} from 'lucide-react'
import { useMemo, useState } from 'react'
import { $servers, pb, navigate } from '@/lib/stores'
@@ -65,7 +66,7 @@ import { cn, copyToClipboard } from '@/lib/utils'
function CellFormatter(info: CellContext<SystemRecord, unknown>) {
const val = info.getValue() as number
return (
<div className="flex gap-2 items-center">
<div className="flex gap-2 items-center tabular-nums tracking-tight">
<span className="grow min-w-10 block bg-muted h-4 relative rounded-sm overflow-hidden">
<span
className={cn(
@@ -103,12 +104,14 @@ export default function () {
const columns: ColumnDef<SystemRecord>[] = useMemo(() => {
return [
{
// size: 70,
// size: 200,
size: 200,
minSize: 0,
accessorKey: 'name',
cell: (info) => {
const { status } = info.row.original
return (
<span className="flex gap-0.5 items-center text-base">
<span className="flex gap-0.5 items-center text-base md:pr-5">
<span
className={cn('w-2 h-2 left-0 rounded-full', {
'bg-green-500': status === 'up',
@@ -147,18 +150,25 @@ export default function () {
},
{
id: 'actions',
size: 32,
maxSize: 32,
size: 120,
// minSize: 0,
cell: ({ row }) => {
const { id, name, status, host } = row.original
return (
<div className={'flex justify-end'}>
<div className={'flex justify-end items-center gap-1'}>
<Button
variant="ghost"
size={'icon'}
onClick={() => alert('notifications coming soon')}
>
<BellIcon className="h-[1.2em] w-[1.2em] pointer-events-none" />
</Button>
<AlertDialog>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<Button variant="ghost" size={'icon'}>
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
<MoreHorizontal className="w-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
@@ -241,6 +251,11 @@ export default function () {
sorting,
columnFilters,
},
defaultColumn: {
minSize: 0,
size: Number.MAX_SAFE_INTEGER,
maxSize: Number.MAX_SAFE_INTEGER,
},
})
return (
@@ -294,8 +309,13 @@ export default function () {
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
style={{ width: `${cell.column.getSize()}px` }}
className={'overflow-hidden relative'}
style={{
width:
cell.column.getSize() === Number.MAX_SAFE_INTEGER
? 'auto'
: cell.column.getSize(),
}}
className={'overflow-hidden relative py-3'}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>

View File

@@ -19,11 +19,11 @@ export const navigate = (urlString: string) => {
$router.open(urlString)
}
export const $servers = atom([] as SystemRecord[])
export const $authenticated = atom(pb.authStore.isValid)
pb.authStore.onChange(() => {
$authenticated.set(pb.authStore.isValid)
})
export const $servers = atom([] as SystemRecord[])
export const $publicKey = atom('')

View File

@@ -40,18 +40,24 @@ const App = () => {
// 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')
break
return cleanup
} else if (server.status === 'up') {
up = true
}
}
updateFavicon(up ? '/favicon-green.svg' : '/favicon.svg')
return cleanup
}
return () => {
updateFavicon('/favicon.svg')

View File

@@ -11,7 +11,7 @@ module.exports = {
theme: {
container: {
center: true,
padding: '2rem',
padding: '1rem',
screens: {
'2xl': '1400px',
},
@@ -22,6 +22,9 @@ module.exports = {
// display: ['Inter', 'sans-serif'],
},
extend: {
screens: {
xs: '425px',
},
colors: {
green: {
50: '#EBF9F0',