feat: Add language and theme context providers to App component, implement global search functionality in Dashboard, and enhance ASNs, Domains, and IPRanges managers with drag-and-drop import feature and confirmation modals for improved user experience
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m48s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m48s
This commit is contained in:
@@ -17,6 +17,9 @@ import {
|
||||
IconClock,
|
||||
IconFileText
|
||||
} from '@tabler/icons-react';
|
||||
import LockBanner from './components/LockBanner.jsx';
|
||||
import S3MetaBar from './components/S3MetaBar.jsx';
|
||||
import ConfirmDiffModal from './components/ConfirmDiffModal.jsx';
|
||||
|
||||
const API_URL = '/api';
|
||||
|
||||
@@ -208,6 +211,7 @@ function IPRangesManager() {
|
||||
|
||||
const [showDiff, setShowDiff] = useState(false);
|
||||
const [diff, setDiff] = useState({ added: [], removed: [], changed: [] });
|
||||
const [confirmSaveOpen, setConfirmSaveOpen] = useState(false);
|
||||
|
||||
const handlePreviewDiff = () => {
|
||||
const valid = items.filter(i => isValidCidr(i.ipRange) && isValidCommunity(i.community))
|
||||
@@ -218,6 +222,15 @@ function IPRangesManager() {
|
||||
};
|
||||
|
||||
const handleSaveChanges = async () => {
|
||||
const valid = items.filter(i => isValidCidr(i.ipRange) && isValidCommunity(i.community))
|
||||
.map(i => ({ ipRange: i.ipRange.trim(), community: String(i.community).trim() }));
|
||||
const unique = deduplicate(valid);
|
||||
setDiff(computeDiff(originalItems, unique));
|
||||
setConfirmSaveOpen(true);
|
||||
};
|
||||
|
||||
const performSave = async () => {
|
||||
setConfirmSaveOpen(false);
|
||||
setLoading(true);
|
||||
try {
|
||||
const valid = items.filter(i => isValidCidr(i.ipRange) && isValidCommunity(i.community))
|
||||
@@ -277,6 +290,28 @@ function IPRangesManager() {
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
// Drag&Drop импорт
|
||||
const onDropImport = async (e) => {
|
||||
e.preventDefault();
|
||||
const file = e.dataTransfer?.files?.[0];
|
||||
if (!file) return;
|
||||
const text = await file.text();
|
||||
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());
|
||||
});
|
||||
};
|
||||
|
||||
const clearInvalid = () => {
|
||||
setItems(prev => prev.filter(i => i.ipRange || i.community)
|
||||
.filter(i => isValidCidr(i.ipRange) && isValidCommunity(i.community))
|
||||
@@ -365,6 +400,7 @@ function IPRangesManager() {
|
||||
|
||||
<div className="row">
|
||||
<div className="col-lg-3">
|
||||
<LockBanner resource="ip-ranges" className="mb-3" />
|
||||
{/* Add New Item Card */}
|
||||
<div className="card card-md">
|
||||
<div className="card-header">
|
||||
@@ -419,6 +455,16 @@ function IPRangesManager() {
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/* Drag & Drop импорт */}
|
||||
<div
|
||||
className="card card-md border-dashed"
|
||||
onDragOver={(e) => { e.preventDefault(); }}
|
||||
onDrop={onDropImport}
|
||||
>
|
||||
<div className="card-body text-center text-muted">
|
||||
Перетащите файл TXT/CSV сюда для импорта (формат: CIDR community)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions Card */}
|
||||
<div className="card card-md">
|
||||
@@ -516,6 +562,7 @@ function IPRangesManager() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<S3MetaBar keys={["ipRanges"]} />
|
||||
<div className="table-responsive">
|
||||
<table className="table card-table table-vcenter table-nowrap mb-0">
|
||||
<thead>
|
||||
@@ -677,6 +724,12 @@ function IPRangesManager() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<ConfirmDiffModal
|
||||
show={confirmSaveOpen}
|
||||
diff={diff}
|
||||
onConfirm={performSave}
|
||||
onClose={() => setConfirmSaveOpen(false)}
|
||||
/>
|
||||
{/* Diff Modal */}
|
||||
{showDiff && (
|
||||
<div className="modal modal-blur fade show" style={{display: 'block'}} tabIndex="-1">
|
||||
|
||||
Reference in New Issue
Block a user