Files
NeveTimePanel/frontend/src/components/Users.jsx

200 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
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 { Users as UsersIcon, Trash2, Shield, User } from 'lucide-react';
import axios from 'axios';
import { API_URL } from '../config';
export default function Users({ token, onViewProfile }) {
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 changeRole = async (username, newRole) => {
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' : user.role === 'support' ? 'bg-purple-600' : user.role === 'banned' ? 'bg-red-600' : 'bg-gray-700'
}`}>
{user.role === 'admin' ? (
<Shield className="w-6 h-6" />
) : (
<User className="w-6 h-6" />
)}
</div>
<div>
<button
onClick={() => onViewProfile && onViewProfile(user.username)}
className="text-lg font-semibold hover:text-blue-400 transition cursor-pointer text-left"
title="Просмотреть профиль"
>
{user.username}
</button>
<p className="text-sm text-gray-400">
{user.role === 'admin' ? 'Администратор' : user.role === 'support' ? 'Тех. поддержка' : user.role === 'banned' ? 'Забанен' : 'Пользователь'}
</p>
</div>
</div>
<div className="flex gap-2">
<select
value={user.role}
onChange={(e) => changeRole(user.username, e.target.value)}
className="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded text-sm border border-gray-600 focus:outline-none focus:border-blue-500"
>
<option value="user">Пользователь</option>
<option value="support">Тех. поддержка</option>
<option value="admin">Администратор</option>
<option value="banned">Забанен</option>
</select>
<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 === 'user' && (
<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>
)}
{user.role === 'support' && (
<p className="text-sm text-gray-400">
Тех. поддержка имеет доступ к системе тикетов
</p>
)}
{user.role === 'banned' && (
<p className="text-sm text-red-400">
Пользователь заблокирован и не имеет доступа к панели
</p>
)}
</div>
))}
</div>
</div>
);
}