mirror of
https://github.com/henrygd/beszel.git
synced 2025-12-17 18:56:17 +01:00
add prettier config and format files site files
This commit is contained in:
@@ -1,21 +1,21 @@
|
||||
import { isAdmin } from '@/lib/utils'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { redirectPage } from '@nanostores/router'
|
||||
import { $router } from '@/components/router'
|
||||
import { AlertCircleIcon, FileSlidersIcon, LoaderCircleIcon } from 'lucide-react'
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
import { pb } from '@/lib/stores'
|
||||
import { useState } from 'react'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { toast } from '@/components/ui/use-toast'
|
||||
import clsx from 'clsx'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { isAdmin } from "@/lib/utils"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { redirectPage } from "@nanostores/router"
|
||||
import { $router } from "@/components/router"
|
||||
import { AlertCircleIcon, FileSlidersIcon, LoaderCircleIcon } from "lucide-react"
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||
import { pb } from "@/lib/stores"
|
||||
import { useState } from "react"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
import clsx from "clsx"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
export default function ConfigYaml() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [configContent, setConfigContent] = useState<string>('')
|
||||
const [configContent, setConfigContent] = useState<string>("")
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const ButtonIcon = isLoading ? LoaderCircleIcon : FileSlidersIcon
|
||||
@@ -23,13 +23,13 @@ export default function ConfigYaml() {
|
||||
async function fetchConfig() {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const { config } = await pb.send<{ config: string }>('/api/beszel/config-yaml', {})
|
||||
const { config } = await pb.send<{ config: string }>("/api/beszel/config-yaml", {})
|
||||
setConfigContent(config)
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
title: "Error",
|
||||
description: error.message,
|
||||
variant: 'destructive',
|
||||
variant: "destructive",
|
||||
})
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
@@ -37,33 +37,29 @@ export default function ConfigYaml() {
|
||||
}
|
||||
|
||||
if (!isAdmin()) {
|
||||
redirectPage($router, 'settings', { name: 'general' })
|
||||
redirectPage($router, "settings", { name: "general" })
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<h3 className="text-xl font-medium mb-2">{t('settings.yaml_config.title')}</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
{t('settings.yaml_config.subtitle')}
|
||||
</p>
|
||||
<h3 className="text-xl font-medium mb-2">{t("settings.yaml_config.title")}</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">{t("settings.yaml_config.subtitle")}</p>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="space-y-2">
|
||||
<div className="mb-4">
|
||||
<p className="text-sm text-muted-foreground leading-relaxed my-1">
|
||||
{t('settings.yaml_config.des_1')}{' '}
|
||||
<code className="bg-muted rounded-sm px-1 text-primary">config.yml</code> {t('settings.yaml_config.des_2')}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
{t('settings.yaml_config.des_3')}
|
||||
{t("settings.yaml_config.des_1")} <code className="bg-muted rounded-sm px-1 text-primary">config.yml</code>{" "}
|
||||
{t("settings.yaml_config.des_2")}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">{t("settings.yaml_config.des_3")}</p>
|
||||
<Alert className="my-4 border-destructive text-destructive w-auto table md:pr-6">
|
||||
<AlertCircleIcon className="h-4 w-4 stroke-destructive" />
|
||||
<AlertTitle>{t('settings.yaml_config.alert.title')}</AlertTitle>
|
||||
<AlertTitle>{t("settings.yaml_config.alert.title")}</AlertTitle>
|
||||
<AlertDescription>
|
||||
<p>
|
||||
{t('settings.yaml_config.alert.des_1')} <code>config.yml</code> {t('settings.yaml_config.alert.des_2')}
|
||||
{t("settings.yaml_config.alert.des_1")} <code>config.yml</code> {t("settings.yaml_config.alert.des_2")}
|
||||
</p>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
@@ -73,20 +69,15 @@ export default function ConfigYaml() {
|
||||
autoFocus
|
||||
defaultValue={configContent}
|
||||
spellCheck="false"
|
||||
rows={Math.min(25, configContent.split('\n').length)}
|
||||
rows={Math.min(25, configContent.split("\n").length)}
|
||||
className="font-mono whitespace-pre"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Separator className="my-5" />
|
||||
<Button
|
||||
type="button"
|
||||
className="mt-2 flex items-center gap-1"
|
||||
onClick={fetchConfig}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<ButtonIcon className={clsx('h-4 w-4 mr-0.5', isLoading && 'animate-spin')} />
|
||||
{t('settings.export_configuration')}
|
||||
<Button type="button" className="mt-2 flex items-center gap-1" onClick={fetchConfig} disabled={isLoading}>
|
||||
<ButtonIcon className={clsx("h-4 w-4 mr-0.5", isLoading && "animate-spin")} />
|
||||
{t("settings.export_configuration")}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { chartTimeData } from '@/lib/utils'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { LanguagesIcon, LoaderCircleIcon, SaveIcon } from 'lucide-react'
|
||||
import { UserSettings } from '@/types'
|
||||
import { saveSettings } from './layout'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { chartTimeData } from "@/lib/utils"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { LanguagesIcon, LoaderCircleIcon, SaveIcon } from "lucide-react"
|
||||
import { UserSettings } from "@/types"
|
||||
import { saveSettings } from "./layout"
|
||||
import { useState, useEffect } from "react"
|
||||
// import { Input } from '@/components/ui/input'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import languages from '../../../lib/languages.json'
|
||||
import { useTranslation } from "react-i18next"
|
||||
import languages from "../../../lib/languages.json"
|
||||
|
||||
export default function SettingsProfilePage({ userSettings }: { userSettings: UserSettings }) {
|
||||
const { t, i18n } = useTranslation()
|
||||
@@ -38,10 +32,8 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<h3 className="text-xl font-medium mb-2">{t('settings.general.title')}</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
{t('settings.general.subtitle')}
|
||||
</p>
|
||||
<h3 className="text-xl font-medium mb-2">{t("settings.general.title")}</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">{t("settings.general.subtitle")}</p>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
@@ -49,18 +41,18 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
||||
<div className="mb-4">
|
||||
<h3 className="mb-1 text-lg font-medium flex items-center gap-2">
|
||||
<LanguagesIcon className="h-4 w-4" />
|
||||
{t('settings.general.language.title')}
|
||||
{t("settings.general.language.title")}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
{t('settings.general.language.subtitle_1')}{' '}
|
||||
{t("settings.general.language.subtitle_1")}{" "}
|
||||
<a href="https://crowdin.com/project/beszel" className="link" target="_blank">
|
||||
Crowdin
|
||||
</a>{' '}
|
||||
{t('settings.general.language.subtitle_2')}
|
||||
</a>{" "}
|
||||
{t("settings.general.language.subtitle_2")}
|
||||
</p>
|
||||
</div>
|
||||
<Label className="block" htmlFor="lang">
|
||||
{t('settings.general.language.preferred_language')}
|
||||
{t("settings.general.language.preferred_language")}
|
||||
</Label>
|
||||
<Select value={i18n.language} onValueChange={(lang: string) => i18n.changeLanguage(lang)}>
|
||||
<SelectTrigger id="lang">
|
||||
@@ -78,21 +70,15 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<div className="mb-4">
|
||||
<h3 className="mb-1 text-lg font-medium">
|
||||
{t('settings.general.chart_options.title')}
|
||||
</h3>
|
||||
<h3 className="mb-1 text-lg font-medium">{t("settings.general.chart_options.title")}</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
{t('settings.general.chart_options.subtitle')}
|
||||
{t("settings.general.chart_options.subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
<Label className="block" htmlFor="chartTime">
|
||||
{t('settings.general.chart_options.default_time_period')}
|
||||
{t("settings.general.chart_options.default_time_period")}
|
||||
</Label>
|
||||
<Select
|
||||
name="chartTime"
|
||||
key={userSettings.chartTime}
|
||||
defaultValue={userSettings.chartTime}
|
||||
>
|
||||
<Select name="chartTime" key={userSettings.chartTime} defaultValue={userSettings.chartTime}>
|
||||
<SelectTrigger id="chartTime">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
@@ -105,21 +91,13 @@ export default function SettingsProfilePage({ userSettings }: { userSettings: Us
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-[0.8rem] text-muted-foreground">
|
||||
{t('settings.general.chart_options.default_time_period_des')}
|
||||
{t("settings.general.chart_options.default_time_period_des")}
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<Button
|
||||
type="submit"
|
||||
className="flex items-center gap-1.5 disabled:opacity-100"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<LoaderCircleIcon className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<SaveIcon className="h-4 w-4" />
|
||||
)}
|
||||
{t('settings.save_settings')}
|
||||
<Button type="submit" className="flex items-center gap-1.5 disabled:opacity-100" disabled={isLoading}>
|
||||
{isLoading ? <LoaderCircleIcon className="h-4 w-4 animate-spin" /> : <SaveIcon className="h-4 w-4" />}
|
||||
{t("settings.save_settings")}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
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'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { $router } from '@/components/router.tsx'
|
||||
import { redirectPage } from '@nanostores/router'
|
||||
import { BellIcon, FileSlidersIcon, SettingsIcon } from 'lucide-react'
|
||||
import { $userSettings, pb } from '@/lib/stores.ts'
|
||||
import { toast } from '@/components/ui/use-toast.ts'
|
||||
import { UserSettings } from '@/types.js'
|
||||
import General from './general.tsx'
|
||||
import Notifications from './notifications.tsx'
|
||||
import ConfigYaml from './config-yaml.tsx'
|
||||
import { isAdmin } from '@/lib/utils.ts'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
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"
|
||||
import { useStore } from "@nanostores/react"
|
||||
import { $router } from "@/components/router.tsx"
|
||||
import { redirectPage } from "@nanostores/router"
|
||||
import { BellIcon, FileSlidersIcon, SettingsIcon } from "lucide-react"
|
||||
import { $userSettings, pb } from "@/lib/stores.ts"
|
||||
import { toast } from "@/components/ui/use-toast.ts"
|
||||
import { UserSettings } from "@/types.js"
|
||||
import General from "./general.tsx"
|
||||
import Notifications from "./notifications.tsx"
|
||||
import ConfigYaml from "./config-yaml.tsx"
|
||||
import { isAdmin } from "@/lib/utils.ts"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
export async function saveSettings(newSettings: Partial<UserSettings>) {
|
||||
try {
|
||||
// get fresh copy of settings
|
||||
const req = await pb.collection('user_settings').getFirstListItem('', {
|
||||
fields: 'id,settings',
|
||||
const req = await pb.collection("user_settings").getFirstListItem("", {
|
||||
fields: "id,settings",
|
||||
})
|
||||
// update user settings
|
||||
const updatedSettings = await pb.collection('user_settings').update(req.id, {
|
||||
const updatedSettings = await pb.collection("user_settings").update(req.id, {
|
||||
settings: {
|
||||
...req.settings,
|
||||
...newSettings,
|
||||
@@ -30,15 +30,15 @@ export async function saveSettings(newSettings: Partial<UserSettings>) {
|
||||
})
|
||||
$userSettings.set(updatedSettings.settings)
|
||||
toast({
|
||||
title: 'Settings saved',
|
||||
description: 'Your user settings have been updated.',
|
||||
title: "Settings saved",
|
||||
description: "Your user settings have been updated.",
|
||||
})
|
||||
} catch (e) {
|
||||
// console.error('update settings', e)
|
||||
toast({
|
||||
title: 'Failed to save settings',
|
||||
description: 'Check logs for more details.',
|
||||
variant: 'destructive',
|
||||
title: "Failed to save settings",
|
||||
description: "Check logs for more details.",
|
||||
variant: "destructive",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -48,21 +48,21 @@ export default function SettingsLayout() {
|
||||
|
||||
const sidebarNavItems = [
|
||||
{
|
||||
title: t('settings.general.title'),
|
||||
href: '/settings/general',
|
||||
title: t("settings.general.title"),
|
||||
href: "/settings/general",
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
title: t('settings.notifications.title'),
|
||||
href: '/settings/notifications',
|
||||
title: t("settings.notifications.title"),
|
||||
href: "/settings/notifications",
|
||||
icon: BellIcon,
|
||||
},
|
||||
]
|
||||
|
||||
if (isAdmin()) {
|
||||
sidebarNavItems.push({
|
||||
title: t('settings.yaml_config.short_title'),
|
||||
href: '/settings/config',
|
||||
title: t("settings.yaml_config.short_title"),
|
||||
href: "/settings/config",
|
||||
icon: FileSlidersIcon,
|
||||
})
|
||||
}
|
||||
@@ -70,18 +70,18 @@ export default function SettingsLayout() {
|
||||
const page = useStore($router)
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'Settings / Beszel'
|
||||
document.title = "Settings / Beszel"
|
||||
// redirect to account page if no page is specified
|
||||
if (page?.path === '/settings') {
|
||||
redirectPage($router, 'settings', { name: 'general' })
|
||||
if (page?.path === "/settings") {
|
||||
redirectPage($router, "settings", { name: "general" })
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Card className="pt-5 px-4 pb-8 sm:pt-6 sm:px-7">
|
||||
<CardHeader className="p-0">
|
||||
<CardTitle className="mb-1">{t('settings.settings')}</CardTitle>
|
||||
<CardDescription>{t('settings.subtitle')}</CardDescription>
|
||||
<CardTitle className="mb-1">{t("settings.settings")}</CardTitle>
|
||||
<CardDescription>{t("settings.subtitle")}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<Separator className="hidden md:block my-5" />
|
||||
@@ -91,7 +91,7 @@ export default function SettingsLayout() {
|
||||
</aside>
|
||||
<div className="flex-1">
|
||||
{/* @ts-ignore */}
|
||||
<SettingsContent name={page?.params?.name ?? 'general'} />
|
||||
<SettingsContent name={page?.params?.name ?? "general"} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -103,11 +103,11 @@ function SettingsContent({ name }: { name: string }) {
|
||||
const userSettings = useStore($userSettings)
|
||||
|
||||
switch (name) {
|
||||
case 'general':
|
||||
case "general":
|
||||
return <General userSettings={userSettings} />
|
||||
case 'notifications':
|
||||
case "notifications":
|
||||
return <Notifications userSettings={userSettings} />
|
||||
case 'config':
|
||||
case "config":
|
||||
return <ConfigYaml />
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { pb } from '@/lib/stores'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { BellIcon, LoaderCircleIcon, PlusIcon, SaveIcon, Trash2Icon } from 'lucide-react'
|
||||
import { ChangeEventHandler, useEffect, useState } from 'react'
|
||||
import { toast } from '@/components/ui/use-toast'
|
||||
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'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { pb } from "@/lib/stores"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { Card } from "@/components/ui/card"
|
||||
import { BellIcon, LoaderCircleIcon, PlusIcon, SaveIcon, Trash2Icon } from "lucide-react"
|
||||
import { ChangeEventHandler, useEffect, useState } from "react"
|
||||
import { toast } from "@/components/ui/use-toast"
|
||||
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"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
interface ShoutrrrUrlCardProps {
|
||||
url: string
|
||||
@@ -39,10 +39,10 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
|
||||
}, [userSettings])
|
||||
|
||||
function addWebhook() {
|
||||
setWebhooks([...webhooks, ''])
|
||||
setWebhooks([...webhooks, ""])
|
||||
// focus on the new input
|
||||
queueMicrotask(() => {
|
||||
const inputs = document.querySelectorAll('#webhooks input') as NodeListOf<HTMLInputElement>
|
||||
const inputs = document.querySelectorAll("#webhooks input") as NodeListOf<HTMLInputElement>
|
||||
inputs[inputs.length - 1]?.focus()
|
||||
})
|
||||
}
|
||||
@@ -61,9 +61,9 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
|
||||
await saveSettings(parsedData)
|
||||
} catch (e: any) {
|
||||
toast({
|
||||
title: 'Failed to save settings',
|
||||
title: "Failed to save settings",
|
||||
description: e.message,
|
||||
variant: 'destructive',
|
||||
variant: "destructive",
|
||||
})
|
||||
}
|
||||
setIsLoading(false)
|
||||
@@ -72,63 +72,51 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<h3 className="text-xl font-medium mb-2">{t('settings.notifications.title')}</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
{t('settings.notifications.subtitle_1')}
|
||||
</p>
|
||||
<h3 className="text-xl font-medium mb-2">{t("settings.notifications.title")}</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">{t("settings.notifications.subtitle_1")}</p>
|
||||
<p className="text-sm text-muted-foreground mt-1.5 leading-relaxed">
|
||||
{t('settings.notifications.subtitle_2')}{' '}
|
||||
<BellIcon className="inline h-4 w-4" /> {t('settings.notifications.subtitle_3')}
|
||||
{t("settings.notifications.subtitle_2")} <BellIcon className="inline h-4 w-4" />{" "}
|
||||
{t("settings.notifications.subtitle_3")}
|
||||
</p>
|
||||
</div>
|
||||
<Separator className="my-4" />
|
||||
<div className="space-y-5">
|
||||
<div className="space-y-2">
|
||||
<div className="mb-4">
|
||||
<h3 className="mb-1 text-lg font-medium">
|
||||
{t('settings.notifications.email.title')}
|
||||
</h3>
|
||||
<h3 className="mb-1 text-lg font-medium">{t("settings.notifications.email.title")}</h3>
|
||||
{isAdmin() && (
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
{t('settings.notifications.email.please')}{' '}
|
||||
{t("settings.notifications.email.please")}{" "}
|
||||
<a href="/_/#/settings/mail" className="link" target="_blank">
|
||||
{t('settings.notifications.email.configure_an_SMTP_server')}
|
||||
</a>{' '}
|
||||
{t('settings.notifications.email.to_ensure_alerts_are_delivered')}{' '}
|
||||
{t("settings.notifications.email.configure_an_SMTP_server")}
|
||||
</a>{" "}
|
||||
{t("settings.notifications.email.to_ensure_alerts_are_delivered")}{" "}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Label className="block" htmlFor="email">
|
||||
{t('settings.notifications.email.to_email_s')}
|
||||
{t("settings.notifications.email.to_email_s")}
|
||||
</Label>
|
||||
<InputTags
|
||||
value={emails}
|
||||
onChange={setEmails}
|
||||
placeholder={t('settings.notifications.email.enter_email_address')}
|
||||
placeholder={t("settings.notifications.email.enter_email_address")}
|
||||
className="w-full"
|
||||
type="email"
|
||||
id="email"
|
||||
/>
|
||||
<p className="text-[0.8rem] text-muted-foreground">
|
||||
{t('settings.notifications.email.des')}
|
||||
</p>
|
||||
<p className="text-[0.8rem] text-muted-foreground">{t("settings.notifications.email.des")}</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h3 className="mb-1 text-lg font-medium">
|
||||
{t('settings.notifications.webhook_push.title')}
|
||||
</h3>
|
||||
<h3 className="mb-1 text-lg font-medium">{t("settings.notifications.webhook_push.title")}</h3>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
{t('settings.notifications.webhook_push.des_1')}{' '}
|
||||
<a
|
||||
href="https://containrrr.dev/shoutrrr/services/overview/"
|
||||
target="_blank"
|
||||
className="link"
|
||||
>
|
||||
{t("settings.notifications.webhook_push.des_1")}{" "}
|
||||
<a href="https://containrrr.dev/shoutrrr/services/overview/" target="_blank" className="link">
|
||||
Shoutrrr
|
||||
</a>{' '}
|
||||
{t('settings.notifications.webhook_push.des_2')}
|
||||
</a>{" "}
|
||||
{t("settings.notifications.webhook_push.des_2")}
|
||||
</p>
|
||||
</div>
|
||||
{webhooks.length > 0 && (
|
||||
@@ -137,9 +125,7 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
|
||||
<ShoutrrrUrlCard
|
||||
key={index}
|
||||
url={webhook}
|
||||
onUrlChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
updateWebhook(index, e.target.value)
|
||||
}
|
||||
onUrlChange={(e: React.ChangeEvent<HTMLInputElement>) => updateWebhook(index, e.target.value)}
|
||||
onRemove={() => removeWebhook(index)}
|
||||
/>
|
||||
))}
|
||||
@@ -153,7 +139,7 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
|
||||
onClick={addWebhook}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4 -ml-0.5" />
|
||||
{t('settings.notifications.webhook_push.add_url')}
|
||||
{t("settings.notifications.webhook_push.add_url")}
|
||||
</Button>
|
||||
</div>
|
||||
<Separator />
|
||||
@@ -163,12 +149,8 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
|
||||
onClick={updateSettings}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<LoaderCircleIcon className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<SaveIcon className="h-4 w-4" />
|
||||
)}
|
||||
{t('settings.save_settings')}
|
||||
{isLoading ? <LoaderCircleIcon className="h-4 w-4 animate-spin" /> : <SaveIcon className="h-4 w-4" />}
|
||||
{t("settings.save_settings")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -180,17 +162,17 @@ const ShoutrrrUrlCard = ({ url, onUrlChange, onRemove }: ShoutrrrUrlCardProps) =
|
||||
|
||||
const sendTestNotification = async () => {
|
||||
setIsLoading(true)
|
||||
const res = await pb.send('/api/beszel/send-test-notification', { url })
|
||||
if ('err' in res && !res.err) {
|
||||
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',
|
||||
title: "Test notification sent",
|
||||
description: "Check your notification service",
|
||||
})
|
||||
} else {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: res.err ?? 'Failed to send test notification',
|
||||
variant: 'destructive',
|
||||
title: "Error",
|
||||
description: res.err ?? "Failed to send test notification",
|
||||
variant: "destructive",
|
||||
})
|
||||
}
|
||||
setIsLoading(false)
|
||||
@@ -211,7 +193,7 @@ const ShoutrrrUrlCard = ({ url, onUrlChange, onRemove }: ShoutrrrUrlCardProps) =
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-20 md:w-28"
|
||||
disabled={isLoading || url === ''}
|
||||
disabled={isLoading || url === ""}
|
||||
onClick={sendTestNotification}
|
||||
>
|
||||
{isLoading ? (
|
||||
@@ -222,14 +204,7 @@ const ShoutrrrUrlCard = ({ url, onUrlChange, onRemove }: ShoutrrrUrlCardProps) =
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="shrink-0"
|
||||
aria-label="Delete"
|
||||
onClick={onRemove}
|
||||
>
|
||||
<Button type="button" variant="outline" size="icon" className="shrink-0" aria-label="Delete" onClick={onRemove}>
|
||||
<Trash2Icon className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '../../ui/button'
|
||||
import { $router, Link, navigate } from '../../router'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "../../ui/button"
|
||||
import { $router, Link, navigate } from "../../router"
|
||||
import { useStore } from "@nanostores/react"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
|
||||
interface SidebarNavProps extends React.HTMLAttributes<HTMLElement> {
|
||||
items: {
|
||||
@@ -46,16 +40,16 @@ export function SidebarNav({ className, items, ...props }: SidebarNavProps) {
|
||||
</div>
|
||||
|
||||
{/* Desktop View */}
|
||||
<nav className={cn('hidden md:grid gap-1', className)} {...props}>
|
||||
<nav className={cn("hidden md:grid gap-1", className)} {...props}>
|
||||
{items.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'ghost' }),
|
||||
'flex items-center gap-3',
|
||||
page?.path === item.href ? 'bg-muted hover:bg-muted' : 'hover:bg-muted/50',
|
||||
'justify-start'
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"flex items-center gap-3",
|
||||
page?.path === item.href ? "bg-muted hover:bg-muted" : "hover:bg-muted/50",
|
||||
"justify-start"
|
||||
)}
|
||||
>
|
||||
{item.icon && <item.icon className="h-4 w-4" />}
|
||||
|
||||
Reference in New Issue
Block a user