import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons' import { Button, Checkbox, Col, Divider, Form, Input, InputNumber, Modal, Popconfirm, Row, Space, Table, Tag, Typography, message, } from 'antd' import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { useCreateObject, useDeleteObject, useUpdateObject, useObjects, type ObjectItem, } from '../api/objects' type ModalMode = 'create' | 'edit' export function ObjectsPage() { const navigate = useNavigate() const { data: objects, isLoading } = useObjects() const createObject = useCreateObject() const updateObject = useUpdateObject() const deleteObject = useDeleteObject() const [modalOpen, setModalOpen] = useState(false) const [modalMode, setModalMode] = useState('create') const [editTarget, setEditTarget] = useState(null) const [form] = Form.useForm() useEffect(() => { if (modalMode === 'edit' && editTarget) { form.setFieldsValue({ name: editTarget.name, description: editTarget.description ?? '', server_ip: editTarget.server_ip ?? '', ssh_user: editTarget.ssh_user ?? '', db_host: editTarget.db_host ?? '', // passwords intentionally left blank — only sent if filled }) } }, [editTarget, modalMode, form]) const openCreate = () => { setModalMode('create') setEditTarget(null) form.resetFields() setModalOpen(true) } const openEdit = (row: ObjectItem) => { setModalMode('edit') setEditTarget(row) form.resetFields() setModalOpen(true) } const handleSubmit = async () => { const values = await form.validateFields() const payload = Object.fromEntries( Object.entries(values).filter(([, v]) => v !== '' && v !== null && v !== undefined) ) try { if (modalMode === 'create') { await createObject.mutateAsync(payload as any) message.success('Объект создан') } else { await updateObject.mutateAsync({ id: editTarget!.id, body: payload as any }) message.success('Объект обновлён') } setModalOpen(false) form.resetFields() } catch { message.error(modalMode === 'create' ? 'Ошибка при создании объекта' : 'Ошибка при обновлении объекта') } } const handleDelete = async (row: ObjectItem) => { try { await deleteObject.mutateAsync(row.id) message.success(`Объект "${row.name}" удалён`) } catch (err: any) { const detail = err?.response?.data?.detail if (err?.response?.status === 409) { message.error('Нельзя удалить объект с устройствами — сначала удалите все устройства') } else { message.error(detail ?? 'Ошибка при удалении объекта') } } } const columns = [ { title: 'Название', dataIndex: 'name', key: 'name', render: (name: string, row: ObjectItem) => ( ), }, { title: 'Сервер', dataIndex: 'server_ip', key: 'server_ip', render: (ip: string | null) => ip ?? , }, { title: 'SSH', dataIndex: 'ssh_user', key: 'ssh_user', render: (u: string | null) => u ? {u} : , }, { title: 'Теги', dataIndex: 'tags', key: 'tags', render: (tags: ObjectItem['tags']) => tags.map((t) => ( {t.name} )), }, { title: 'Статус', dataIndex: 'is_active', key: 'is_active', render: (active: boolean) => active ? Активен : Неактивен, }, { title: '', key: 'actions', width: 160, render: (_: unknown, row: ObjectItem) => ( { setModalOpen(false); form.resetFields() }} onOk={handleSubmit} confirmLoading={isSubmitting} okText={modalMode === 'create' ? 'Создать' : 'Сохранить'} width={640} >
SSH (по умолчанию для устройств) Подключение к БД объекта{' '} (для синхронизации устройств) Подключаться через SSH-туннель ) }