Initial commit

This commit is contained in:
2026-01-14 20:23:10 +06:00
commit 954dd473d1
57 changed files with 8854 additions and 0 deletions

343
frontend/src/App1.jsx Normal file
View File

@@ -0,0 +1,343 @@
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;