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

This commit is contained in:
2025-07-10 10:02:01 +07:00
parent aaa4878229
commit 9a109a0bf2
2 changed files with 116 additions and 81 deletions
+31 -38
View File
@@ -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>
);
+85 -43
View File
@@ -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>