progress on settings / alerts

This commit is contained in:
Henry Dollman
2024-09-11 15:50:15 -04:00
parent b4cf5bb1c0
commit ce6e887d1b
5 changed files with 118 additions and 69 deletions

View File

@@ -185,46 +185,47 @@ func (am *AlertManager) sendAlert(data AlertData) {
} }
func (am *AlertManager) SendShoutrrrAlert(notificationUrl, title, message, link, linkText string) error { 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"} supportsTitle := []string{"bark", "discord", "gotify", "ifttt", "join", "matrix", "ntfy", "opsgenie", "pushbullet", "pushover", "slack", "teams", "telegram", "zulip"}
supportsLink := []string{"ntfy"}
// Parse the URL // Parse the URL
parsedURL, err := url.Parse(notificationUrl) parsedURL, err := url.Parse(notificationUrl)
if err != nil { if err != nil {
return fmt.Errorf("error parsing URL: %v", err) return fmt.Errorf("error parsing URL: %v", err)
} }
scheme := parsedURL.Scheme scheme := parsedURL.Scheme
// // Get query parameters
queryParams := parsedURL.Query() queryParams := parsedURL.Query()
// Add title // Add title
if !sliceContains(supportsTitle, scheme) { if sliceContains(supportsTitle, scheme) {
message = title + "\n\n" + message
} else {
queryParams.Add("title", title) queryParams.Add("title", title)
} else if scheme == "mattermost" {
// use markdown title for mattermost
message = "##### " + title + "\n\n" + message
} else if scheme == "generic" && queryParams.Has("template") {
// add title as property if using generic with template json
titleKey := queryParams.Get("titlekey")
if titleKey == "" {
titleKey = "title"
} }
// Add link queryParams.Add("$"+titleKey, title)
if !sliceContains(supportsLink, scheme) {
// add link to the message
message += "\n\n" + link
} else { } else {
// ntfy link // otherwise just add title to message
if scheme == "ntfy" { message = title + "\n\n" + message
queryParams.Add("Actions", fmt.Sprintf("view, %s, %s", linkText, link))
}
} }
// // Add link
if scheme == "generic" { if scheme == "ntfy" {
queryParams.Add("template", "json") // if ntfy, add link to actions
queryParams.Add("$title", title) queryParams.Add("Actions", fmt.Sprintf("view, %s, %s", linkText, link))
} else {
// else add link directly to the message
message += "\n\n" + link
} }
// Encode the modified query parameters back into the URL // Encode the modified query parameters back into the URL
parsedURL.RawQuery = queryParams.Encode() parsedURL.RawQuery = queryParams.Encode()
log.Println("URL after modification:", parsedURL.String()) // log.Println("URL after modification:", parsedURL.String())
err = shoutrrr.Send(parsedURL.String(), message) err = shoutrrr.Send(parsedURL.String(), message)

View File

@@ -9,6 +9,7 @@ import {
} from '@/components/ui/select' } from '@/components/ui/select'
import { chartTimeData } from '@/lib/utils' import { chartTimeData } from '@/lib/utils'
import { Separator } from '@/components/ui/separator' import { Separator } from '@/components/ui/separator'
import { SaveIcon } from 'lucide-react'
export default function SettingsProfilePage() { export default function SettingsProfilePage() {
return ( return (
@@ -20,8 +21,11 @@ export default function SettingsProfilePage() {
<Separator className="mt-6 mb-5" /> */} <Separator className="mt-6 mb-5" /> */}
<div className="space-y-5"> <div className="space-y-5">
<div className="space-y-2"> <div className="space-y-2">
<Label>Default chart period</Label> <div className="mb-4">
{/* <Input placeholder="Username" /> */} <h3 className="mb-1 text-lg font-medium">Chart options</h3>
<p className="text-sm text-muted-foreground">Adjust display options for charts.</p>
</div>
<Label className="block">Default time period</Label>
<Select defaultValue="1h"> <Select defaultValue="1h">
<SelectTrigger> <SelectTrigger>
<SelectValue /> <SelectValue />
@@ -35,11 +39,14 @@ export default function SettingsProfilePage() {
</SelectContent> </SelectContent>
</Select> </Select>
<p className="text-[0.8rem] text-muted-foreground"> <p className="text-[0.8rem] text-muted-foreground">
Sets the default time range for charts. Sets the default time range for charts when a system is viewed.
</p> </p>
</div> </div>
<Separator /> <Separator />
<Button type="submit">Save settings</Button> <Button type="submit" className="flex items-center gap-1.5">
<SaveIcon className="h-4 w-4" />
Save settings
</Button>
</div> </div>
</div> </div>
) )

View File

@@ -35,10 +35,10 @@ export default function SettingsLayout() {
}, []) }, [])
return ( return (
<Card className="pt-5 px-4 pb-9 sm:pt-6 sm:px-7"> <Card className="pt-5 px-4 pb-8 sm:pt-6 sm:px-7">
<CardHeader className="p-0"> <CardHeader className="p-0">
<CardTitle className="mb-1">Settings</CardTitle> <CardTitle className="mb-1">Settings</CardTitle>
<CardDescription>Manage your account settings and set e-mail preferences.</CardDescription> <CardDescription>Manage notification and display preferences.</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="p-0"> <CardContent className="p-0">
<Separator className="my-5" /> <Separator className="my-5" />

View File

@@ -5,7 +5,9 @@ import { pb } from '@/lib/stores'
import { Separator } from '@/components/ui/separator' import { Separator } from '@/components/ui/separator'
import { Card } from '@/components/ui/card' import { Card } from '@/components/ui/card'
// import { Switch } from '@/components/ui/switch' // import { Switch } from '@/components/ui/switch'
import { Trash2Icon } from 'lucide-react' import { LoaderCircleIcon, PlusIcon, SaveIcon, Trash2Icon } from 'lucide-react'
import { useState } from 'react'
import { toast } from '@/components/ui/use-toast'
export default function SettingsNotificationsPage() { export default function SettingsNotificationsPage() {
return ( return (
@@ -20,7 +22,7 @@ export default function SettingsNotificationsPage() {
<div className="mb-4"> <div className="mb-4">
<h3 className="mb-1 text-lg font-medium">Email notifications</h3> <h3 className="mb-1 text-lg font-medium">Email notifications</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Get notified when new alerts are created. Leave the emails field to disable email notifications.
</p> </p>
</div> </div>
<Label className="block">To email(s)</Label> <Label className="block">To email(s)</Label>
@@ -36,15 +38,59 @@ export default function SettingsNotificationsPage() {
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Beszel uses{' '} Beszel uses{' '}
<a <a
href="https://containrrr.dev/shoutrrr/v0.8/services/overview/" href="https://containrrr.dev/shoutrrr/services/overview/"
target="_blank" target="_blank"
className="font-medium text-primary opacity-80 hover:opacity-100 duration-100" className="link"
> >
Shoutrrr Shoutrrr
</a>{' '} </a>{' '}
to integrate with popular notification services. to integrate with popular notification services.
</p> </p>
</div> </div>
<ShoutrrrUrlCard />
<Button
type="button"
variant="outline"
size="sm"
className="mt-2 flex items-center gap-1"
// onClick={() => append({ value: '' })}
>
<PlusIcon className="h-4 w-4 -ml-0.5" />
Add URL
</Button>
</div>
<Separator />
<Button type="submit" className="flex items-center gap-1.5">
<SaveIcon className="h-4 w-4" />
Save settings
</Button>
</div>
</div>
)
}
async function sendTestNotification(url: string) {
const res = await pb.send('/api/beszel/send-test-notification', { url })
if ('err' in res && !res.err) {
toast({
title: 'Test notification sent',
description: 'Check your notification service',
})
} else {
toast({
title: 'Error',
description: res.err ?? 'Failed to send test notification',
variant: 'destructive',
})
}
}
// todo unique ids
function ShoutrrrUrlCard() {
const [url, setUrl] = useState('')
const [isLoading, setIsLoading] = useState(false)
return (
<Card className="bg-muted/30 p-3.5"> <Card className="bg-muted/30 p-3.5">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Label htmlFor="name" className="sr-only"> <Label htmlFor="name" className="sr-only">
@@ -55,15 +101,21 @@ export default function SettingsNotificationsPage() {
name="name" name="name"
className="light:bg-card" className="light:bg-card"
required required
placeholder="generic://example.com?@header=value" placeholder="generic://webhook.site/xxxxxx"
onChange={(e) => setUrl(e.target.value)}
/> />
<Button <Button
type="button" type="button"
variant="outline" variant="outline"
className="" className="w-28"
// onClick={() => append({ value: '' })} disabled={isLoading || url === ''}
onClick={async () => {
setIsLoading(true)
await sendTestNotification(url)
setIsLoading(false)
}}
> >
Test {isLoading ? <LoaderCircleIcon className="sh-4 w-4 animate-spin" /> : 'Test URL'}
</Button> </Button>
<Button <Button
type="button" type="button"
@@ -72,7 +124,7 @@ export default function SettingsNotificationsPage() {
className="shrink-0" className="shrink-0"
// onClick={() => append({ value: '' })} // onClick={() => append({ value: '' })}
> >
<Trash2Icon className="h-4 w-4" /> <Trash2Icon className="sh-4 w-4" />
</Button> </Button>
{/* <Label htmlFor="enabled-01" className="sr-only"> {/* <Label htmlFor="enabled-01" className="sr-only">
Enabled Enabled
@@ -80,19 +132,5 @@ export default function SettingsNotificationsPage() {
<Switch defaultChecked id="enabled-01" className="ml-2" /> */} <Switch defaultChecked id="enabled-01" className="ml-2" /> */}
</div> </div>
</Card> </Card>
<Button
type="button"
variant="outline"
size="sm"
className="mt-2"
// onClick={() => append({ value: '' })}
>
Add URL
</Button>
</div>
<Separator />
<Button type="submit">Save settings</Button>
</div>
</div>
) )
} }

View File

@@ -78,6 +78,9 @@
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
.link {
@apply text-primary font-medium underline-offset-4 hover:underline;
}
} }
.recharts-tooltip-wrapper { .recharts-tooltip-wrapper {