feat: initial commit - ServerManager Pro v2.0.0

This commit is contained in:
2025-11-26 23:21:44 +03:00
commit af51c68d7f
39 changed files with 23191 additions and 0 deletions
+300
View File
@@ -0,0 +1,300 @@
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;