Files
EvoBGP/apps/web/src/components/modules/module-edit-dialog.tsx
T
Denozordec 653bc6cc91
CI / changes (push) Successful in 8s
CI / commitlint (push) Skipped
CI / go (push) Skipped
CI / bird2 (push) Skipped
CI / openapi (push) Successful in 38s
CI / web (push) Successful in 56s
CI / release (push) Successful in 4m4s
fix(web): update Russian translations for various components
Обновлены переводы на русский язык для компонентов, включая карточки мониторинга, сетевые панели и настройки. Исправлены описания и метки для улучшения пользовательского интерфейса, а также добавлены новые элементы для поддержки локализации в компонентах, таких как DataGrid и ResourcePage.
2026-08-18 17:00:15 +07:00

261 lines
8.3 KiB
TypeScript

import { useEffect, useState } from 'react'
import { toast } from 'sonner'
import { Button } from '@evobgp/ui/components/button'
import { Checkbox } from '@evobgp/ui/components/checkbox'
import { Input } from '@evobgp/ui/components/input'
import { Label } from '@evobgp/ui/components/label'
import { FormDrawer } from '@/components/form-drawer'
import { LoadingButton } from '@/components/loading-button'
import { CommunitySelect } from '@/components/modules/community-select'
import { SelectField } from '@/components/select-field'
import {
dohProfileShortLabel,
moduleDohProfileIds,
} from '@/lib/modules/helpers'
import { dohPolicyRu, moduleTypeRu } from '@/lib/ui-labels'
import { useUpdateModuleMutation } from '@/queries/modules'
import type {
BgpCommunity,
DohProfile,
DohResolverPolicy,
ModulePatch,
ModuleRow,
} from '@/types/api'
interface ModuleEditDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
mod: ModuleRow
communities: BgpCommunity[]
dohProfiles: DohProfile[]
}
const DOH_POLICY_ITEMS: { value: DohResolverPolicy; label: string }[] = [
{ value: 'primary_only', label: dohPolicyRu('primary_only') },
{ value: 'failover', label: dohPolicyRu('failover') },
{ value: 'union', label: dohPolicyRu('union') },
]
export function ModuleEditDialog({
open,
onOpenChange,
mod,
communities,
dohProfiles,
}: ModuleEditDialogProps) {
const updateMutation = useUpdateModuleMutation()
const isDomains = mod.type === 'DOMAINS'
const [name, setName] = useState('')
const [enabled, setEnabled] = useState(true)
const [priority, setPriority] = useState('0')
const [refreshIntervalSec, setRefreshIntervalSec] = useState('')
const [cronExpr, setCronExpr] = useState('')
const [defaultCommunityId, setDefaultCommunityId] = useState<string | null>(null)
const [dohResolverPolicy, setDohResolverPolicy] = useState<DohResolverPolicy>('primary_only')
const [dohProfileIds, setDohProfileIds] = useState<string[]>([])
useEffect(() => {
if (!open) return
setName(mod.name ?? '')
setEnabled(mod.enabled !== false)
setPriority(String(mod.priority ?? 0))
setRefreshIntervalSec(
mod.refresh_interval_sec === null || mod.refresh_interval_sec === undefined
? ''
: String(mod.refresh_interval_sec),
)
setCronExpr(mod.cron_expr ?? '')
setDefaultCommunityId(mod.default_community_id ?? null)
setDohResolverPolicy(mod.doh_resolver_policy ?? 'primary_only')
setDohProfileIds(moduleDohProfileIds(mod))
}, [mod, open])
function toggleDohProfile(id: string, checked: boolean) {
setDohProfileIds((prev) => {
if (checked) {
if (prev.includes(id)) return prev
return [...prev, id]
}
return prev.filter((x) => x !== id)
})
}
async function save() {
const trimmedName = name.trim()
if (!trimmedName) {
toast.error('Укажите название модуля')
return
}
const priorityNum = Number(priority)
if (!Number.isFinite(priorityNum) || !Number.isInteger(priorityNum)) {
toast.error('Приоритет должен быть целым числом')
return
}
let refresh: number | null = null
if (refreshIntervalSec.trim() !== '') {
const n = Number(refreshIntervalSec)
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0) {
toast.error('Интервал обновления должен быть целым числом ≥ 0')
return
}
refresh = n
}
const body: ModulePatch = {
name: trimmedName,
enabled,
priority: priorityNum,
refresh_interval_sec: refresh,
cron_expr: cronExpr.trim() || null,
default_community_id: defaultCommunityId,
}
if (isDomains) {
body.doh_resolver_policy = dohResolverPolicy
body.doh_profile_ids = dohProfileIds
}
try {
await updateMutation.mutateAsync({ id: mod.id, body })
onOpenChange(false)
} catch {
// toast in mutation
}
}
return (
<FormDrawer
open={open}
onOpenChange={onOpenChange}
title="Редактировать модуль"
description={`${moduleTypeRu(mod.type)} · ${mod.type}`}
className="sm:max-w-md"
footer={
<>
<Button variant="outline" type="button" onClick={() => onOpenChange(false)}>
Отмена
</Button>
<LoadingButton type="button" loading={updateMutation.isPending} onClick={() => void save()}>
Сохранить
</LoadingButton>
</>
}
>
<div className="flex flex-col gap-2">
<Label htmlFor="mod-name">Название</Label>
<Input
id="mod-name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Имя модуля"
/>
</div>
<div className="flex flex-row items-center justify-between gap-4 rounded-lg border border-border bg-muted/30 p-3">
<div className="grid min-w-0 flex-1 gap-1 pr-2">
<Label htmlFor="mod-enabled">Включён</Label>
<p className="text-xs text-muted-foreground">
Выключенный модуль не участвует в обновлении и применении.
</p>
</div>
<Checkbox
id="mod-enabled"
checked={enabled}
onCheckedChange={(v) => setEnabled(v === true)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="mod-priority">Приоритет</Label>
<Input
id="mod-priority"
type="number"
value={priority}
onChange={(e) => setPriority(e.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="mod-interval">Интервал обновления (сек)</Label>
<Input
id="mod-interval"
type="number"
min={0}
placeholder="пусто = по умолчанию"
value={refreshIntervalSec}
onChange={(e) => setRefreshIntervalSec(e.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="mod-cron">Cron (опционально)</Label>
<Input
id="mod-cron"
placeholder="0 * * * *"
value={cronExpr}
onChange={(e) => setCronExpr(e.target.value)}
/>
</div>
<CommunitySelect
id="mod-community"
label="Community по умолчанию"
value={defaultCommunityId}
onValueChange={setDefaultCommunityId}
communities={communities}
nullable
/>
{isDomains ? (
<>
<SelectField
id="mod-doh-policy"
label="Политика DoH"
items={DOH_POLICY_ITEMS}
value={dohResolverPolicy}
onValueChange={(v) => setDohResolverPolicy(v as DohResolverPolicy)}
/>
<div className="flex flex-col gap-2">
<Label>DoH профили</Label>
{dohProfiles.length === 0 ? (
<p className="text-muted-foreground text-sm">Нет профилей в справочнике</p>
) : (
<div className="flex flex-col gap-2 rounded-lg border border-border p-3">
{dohProfiles.map((p) => {
const checked = dohProfileIds.includes(p.id)
return (
<label
key={p.id}
htmlFor={`mod-doh-${p.id}`}
className="flex cursor-pointer items-start gap-3"
>
<Checkbox
id={`mod-doh-${p.id}`}
checked={checked}
onCheckedChange={(v) => toggleDohProfile(p.id, v === true)}
className="mt-0.5"
/>
<span className="flex min-w-0 flex-col gap-0.5">
<span className="text-sm font-medium">{dohProfileShortLabel(p.id, dohProfiles)}</span>
<span className="text-muted-foreground truncate text-xs" title={p.url}>
{p.url}
</span>
</span>
</label>
)
})}
</div>
)}
</div>
</>
) : null}
</FormDrawer>
)
}