feat: Добавить модальные окна для импорта данных и компонент QuickAddBar в менеджерах ASNs, Domains и IPRanges, улучшив пользовательский интерфейс и упрощая процесс добавления записей
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m39s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 1m39s
This commit is contained in:
@@ -29,6 +29,8 @@ import ConfirmDiffModal from './components/ConfirmDiffModal.jsx';
|
||||
import HistoryModal from './components/HistoryModal.jsx';
|
||||
import ErrorAlert from './components/ErrorAlert.jsx';
|
||||
import { notifyMutationSuccess } from './components/NotifyProvider.jsx';
|
||||
import ImportModal from './components/ImportModal.jsx';
|
||||
import QuickAddBar from './components/QuickAddBar.jsx';
|
||||
|
||||
const API_URL = '/api';
|
||||
|
||||
@@ -289,22 +291,7 @@ function IPRangesManager() {
|
||||
};
|
||||
|
||||
const handleImport = () => {
|
||||
const text = window.prompt('Вставьте строки: CIDR ПРОБЕЛ COMMUNITY (по одной записи на строку)');
|
||||
if (!text) return;
|
||||
const lines = text.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
||||
const parsed = lines.map(l => {
|
||||
const [cidr, c] = l.split(/\s+/);
|
||||
return { ipRange: (cidr || '').trim(), community: (c || '').trim() };
|
||||
}).filter(i => isValidCidr(i.ipRange) && isValidCommunity(i.community));
|
||||
setItems(prev => {
|
||||
const merged = [...prev, ...parsed];
|
||||
const map = new Map();
|
||||
for (const it of merged) {
|
||||
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());
|
||||
});
|
||||
setImportOpen(true);
|
||||
};
|
||||
|
||||
const handleExport = async () => {
|
||||
@@ -328,7 +315,7 @@ function IPRangesManager() {
|
||||
}
|
||||
};
|
||||
|
||||
// Drag&Drop импорт
|
||||
// Drag&Drop импорт (оставим как быстрый путь)
|
||||
const onDropImport = async (e) => {
|
||||
e.preventDefault();
|
||||
const file = e.dataTransfer?.files?.[0];
|
||||
@@ -356,6 +343,26 @@ function IPRangesManager() {
|
||||
);
|
||||
};
|
||||
|
||||
// ===== Импорт через модалку =====
|
||||
const [importOpen, setImportOpen] = useState(false);
|
||||
const parseLine = (line) => {
|
||||
const [cidr, c] = String(line).split(/\s+/);
|
||||
return { ipRange: (cidr || '').trim(), community: (c || '').trim() };
|
||||
};
|
||||
const validateItem = (obj) => isValidCidr(obj.ipRange) && isValidCommunity(obj.community);
|
||||
const applyImportedItems = (list) => {
|
||||
setItems(prev => {
|
||||
const merged = [...prev, ...list];
|
||||
const map = new Map();
|
||||
for (const it of merged) {
|
||||
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());
|
||||
});
|
||||
setImportOpen(false);
|
||||
};
|
||||
|
||||
// Сортировка
|
||||
const sortedItems = [...items].sort((a, b) => {
|
||||
let valA = a[sortField] || '';
|
||||
@@ -684,6 +691,8 @@ function IPRangesManager() {
|
||||
actions={(
|
||||
<PageHeaderActions
|
||||
loading={loading}
|
||||
onPreview={handlePreviewDiff}
|
||||
disablePreview={loading || items.length === 0}
|
||||
onRefresh={fetchItems}
|
||||
disableRefresh={loading}
|
||||
onImport={handleImport}
|
||||
@@ -714,61 +723,23 @@ function IPRangesManager() {
|
||||
/>
|
||||
<div className="row">
|
||||
<div className="col-lg-3">
|
||||
{/* Add New Item Card */}
|
||||
<div className="card card-md">
|
||||
<div className="card-header">
|
||||
<h3 className="card-title">
|
||||
<IconPlus className="icon me-2" />
|
||||
Добавить новый IP-диапазон
|
||||
</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<form onSubmit={(e) => { e.preventDefault(); handleAddItem(); }}>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">IP-диапазон</label>
|
||||
<input
|
||||
type="text"
|
||||
className={`form-control${newInvalid.ipRange ? ' is-invalid' : ''}`}
|
||||
placeholder="192.168.1.0/24"
|
||||
value={newItem.ipRange}
|
||||
onChange={(e) => setNewItem({ ...newItem, ipRange: e.target.value })}
|
||||
/>
|
||||
{newInvalid.ipRange && <div className="invalid-feedback">Некорректный CIDR</div>}
|
||||
<div className="form-text">Формат: A.B.C.D/Маска, например 10.0.0.0/24</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Community</label>
|
||||
<CommunityAutocompleteInput
|
||||
value={newItem.community}
|
||||
onChange={(v) => setNewItem({ ...newItem, community: v })}
|
||||
communities={communities}
|
||||
placeholder="Начните вводить номер или имя"
|
||||
className={`form-control${newInvalid.community ? ' is-invalid' : ''}`}
|
||||
/>
|
||||
{newInvalid.community && <div className="invalid-feedback">Только цифры</div>}
|
||||
{(() => {
|
||||
const match = communities.find(c => c.value === String(newItem.community).trim());
|
||||
if (!match) return null;
|
||||
return (
|
||||
<div className="form-text">
|
||||
{match.name && (<><strong>{match.name}</strong> — </>)}
|
||||
{match.description || ''}
|
||||
{match.tags && match.tags.length > 0 && (
|
||||
<> (<span className="text-muted">{match.tags.join(', ')}</span>)</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
<div className="form-footer">
|
||||
<button type="submit" className="btn btn-primary w-100">
|
||||
<IconPlus className="icon me-2" />
|
||||
Добавить
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<QuickAddBar
|
||||
placeholder={'192.168.1.0/24 65000:100'}
|
||||
help={'Вставьте строки: CIDR ПРОБЕЛ COMMUNITY'}
|
||||
parseLine={(line) => { const [cidr, c] = String(line).split(/\s+/); return { ipRange: (cidr||'').trim(), community: (c||'').trim() }; }}
|
||||
validateItem={(obj) => isValidCidr(obj.ipRange) && isValidCommunity(obj.community)}
|
||||
onApply={(list) => {
|
||||
setItems(prev => {
|
||||
const merged = [...prev, ...list];
|
||||
const map = new Map();
|
||||
for (const it of merged) {
|
||||
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());
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{/* Drag & Drop импорт */}
|
||||
<div
|
||||
className="card card-md border-dashed"
|
||||
@@ -980,6 +951,17 @@ function IPRangesManager() {
|
||||
onConfirm={performSave}
|
||||
onClose={() => setConfirmSaveOpen(false)}
|
||||
/>
|
||||
<ImportModal
|
||||
show={importOpen}
|
||||
title={'Импорт IP-диапазонов'}
|
||||
description={'Формат: CIDR ПРОБЕЛ COMMUNITY'}
|
||||
parseLine={parseLine}
|
||||
validateItem={validateItem}
|
||||
onConfirm={applyImportedItems}
|
||||
onClose={() => setImportOpen(false)}
|
||||
sampleHeader={['ipRange','community']}
|
||||
placeholder={'192.168.1.0/24 65000:100'}
|
||||
/>
|
||||
<HistoryModal
|
||||
resource="ip-ranges"
|
||||
show={historyOpen}
|
||||
|
||||
Reference in New Issue
Block a user