Обновлены переводы на русский язык для компонентов, включая карточки мониторинга, сетевые панели и настройки. Исправлены описания и метки для улучшения пользовательского интерфейса, а также добавлены новые элементы для поддержки локализации в компонентах, таких как DataGrid и ResourcePage.
169 lines
5.3 KiB
TypeScript
169 lines
5.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 { SelectField } from '@/components/select-field'
|
|
import { useCreatePeerMutation, useUpdatePeerMutation } from '@/queries/network'
|
|
import type { BgpPeerCreate, PeerRow, SpeakerRow } from '@/types/api'
|
|
|
|
interface PeerFormDialogProps {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
speakers: SpeakerRow[]
|
|
editTarget?: PeerRow | null
|
|
}
|
|
|
|
function speakerLabel(s: SpeakerRow): string {
|
|
if (s.role === 'master') {
|
|
const host = s.agent_domain ?? s.endpoint
|
|
return host ? `Плоскость · ${host}` : 'Плоскость управления'
|
|
}
|
|
return s.agent_domain ?? s.endpoint ?? `${s.id.slice(0, 8)}…`
|
|
}
|
|
|
|
export function PeerFormDialog({
|
|
open,
|
|
onOpenChange,
|
|
speakers,
|
|
editTarget = null,
|
|
}: PeerFormDialogProps) {
|
|
const createMutation = useCreatePeerMutation()
|
|
const updateMutation = useUpdatePeerMutation()
|
|
const saving = createMutation.isPending || updateMutation.isPending
|
|
|
|
const [name, setName] = useState('')
|
|
const [neighbor, setNeighbor] = useState('')
|
|
const [remoteAsn, setRemoteAsn] = useState('')
|
|
const [bgpSpeakerId, setBgpSpeakerId] = useState<string | null>(null)
|
|
const [enabled, setEnabled] = useState(true)
|
|
|
|
useEffect(() => {
|
|
if (!open) return
|
|
if (editTarget) {
|
|
setName(editTarget.name ?? '')
|
|
setNeighbor(editTarget.neighbor)
|
|
setRemoteAsn(String(editTarget.remote_asn ?? ''))
|
|
setBgpSpeakerId(editTarget.bgp_speaker_id ?? null)
|
|
setEnabled(editTarget.enabled !== false)
|
|
} else {
|
|
setName('')
|
|
setNeighbor('')
|
|
setRemoteAsn('')
|
|
setBgpSpeakerId(null)
|
|
setEnabled(true)
|
|
}
|
|
}, [editTarget, open])
|
|
|
|
const speakerItems = [
|
|
{ value: '', label: 'Все спикеры' },
|
|
...speakers.map((s) => ({ value: s.id, label: speakerLabel(s) })),
|
|
]
|
|
|
|
async function save() {
|
|
if (!neighbor.trim()) {
|
|
toast.error('Укажите адрес соседа')
|
|
return
|
|
}
|
|
const asn = Number(remoteAsn)
|
|
if (!asn || asn <= 0) {
|
|
toast.error('ASN соседа должен быть больше 0')
|
|
return
|
|
}
|
|
const body: BgpPeerCreate = {
|
|
name: name.trim() || undefined,
|
|
neighbor: neighbor.trim(),
|
|
remote_asn: asn,
|
|
bgp_speaker_id: bgpSpeakerId || null,
|
|
enabled,
|
|
}
|
|
try {
|
|
if (editTarget) {
|
|
await updateMutation.mutateAsync({ id: editTarget.id, body })
|
|
} else {
|
|
await createMutation.mutateAsync(body)
|
|
}
|
|
onOpenChange(false)
|
|
} catch {
|
|
// toast in mutation
|
|
}
|
|
}
|
|
|
|
return (
|
|
<FormDrawer
|
|
open={open}
|
|
onOpenChange={onOpenChange}
|
|
title={editTarget ? 'Редактировать пира' : 'Новый пир'}
|
|
description="BGP-сосед для установки сессии"
|
|
className="sm:max-w-sm"
|
|
footer={
|
|
<>
|
|
<Button variant="outline" type="button" onClick={() => onOpenChange(false)}>
|
|
Отмена
|
|
</Button>
|
|
<LoadingButton type="button" loading={saving} onClick={save}>
|
|
{editTarget ? 'Сохранить' : 'Создать'}
|
|
</LoadingButton>
|
|
</>
|
|
}
|
|
>
|
|
<div className="flex flex-col gap-2">
|
|
<Label htmlFor="peer-name">Имя пира (опционально)</Label>
|
|
<Input
|
|
id="peer-name"
|
|
placeholder="Core-RTR-1"
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<Label htmlFor="peer-neighbor">Адрес соседа</Label>
|
|
<Input
|
|
id="peer-neighbor"
|
|
placeholder="192.0.2.1"
|
|
value={neighbor}
|
|
onChange={(e) => setNeighbor(e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<Label htmlFor="peer-asn">ASN соседа</Label>
|
|
<Input
|
|
id="peer-asn"
|
|
type="number"
|
|
placeholder="65000"
|
|
value={remoteAsn}
|
|
onChange={(e) => setRemoteAsn(e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
<SelectField
|
|
id="peer-speaker"
|
|
label="Спикер (опционально)"
|
|
items={speakerItems}
|
|
value={bgpSpeakerId ?? ''}
|
|
onValueChange={(v) => setBgpSpeakerId(v || null)}
|
|
placeholder="Все спикеры"
|
|
/>
|
|
<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="peer-enabled">Включён</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
Выключенный пир не попадает в конфиг BIRD до следующей ревизии.
|
|
</p>
|
|
</div>
|
|
<Checkbox
|
|
id="peer-enabled"
|
|
checked={enabled}
|
|
onCheckedChange={(v) => setEnabled(v === true)}
|
|
/>
|
|
</div>
|
|
</FormDrawer>
|
|
)
|
|
}
|