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

View File

@@ -0,0 +1,183 @@
import { useState, useEffect } from 'react';
import { Users as UsersIcon, Trash2, Shield, User } from 'lucide-react';
import axios from 'axios';
import { API_URL } from '../config';
export default function Users({ token }) {
const [users, setUsers] = useState([]);
const [servers, setServers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
const [usersRes, serversRes] = await Promise.all([
axios.get(`${API_URL}/api/users`, {
headers: { Authorization: `Bearer ${token}` }
}),
axios.get(`${API_URL}/api/servers`, {
headers: { Authorization: `Bearer ${token}` }
})
]);
setUsers(usersRes.data);
setServers(serversRes.data);
} catch (error) {
console.error('Ошибка загрузки данных:', error);
} finally {
setLoading(false);
}
};
const toggleServerAccess = async (username, serverName) => {
const user = users.find(u => u.username === username);
const currentServers = user.servers || [];
const newServers = currentServers.includes(serverName)
? currentServers.filter(s => s !== serverName)
: [...currentServers, serverName];
try {
await axios.put(
`${API_URL}/api/users/${username}/servers`,
{ servers: newServers },
{ headers: { Authorization: `Bearer ${token}` } }
);
loadData();
} catch (error) {
alert(error.response?.data?.detail || 'Ошибка обновления доступа');
}
};
const toggleRole = async (username, currentRole) => {
const newRole = currentRole === 'admin' ? 'user' : 'admin';
if (!confirm(`Изменить роль пользователя ${username} на ${newRole}?`)) {
return;
}
try {
await axios.put(
`${API_URL}/api/users/${username}/role`,
{ role: newRole },
{ headers: { Authorization: `Bearer ${token}` } }
);
loadData();
} catch (error) {
alert(error.response?.data?.detail || 'Ошибка изменения роли');
}
};
const deleteUser = async (username) => {
if (!confirm(`Удалить пользователя ${username}?`)) {
return;
}
try {
await axios.delete(`${API_URL}/api/users/${username}`, {
headers: { Authorization: `Bearer ${token}` }
});
loadData();
} catch (error) {
alert(error.response?.data?.detail || 'Ошибка удаления пользователя');
}
};
if (loading) {
return (
<div className="flex items-center justify-center h-full">
<div className="text-gray-400">Загрузка...</div>
</div>
);
}
return (
<div className="p-8 bg-gray-900 h-full overflow-y-auto">
<h2 className="text-2xl font-bold mb-6 flex items-center gap-2">
<UsersIcon className="w-8 h-8" />
Управление пользователями
</h2>
<div className="space-y-4">
{users.map((user) => (
<div
key={user.username}
className="bg-gray-800 rounded-lg p-6 border border-gray-700"
>
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className={`p-2 rounded ${
user.role === 'admin' ? 'bg-blue-600' : 'bg-gray-700'
}`}>
{user.role === 'admin' ? (
<Shield className="w-6 h-6" />
) : (
<User className="w-6 h-6" />
)}
</div>
<div>
<h3 className="text-lg font-semibold">{user.username}</h3>
<p className="text-sm text-gray-400">
{user.role === 'admin' ? 'Администратор' : 'Пользователь'}
</p>
</div>
</div>
<div className="flex gap-2">
<button
onClick={() => toggleRole(user.username, user.role)}
className="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded text-sm"
>
{user.role === 'admin' ? 'Сделать пользователем' : 'Сделать админом'}
</button>
<button
onClick={() => deleteUser(user.username)}
className="bg-red-600 hover:bg-red-700 p-2 rounded"
title="Удалить"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
</div>
{user.role !== 'admin' && (
<div>
<h4 className="text-sm font-medium mb-2 text-gray-400">
Доступ к серверам:
</h4>
<div className="flex flex-wrap gap-2">
{servers.map((server) => {
const hasAccess = user.servers?.includes(server.name);
return (
<button
key={server.name}
onClick={() => toggleServerAccess(user.username, server.name)}
className={`px-3 py-1 rounded text-sm transition ${
hasAccess
? 'bg-green-600 hover:bg-green-700'
: 'bg-gray-700 hover:bg-gray-600'
}`}
>
{server.displayName}
</button>
);
})}
{servers.length === 0 && (
<p className="text-gray-500 text-sm">Нет доступных серверов</p>
)}
</div>
</div>
)}
{user.role === 'admin' && (
<p className="text-sm text-gray-400">
Администратор имеет доступ ко всем серверам
</p>
)}
</div>
))}
</div>
</div>
);
}