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';
|
||||
|
||||
@@ -193,6 +196,7 @@ function ASNsNewManager() {
|
||||
|
||||
const [showDiff, setShowDiff] = useState(false);
|
||||
const [diff, setDiff] = useState({ added: [], removed: [], changed: [] });
|
||||
const [confirmSaveOpen, setConfirmSaveOpen] = useState(false);
|
||||
|
||||
const handlePreviewDiff = () => {
|
||||
const valid = items.filter(i => isValidAsn(i.asn) && isValidCommunity(i.community))
|
||||
@@ -203,9 +207,17 @@ function ASNsNewManager() {
|
||||
};
|
||||
|
||||
const handleSaveChanges = async () => {
|
||||
const valid = items.filter(i => isValidAsn(i.asn) && isValidCommunity(i.community))
|
||||
.map(i => ({ asn: String(i.asn).trim(), community: String(i.community).trim() }));
|
||||
const unique = deduplicate(valid);
|
||||
setDiff(computeDiff(originalItems, unique));
|
||||
setConfirmSaveOpen(true);
|
||||
};
|
||||
|
||||
const performSave = async () => {
|
||||
setConfirmSaveOpen(false);
|
||||
setLoading(true);
|
||||
try {
|
||||
// API ожидает domains: [{domain, type}]
|
||||
const valid = items.filter(i => isValidAsn(i.asn) && isValidCommunity(i.community))
|
||||
.map(i => ({ asn: String(i.asn).trim(), community: String(i.community).trim() }));
|
||||
const unique = deduplicate(valid);
|
||||
@@ -262,6 +274,28 @@ function ASNsNewManager() {
|
||||
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 [a, c] = l.split(/\s+/);
|
||||
return { asn: (a || '').trim(), community: (c || '').trim() };
|
||||
}).filter(i => isValidAsn(i.asn) && isValidCommunity(i.community));
|
||||
setItems(prev => {
|
||||
const merged = [...prev, ...parsed];
|
||||
const map = new Map();
|
||||
for (const it of merged) {
|
||||
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());
|
||||
});
|
||||
};
|
||||
|
||||
const clearInvalid = () => {
|
||||
setItems(prev => prev.filter(i => i.asn || i.community)
|
||||
.filter(i => isValidAsn(i.asn) && isValidCommunity(i.community))
|
||||
@@ -350,6 +384,7 @@ function ASNsNewManager() {
|
||||
|
||||
<div className="row">
|
||||
<div className="col-lg-3">
|
||||
<LockBanner resource="asns" className="mb-3" />
|
||||
{/* Add New ASN Card */}
|
||||
<div className="card card-md">
|
||||
<div className="card-header">
|
||||
@@ -404,6 +439,16 @@ function ASNsNewManager() {
|
||||
</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 сюда для импорта (формат: ASN community)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions Card */}
|
||||
<div className="card card-md">
|
||||
@@ -501,6 +546,7 @@ function ASNsNewManager() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<S3MetaBar keys={["asns"]} />
|
||||
<div className="table-responsive">
|
||||
<table className="table card-table table-vcenter table-nowrap mb-0">
|
||||
<thead>
|
||||
@@ -657,6 +703,12 @@ function ASNsNewManager() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<ConfirmDiffModal
|
||||
show={confirmSaveOpen}
|
||||
diff={diff}
|
||||
onConfirm={performSave}
|
||||
onClose={() => setConfirmSaveOpen(false)}
|
||||
/>
|
||||
{/* datalist больше не нужен, т.к. используем кастомный автокомплит */}
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user