mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-30 11:26:23 +00:00
Update notification service
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
|||||||
AlertTriangle, Info, Settings2, Zap, Eye, EyeOff,
|
AlertTriangle, Info, Settings2, Zap, Eye, EyeOff,
|
||||||
Trash2, ChevronDown, ChevronUp, ChevronRight, TestTube2, Mail, Webhook,
|
Trash2, ChevronDown, ChevronUp, ChevronRight, TestTube2, Mail, Webhook,
|
||||||
Copy, Server, Shield, ExternalLink, RefreshCw, Download, Upload,
|
Copy, Server, Shield, ExternalLink, RefreshCw, Download, Upload,
|
||||||
Cloud, Cpu, Globe, MessageSquareText, Sparkles
|
Cloud, Brain, Globe, MessageSquareText, Sparkles, Pencil, Save, RotateCcw
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
|
||||||
interface ChannelConfig {
|
interface ChannelConfig {
|
||||||
@@ -282,6 +282,8 @@ export function NotificationSettings() {
|
|||||||
const [providerModels, setProviderModels] = useState<string[]>([])
|
const [providerModels, setProviderModels] = useState<string[]>([])
|
||||||
const [loadingProviderModels, setLoadingProviderModels] = useState(false)
|
const [loadingProviderModels, setLoadingProviderModels] = useState(false)
|
||||||
const [showCustomPromptInfo, setShowCustomPromptInfo] = useState(false)
|
const [showCustomPromptInfo, setShowCustomPromptInfo] = useState(false)
|
||||||
|
const [editingCustomPrompt, setEditingCustomPrompt] = useState(false)
|
||||||
|
const [customPromptDraft, setCustomPromptDraft] = useState("")
|
||||||
const [webhookSetup, setWebhookSetup] = useState<{
|
const [webhookSetup, setWebhookSetup] = useState<{
|
||||||
status: "idle" | "running" | "success" | "failed"
|
status: "idle" | "running" | "success" | "failed"
|
||||||
fallback_commands: string[]
|
fallback_commands: string[]
|
||||||
@@ -1624,7 +1626,7 @@ export function NotificationSettings() {
|
|||||||
{/* Model - selector with Load button for all providers */}
|
{/* Model - selector with Load button for all providers */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Cpu className="h-4 w-4 text-blue-400" />
|
<Brain className="h-4 w-4 text-blue-400" />
|
||||||
<Label className="text-xs sm:text-sm text-foreground/80">Model</Label>
|
<Label className="text-xs sm:text-sm text-foreground/80">Model</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -1775,11 +1777,56 @@ export function NotificationSettings() {
|
|||||||
{config.ai_prompt_mode === "custom" && (
|
{config.ai_prompt_mode === "custom" && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-xs sm:text-sm text-foreground/80">Custom Prompt</Label>
|
<div className="flex items-center justify-between">
|
||||||
|
<Label className="text-xs sm:text-sm text-foreground/80">Custom Prompt</Label>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
{!editingCustomPrompt ? (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setCustomPromptDraft(config.ai_custom_prompt || "")
|
||||||
|
setEditingCustomPrompt(true)
|
||||||
|
}}
|
||||||
|
className="h-7 px-2 text-xs flex items-center gap-1"
|
||||||
|
>
|
||||||
|
<Pencil className="h-3 w-3" />
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
updateConfig(p => ({ ...p, ai_custom_prompt: customPromptDraft }))
|
||||||
|
setEditingCustomPrompt(false)
|
||||||
|
handleSave()
|
||||||
|
}}
|
||||||
|
className="h-7 px-2 text-xs flex items-center gap-1 bg-green-600 hover:bg-green-700 text-white border-green-600"
|
||||||
|
>
|
||||||
|
<Save className="h-3 w-3" />
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingCustomPrompt(false)
|
||||||
|
setCustomPromptDraft("")
|
||||||
|
}}
|
||||||
|
className="h-7 px-2 text-xs"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
value={config.ai_custom_prompt || ""}
|
value={editingCustomPrompt ? customPromptDraft : (config.ai_custom_prompt || "")}
|
||||||
onChange={e => updateConfig(p => ({ ...p, ai_custom_prompt: e.target.value }))}
|
onChange={e => setCustomPromptDraft(e.target.value)}
|
||||||
disabled={!editMode}
|
disabled={!editingCustomPrompt}
|
||||||
placeholder="Enter your custom prompt instructions for the AI..."
|
placeholder="Enter your custom prompt instructions for the AI..."
|
||||||
className="w-full h-48 px-3 py-2 text-sm rounded-md border border-border bg-background resize-y focus:outline-none focus:ring-2 focus:ring-purple-500/50 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="w-full h-48 px-3 py-2 text-sm rounded-md border border-border bg-background resize-y focus:outline-none focus:ring-2 focus:ring-purple-500/50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
/>
|
/>
|
||||||
@@ -1788,7 +1835,7 @@ export function NotificationSettings() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
disabled={!editMode}
|
disabled={editingCustomPrompt}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const blob = new Blob([config.ai_custom_prompt || ""], { type: "text/plain" })
|
const blob = new Blob([config.ai_custom_prompt || ""], { type: "text/plain" })
|
||||||
const url = URL.createObjectURL(blob)
|
const url = URL.createObjectURL(blob)
|
||||||
@@ -1806,7 +1853,7 @@ export function NotificationSettings() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
disabled={!editMode}
|
disabled={editingCustomPrompt}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const input = document.createElement("input")
|
const input = document.createElement("input")
|
||||||
input.type = "file"
|
input.type = "file"
|
||||||
@@ -1816,6 +1863,7 @@ export function NotificationSettings() {
|
|||||||
if (file) {
|
if (file) {
|
||||||
const text = await file.text()
|
const text = await file.text()
|
||||||
updateConfig(p => ({ ...p, ai_custom_prompt: text }))
|
updateConfig(p => ({ ...p, ai_custom_prompt: text }))
|
||||||
|
handleSave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input.click()
|
input.click()
|
||||||
@@ -1825,6 +1873,21 @@ export function NotificationSettings() {
|
|||||||
<Upload className="h-4 w-4" />
|
<Upload className="h-4 w-4" />
|
||||||
Import
|
Import
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
disabled={editingCustomPrompt || !config.ai_custom_prompt}
|
||||||
|
onClick={() => {
|
||||||
|
if (confirm("Clear the custom prompt and start from scratch?")) {
|
||||||
|
updateConfig(p => ({ ...p, ai_custom_prompt: "" }))
|
||||||
|
handleSave()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-1 text-red-400 hover:text-red-300 hover:border-red-400"
|
||||||
|
>
|
||||||
|
<RotateCcw className="h-4 w-4" />
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start gap-2 p-3 rounded-md bg-purple-500/10 border border-purple-500/20">
|
<div className="flex items-start gap-2 p-3 rounded-md bg-purple-500/10 border border-purple-500/20">
|
||||||
<Info className="h-4 w-4 text-purple-400 shrink-0 mt-0.5" />
|
<Info className="h-4 w-4 text-purple-400 shrink-0 mt-0.5" />
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
You are a system notification formatter for ProxMenux Monitor, a Proxmox VE monitoring tool.
|
|
||||||
|
|
||||||
Your task is to translate and reformat incoming server alert messages into {language}.
|
|
||||||
|
|
||||||
═══ ABSOLUTE RULES ═══
|
|
||||||
1. Translate BOTH title and body to {language}. Every word, label, and unit must be in {language}.
|
|
||||||
2. NO markdown: no **bold**, no *italic*, no `code`, no headers (#), no bullet lists (- or *)
|
|
||||||
3. Plain text only — the output is sent to chat apps and email which handle their own formatting
|
|
||||||
4. Tone: factual, concise, technical. No greetings, no closings, no apologies
|
|
||||||
5. DO NOT add recommendations, action items, or suggestions ("you should…", "consider…")
|
|
||||||
6. Present ONLY the facts already in the input — do not invent or assume information
|
|
||||||
7. OUTPUT ONLY THE FINAL RESULT — never include both original and processed versions.
|
|
||||||
Do NOT append "Original message:", "Original:", "Source:", or any before/after comparison.
|
|
||||||
Return ONLY the single, final formatted message in {language}.
|
|
||||||
8. PLAIN NARRATIVE LINES — if a line in the input is a complete sentence (not a "Label: value"
|
|
||||||
pair), translate it as-is. Never prepend "Message:", "Note:", or any other label to a sentence.
|
|
||||||
9. Detail level to apply: {detail_level}
|
|
||||||
- brief → 2-3 lines, essential data only (status + key metric)
|
|
||||||
- standard → short paragraph covering who/what/where and the key value
|
|
||||||
- detailed → full technical breakdown of all available fields
|
|
||||||
10. Keep the "hostname: " prefix in the title. Translate only the descriptive part.
|
|
||||||
Example: "pve01: Updates available" → "pve01: Actualizaciones disponibles"
|
|
||||||
11. EMPTY LIST VALUES — if a list field is empty, "none", or "0":
|
|
||||||
Always write the translated word for "none" on the line after the label, never leave it blank.
|
|
||||||
12. DEDUPLICATION — input may contain redundant or repeated information from multiple monitoring sources:
|
|
||||||
- Identify and merge duplicate facts (same device, same error, same metric mentioned twice)
|
|
||||||
- Present each unique fact exactly once in a clear, consolidated form
|
|
||||||
- If the same data appears in different formats, choose the most informative version
|
|
||||||
|
|
||||||
{emoji_instructions}
|
|
||||||
|
|
||||||
═══ OUTPUT FORMAT ═══
|
|
||||||
TITLE: <translated title>
|
|
||||||
BODY:
|
|
||||||
<translated body>
|
|
||||||
|
|
||||||
IMPORTANT:
|
|
||||||
- Do NOT include the literal words TITLE or BODY anywhere in the translated content
|
|
||||||
Reference in New Issue
Block a user