'use client'; import { getNodeConfig, getNodeMultiplier, setNodeMultiplier, updateNodeConfig, } from '@/services/admin/system'; import { zodResolver } from '@hookform/resolvers/zod'; import { useQuery } from '@tanstack/react-query'; import { Button } from '@workspace/ui/components/button'; import { ChartContainer, ChartTooltip } from '@workspace/ui/components/chart'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@workspace/ui/components/form'; import { Label } from '@workspace/ui/components/label'; import { ScrollArea } from '@workspace/ui/components/scroll-area'; import { Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, } from '@workspace/ui/components/sheet'; import { ArrayInput } from '@workspace/ui/custom-components/dynamic-Inputs'; import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input'; import { Icon } from '@workspace/ui/custom-components/icon'; import { DicesIcon } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { uid } from 'radash'; import { useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { Cell, Legend, Pie, PieChart } from 'recharts'; import { toast } from 'sonner'; import { z } from 'zod'; const COLORS = [ 'hsl(var(--chart-1))', 'hsl(var(--chart-2))', 'hsl(var(--chart-3))', 'hsl(var(--chart-4))', 'hsl(var(--chart-5))', ]; const MINUTES_IN_DAY = 1440; function getTimeRangeData(slots: API.TimePeriod[]) { const timePoints = slots .filter((slot) => slot.start_time && slot.end_time) .flatMap((slot) => { const [startH = 0, startM = 0] = slot.start_time.split(':').map(Number); const [endH = 0, endM = 0] = slot.end_time.split(':').map(Number); const start = startH * 60 + startM; let end = endH * 60 + endM; if (end < start) end += MINUTES_IN_DAY; return { start, end, multiplier: slot.multiplier }; }) .sort((a, b) => a.start - b.start); const result: { name: string; value: number; multiplier: number }[] = []; let currentMinute = 0; timePoints.forEach((point) => { if (point.start > currentMinute) { result.push({ name: `${Math.floor(currentMinute / 60)}:${String(currentMinute % 60).padStart(2, '0')} - ${Math.floor(point.start / 60)}:${String(point.start % 60).padStart(2, '0')}`, value: point.start - currentMinute, multiplier: 1, }); } result.push({ name: `${Math.floor(point.start / 60)}:${String(point.start % 60).padStart(2, '0')} - ${Math.floor((point.end / 60) % 24)}:${String(point.end % 60).padStart(2, '0')}`, value: point.end - point.start, multiplier: point.multiplier, }); currentMinute = point.end % MINUTES_IN_DAY; }); if (currentMinute < MINUTES_IN_DAY) { result.push({ name: `${Math.floor(currentMinute / 60)}:${String(currentMinute % 60).padStart(2, '0')} - 24:00`, value: MINUTES_IN_DAY - currentMinute, multiplier: 1, }); } return result; } const nodeConfigSchema = z.object({ node_secret: z.string().optional(), node_pull_interval: z.number().optional(), node_push_interval: z.number().optional(), }); type NodeConfigFormData = z.infer; export default function ServerConfig() { const t = useTranslations('servers'); const [open, setOpen] = useState(false); const [saving, setSaving] = useState(false); const [timeSlots, setTimeSlots] = useState([]); const { data: cfgResp, refetch: refetchCfg } = useQuery({ queryKey: ['getNodeConfig'], queryFn: async () => { const { data } = await getNodeConfig(); return data.data as API.NodeConfig | undefined; }, enabled: open, }); const { data: periodsResp, refetch: refetchPeriods } = useQuery({ queryKey: ['getNodeMultiplier'], queryFn: async () => { const { data } = await getNodeMultiplier(); return (data.data?.periods || []) as API.TimePeriod[]; }, enabled: open, }); const form = useForm({ resolver: zodResolver(nodeConfigSchema), defaultValues: { node_secret: '', node_pull_interval: undefined, node_push_interval: undefined, }, }); useEffect(() => { if (cfgResp) { form.reset({ node_secret: cfgResp.node_secret ?? '', node_pull_interval: cfgResp.node_pull_interval as number | undefined, node_push_interval: cfgResp.node_push_interval as number | undefined, }); } }, [cfgResp, form]); useEffect(() => { if (periodsResp) { setTimeSlots(periodsResp); } }, [periodsResp]); const chartTimeSlots = useMemo(() => getTimeRangeData(timeSlots), [timeSlots]); const chartConfig = useMemo(() => { return chartTimeSlots.reduce( (acc, item, index) => { acc[item.name] = { label: item.name, color: COLORS[index % COLORS.length] || 'hsl(var(--default-chart-color))', }; return acc; }, {} as Record, ); }, [chartTimeSlots]); async function onSubmit(values: NodeConfigFormData) { setSaving(true); try { await updateNodeConfig(values as API.NodeConfig); toast.success(t('config.saveSuccess')); await refetchCfg(); setOpen(false); } finally { setSaving(false); } } async function savePeriods() { await setNodeMultiplier({ periods: timeSlots }); await refetchPeriods(); toast.success(t('config.saveSuccess')); } return (

{t('config.title')}

{t('config.description')}

{t('config.title')}
( {t('config.communicationKey')} { const id = uid(32).toLowerCase(); const formatted = `${id.slice(0, 8)}-${id.slice(8, 12)}-${id.slice(12, 16)}-${id.slice(16, 20)}-${id.slice(20)}`; form.setValue('node_secret', formatted); }} className='cursor-pointer' /> } /> {t('config.communicationKeyDescription')} )} /> ( {t('config.nodePullInterval')} {t('config.nodePullIntervalDescription')} )} /> ( {t('config.nodePushInterval')} {t('config.nodePushIntervalDescription')} )} />

{t('config.dynamicMultiplierDescription')}

fields={[ { name: 'start_time', prefix: t('config.startTime'), type: 'time' }, { name: 'end_time', prefix: t('config.endTime'), type: 'time' }, { name: 'multiplier', prefix: t('config.multiplier'), type: 'number', placeholder: '0', }, ]} value={timeSlots} onChange={setTimeSlots} />
`${(multiplier || 0)?.toFixed(2)}x (${(percent * 100).toFixed(0)}%)` } > {chartTimeSlots.map((entry, index) => ( ))} { if (payload && payload.length) { const d = payload[0]?.payload as any; return (
{t('config.timeSlot')} {d.name || '—'}
{t('config.multiplier')} {Number(d.multiplier).toFixed(2)}x
); } return null; }} />
); }