diff --git a/ui/web/src/pages/cron/cron-detail/cron-advanced-dialog.tsx b/ui/web/src/pages/cron/cron-detail/cron-advanced-dialog.tsx index 09556a99..da6b242c 100644 --- a/ui/web/src/pages/cron/cron-detail/cron-advanced-dialog.tsx +++ b/ui/web/src/pages/cron/cron-detail/cron-advanced-dialog.tsx @@ -1,17 +1,28 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { Save, Settings, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { ConfigGroupHeader } from "@/components/shared/config-group-header"; import { Combobox } from "@/components/ui/combobox"; import { getAllIanaTimezones, isValidIanaTimezone } from "@/lib/constants"; import { toast } from "@/stores/use-toast-store"; +import { useChannels } from "@/pages/channels/hooks/use-channels"; +import { useWs } from "@/hooks/use-ws"; +import { Methods } from "@/api/protocol"; import type { CronJob, CronJobPatch } from "../hooks/use-cron"; +interface DeliveryTarget { + channel: string; + chatId: string; + title?: string; + kind: string; +} + interface CronAdvancedDialogProps { open: boolean; onOpenChange: (open: boolean) => void; @@ -34,6 +45,9 @@ function deriveState(job: CronJob) { export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAdvancedDialogProps) { const { t } = useTranslation("cron"); const { t: tc } = useTranslation("common"); + const ws = useWs(); + const { channels: availableChannels } = useChannels(); + const channelNames = Object.keys(availableChannels); const init = deriveState(job); const [timezone, setTimezone] = useState(init.timezone); @@ -44,6 +58,17 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd const [deleteAfterRun, setDeleteAfterRun] = useState(init.deleteAfterRun); const [stateless, setStateless] = useState(init.stateless); const [saving, setSaving] = useState(false); + const [targets, setTargets] = useState([]); + + const fetchTargets = useCallback(async () => { + if (!job.agentId || !ws.isConnected) return; + try { + const res = await ws.call<{ targets: DeliveryTarget[] }>( + Methods.HEARTBEAT_TARGETS, { agentId: job.agentId }, + ); + setTargets(res.targets ?? []); + } catch { /* ignore — fallback to Input */ } + }, [ws, job.agentId]); // Re-sync when dialog opens useEffect(() => { @@ -56,6 +81,7 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd setWakeHeartbeat(s.wakeHeartbeat); setDeleteAfterRun(s.deleteAfterRun); setStateless(s.stateless); + fetchTargets(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); @@ -73,7 +99,7 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd await onUpdate(job.id, { schedule: { ...job.schedule, - tz: timezone !== "UTC" ? timezone : undefined, + tz: timezone !== "UTC" ? timezone : "", }, deliver, deliverChannel: deliver ? channel.trim() || undefined : undefined, @@ -131,28 +157,77 @@ export function CronAdvancedDialog({ open, onOpenChange, job, onUpdate }: CronAd {deliver && ( - <> -
- - setChannel(e.target.value)} - placeholder={t("detail.channelPlaceholder")} - className="text-base md:text-sm" - /> +
+
+ + {channelNames.length > 0 ? ( + + ) : ( + setChannel(e.target.value)} + placeholder={t("detail.channelPlaceholder")} + className="text-base md:text-sm" + /> + )}
-
- - setTo(e.target.value)} - placeholder={t("detail.toPlaceholder")} - className="text-base md:text-sm" - /> +
+ + {(() => { + if (!channel) { + return ( + + ); + } + const filtered = targets.filter((tgt) => tgt.channel === channel); + if (filtered.length > 0) { + return ( + + ); + } + return ( + setTo(e.target.value)} + placeholder={t("detail.toPlaceholder")} + className="text-base md:text-sm" + /> + ); + })()}
- +
)}