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">
|
||||
{/* Верхнее меню Tabler Admin */}
|
||||
<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">
|
||||
<IconBrandTabler className="navbar-brand-icon me-2" />
|
||||
<span className="fw-bold">S3 Lists Manager</span>
|
||||
</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>
|
||||
</header>
|
||||
<div className="container-xl mt-4">
|
||||
<div className="row">
|
||||
<div className="col-2">
|
||||
<nav className="nav flex-column nav-pills">
|
||||
{navItems.slice(0, 3).map(item => (
|
||||
<Link
|
||||
key={item.id}
|
||||
to={item.path}
|
||||
className={`nav-link${activeTab === item.id ? ' active' : ''}`}
|
||||
style={{display: 'flex', alignItems: 'center'}}>
|
||||
<item.icon className="me-2" />
|
||||
{item.title}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
<div className="col-10">
|
||||
<Routes>
|
||||
<Route path="/domains" element={
|
||||
<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>
|
||||
<Routes>
|
||||
<Route path="/domains" element={
|
||||
<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>
|
||||
);
|
||||
|
||||
@@ -492,24 +492,12 @@ function ServerManager() {
|
||||
|
||||
{/* Delete Modal */}
|
||||
{showDeleteModal && (
|
||||
<div className="modal fade show" style={{display: 'block'}} tabIndex="-1">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Подтверждение удаления</h5>
|
||||
<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>
|
||||
<DeleteServerModal
|
||||
show={showDeleteModal}
|
||||
server={serverToDelete}
|
||||
onDelete={executeDelete}
|
||||
onClose={() => setShowDeleteModal(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Edit Server Modal */}
|
||||
@@ -527,6 +515,13 @@ function ServerManager() {
|
||||
// Модальное окно для редактирования сервера с backdrop и анимацией
|
||||
function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
||||
const modalRef = useRef(null);
|
||||
// Локальное состояние для формы
|
||||
const [localServer, setLocalServer] = useState(server || {});
|
||||
|
||||
// Синхронизируем локальное состояние с пропсами
|
||||
useEffect(() => {
|
||||
setLocalServer(server || {});
|
||||
}, [server]);
|
||||
|
||||
useEffect(() => {
|
||||
if (window.Tabler && window.Tabler.Modal && modalRef.current) {
|
||||
@@ -545,6 +540,12 @@ function EditServerModal({ show, server, onChange, onSave, onClose }) {
|
||||
}
|
||||
}, [show, onClose]);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
const updated = { ...localServer, [field]: value };
|
||||
setLocalServer(updated);
|
||||
onChange && onChange(updated);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="modal" tabIndex="-1" ref={modalRef}>
|
||||
<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>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{server && (
|
||||
<form onSubmit={e => { e.preventDefault(); onSave(); }}>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">IP адрес</label>
|
||||
<input type="text" className="form-control" value={server.ip} onChange={e => onChange({ ...server, ip: e.target.value })} />
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">DNS имя</label>
|
||||
<input type="text" className="form-control" value={server.dns} onChange={e => onChange({ ...server, dns: e.target.value })} />
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Страна</label>
|
||||
<input type="text" className="form-control" value={server.country} onChange={e => onChange({ ...server, country: e.target.value })} />
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Провайдер</label>
|
||||
<input type="text" className="form-control" value={server.provider} onChange={e => onChange({ ...server, provider: e.target.value })} />
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Тип туннеля</label>
|
||||
<input type="text" className="form-control" value={server.tunnel} onChange={e => onChange({ ...server, tunnel: e.target.value })} />
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
<form onSubmit={e => { e.preventDefault(); onSave && onSave(localServer); }}>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">IP адрес</label>
|
||||
<input type="text" className="form-control" value={localServer.ip || ''} onChange={e => handleChange('ip', e.target.value)} />
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">DNS имя</label>
|
||||
<input type="text" className="form-control" value={localServer.dns || ''} onChange={e => handleChange('dns', e.target.value)} />
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Страна</label>
|
||||
<input type="text" className="form-control" value={localServer.country || ''} onChange={e => handleChange('country', e.target.value)} />
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Провайдер</label>
|
||||
<input type="text" className="form-control" value={localServer.provider || ''} onChange={e => handleChange('provider', e.target.value)} />
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Тип туннеля</label>
|
||||
<input type="text" className="form-control" value={localServer.tunnel || ''} onChange={e => handleChange('tunnel', e.target.value)} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user