mirror of
https://github.com/fankes/beszel.git
synced 2025-10-20 02:09:28 +08:00
progress on settings / alerts
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
@@ -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>
|
||||||
)
|
)
|
||||||
|
@@ -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" />
|
||||||
|
@@ -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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -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 {
|
||||||
|
Reference in New Issue
Block a user