Files
Server_manager/src/components/PasswordManager.js
T

300 lines
12 KiB
JavaScript
Raw 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.
import React, { useState, useEffect } from 'react';
const PasswordManager = () => {
const [passwords, setPasswords] = useState([]);
const [showAddForm, setShowAddForm] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [newPassword, setNewPassword] = useState({
title: '',
server: '',
username: '',
password: '',
category: 'server',
notes: ''
});
useEffect(() => {
loadPasswords();
}, []);
const loadPasswords = async () => {
if (window.electronAPI) {
const result = await window.electronAPI.loadPasswords();
if (result.success) {
setPasswords(result.passwords || []);
}
}
};
const savePasswords = async (passwordsList) => {
if (window.electronAPI) {
await window.electronAPI.savePasswords(passwordsList);
}
};
const addPassword = async () => {
if (!newPassword.title) {
alert('Заполните название');
return;
}
const password = {
...newPassword,
id: Date.now().toString(),
createdAt: new Date().toISOString()
};
const updatedPasswords = [...passwords, password];
setPasswords(updatedPasswords);
await savePasswords(updatedPasswords);
setShowAddForm(false);
setNewPassword({
title: '',
server: '',
username: '',
password: '',
category: 'server',
notes: ''
});
};
const deletePassword = async (passwordId) => {
if (confirm('Удалить эту запись?')) {
const updatedPasswords = passwords.filter(p => p.id !== passwordId);
setPasswords(updatedPasswords);
await savePasswords(updatedPasswords);
}
};
const copyToClipboard = async (text) => {
if (window.electronAPI) {
await window.electronAPI.copyToClipboard(text);
alert('Скопировано в буфер обмена');
}
};
const generatePassword = () => {
const length = 16;
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
let password = "";
for (let i = 0; i < length; i++) {
password += charset.charAt(Math.floor(Math.random() * charset.length));
}
setNewPassword({...newPassword, password});
};
const filteredPasswords = passwords.filter(password =>
password.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
password.username.toLowerCase().includes(searchTerm.toLowerCase()) ||
password.category.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<div className="p-4 md:p-6">
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-4 md:mb-6 gap-4">
<div>
<h1 className="text-xl md:text-2xl font-bold text-white">Менеджер паролей</h1>
<p className="text-gray-400 text-sm md:text-base">Безопасное хранение учетных данных</p>
</div>
<div className="flex flex-col md:flex-row gap-3 w-full md:w-auto">
<div className="relative w-full md:w-64">
<input
type="text"
placeholder="Поиск паролей..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-3 pr-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-primary-500 focus:border-transparent text-sm"
/>
</div>
<button
onClick={() => setShowAddForm(true)}
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors w-full md:w-auto text-sm"
>
+ Добавить запись
</button>
</div>
</div>
{showAddForm && (
<div className="bg-gray-800 p-4 md:p-6 rounded-lg shadow-lg mb-4 md:mb-6 border border-gray-700">
<h3 className="text-lg font-semibold mb-4 text-white">Добавить новую запись</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 md:gap-4">
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-300 mb-1">
Название *
</label>
<input
type="text"
value={newPassword.title}
onChange={(e) => setNewPassword({...newPassword, title: e.target.value})}
className="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent text-sm"
placeholder="Root доступ к серверу"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">
Сервер
</label>
<input
type="text"
value={newPassword.server}
onChange={(e) => setNewPassword({...newPassword, server: e.target.value})}
className="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent text-sm"
placeholder="server.example.com"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">
Категория
</label>
<select
value={newPassword.category}
onChange={(e) => setNewPassword({...newPassword, category: e.target.value})}
className="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent text-sm"
>
<option value="server">Сервер</option>
<option value="database">База данных</option>
<option value="web">Веб-сайт</option>
<option value="other">Другое</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">
Имя пользователя
</label>
<input
type="text"
value={newPassword.username}
onChange={(e) => setNewPassword({...newPassword, username: e.target.value})}
className="w-full bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent text-sm"
placeholder="admin"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">
Пароль
</label>
<div className="flex gap-2">
<input
type="text"
value={newPassword.password}
onChange={(e) => setNewPassword({...newPassword, password: e.target.value})}
className="flex-1 bg-gray-700 border border-gray-600 rounded-lg px-3 py-2 text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent text-sm"
placeholder="Пароль"
/>
<button
onClick={generatePassword}
className="px-3 bg-primary-600 text-white rounded-lg hover:bg-primary-500 text-sm"
>
🎲
</button>
</div>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-300 mb-1">
Заметки
</label>
<textarea
value={newPassword.notes}
onChange={(e) => setNewPassword({...newPassword, notes: e.target.value})}
rows="3"
className="w-full bg-gray-700 border border-gray-600 rounded-lg p-3 text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent text-sm"
placeholder="Дополнительная информация..."
/>
</div>
</div>
<div className="flex flex-col md:flex-row justify-end space-y-2 md:space-y-0 md:space-x-3 mt-4">
<button
onClick={() => setShowAddForm(false)}
className="px-4 py-2 border border-gray-600 text-gray-300 rounded-lg hover:bg-gray-700 transition-colors w-full md:w-auto text-sm"
>
Отмена
</button>
<button
onClick={addPassword}
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors w-full md:w-auto text-sm"
>
Сохранить
</button>
</div>
</div>
)}
{filteredPasswords.length === 0 ? (
<div className="text-center py-8 md:py-12">
<div className="text-gray-400 text-4xl md:text-6xl mb-4">🔐</div>
<h3 className="text-lg font-medium text-white">Нет сохраненных паролей</h3>
<p className="text-gray-400 mt-1">
{searchTerm ? 'Ничего не найдено' : 'Добавьте вашу первую запись'}
</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6">
{filteredPasswords.map(password => (
<div key={password.id} className="bg-gray-800 p-4 md:p-6 rounded-lg shadow-lg border border-gray-700">
<div className="flex justify-between items-start mb-3 md:mb-4">
<div>
<h3 className="font-semibold text-white text-base md:text-lg">{password.title}</h3>
{password.server && (
<p className="text-gray-400 text-sm mt-1">{password.server}</p>
)}
</div>
<button
onClick={() => deletePassword(password.id)}
className="text-gray-400 hover:text-red-500 transition-colors"
>
🗑
</button>
</div>
<div className="space-y-3 md:space-y-4">
<div className="flex justify-between items-center">
<span className="text-sm text-gray-400">Пользователь:</span>
<div className="flex items-center space-x-2">
<span className="text-sm text-white font-mono">{password.username}</span>
<button
onClick={() => copyToClipboard(password.username)}
className="text-gray-400 hover:text-primary-500 transition-colors"
>
📋
</button>
</div>
</div>
<div className="flex justify-between items-center">
<span className="text-sm text-gray-400">Пароль:</span>
<div className="flex items-center space-x-2">
<span className="text-sm text-white font-mono"></span>
<button
onClick={() => copyToClipboard(password.password)}
className="text-gray-400 hover:text-primary-500 transition-colors"
>
📋
</button>
</div>
</div>
{password.notes && (
<div className="pt-3 border-t border-gray-700">
<p className="text-sm text-gray-400 italic">{password.notes}</p>
</div>
)}
<div className="flex justify-between items-center pt-3 border-t border-gray-700">
<span className="text-xs text-gray-500">
{new Date(password.createdAt).toLocaleDateString()}
</span>
<span className="text-xs px-2 py-1 bg-primary-500 text-white rounded">
{password.category}
</span>
</div>
</div>
</div>
))}
</div>
)}
</div>
);
};
export default PasswordManager;