Files
NeveTimePanel/frontend/src/App1.jsx
2026-01-14 20:23:10 +06:00

344 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react';
import { Server, Play, Square, Terminal, FolderOpen, HardDrive, Settings, Plus, Users as UsersIcon, LogOut } from 'lucide-react';
import Console from './components/Console';
import FileManager from './components/FileManager';
import Stats from './components/Stats';
import ServerSettings from './components/ServerSettings';
import CreateServerModal from './components/CreateServerModal';
import Users from './components/Users';
import Auth from './components/Auth';
import ErrorBoundary from './components/ErrorBoundary';
import axios from 'axios';
import { API_URL } from './config';
function App() {
const [token, setToken] = useState(localStorage.getItem('token'));
const [user, setUser] = useState(null);
const [servers, setServers] = useState([]);
const [selectedServer, setSelectedServer] = useState(null);
const [activeTab, setActiveTab] = useState('console');
const [showCreateModal, setShowCreateModal] = useState(false);
const [showUsers, setShowUsers] = useState(false);
const [connectionError, setConnectionError] = useState(false);
useEffect(() => {
if (token) {
loadUser();
loadServers();
const interval = setInterval(loadServers, 5000);
return () => clearInterval(interval);
}
}, [token]);
const loadUser = async () => {
try {
const { data } = await axios.get(`${API_URL}/api/auth/me`, {
headers: { Authorization: `Bearer ${token}` }
});
setUser(data);
} catch (error) {
console.error('Ошибка загрузки пользователя:', error);
handleLogout();
}
};
const loadServers = async () => {
try {
const { data } = await axios.get(`${API_URL}/api/servers`, {
headers: { Authorization: `Bearer ${token}` }
});
setServers(data);
setConnectionError(false);
} catch (error) {
console.error('Ошибка загрузки серверов:', error);
if (error.response?.status === 401) {
handleLogout();
} else {
setConnectionError(true);
}
}
};
const handleLogin = async (username, password, isLogin) => {
const endpoint = isLogin ? '/api/auth/login' : '/api/auth/register';
const { data } = await axios.post(`${API_URL}${endpoint}`, {
username,
password
});
localStorage.setItem('token', data.access_token);
setToken(data.access_token);
setUser({ username: data.username, role: data.role });
};
const handleLogout = () => {
localStorage.removeItem('token');
setToken(null);
setUser(null);
setServers([]);
setSelectedServer(null);
};
const handleServerDeleted = () => {
setSelectedServer(null);
loadServers();
};
const startServer = async (serverName) => {
try {
const response = await axios.post(
`${API_URL}/api/servers/${serverName}/start`,
{},
{ headers: { Authorization: `Bearer ${token}` } }
);
console.log('Сервер запущен:', response.data);
setTimeout(() => {
loadServers();
}, 1000);
} catch (error) {
console.error('Ошибка запуска сервера:', error);
alert(error.response?.data?.detail || 'Ошибка запуска сервера');
}
};
const stopServer = async (serverName) => {
try {
const response = await axios.post(
`${API_URL}/api/servers/${serverName}/stop`,
{},
{ headers: { Authorization: `Bearer ${token}` } }
);
console.log('Сервер остановлен:', response.data);
setTimeout(() => {
loadServers();
}, 1000);
} catch (error) {
console.error('Ошибка остановки сервера:', error);
alert(error.response?.data?.detail || 'Ошибка остановки сервера');
}
};
if (!token) {
return <Auth onLogin={handleLogin} />;
}
if (showUsers) {
return (
<div className="min-h-screen bg-gray-900 text-white">
<header className="bg-gray-800 border-b border-gray-700 p-4">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold flex items-center gap-2">
<Server className="w-8 h-8" />
MC Panel
</h1>
<div className="flex items-center gap-4">
<span className="text-gray-400">
{user?.username} ({user?.role === 'admin' ? 'Админ' : 'Пользователь'})
</span>
<button
onClick={() => setShowUsers(false)}
className="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded"
>
Назад к серверам
</button>
<button
onClick={handleLogout}
className="bg-red-600 hover:bg-red-700 px-4 py-2 rounded flex items-center gap-2"
>
<LogOut className="w-4 h-4" />
Выход
</button>
</div>
</div>
</header>
<Users token={token} />
</div>
);
}
return (
<div className="min-h-screen bg-gray-900 text-white">
<header className="bg-gray-800 border-b border-gray-700 p-4">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold flex items-center gap-2">
<Server className="w-8 h-8" />
MC Panel
</h1>
<div className="flex items-center gap-4">
{connectionError && (
<div className="bg-red-600 px-4 py-2 rounded text-sm">
Нет связи с сервером
</div>
)}
<span className="text-gray-400">
{user?.username} ({user?.role === 'admin' ? 'Админ' : 'Пользователь'})
</span>
{user?.role === 'admin' && (
<button
onClick={() => setShowUsers(true)}
className="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded flex items-center gap-2"
>
<UsersIcon className="w-4 h-4" />
Пользователи
</button>
)}
<button
onClick={handleLogout}
className="bg-red-600 hover:bg-red-700 px-4 py-2 rounded flex items-center gap-2"
>
<LogOut className="w-4 h-4" />
Выход
</button>
</div>
</div>
</header>
<div className="flex h-[calc(100vh-73px)]">
<aside className="w-64 bg-gray-800 border-r border-gray-700 p-4 overflow-y-auto">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">Серверы</h2>
<button
onClick={() => setShowCreateModal(true)}
className="bg-blue-600 hover:bg-blue-700 p-2 rounded"
title="Создать сервер"
>
<Plus className="w-4 h-4" />
</button>
</div>
{servers.map((server) => (
<div
key={server.name}
className={`p-3 mb-2 rounded cursor-pointer transition ${
selectedServer === server.name
? 'bg-blue-600'
: 'bg-gray-700 hover:bg-gray-600'
}`}
onClick={() => setSelectedServer(server.name)}
>
<div className="flex items-center justify-between">
<span className="font-medium truncate">{server.displayName}</span>
<span
className={`w-2 h-2 rounded-full flex-shrink-0 ${
server.status === 'running' ? 'bg-green-500' : 'bg-red-500'
}`}
/>
</div>
<div className="flex gap-2 mt-2">
{server.status === 'stopped' ? (
<button
onClick={(e) => {
e.stopPropagation();
startServer(server.name);
}}
className="flex-1 bg-green-600 hover:bg-green-700 p-1 rounded text-sm flex items-center justify-center gap-1"
>
<Play className="w-3 h-3" />
Старт
</button>
) : (
<button
onClick={(e) => {
e.stopPropagation();
stopServer(server.name);
}}
className="flex-1 bg-red-600 hover:bg-red-700 p-1 rounded text-sm flex items-center justify-center gap-1"
>
<Square className="w-3 h-3" />
Стоп
</button>
)}
</div>
</div>
))}
</aside>
<main className="flex-1 flex flex-col">
{selectedServer ? (
<>
<div className="bg-gray-800 border-b border-gray-700 flex">
<button
onClick={() => setActiveTab('console')}
className={`px-6 py-3 flex items-center gap-2 ${
activeTab === 'console'
? 'bg-gray-900 border-b-2 border-blue-500'
: 'hover:bg-gray-700'
}`}
>
<Terminal className="w-4 h-4" />
Консоль
</button>
<button
onClick={() => setActiveTab('files')}
className={`px-6 py-3 flex items-center gap-2 ${
activeTab === 'files'
? 'bg-gray-900 border-b-2 border-blue-500'
: 'hover:bg-gray-700'
}`}
>
<FolderOpen className="w-4 h-4" />
Файлы
</button>
<button
onClick={() => setActiveTab('stats')}
className={`px-6 py-3 flex items-center gap-2 ${
activeTab === 'stats'
? 'bg-gray-900 border-b-2 border-blue-500'
: 'hover:bg-gray-700'
}`}
>
<HardDrive className="w-4 h-4" />
Статистика
</button>
<button
onClick={() => setActiveTab('settings')}
className={`px-6 py-3 flex items-center gap-2 ${
activeTab === 'settings'
? 'bg-gray-900 border-b-2 border-blue-500'
: 'hover:bg-gray-700'
}`}
>
<Settings className="w-4 h-4" />
Настройки
</button>
</div>
<div className="flex-1 overflow-hidden">
<ErrorBoundary>
{activeTab === 'console' && <Console serverName={selectedServer} token={token} />}
{activeTab === 'files' && <FileManager serverName={selectedServer} token={token} />}
{activeTab === 'stats' && <Stats serverName={selectedServer} token={token} />}
{activeTab === 'settings' && (
<ServerSettings
serverName={selectedServer}
token={token}
user={user}
onDeleted={handleServerDeleted}
/>
)}
</ErrorBoundary>
</div>
</>
) : (
<div className="flex-1 flex items-center justify-center text-gray-500">
<div className="text-center">
<Server className="w-16 h-16 mx-auto mb-4 opacity-50" />
<p className="text-xl">Выберите сервер</p>
</div>
</div>
)}
</main>
</div>
{showCreateModal && (
<CreateServerModal
token={token}
onClose={() => setShowCreateModal(false)}
onCreated={loadServers}
/>
)}
</div>
);
}
export default App;