mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-25 08:56:21 +00:00
update notification service
This commit is contained in:
@@ -1,103 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
|
||||
const CHANNELS = [
|
||||
{ key: "telegram", label: "Telegram", icon: "/icons/telegram.svg", color: "blue", switchOn: "bg-blue-600" },
|
||||
{ key: "gotify", label: "Gotify", icon: "/icons/gotify.svg", color: "green", switchOn: "bg-green-600" },
|
||||
{ key: "discord", label: "Discord", icon: "/icons/discord.svg", color: "indigo", switchOn: "bg-indigo-600" },
|
||||
{ key: "email", label: "Email", icon: "/icons/mail.svg", color: "amber", switchOn: "bg-amber-600" },
|
||||
]
|
||||
|
||||
const SELECTED_BORDER = {
|
||||
blue: "border-blue-500/60 bg-blue-500/10",
|
||||
green: "border-green-500/60 bg-green-500/10",
|
||||
indigo: "border-indigo-500/60 bg-indigo-500/10",
|
||||
amber: "border-amber-500/60 bg-amber-500/10",
|
||||
}
|
||||
|
||||
interface ChannelGridProps {
|
||||
enabledChannels: { telegram: boolean; gotify: boolean; discord: boolean; email: boolean }
|
||||
onToggle: (channel: string, enabled: boolean) => void
|
||||
selectedChannel: string | null
|
||||
onSelect: (channel: string | null) => void
|
||||
}
|
||||
|
||||
export function ChannelGrid({ enabledChannels, onToggle, selectedChannel, onSelect }: ChannelGridProps) {
|
||||
return (
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
{CHANNELS.map(ch => {
|
||||
const isEnabled = enabledChannels[ch.key as keyof typeof enabledChannels] || false
|
||||
const isSelected = selectedChannel === ch.key
|
||||
const selStyle = SELECTED_BORDER[ch.color as keyof typeof SELECTED_BORDER]
|
||||
|
||||
return (
|
||||
<button
|
||||
key={ch.key}
|
||||
type="button"
|
||||
onClick={() => onSelect(isSelected ? null : ch.key)}
|
||||
className={
|
||||
"group relative flex flex-col items-center justify-center gap-2 rounded-lg border p-4 transition-all cursor-pointer " +
|
||||
(isSelected
|
||||
? selStyle + " ring-1 ring-offset-0"
|
||||
: isEnabled
|
||||
? "border-border/60 bg-muted/30 hover:bg-muted/40"
|
||||
: "border-border/30 bg-muted/10 hover:border-border/50 hover:bg-muted/20")
|
||||
}
|
||||
>
|
||||
{/* Status dot */}
|
||||
{isEnabled && (
|
||||
<span className={"absolute top-2 right-2 h-1.5 w-1.5 rounded-full " + ch.switchOn} />
|
||||
)}
|
||||
|
||||
{/* Logo */}
|
||||
<img
|
||||
src={ch.icon}
|
||||
alt={ch.label}
|
||||
className={
|
||||
"h-7 w-7 transition-opacity " +
|
||||
(isEnabled || isSelected ? "opacity-100" : "opacity-30 group-hover:opacity-70")
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Label */}
|
||||
<span
|
||||
className={
|
||||
"text-[11px] font-medium transition-colors " +
|
||||
(isEnabled || isSelected ? "text-foreground" : "text-muted-foreground/60")
|
||||
}
|
||||
>
|
||||
{ch.label}
|
||||
</span>
|
||||
|
||||
{/* Hover overlay with switch */}
|
||||
<div
|
||||
className="absolute inset-x-0 bottom-0 flex items-center justify-center py-1.5 rounded-b-lg bg-background/80 backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onToggle(ch.key, !isEnabled)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
"relative w-8 h-4 rounded-full transition-colors " +
|
||||
(isEnabled ? ch.switchOn : "bg-muted-foreground/30")
|
||||
}
|
||||
role="switch"
|
||||
aria-checked={isEnabled}
|
||||
aria-label={"Enable " + ch.label}
|
||||
>
|
||||
<span
|
||||
className={
|
||||
"absolute top-[2px] left-[2px] h-3 w-3 rounded-full bg-white shadow transition-transform " +
|
||||
(isEnabled ? "translate-x-4" : "translate-x-0")
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useEffect, useCallback } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card"
|
||||
import { ChannelGrid } from "./channel-grid"
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "./ui/tabs"
|
||||
import { Input } from "./ui/input"
|
||||
import { Label } from "./ui/label"
|
||||
import { Badge } from "./ui/badge"
|
||||
@@ -143,7 +143,6 @@ export function NotificationSettings() {
|
||||
const [editMode, setEditMode] = useState(false)
|
||||
const [hasChanges, setHasChanges] = useState(false)
|
||||
const [expandedCategories, setExpandedCategories] = useState<Set<string>>(new Set())
|
||||
const [selectedChannel, setSelectedChannel] = useState<string | null>(null)
|
||||
const [originalConfig, setOriginalConfig] = useState<NotificationConfig>(DEFAULT_CONFIG)
|
||||
const [webhookSetup, setWebhookSetup] = useState<{
|
||||
status: "idle" | "running" | "success" | "failed"
|
||||
@@ -671,22 +670,41 @@ matcher: proxmenux-pbs
|
||||
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">Channels</span>
|
||||
</div>
|
||||
|
||||
<ChannelGrid
|
||||
enabledChannels={{
|
||||
telegram: config.channels.telegram?.enabled || false,
|
||||
gotify: config.channels.gotify?.enabled || false,
|
||||
discord: config.channels.discord?.enabled || false,
|
||||
email: config.channels.email?.enabled || false,
|
||||
}}
|
||||
onToggle={(ch, val) => updateChannel(ch, "enabled", val)}
|
||||
selectedChannel={selectedChannel}
|
||||
onSelect={setSelectedChannel}
|
||||
/>
|
||||
<div className="rounded-lg border border-border/50 bg-muted/20 p-3">
|
||||
<Tabs defaultValue="telegram" className="w-full">
|
||||
<TabsList className="w-full grid grid-cols-4 h-8">
|
||||
<TabsTrigger value="telegram" className="text-xs data-[state=active]:text-blue-500">
|
||||
Telegram
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="gotify" className="text-xs data-[state=active]:text-green-500">
|
||||
Gotify
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="discord" className="text-xs data-[state=active]:text-indigo-500">
|
||||
Discord
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="email" className="text-xs data-[state=active]:text-amber-500">
|
||||
Email
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* ── Telegram Config ── */}
|
||||
{selectedChannel === "telegram" && (
|
||||
<div className="rounded-lg border border-blue-500/30 bg-blue-500/5 p-3 space-y-3 mt-3">
|
||||
{config.channels.telegram?.enabled ? (
|
||||
{/* Telegram */}
|
||||
<TabsContent value="telegram" className="space-y-3 pt-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs font-medium">Enable Telegram</Label>
|
||||
<button
|
||||
className={`relative w-9 h-[18px] rounded-full transition-colors ${
|
||||
config.channels.telegram?.enabled ? "bg-blue-600" : "bg-muted-foreground/30"
|
||||
} cursor-pointer`}
|
||||
onClick={() => updateChannel("telegram", "enabled", !config.channels.telegram?.enabled)}
|
||||
role="switch"
|
||||
aria-checked={config.channels.telegram?.enabled || false}
|
||||
>
|
||||
<span className={`absolute top-[1px] left-[1px] h-4 w-4 rounded-full bg-white shadow transition-transform ${
|
||||
config.channels.telegram?.enabled ? "translate-x-[18px]" : "translate-x-0"
|
||||
}`} />
|
||||
</button>
|
||||
</div>
|
||||
{config.channels.telegram?.enabled && (
|
||||
<>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-[11px] text-muted-foreground">Bot Token</Label>
|
||||
@@ -715,6 +733,7 @@ matcher: proxmenux-pbs
|
||||
onChange={e => updateChannel("telegram", "chat_id", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{/* Per-channel action bar */}
|
||||
<div className="flex items-center gap-2 pt-2 border-t border-border/50">
|
||||
<button
|
||||
className="h-7 px-3 text-xs rounded-md bg-blue-600 hover:bg-blue-700 text-white transition-colors disabled:opacity-50 flex items-center gap-1.5"
|
||||
@@ -734,16 +753,27 @@ matcher: proxmenux-pbs
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground text-center py-2">Enable Telegram using the switch on hover to configure it.</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* ── Gotify Config ── */}
|
||||
{selectedChannel === "gotify" && (
|
||||
<div className="rounded-lg border border-green-500/30 bg-green-500/5 p-3 space-y-3 mt-3">
|
||||
{config.channels.gotify?.enabled ? (
|
||||
{/* Gotify */}
|
||||
<TabsContent value="gotify" className="space-y-3 pt-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs font-medium">Enable Gotify</Label>
|
||||
<button
|
||||
className={`relative w-9 h-[18px] rounded-full transition-colors ${
|
||||
config.channels.gotify?.enabled ? "bg-green-600" : "bg-muted-foreground/30"
|
||||
} cursor-pointer`}
|
||||
onClick={() => updateChannel("gotify", "enabled", !config.channels.gotify?.enabled)}
|
||||
role="switch"
|
||||
aria-checked={config.channels.gotify?.enabled || false}
|
||||
>
|
||||
<span className={`absolute top-[1px] left-[1px] h-4 w-4 rounded-full bg-white shadow transition-transform ${
|
||||
config.channels.gotify?.enabled ? "translate-x-[18px]" : "translate-x-0"
|
||||
}`} />
|
||||
</button>
|
||||
</div>
|
||||
{config.channels.gotify?.enabled && (
|
||||
<>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-[11px] text-muted-foreground">Server URL</Label>
|
||||
@@ -772,6 +802,7 @@ matcher: proxmenux-pbs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Per-channel action bar */}
|
||||
<div className="flex items-center gap-2 pt-2 border-t border-border/50">
|
||||
<button
|
||||
className="h-7 px-3 text-xs rounded-md bg-green-600 hover:bg-green-700 text-white transition-colors disabled:opacity-50 flex items-center gap-1.5"
|
||||
@@ -791,16 +822,27 @@ matcher: proxmenux-pbs
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground text-center py-2">Enable Gotify using the switch on hover to configure it.</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* ── Discord Config ── */}
|
||||
{selectedChannel === "discord" && (
|
||||
<div className="rounded-lg border border-indigo-500/30 bg-indigo-500/5 p-3 space-y-3 mt-3">
|
||||
{config.channels.discord?.enabled ? (
|
||||
{/* Discord */}
|
||||
<TabsContent value="discord" className="space-y-3 pt-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs font-medium">Enable Discord</Label>
|
||||
<button
|
||||
className={`relative w-9 h-[18px] rounded-full transition-colors ${
|
||||
config.channels.discord?.enabled ? "bg-indigo-600" : "bg-muted-foreground/30"
|
||||
} cursor-pointer`}
|
||||
onClick={() => updateChannel("discord", "enabled", !config.channels.discord?.enabled)}
|
||||
role="switch"
|
||||
aria-checked={config.channels.discord?.enabled || false}
|
||||
>
|
||||
<span className={`absolute top-[1px] left-[1px] h-4 w-4 rounded-full bg-white shadow transition-transform ${
|
||||
config.channels.discord?.enabled ? "translate-x-[18px]" : "translate-x-0"
|
||||
}`} />
|
||||
</button>
|
||||
</div>
|
||||
{config.channels.discord?.enabled && (
|
||||
<>
|
||||
<div className="space-y-1.5">
|
||||
<Label className="text-[11px] text-muted-foreground">Webhook URL</Label>
|
||||
@@ -820,6 +862,7 @@ matcher: proxmenux-pbs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Per-channel action bar */}
|
||||
<div className="flex items-center gap-2 pt-2 border-t border-border/50">
|
||||
<button
|
||||
className="h-7 px-3 text-xs rounded-md bg-indigo-600 hover:bg-indigo-700 text-white transition-colors disabled:opacity-50 flex items-center gap-1.5"
|
||||
@@ -839,16 +882,27 @@ matcher: proxmenux-pbs
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground text-center py-2">Enable Discord using the switch on hover to configure it.</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* ── Email Config ── */}
|
||||
{selectedChannel === "email" && (
|
||||
<div className="rounded-lg border border-amber-500/30 bg-amber-500/5 p-3 space-y-3 mt-3">
|
||||
{config.channels.email?.enabled ? (
|
||||
{/* Email */}
|
||||
<TabsContent value="email" className="space-y-3 pt-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs font-medium">Enable Email</Label>
|
||||
<button
|
||||
className={`relative w-9 h-[18px] rounded-full transition-colors ${
|
||||
config.channels.email?.enabled ? "bg-amber-600" : "bg-muted-foreground/30"
|
||||
} cursor-pointer`}
|
||||
onClick={() => updateChannel("email", "enabled", !config.channels.email?.enabled)}
|
||||
role="switch"
|
||||
aria-checked={config.channels.email?.enabled || false}
|
||||
>
|
||||
<span className={`absolute top-[1px] left-[1px] h-4 w-4 rounded-full bg-white shadow transition-transform ${
|
||||
config.channels.email?.enabled ? "translate-x-[18px]" : "translate-x-0"
|
||||
}`} />
|
||||
</button>
|
||||
</div>
|
||||
{config.channels.email?.enabled && (
|
||||
<>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<div className="space-y-1.5">
|
||||
@@ -949,6 +1003,7 @@ matcher: proxmenux-pbs
|
||||
For Gmail, use an App Password instead of your account password.
|
||||
</p>
|
||||
</div>
|
||||
{/* Per-channel action bar */}
|
||||
<div className="flex items-center gap-2 pt-2 border-t border-border/50">
|
||||
<button
|
||||
className="h-7 px-3 text-xs rounded-md bg-amber-600 hover:bg-amber-700 text-white transition-colors disabled:opacity-50 flex items-center gap-1.5"
|
||||
@@ -968,11 +1023,9 @@ matcher: proxmenux-pbs
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground text-center py-2">Enable Email using the switch on hover to configure it.</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
{/* Test Result */}
|
||||
{testResult && (
|
||||
@@ -989,6 +1042,7 @@ matcher: proxmenux-pbs
|
||||
<span>{testResult.message}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>{/* close bordered channel container */}
|
||||
</div>
|
||||
|
||||
{/* ── Filters ── */}
|
||||
|
||||
Reference in New Issue
Block a user