feat: Добавить компонент AccordionCard в менеджеры ASNs, Domains и IPRanges для улучшения интерфейса ввода данных и поддержки функции перетаскивания файлов для импорта
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m43s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m43s
This commit is contained in:
@@ -31,6 +31,7 @@ import ErrorAlert from './components/ErrorAlert.jsx';
|
|||||||
import { notifyMutationSuccess } from './components/NotifyProvider.jsx';
|
import { notifyMutationSuccess } from './components/NotifyProvider.jsx';
|
||||||
import ImportModal from './components/ImportModal.jsx';
|
import ImportModal from './components/ImportModal.jsx';
|
||||||
import QuickAddBar from './components/QuickAddBar.jsx';
|
import QuickAddBar from './components/QuickAddBar.jsx';
|
||||||
|
import AccordionCard from './components/AccordionCard.jsx';
|
||||||
|
|
||||||
const API_URL = '/api';
|
const API_URL = '/api';
|
||||||
|
|
||||||
@@ -523,24 +524,34 @@ function ASNsNewManager() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* QuickAdd stays for batch input */}
|
{/* Дополнительные варианты */}
|
||||||
<QuickAddBar
|
<AccordionCard title={'Дополнительные варианты'} defaultOpen={false}>
|
||||||
placeholder={'12345 65000:100'}
|
<QuickAddBar
|
||||||
help={'Вставьте строки: ASN ПРОБЕЛ COMMUNITY'}
|
placeholder={'12345 65000:100'}
|
||||||
parseLine={(line) => { const [a, c] = String(line).split(/\s+/); return { asn: (a||'').trim(), community: (c||'').trim() }; }}
|
help={'Вставьте строки: ASN ПРОБЕЛ COMMUNITY'}
|
||||||
validateItem={(obj) => isValidAsn(obj.asn) && isValidCommunity(obj.community)}
|
parseLine={(line) => { const [a, c] = String(line).split(/\s+/); return { asn: (a||'').trim(), community: (c||'').trim() }; }}
|
||||||
onApply={(list) => {
|
validateItem={(obj) => isValidAsn(obj.asn) && isValidCommunity(obj.community)}
|
||||||
setItems(prev => {
|
onApply={(list) => {
|
||||||
const merged = [...prev, ...list];
|
setItems(prev => {
|
||||||
const map = new Map();
|
const merged = [...prev, ...list];
|
||||||
for (const it of merged) {
|
const map = new Map();
|
||||||
const key = String(it.asn).trim();
|
for (const it of merged) {
|
||||||
if (!map.has(key)) map.set(key, { asn: key, community: String(it.community).trim() });
|
const key = String(it.asn).trim();
|
||||||
}
|
if (!map.has(key)) map.set(key, { asn: key, community: String(it.community).trim() });
|
||||||
return Array.from(map.values());
|
}
|
||||||
});
|
return Array.from(map.values());
|
||||||
}}
|
});
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
<div className="card card-md border-dashed mt-3"
|
||||||
|
onDragOver={(e) => { e.preventDefault(); }}
|
||||||
|
onDrop={onDropImport}
|
||||||
|
>
|
||||||
|
<div className="card-body text-center text-muted">
|
||||||
|
Перетащите файл TXT/CSV сюда для импорта (формат: ASN community)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionCard>
|
||||||
{/* Drag & Drop импорт */}
|
{/* Drag & Drop импорт */}
|
||||||
<div
|
<div
|
||||||
className="card card-md border-dashed"
|
className="card card-md border-dashed"
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import ErrorAlert from './components/ErrorAlert.jsx';
|
|||||||
import { notifyMutationSuccess } from './components/NotifyProvider.jsx';
|
import { notifyMutationSuccess } from './components/NotifyProvider.jsx';
|
||||||
import ImportModal from './components/ImportModal.jsx';
|
import ImportModal from './components/ImportModal.jsx';
|
||||||
import QuickAddBar from './components/QuickAddBar.jsx';
|
import QuickAddBar from './components/QuickAddBar.jsx';
|
||||||
|
import AccordionCard from './components/AccordionCard.jsx';
|
||||||
|
|
||||||
const API_URL = '/api';
|
const API_URL = '/api';
|
||||||
|
|
||||||
@@ -529,24 +530,34 @@ function DomainsNewManager() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* QuickAdd stays for batch input */}
|
{/* Дополнительные варианты */}
|
||||||
<QuickAddBar
|
<AccordionCard title={'Дополнительные варианты'} defaultOpen={false}>
|
||||||
placeholder={'example.com 65000:100'}
|
<QuickAddBar
|
||||||
help={'Вставьте строки: DOMAIN ПРОБЕЛ COMMUNITY'}
|
placeholder={'example.com 65000:100'}
|
||||||
parseLine={(line) => { const [d, c] = String(line).split(/\s+/); return { domain: (d||'').toLowerCase(), community: (c||'').trim() }; }}
|
help={'Вставьте строки: DOMAIN ПРОБЕЛ COMMUNITY'}
|
||||||
validateItem={(obj) => isValidDomain(obj.domain) && isValidCommunity(obj.community)}
|
parseLine={(line) => { const [d, c] = String(line).split(/\s+/); return { domain: (d||'').toLowerCase(), community: (c||'').trim() }; }}
|
||||||
onApply={(list) => {
|
validateItem={(obj) => isValidDomain(obj.domain) && isValidCommunity(obj.community)}
|
||||||
setItems(prev => {
|
onApply={(list) => {
|
||||||
const merged = [...prev, ...list];
|
setItems(prev => {
|
||||||
const map = new Map();
|
const merged = [...prev, ...list];
|
||||||
for (const it of merged) {
|
const map = new Map();
|
||||||
const key = String(it.domain).trim().toLowerCase();
|
for (const it of merged) {
|
||||||
if (!map.has(key)) map.set(key, { domain: key, community: String(it.community).trim() });
|
const key = String(it.domain).trim().toLowerCase();
|
||||||
}
|
if (!map.has(key)) map.set(key, { domain: key, community: String(it.community).trim() });
|
||||||
return Array.from(map.values());
|
}
|
||||||
});
|
return Array.from(map.values());
|
||||||
}}
|
});
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
<div className="card card-md border-dashed mt-3"
|
||||||
|
onDragOver={(e) => { e.preventDefault(); }}
|
||||||
|
onDrop={onDropImport}
|
||||||
|
>
|
||||||
|
<div className="card-body text-center text-muted">
|
||||||
|
Перетащите файл TXT/CSV сюда для импорта (формат: domain community)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionCard>
|
||||||
|
|
||||||
{/* Карточка действий больше не нужна — действия перенесены в page-header */}
|
{/* Карточка действий больше не нужна — действия перенесены в page-header */}
|
||||||
{/* Drag & Drop импорт */}
|
{/* Drag & Drop импорт */}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import ErrorAlert from './components/ErrorAlert.jsx';
|
|||||||
import { notifyMutationSuccess } from './components/NotifyProvider.jsx';
|
import { notifyMutationSuccess } from './components/NotifyProvider.jsx';
|
||||||
import ImportModal from './components/ImportModal.jsx';
|
import ImportModal from './components/ImportModal.jsx';
|
||||||
import QuickAddBar from './components/QuickAddBar.jsx';
|
import QuickAddBar from './components/QuickAddBar.jsx';
|
||||||
|
import AccordionCard from './components/AccordionCard.jsx';
|
||||||
|
|
||||||
const API_URL = '/api';
|
const API_URL = '/api';
|
||||||
|
|
||||||
@@ -779,24 +780,34 @@ function IPRangesManager() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* QuickAdd stays for batch input */}
|
{/* Дополнительные варианты */}
|
||||||
<QuickAddBar
|
<AccordionCard title={'Дополнительные варианты'} defaultOpen={false}>
|
||||||
placeholder={'192.168.1.0/24 65000:100'}
|
<QuickAddBar
|
||||||
help={'Вставьте строки: CIDR ПРОБЕЛ COMMUNITY'}
|
placeholder={'192.168.1.0/24 65000:100'}
|
||||||
parseLine={(line) => { const [cidr, c] = String(line).split(/\s+/); return { ipRange: (cidr||'').trim(), community: (c||'').trim() }; }}
|
help={'Вставьте строки: CIDR ПРОБЕЛ COMMUNITY'}
|
||||||
validateItem={(obj) => isValidCidr(obj.ipRange) && isValidCommunity(obj.community)}
|
parseLine={(line) => { const [cidr, c] = String(line).split(/\s+/); return { ipRange: (cidr||'').trim(), community: (c||'').trim() }; }}
|
||||||
onApply={(list) => {
|
validateItem={(obj) => isValidCidr(obj.ipRange) && isValidCommunity(obj.community)}
|
||||||
setItems(prev => {
|
onApply={(list) => {
|
||||||
const merged = [...prev, ...list];
|
setItems(prev => {
|
||||||
const map = new Map();
|
const merged = [...prev, ...list];
|
||||||
for (const it of merged) {
|
const map = new Map();
|
||||||
const key = String(it.ipRange).trim();
|
for (const it of merged) {
|
||||||
if (!map.has(key)) map.set(key, { ipRange: key, community: String(it.community).trim() });
|
const key = String(it.ipRange).trim();
|
||||||
}
|
if (!map.has(key)) map.set(key, { ipRange: key, community: String(it.community).trim() });
|
||||||
return Array.from(map.values());
|
}
|
||||||
});
|
return Array.from(map.values());
|
||||||
}}
|
});
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
<div className="card card-md border-dashed mt-3"
|
||||||
|
onDragOver={(e) => { e.preventDefault(); }}
|
||||||
|
onDrop={onDropImport}
|
||||||
|
>
|
||||||
|
<div className="card-body text-center text-muted">
|
||||||
|
Перетащите файл TXT/CSV сюда для импорта (формат: CIDR community)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AccordionCard>
|
||||||
{/* Drag & Drop импорт */}
|
{/* Drag & Drop импорт */}
|
||||||
<div
|
<div
|
||||||
className="card card-md border-dashed"
|
className="card card-md border-dashed"
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { IconChevronDown } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
function AccordionCard({ title, defaultOpen = false, children }) {
|
||||||
|
const [open, setOpen] = useState(Boolean(defaultOpen));
|
||||||
|
return (
|
||||||
|
<div className="card card-md">
|
||||||
|
<div className="card-header d-flex align-items-center justify-content-between" role="button" onClick={() => setOpen(v => !v)} aria-expanded={open}>
|
||||||
|
<h3 className="card-title m-0">{title}</h3>
|
||||||
|
<IconChevronDown className={`icon ms-2 transition ${open ? 'rotate-180' : ''}`} />
|
||||||
|
</div>
|
||||||
|
{open && (
|
||||||
|
<div className="card-body">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccordionCard;
|
||||||
|
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ function PageHeader({ title, pretitle, actions, meta }) {
|
|||||||
</div>
|
</div>
|
||||||
{/* Отдельная строка под заголовком: действия страницы */}
|
{/* Отдельная строка под заголовком: действия страницы */}
|
||||||
{actions && (
|
{actions && (
|
||||||
<div className="mt-3 d-print-none">
|
<div className="mt-4 d-print-none">
|
||||||
{actions}
|
{actions}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user