updates to settings page and alerts

This commit is contained in:
Henry Dollman
2024-09-13 18:19:12 -04:00
parent 9710d0d2f1
commit f16e22e521
8 changed files with 75 additions and 68 deletions

View File

@@ -29,7 +29,7 @@ type AlertData struct {
LinkText string
}
type UserAlertSettings struct {
type UserNotificationSettings struct {
Emails []string `json:"emails"`
Webhooks []string `json:"webhooks"`
}
@@ -166,16 +166,16 @@ func (am *AlertManager) sendAlert(data AlertData) {
dbx.Params{"user": data.UserID},
)
if err != nil {
log.Println("Failed to get user settings", "err", err.Error())
am.app.Logger().Error("Failed to get user settings", "err", err.Error())
return
}
// unmarshal user settings
userAlertSettings := UserAlertSettings{
userAlertSettings := UserNotificationSettings{
Emails: []string{},
Webhooks: []string{},
}
if err := record.UnmarshalJSONField("settings", &userAlertSettings); err != nil {
log.Println("Failed to unmarshal user settings", "err", err.Error())
am.app.Logger().Error("Failed to unmarshal user settings", "err", err.Error())
}
// send alerts via webhooks
for _, webhook := range userAlertSettings.Webhooks {
@@ -186,13 +186,12 @@ func (am *AlertManager) sendAlert(data AlertData) {
}
// send alerts via email
if len(userAlertSettings.Emails) == 0 {
log.Println("No email addresses found")
// log.Println("No email addresses found")
return
}
addresses := []mail.Address{}
for _, email := range userAlertSettings.Emails {
addresses = append(addresses, mail.Address{Address: email})
log.Println("Sending alert via email to", email)
}
message := mailer.Message{
To: addresses,
@@ -211,6 +210,7 @@ func (am *AlertManager) sendAlert(data AlertData) {
}
}
// SendShoutrrrAlert sends an alert via a Shoutrrr URL
func (am *AlertManager) SendShoutrrrAlert(notificationUrl, title, message, link, linkText string) error {
// services that support title param
supportsTitle := []string{"bark", "discord", "gotify", "ifttt", "join", "matrix", "ntfy", "opsgenie", "pushbullet", "pushover", "slack", "teams", "telegram", "zulip"}
@@ -259,7 +259,7 @@ func (am *AlertManager) SendShoutrrrAlert(notificationUrl, title, message, link,
if err == nil {
am.app.Logger().Info("Sent shoutrrr alert", "title", title)
} else {
am.app.Logger().Error("Error sending shoutrrr alert", "errs", err)
am.app.Logger().Error("Error sending shoutrrr alert", "err", err.Error())
return err
}
return nil

View File

@@ -13,6 +13,7 @@ import { LoaderCircleIcon, SaveIcon } from 'lucide-react'
import { UserSettings } from '@/types'
import { saveSettings } from './layout'
import { useState } from 'react'
// import { Input } from '@/components/ui/input'
export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) {
const [isLoading, setIsLoading] = useState(false)
@@ -30,17 +31,22 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
<div>
<div>
<h3 className="text-xl font-medium mb-2">General</h3>
<p className="text-sm text-muted-foreground">
Set your preferred language and chart display options.
<p className="text-sm text-muted-foreground leading-relaxed">
Change general application options.
</p>
</div>
<Separator className="my-4" />
<form onSubmit={handleSubmit} className="space-y-5">
{/* <Separator />
<div className="space-y-2">
<div className="mb-4">
<h3 className="mb-1 text-lg font-medium">Language</h3>
<p className="text-sm text-muted-foreground">
Additional language support coming soon.
<p className="text-sm text-muted-foreground leading-relaxed">
Internationalization will be added in a future release. Please see the{' '}
<a href="#" className="link" target="_blank">
discussion on GitHub
</a>{' '}
for more details.
</p>
</div>
<Label className="block" htmlFor="lang">
@@ -54,12 +60,13 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
<SelectItem value="en">English</SelectItem>
</SelectContent>
</Select>
</div>
<Separator />
</div> */}
<div className="space-y-2">
<div className="mb-4">
<h3 className="mb-1 text-lg font-medium">Chart options</h3>
<p className="text-sm text-muted-foreground">Adjust display options for charts.</p>
<p className="text-sm text-muted-foreground leading-relaxed">
Adjust display options for charts.
</p>
</div>
<Label className="block" htmlFor="chartTime">
Default time period

View File

@@ -1,4 +1,4 @@
import { Suspense, lazy, useEffect } from 'react'
import { useEffect } from 'react'
import { Separator } from '../../ui/separator'
import { SidebarNav } from './sidebar-nav.tsx'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card.tsx'
@@ -9,9 +9,8 @@ import { BellIcon, SettingsIcon } from 'lucide-react'
import { $userSettings, pb } from '@/lib/stores.ts'
import { toast } from '@/components/ui/use-toast.ts'
import { UserSettings } from '@/types.js'
const General = lazy(() => import('./general.tsx'))
const Notifications = lazy(() => import('./notifications.tsx'))
import General from './general.tsx'
import Notifications from './notifications.tsx'
const sidebarNavItems = [
{
@@ -27,31 +26,28 @@ const sidebarNavItems = [
]
export async function saveSettings(newSettings: Partial<UserSettings>) {
// console.log('Updating settings:', newSettings)
try {
// get fresh copy of settings
const req = await pb.collection('user_settings').getFirstListItem('', {
fields: 'id,settings',
})
// make new user settings
const mergedSettings = {
...req.settings,
...newSettings,
}
// update user settings
const updatedSettings = await pb.collection('user_settings').update(req.id, {
settings: mergedSettings,
settings: {
...req.settings,
...newSettings,
},
})
$userSettings.set(updatedSettings.settings)
toast({
title: 'Settings saved',
description: 'Your notification settings have been updated.',
description: 'Your user settings have been updated.',
})
} catch (e) {
console.log('update settings', e)
// console.error('update settings', e)
toast({
title: 'Failed to save settings',
description: 'Please check logs for more details.',
description: 'Check logs for more details.',
variant: 'destructive',
})
}
@@ -72,19 +68,17 @@ export default function SettingsLayout() {
<Card className="pt-5 px-4 pb-8 sm:pt-6 sm:px-7">
<CardHeader className="p-0">
<CardTitle className="mb-1">Settings</CardTitle>
<CardDescription>Manage notification and display preferences.</CardDescription>
<CardDescription>Manage display and notification preferences.</CardDescription>
</CardHeader>
<CardContent className="p-0">
<Separator className="my-3 md:my-5" />
<div className="flex flex-col gap-3 md:flex-row md:gap-5 lg:gap-10">
<Separator className="hidden md:block my-5" />
<div className="flex flex-col gap-3.5 md:flex-row md:gap-5 lg:gap-10">
<aside className="md:w-48 w-full">
<SidebarNav items={sidebarNavItems} />
</aside>
<div className="flex-1">
<Suspense>
{/* @ts-ignore */}
<SettingsContent name={page?.params?.name ?? 'general'} />
</Suspense>
{/* @ts-ignore */}
<SettingsContent name={page?.params?.name ?? 'general'} />
</div>
</div>
</CardContent>
@@ -101,5 +95,4 @@ function SettingsContent({ name }: { name: string }) {
case 'notifications':
return <Notifications userSettings={userSettings} />
}
return ''
}

View File

@@ -11,6 +11,7 @@ import { InputTags } from '@/components/ui/input-tags'
import { UserSettings } from '@/types'
import { saveSettings } from './layout'
import * as v from 'valibot'
import { isAdmin } from '@/lib/utils'
interface ShoutrrrUrlCardProps {
url: string
@@ -41,7 +42,6 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
setIsLoading(true)
try {
const parsedData = v.parse(NotificationSchema, { emails, webhooks })
console.log('parsedData', parsedData)
await saveSettings(parsedData)
} catch (e: any) {
toast({
@@ -57,12 +57,12 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
<div>
<div>
<h3 className="text-xl font-medium mb-2">Notifications</h3>
<p className="text-sm text-muted-foreground">
<p className="text-sm text-muted-foreground leading-relaxed">
Configure how you receive alert notifications.
</p>
<p className="text-sm text-muted-foreground mt-1.5">
Looking for where to create system alerts? Click the bell icons{' '}
<BellIcon className="inline h-4 w-4" /> in the dashboard table.
<p className="text-sm text-muted-foreground mt-1.5 leading-relaxed">
Looking instead for where to create system alerts? Click the bell{' '}
<BellIcon className="inline h-4 w-4" /> icons in the systems table.
</p>
</div>
<Separator className="my-4" />
@@ -70,26 +70,36 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
<div className="space-y-2">
<div className="mb-4">
<h3 className="mb-1 text-lg font-medium">Email notifications</h3>
<p className="text-sm text-muted-foreground">
Leave blank to disable email notifications.
</p>
{isAdmin() && (
<p className="text-sm text-muted-foreground leading-relaxed">
Please{' '}
<a href="/_/#/settings/mail" className="link" target="_blank">
configure an SMTP server
</a>{' '}
to ensure alerts are delivered.{' '}
</p>
)}
</div>
<Label className="block">To email(s)</Label>
<Label className="block" htmlFor="email">
To email(s)
</Label>
<InputTags
value={emails}
onChange={setEmails}
placeholder="Enter email address..."
className="w-full"
type="email"
id="email"
/>
<p className="text-[0.8rem] text-muted-foreground">
Save address using enter key or comma.
Save address using enter key or comma. Leave blank to disable email notifications.
</p>
</div>
<Separator />
<div className="space-y-4">
<div className="space-y-3">
<div>
<h3 className="mb-1 text-lg font-medium">Webhook / Push notifications</h3>
<p className="text-sm text-muted-foreground">
<p className="text-sm text-muted-foreground leading-relaxed">
Beszel uses{' '}
<a
href="https://containrrr.dev/shoutrrr/services/overview/"
@@ -102,7 +112,7 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
</p>
</div>
{webhooks.length > 0 && (
<div className="grid gap-3">
<div className="grid gap-2.5">
{webhooks.map((webhook, index) => (
<ShoutrrrUrlCard
key={index}

View File

@@ -27,8 +27,8 @@ export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
<>
{/* Mobile View */}
<div className="md:hidden">
<Select onValueChange={(value: string) => navigate(value)} defaultValue={page?.path}>
<SelectTrigger className="w-full mb-3">
<Select onValueChange={(value: string) => navigate(value)} value={page?.path}>
<SelectTrigger className="w-full my-3.5">
<SelectValue placeholder="Select a page" />
</SelectTrigger>
<SelectContent>

View File

@@ -260,8 +260,8 @@ export default function SystemDetail({ name }: { name: string }) {
<Tooltip>
<Separator orientation="vertical" className="h-4 bg-primary/30" />
<TooltipTrigger asChild>
<div className="flex gap-1.5">
<ClockArrowUp className="h-4 w-4 mt-[1px]" /> {uptime}
<div className="flex gap-1.5 items-center">
<ClockArrowUp className="h-4 w-4" /> {uptime}
</div>
</TooltipTrigger>
<TooltipContent>Uptime</TooltipContent>
@@ -271,8 +271,8 @@ export default function SystemDetail({ name }: { name: string }) {
{system.info?.m && (
<>
<Separator orientation="vertical" className="h-4 bg-primary/30" />
<div className="flex gap-1.5">
<CpuIcon className="h-4 w-4 mt-[1px]" />
<div className="flex gap-1.5 items-center">
<CpuIcon className="h-4 w-4" />
{system.info.m} ({system.info.c}c / {system.info.t}t)
</div>
</>

View File

@@ -324,7 +324,7 @@ export default function SystemsTable({ filter }: { filter?: string }) {
? 'auto'
: cell.column.getSize(),
}}
className={'overflow-hidden relative py-2.5'}
className={cn('overflow-hidden relative', data.length > 10 ? 'py-2' : 'py-2.5')}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>

View File

@@ -15,6 +15,7 @@ import { Switch } from '@/components/ui/switch'
import { AlertRecord, SystemRecord } from '@/types'
import { lazy, Suspense, useMemo, useState } from 'react'
import { toast } from './ui/use-toast'
import { Link } from './router'
const Slider = lazy(() => import('./ui/slider'))
@@ -49,17 +50,13 @@ export default function AlertsButton({ system }: { system: SystemRecord }) {
</DialogTrigger>
<DialogContent className="max-h-full overflow-auto">
<DialogHeader>
<DialogTitle className="mb-1">Alerts for {system.name}</DialogTitle>
<DialogDescription>
{isAdmin() && (
<span>
Please{' '}
<a href="/_/#/settings/mail" className="link">
configure an SMTP server
</a>{' '}
to ensure alerts are delivered.{' '}
</span>
)}
<DialogTitle className="text-xl">{system.name} alerts</DialogTitle>
<DialogDescription className="mb-1">
See{' '}
<Link href="/settings/notifications" className="link">
notification settings
</Link>{' '}
to configure how you receive alerts.
</DialogDescription>
</DialogHeader>
<div className="grid gap-3">
@@ -83,7 +80,7 @@ export default function AlertsButton({ system }: { system: SystemRecord }) {
alerts={systemAlerts}
name="Disk"
title="Disk Usage"
description="Triggers when disk usage exceeds a threshold."
description="Triggers when root usage exceeds a threshold."
/>
</div>
</DialogContent>