feat: Refactor App and ServerManager components to improve navigation and modal handling
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 13m26s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 13m26s
This commit is contained in:
+31
-38
@@ -64,51 +64,44 @@ function MainLayout() {
|
|||||||
<div className="page">
|
<div className="page">
|
||||||
{/* Верхнее меню Tabler Admin */}
|
{/* Верхнее меню Tabler Admin */}
|
||||||
<header className="navbar navbar-expand-md navbar-light d-print-none" style={{boxShadow: '0 1px 0 0 #e9ecef'}}>
|
<header className="navbar navbar-expand-md navbar-light d-print-none" style={{boxShadow: '0 1px 0 0 #e9ecef'}}>
|
||||||
<div className="container-xl">
|
<div className="container-xl d-flex align-items-center justify-content-between">
|
||||||
<Link to="/" className="navbar-brand d-flex align-items-center">
|
<Link to="/" className="navbar-brand d-flex align-items-center">
|
||||||
<IconBrandTabler className="navbar-brand-icon me-2" />
|
<IconBrandTabler className="navbar-brand-icon me-2" />
|
||||||
<span className="fw-bold">S3 Lists Manager</span>
|
<span className="fw-bold">S3 Lists Manager</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
<nav className="nav nav-tabs ms-4">
|
||||||
|
{navItems.slice(0, 3).map(item => (
|
||||||
|
<Link
|
||||||
|
key={item.id}
|
||||||
|
to={item.path}
|
||||||
|
className={`nav-link${activeTab === item.id ? ' active fw-bold' : ''}`}
|
||||||
|
style={{display: 'flex', alignItems: 'center'}}>
|
||||||
|
<item.icon className="me-2" />
|
||||||
|
{item.title}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="container-xl mt-4">
|
<div className="container-xl mt-4">
|
||||||
<div className="row">
|
<Routes>
|
||||||
<div className="col-2">
|
<Route path="/domains" element={
|
||||||
<nav className="nav flex-column nav-pills">
|
<DataManager
|
||||||
{navItems.slice(0, 3).map(item => (
|
entityName="домен"
|
||||||
<Link
|
entityKey="domains"
|
||||||
key={item.id}
|
placeholder="example.com"
|
||||||
to={item.path}
|
/>
|
||||||
className={`nav-link${activeTab === item.id ? ' active' : ''}`}
|
} />
|
||||||
style={{display: 'flex', alignItems: 'center'}}>
|
<Route path="/asns" element={
|
||||||
<item.icon className="me-2" />
|
<DataManager
|
||||||
{item.title}
|
entityName="AS"
|
||||||
</Link>
|
entityKey="asns"
|
||||||
))}
|
placeholder="AS12345"
|
||||||
</nav>
|
/>
|
||||||
</div>
|
} />
|
||||||
<div className="col-10">
|
<Route path="/servers" element={<ServerManager />} />
|
||||||
<Routes>
|
<Route path="/" element={<Navigate to="/domains" replace />} />
|
||||||
<Route path="/domains" element={
|
</Routes>
|
||||||
<DataManager
|
|
||||||
entityName="домен"
|
|
||||||
entityKey="domains"
|
|
||||||
placeholder="example.com"
|
|
||||||
/>
|
|
||||||
} />
|
|
||||||
<Route path="/asns" element={
|
|
||||||
<DataManager
|
|
||||||
entityName="AS"
|
|
||||||
entityKey="asns"
|
|
||||||
placeholder="AS12345"
|
|
||||||
/>
|
|
||||||
} />
|
|
||||||
<Route path="/servers" element={<ServerManager />} />
|
|
||||||
<Route path="/" element={<Navigate to="/domains" replace />} />
|
|
||||||
{/* Можно добавить другие разделы по необходимости */}
|
|
||||||
</Routes>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -492,24 +492,12 @@ function ServerManager() {
|
|||||||
|
|
||||||
{/* Delete Modal */}
|
{/* Delete Modal */}
|
||||||
{showDeleteModal && (
|
{showDeleteModal && (
|
||||||
<div className="modal fade show" style={{display: 'block'}} tabIndex="-1">
|
<DeleteServerModal
|
||||||
<div className="modal-dialog">
|
show={showDeleteModal}
|
||||||
<div className="modal-content">
|
server={serverToDelete}
|
||||||
<div className="modal-header">
|
onDelete={executeDelete}
|
||||||
<h5 className="modal-title">Подтверждение удаления</h5>
|
onClose={() => setShowDeleteModal(false)}
|
||||||
<button type="button" className="btn-close" onClick={() => setShowDeleteModal(false)}></button>
|
/>
|
||||||
</div>
|
|
||||||
<div className="modal-body">
|
|
||||||
<p>Вы уверены, что хотите удалить сервер <strong>{serverToDelete?.ip}</strong>?</p>
|
|
||||||
<p className="text-muted">Это действие нельзя отменить.</p>
|
|
||||||
</div>
|
|
||||||
<div className="modal-footer">
|
|
||||||
<button type="button" className="btn btn-secondary" onClick={() => setShowDeleteModal(false)}>Отмена</button>
|
|
||||||
<button type="button" className="btn btn-danger" onClick={executeDelete}>Удалить</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Edit Server Modal */}
|
{/* Edit Server Modal */}
|
||||||
@@ -527,6 +515,13 @@ function ServerManager() {
|
|||||||
// Модальное окно для редактирования сервера с backdrop и анимацией
|
// Модальное окно для редактирования сервера с backdrop и анимацией
|
||||||
function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
||||||
const modalRef = useRef(null);
|
const modalRef = useRef(null);
|
||||||
|
// Локальное состояние для формы
|
||||||
|
const [localServer, setLocalServer] = useState(server || {});
|
||||||
|
|
||||||
|
// Синхронизируем локальное состояние с пропсами
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalServer(server || {});
|
||||||
|
}, [server]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.Tabler && window.Tabler.Modal && modalRef.current) {
|
if (window.Tabler && window.Tabler.Modal && modalRef.current) {
|
||||||
@@ -545,6 +540,12 @@ function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
|||||||
}
|
}
|
||||||
}, [show, onClose]);
|
}, [show, onClose]);
|
||||||
|
|
||||||
|
const handleChange = (field, value) => {
|
||||||
|
const updated = { ...localServer, [field]: value };
|
||||||
|
setLocalServer(updated);
|
||||||
|
onChange && onChange(updated);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal" tabIndex="-1" ref={modalRef}>
|
<div className="modal" tabIndex="-1" ref={modalRef}>
|
||||||
<div className="modal-dialog">
|
<div className="modal-dialog">
|
||||||
@@ -554,34 +555,75 @@ function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
|||||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
{server && (
|
<form onSubmit={e => { e.preventDefault(); onSave && onSave(localServer); }}>
|
||||||
<form onSubmit={e => { e.preventDefault(); onSave(); }}>
|
<div className="mb-3">
|
||||||
<div className="mb-3">
|
<label className="form-label">IP адрес</label>
|
||||||
<label className="form-label">IP адрес</label>
|
<input type="text" className="form-control" value={localServer.ip || ''} onChange={e => handleChange('ip', e.target.value)} />
|
||||||
<input type="text" className="form-control" value={server.ip} onChange={e => onChange({ ...server, ip: e.target.value })} />
|
</div>
|
||||||
</div>
|
<div className="mb-3">
|
||||||
<div className="mb-3">
|
<label className="form-label">DNS имя</label>
|
||||||
<label className="form-label">DNS имя</label>
|
<input type="text" className="form-control" value={localServer.dns || ''} onChange={e => handleChange('dns', e.target.value)} />
|
||||||
<input type="text" className="form-control" value={server.dns} onChange={e => onChange({ ...server, dns: e.target.value })} />
|
</div>
|
||||||
</div>
|
<div className="mb-3">
|
||||||
<div className="mb-3">
|
<label className="form-label">Страна</label>
|
||||||
<label className="form-label">Страна</label>
|
<input type="text" className="form-control" value={localServer.country || ''} onChange={e => handleChange('country', e.target.value)} />
|
||||||
<input type="text" className="form-control" value={server.country} onChange={e => onChange({ ...server, country: e.target.value })} />
|
</div>
|
||||||
</div>
|
<div className="mb-3">
|
||||||
<div className="mb-3">
|
<label className="form-label">Провайдер</label>
|
||||||
<label className="form-label">Провайдер</label>
|
<input type="text" className="form-control" value={localServer.provider || ''} onChange={e => handleChange('provider', e.target.value)} />
|
||||||
<input type="text" className="form-control" value={server.provider} onChange={e => onChange({ ...server, provider: e.target.value })} />
|
</div>
|
||||||
</div>
|
<div className="mb-3">
|
||||||
<div className="mb-3">
|
<label className="form-label">Тип туннеля</label>
|
||||||
<label className="form-label">Тип туннеля</label>
|
<input type="text" className="form-control" value={localServer.tunnel || ''} onChange={e => handleChange('tunnel', e.target.value)} />
|
||||||
<input type="text" className="form-control" value={server.tunnel} onChange={e => onChange({ ...server, tunnel: e.target.value })} />
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-footer">
|
<div className="modal-footer">
|
||||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
|
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
|
||||||
<button type="button" className="btn btn-primary" onClick={onSave}>Сохранить</button>
|
<button type="button" className="btn btn-primary" onClick={() => onSave && onSave(localServer)}>Сохранить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DeleteServerModal({ show, server, onDelete, onClose }) {
|
||||||
|
const modalRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (window.Tabler && window.Tabler.Modal && modalRef.current) {
|
||||||
|
const modalInstance = window.Tabler.Modal.getOrCreateInstance(modalRef.current);
|
||||||
|
if (show) {
|
||||||
|
modalInstance.show();
|
||||||
|
} else {
|
||||||
|
modalInstance.hide();
|
||||||
|
}
|
||||||
|
const handler = () => onClose && onClose();
|
||||||
|
modalRef.current.addEventListener('hide.bs.modal', handler);
|
||||||
|
return () => {
|
||||||
|
modalRef.current.removeEventListener('hide.bs.modal', handler);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [show, onClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal" tabIndex="-1" ref={modalRef}>
|
||||||
|
<div className="modal-dialog">
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-status bg-danger"></div>
|
||||||
|
<div className="modal-header">
|
||||||
|
<IconAlertTriangle className="icon text-danger me-2" size={28} />
|
||||||
|
<h5 className="modal-title">Подтверждение удаления</h5>
|
||||||
|
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<p>Вы уверены, что хотите удалить сервер <strong>{server?.ip}</strong>?</p>
|
||||||
|
<p className="text-muted">Это действие нельзя отменить.</p>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
|
||||||
|
<button type="button" className="btn btn-danger" onClick={onDelete}>Удалить</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user