feat: Add WebSocket and background update functionality to ASNs, Domains, and IPRanges managers; integrate WsUpdateModal for real-time updates
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m33s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 5m33s
This commit is contained in:
@@ -3,7 +3,9 @@ import {
|
||||
IconRefresh,
|
||||
IconUpload,
|
||||
IconDownload,
|
||||
IconDeviceFloppy
|
||||
IconDeviceFloppy,
|
||||
IconPlayerPlay,
|
||||
IconBolt
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
function PageHeaderActions({
|
||||
@@ -20,9 +22,21 @@ function PageHeaderActions({
|
||||
onSave,
|
||||
disableSave = false,
|
||||
onHistory,
|
||||
onOnlineUpdate,
|
||||
onBackgroundUpdate,
|
||||
}) {
|
||||
return (
|
||||
<div className="btn-list">
|
||||
{onOnlineUpdate && (
|
||||
<button className="btn btn-outline-warning" onClick={onOnlineUpdate} title="Online-обновление BGP (WebSocket)">
|
||||
<IconBolt className="me-1" /> Online-обновление
|
||||
</button>
|
||||
)}
|
||||
{onBackgroundUpdate && (
|
||||
<button className="btn btn-outline-success" onClick={onBackgroundUpdate} title="Фоновое обновление BGP (POST)">
|
||||
<IconPlayerPlay className="me-1" /> Фоновое обновление
|
||||
</button>
|
||||
)}
|
||||
{onPreview && (
|
||||
<button className="btn btn-outline-secondary" onClick={onPreview} disabled={disablePreview} title="Предпросмотр изменений">
|
||||
<IconEye className="me-1" /> Предпросмотр
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { IconX, IconPlayerPlay, IconPlugConnected } from '@tabler/icons-react';
|
||||
|
||||
function WsUpdateModal({ show, url, onClose }) {
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [status, setStatus] = useState('connecting'); // connecting | open | closed | error
|
||||
const wsRef = useRef(null);
|
||||
const bottomRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!show) return;
|
||||
try {
|
||||
const ws = new WebSocket(url);
|
||||
wsRef.current = ws;
|
||||
setStatus('connecting');
|
||||
ws.onopen = () => setStatus('open');
|
||||
ws.onmessage = (evt) => {
|
||||
const text = typeof evt.data === 'string' ? evt.data : '[binary]';
|
||||
setMessages((prev) => [...prev, text]);
|
||||
};
|
||||
ws.onerror = () => setStatus('error');
|
||||
ws.onclose = () => setStatus('closed');
|
||||
} catch (e) {
|
||||
setStatus('error');
|
||||
}
|
||||
return () => {
|
||||
try { wsRef.current?.close(); } catch {}
|
||||
wsRef.current = null;
|
||||
};
|
||||
}, [show, url]);
|
||||
|
||||
useEffect(() => {
|
||||
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
}, [messages, show]);
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<div className="modal show d-block" tabIndex="-1" style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}>
|
||||
<div className="modal-dialog modal-lg">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title d-flex align-items-center">
|
||||
<IconPlugConnected className="me-2" /> Online-обновление BGP
|
||||
<span className={`badge ms-2 ${status === 'open' ? 'bg-green-lt text-green' : status === 'connecting' ? 'bg-orange-lt text-orange' : status === 'error' ? 'bg-red-lt text-red' : 'bg-secondary-lt text-secondary'}`}>{status}</span>
|
||||
</h5>
|
||||
<button type="button" className="btn btn-close" onClick={onClose}><IconX size={16} /></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="card">
|
||||
<div className="card-body" style={{ maxHeight: 360, overflowY: 'auto', fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace', fontSize: '0.875rem' }}>
|
||||
{messages.length === 0 ? (
|
||||
<div className="text-muted">Ожидание сообщений от сервера...</div>
|
||||
) : (
|
||||
messages.map((m, i) => (
|
||||
<div key={i} className="text-break"><span className="text-muted">$</span> {m}</div>
|
||||
))
|
||||
)}
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button className="btn btn-secondary" onClick={onClose}><IconX className="me-1" /> Закрыть</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WsUpdateModal;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user