Change drone.yml
This commit is contained in:
136
.drone.yml
136
.drone.yml
@@ -117,7 +117,7 @@ steps:
|
||||
- push
|
||||
- tag
|
||||
|
||||
# Сканирование образа на уязвимости (опционально)
|
||||
# Сканирование образа на уязвимости
|
||||
- name: scan-image
|
||||
image: aquasec/trivy
|
||||
commands:
|
||||
@@ -128,137 +128,3 @@ steps:
|
||||
- tag
|
||||
depends_on:
|
||||
- build-and-push
|
||||
|
||||
# Уведомление об успешной сборке (опционально)
|
||||
- name: notify-success
|
||||
image: plugins/slack
|
||||
settings:
|
||||
webhook:
|
||||
from_secret: slack_webhook
|
||||
channel: deployments
|
||||
username: drone
|
||||
template: >
|
||||
✅ Build #{{build.number}} succeeded!
|
||||
|
||||
Repository: {{repo.name}}
|
||||
Branch: {{build.branch}}
|
||||
Commit: {{build.commit}}
|
||||
Author: {{build.author}}
|
||||
|
||||
Docker image: registry.example.com/mc-panel:{{build.commit}}
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
depends_on:
|
||||
- build-and-push
|
||||
|
||||
# Уведомление об ошибке (опционально)
|
||||
- name: notify-failure
|
||||
image: plugins/slack
|
||||
settings:
|
||||
webhook:
|
||||
from_secret: slack_webhook
|
||||
channel: deployments
|
||||
username: drone
|
||||
template: >
|
||||
❌ Build #{{build.number}} failed!
|
||||
|
||||
Repository: {{repo.name}}
|
||||
Branch: {{build.branch}}
|
||||
Commit: {{build.commit}}
|
||||
Author: {{build.author}}
|
||||
|
||||
Link: {{build.link}}
|
||||
when:
|
||||
status:
|
||||
- failure
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: deploy-staging
|
||||
|
||||
# Пайплайн для деплоя на staging (опционально)
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- develop
|
||||
|
||||
depends_on:
|
||||
- build-and-publish
|
||||
|
||||
steps:
|
||||
- name: deploy-to-staging
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
host:
|
||||
from_secret: staging_host
|
||||
username:
|
||||
from_secret: staging_username
|
||||
key:
|
||||
from_secret: staging_ssh_key
|
||||
port: 22
|
||||
script:
|
||||
- cd /opt/mc-panel
|
||||
- docker-compose pull
|
||||
- docker-compose up -d
|
||||
- docker-compose ps
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: deploy-production
|
||||
|
||||
# Пайплайн для деплоя на production (только для тегов)
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
ref:
|
||||
- refs/tags/v*
|
||||
|
||||
depends_on:
|
||||
- build-and-publish
|
||||
|
||||
steps:
|
||||
- name: deploy-to-production
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
host:
|
||||
from_secret: production_host
|
||||
username:
|
||||
from_secret: production_username
|
||||
key:
|
||||
from_secret: production_ssh_key
|
||||
port: 22
|
||||
script:
|
||||
- cd /opt/mc-panel
|
||||
- docker-compose pull
|
||||
- docker-compose up -d
|
||||
- docker-compose ps
|
||||
- echo "Deployed version ${DRONE_TAG}"
|
||||
|
||||
- name: notify-production-deploy
|
||||
image: plugins/slack
|
||||
settings:
|
||||
webhook:
|
||||
from_secret: slack_webhook
|
||||
channel: deployments
|
||||
username: drone
|
||||
template: >
|
||||
🚀 Production deployment successful!
|
||||
|
||||
Version: {{build.tag}}
|
||||
Repository: {{repo.name}}
|
||||
Author: {{build.author}}
|
||||
|
||||
Docker image: registry.example.com/mc-panel:{{build.tag}}
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
|
||||
391
DRONE_SIMPLIFIED.md
Normal file
391
DRONE_SIMPLIFIED.md
Normal file
@@ -0,0 +1,391 @@
|
||||
# 🚀 Упрощённый Drone CI/CD
|
||||
|
||||
**Дата:** 15 января 2026
|
||||
**Статус:** УПРОЩЕНО ✅
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Что изменилось
|
||||
|
||||
### До изменения
|
||||
|
||||
**4 пайплайна:**
|
||||
1. code-quality - Проверка качества кода
|
||||
2. build-and-publish - Сборка и публикация образа
|
||||
3. deploy-staging - Деплой на staging
|
||||
4. deploy-production - Деплой на production
|
||||
|
||||
**Уведомления:**
|
||||
- notify-success - Уведомление об успешной сборке
|
||||
- notify-failure - Уведомление об ошибке
|
||||
- notify-production-deploy - Уведомление о деплое
|
||||
|
||||
---
|
||||
|
||||
### После изменения
|
||||
|
||||
**2 пайплайна:**
|
||||
1. code-quality - Проверка качества кода
|
||||
2. build-and-publish - Сборка и публикация образа
|
||||
|
||||
**Уведомления:** Удалены
|
||||
|
||||
**Деплой пайплайны:** Удалены
|
||||
|
||||
---
|
||||
|
||||
## 📋 Оставшиеся пайплайны
|
||||
|
||||
### 1. code-quality
|
||||
|
||||
**Назначение:** Проверка качества и безопасности кода
|
||||
|
||||
**Триггеры:**
|
||||
- Push в любую ветку
|
||||
- Pull Request
|
||||
|
||||
**Шаги:**
|
||||
1. **python-lint** - Проверка Python кода
|
||||
- flake8 (синтаксис и стиль)
|
||||
- pylint (качество кода)
|
||||
- black (форматирование)
|
||||
- isort (сортировка импортов)
|
||||
|
||||
2. **frontend-lint** - Проверка JavaScript/React кода
|
||||
- ESLint (синтаксис и стиль)
|
||||
- Prettier (форматирование)
|
||||
|
||||
3. **python-security** - Проверка безопасности Python
|
||||
- safety (известные уязвимости)
|
||||
- bandit (security linter)
|
||||
|
||||
4. **frontend-security** - Проверка безопасности Node.js
|
||||
- npm audit (уязвимости зависимостей)
|
||||
|
||||
---
|
||||
|
||||
### 2. build-and-publish
|
||||
|
||||
**Назначение:** Сборка и публикация Docker образа
|
||||
|
||||
**Триггеры:**
|
||||
- Push в ветки: main, master, develop
|
||||
- Push тега
|
||||
|
||||
**Зависимости:**
|
||||
- Запускается только после успешного code-quality
|
||||
|
||||
**Шаги:**
|
||||
1. **build-and-push** - Сборка и публикация образа
|
||||
- Сборка Docker образа
|
||||
- Публикация в registry
|
||||
- Автоматическое тегирование
|
||||
|
||||
2. **scan-image** - Сканирование на уязвимости
|
||||
- Trivy сканирование
|
||||
- Проверка HIGH и CRITICAL уязвимостей
|
||||
|
||||
---
|
||||
|
||||
## 🗑️ Удалённые пайплайны
|
||||
|
||||
### deploy-staging
|
||||
**Причина удаления:** Упрощение конфигурации
|
||||
|
||||
**Что делал:**
|
||||
- SSH подключение к staging серверу
|
||||
- Обновление Docker образа
|
||||
- Перезапуск контейнеров
|
||||
|
||||
**Альтернатива:** Ручной деплой через SSH
|
||||
|
||||
---
|
||||
|
||||
### deploy-production
|
||||
**Причина удаления:** Упрощение конфигурации
|
||||
|
||||
**Что делал:**
|
||||
- SSH подключение к production серверу
|
||||
- Обновление Docker образа
|
||||
- Перезапуск контейнеров
|
||||
- Уведомление в Slack
|
||||
|
||||
**Альтернатива:** Ручной деплой через SSH
|
||||
|
||||
---
|
||||
|
||||
## 🗑️ Удалённые уведомления
|
||||
|
||||
### notify-success
|
||||
**Что делал:** Отправка уведомления в Slack об успешной сборке
|
||||
|
||||
### notify-failure
|
||||
**Что делал:** Отправка уведомления в Slack об ошибке сборки
|
||||
|
||||
### notify-production-deploy
|
||||
**Что делал:** Отправка уведомления в Slack о деплое на production
|
||||
|
||||
**Причина удаления:** Упрощение, не требуется настройка Slack webhook
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Настройка
|
||||
|
||||
### Необходимые Drone Secrets
|
||||
|
||||
Для работы оставшихся пайплайнов нужны только:
|
||||
|
||||
```bash
|
||||
# Docker Registry
|
||||
docker_username=your_username
|
||||
docker_password=your_password
|
||||
```
|
||||
|
||||
### Удалённые Secrets (больше не нужны)
|
||||
|
||||
```bash
|
||||
# Slack (удалено)
|
||||
slack_webhook
|
||||
|
||||
# SSH для staging (удалено)
|
||||
staging_host
|
||||
staging_username
|
||||
staging_ssh_key
|
||||
|
||||
# SSH для production (удалено)
|
||||
production_host
|
||||
production_username
|
||||
production_ssh_key
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Как использовать
|
||||
|
||||
### Автоматическая проверка кода
|
||||
|
||||
При каждом push или pull request:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Update code"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
**Результат:**
|
||||
1. ✅ Запускается code-quality
|
||||
2. ✅ Проверяется качество кода
|
||||
3. ✅ Проверяется безопасность
|
||||
4. ✅ Если всё ОК → запускается build-and-publish
|
||||
5. ✅ Собирается Docker образ
|
||||
6. ✅ Публикуется в registry
|
||||
7. ✅ Сканируется на уязвимости
|
||||
|
||||
---
|
||||
|
||||
### Ручной деплой на staging
|
||||
|
||||
```bash
|
||||
# SSH на staging сервер
|
||||
ssh user@staging-server
|
||||
|
||||
# Обновление
|
||||
cd /opt/mc-panel
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Ручной деплой на production
|
||||
|
||||
```bash
|
||||
# SSH на production сервер
|
||||
ssh user@production-server
|
||||
|
||||
# Обновление
|
||||
cd /opt/mc-panel
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Сравнение
|
||||
|
||||
### До упрощения
|
||||
|
||||
```
|
||||
Push → code-quality → build-and-publish → deploy-staging
|
||||
↓
|
||||
notify-success
|
||||
↓
|
||||
notify-failure
|
||||
|
||||
Tag → code-quality → build-and-publish → deploy-production
|
||||
↓
|
||||
notify-production-deploy
|
||||
```
|
||||
|
||||
**Требуется:**
|
||||
- 7 Drone secrets
|
||||
- Настройка Slack
|
||||
- Настройка SSH для 2 серверов
|
||||
|
||||
---
|
||||
|
||||
### После упрощения
|
||||
|
||||
```
|
||||
Push → code-quality → build-and-publish
|
||||
|
||||
Tag → code-quality → build-and-publish
|
||||
```
|
||||
|
||||
**Требуется:**
|
||||
- 2 Drone secrets (docker_username, docker_password)
|
||||
- Ручной деплой через SSH
|
||||
|
||||
---
|
||||
|
||||
## ✅ Преимущества упрощения
|
||||
|
||||
### Меньше настроек
|
||||
- ❌ Не нужен Slack webhook
|
||||
- ❌ Не нужны SSH ключи
|
||||
- ❌ Не нужны настройки серверов
|
||||
- ✅ Только Docker registry
|
||||
|
||||
### Больше контроля
|
||||
- ✅ Ручной деплой = больше контроля
|
||||
- ✅ Можно проверить перед деплоем
|
||||
- ✅ Можно откатить если нужно
|
||||
|
||||
### Проще поддержка
|
||||
- ✅ Меньше кода
|
||||
- ✅ Меньше зависимостей
|
||||
- ✅ Проще понять
|
||||
- ✅ Проще отладить
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Workflow
|
||||
|
||||
### Разработка
|
||||
|
||||
```bash
|
||||
# 1. Разработка
|
||||
git checkout -b feature/new-feature
|
||||
# ... код ...
|
||||
git commit -m "Add new feature"
|
||||
git push origin feature/new-feature
|
||||
|
||||
# 2. Pull Request
|
||||
# Создать PR на GitHub/GitLab
|
||||
# Drone автоматически проверит код
|
||||
|
||||
# 3. Merge
|
||||
# После одобрения PR
|
||||
git checkout main
|
||||
git merge feature/new-feature
|
||||
git push origin main
|
||||
|
||||
# 4. Автоматическая сборка
|
||||
# Drone соберёт и опубликует образ
|
||||
|
||||
# 5. Ручной деплой
|
||||
ssh user@server
|
||||
cd /opt/mc-panel
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Production Release
|
||||
|
||||
```bash
|
||||
# 1. Создать тег
|
||||
git tag -a v1.1.0 -m "Release 1.1.0"
|
||||
git push origin v1.1.0
|
||||
|
||||
# 2. Автоматическая сборка
|
||||
# Drone соберёт образ с тегом v1.1.0
|
||||
|
||||
# 3. Ручной деплой на production
|
||||
ssh user@production-server
|
||||
cd /opt/mc-panel
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Файл .drone.yml
|
||||
|
||||
### Структура
|
||||
|
||||
```yaml
|
||||
---
|
||||
# Пайплайн 1: Проверка качества
|
||||
kind: pipeline
|
||||
name: code-quality
|
||||
trigger: push, pull_request
|
||||
steps:
|
||||
- python-lint
|
||||
- frontend-lint
|
||||
- python-security
|
||||
- frontend-security
|
||||
|
||||
---
|
||||
# Пайплайн 2: Сборка и публикация
|
||||
kind: pipeline
|
||||
name: build-and-publish
|
||||
trigger: push (main/master/develop), tag
|
||||
depends_on: code-quality
|
||||
steps:
|
||||
- build-and-push
|
||||
- scan-image
|
||||
```
|
||||
|
||||
### Размер файла
|
||||
|
||||
**До:** ~300 строк
|
||||
**После:** ~120 строк
|
||||
**Уменьшение:** 60%
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Итог
|
||||
|
||||
**Конфигурация упрощена!** ✅
|
||||
|
||||
### Что осталось:
|
||||
- ✅ Проверка качества кода
|
||||
- ✅ Проверка безопасности
|
||||
- ✅ Сборка Docker образа
|
||||
- ✅ Публикация в registry
|
||||
- ✅ Сканирование на уязвимости
|
||||
|
||||
### Что удалено:
|
||||
- ❌ Автоматический деплой
|
||||
- ❌ Уведомления в Slack
|
||||
- ❌ SSH настройки
|
||||
|
||||
### Результат:
|
||||
- 🎯 Проще настроить
|
||||
- 🎯 Проще поддерживать
|
||||
- 🎯 Больше контроля над деплоем
|
||||
- 🎯 Меньше зависимостей
|
||||
|
||||
---
|
||||
|
||||
**Версия:** 1.1.0
|
||||
**Дата:** 15 января 2026
|
||||
**Статус:** УПРОЩЕНО ✅
|
||||
|
||||
**Меньше сложности - больше контроля!** 🚀
|
||||
|
||||
134
FIX_BASEMODEL.md
134
FIX_BASEMODEL.md
@@ -1,134 +0,0 @@
|
||||
# 🔧 Исправление ошибки BaseModel
|
||||
|
||||
**Ошибка:** `NameError: name 'BaseModel' is not defined`
|
||||
**Статус:** ИСПРАВЛЕНО ✅
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Проблема
|
||||
|
||||
При запуске `backend/main.py` возникала ошибка:
|
||||
|
||||
```
|
||||
Traceback (most recent call last):
|
||||
File "backend\main.py", line 1655, in <module>
|
||||
class RoleChange(BaseModel):
|
||||
^^^^^^^^^
|
||||
NameError: name 'BaseModel' is not defined
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Решение
|
||||
|
||||
Добавлен импорт `BaseModel` из `pydantic` в начало файла `backend/main.py`:
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Что было сделано
|
||||
|
||||
### Изменение в backend/main.py
|
||||
|
||||
**Было:**
|
||||
```python
|
||||
from fastapi import FastAPI, WebSocket, UploadFile, File, HTTPException, Depends, status, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, RedirectResponse
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
import asyncio
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
```python
|
||||
from fastapi import FastAPI, WebSocket, UploadFile, File, HTTPException, Depends, status, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, RedirectResponse
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from pydantic import BaseModel # ← Добавлено
|
||||
import asyncio
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Запуск
|
||||
|
||||
Теперь можно запустить панель:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python main.py
|
||||
```
|
||||
|
||||
Или используйте:
|
||||
```bash
|
||||
RESTART_ALL.bat
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Проверка
|
||||
|
||||
После запуска вы должны увидеть:
|
||||
|
||||
```
|
||||
INFO: Started server process [PID]
|
||||
INFO: Waiting for application startup.
|
||||
INFO: Application startup complete.
|
||||
INFO: Uvicorn running on http://0.0.0.0:8000
|
||||
```
|
||||
|
||||
Без ошибок! ✅
|
||||
|
||||
---
|
||||
|
||||
## 📚 Что такое BaseModel?
|
||||
|
||||
`BaseModel` - это базовый класс из библиотеки `pydantic`, который используется для создания моделей данных с валидацией.
|
||||
|
||||
**Используется для:**
|
||||
- Валидация входных данных API
|
||||
- Автоматическая генерация документации
|
||||
- Сериализация/десериализация JSON
|
||||
|
||||
**Примеры в коде:**
|
||||
```python
|
||||
class RoleChange(BaseModel):
|
||||
role: str
|
||||
|
||||
class BanRequest(BaseModel):
|
||||
reason: str = "Заблокирован администратором"
|
||||
|
||||
class ServerAccess(BaseModel):
|
||||
server_name: str
|
||||
|
||||
class PermissionsUpdate(BaseModel):
|
||||
permissions: dict
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Почему возникла ошибка?
|
||||
|
||||
При добавлении новых эндпоинтов управления пользователями были созданы новые модели данных (`RoleChange`, `BanRequest`, и т.д.), но импорт `BaseModel` не был добавлен.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Готово!
|
||||
|
||||
Ошибка исправлена, панель должна запускаться без проблем.
|
||||
|
||||
**Запустите:**
|
||||
```bash
|
||||
cd backend
|
||||
python main.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Дата исправления:** 15 января 2026
|
||||
**Статус:** РАБОТАЕТ ✅
|
||||
|
||||
431
MULTIPLE_OWNERS.md
Normal file
431
MULTIPLE_OWNERS.md
Normal file
@@ -0,0 +1,431 @@
|
||||
# 👑 Несколько владельцев
|
||||
|
||||
**Дата:** 15 января 2026
|
||||
**Статус:** РЕАЛИЗОВАНО ✅
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Что изменилось
|
||||
|
||||
### До изменения
|
||||
|
||||
**Ограничение:** Мог быть только ОДИН владелец
|
||||
|
||||
```
|
||||
❌ При назначении нового owner, старый owner автоматически становился admin
|
||||
❌ Нельзя было удалить владельца
|
||||
❌ Нельзя было заблокировать владельца
|
||||
```
|
||||
|
||||
**Проблема:** Если нужно несколько администраторов с полными правами, приходилось использовать роль admin, у которой нет права изменять роли.
|
||||
|
||||
---
|
||||
|
||||
### После изменения
|
||||
|
||||
**Возможность:** Может быть НЕСКОЛЬКО владельцев
|
||||
|
||||
```
|
||||
✅ Можно назначить несколько пользователей с ролью owner
|
||||
✅ Можно удалить владельца (если их больше одного)
|
||||
✅ Можно заблокировать владельца (если их больше одного)
|
||||
✅ Всегда должен остаться хотя бы один владелец
|
||||
```
|
||||
|
||||
**Преимущество:** Несколько человек могут иметь полный контроль над панелью.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Новая логика
|
||||
|
||||
### Назначение владельца
|
||||
|
||||
**Было:**
|
||||
```python
|
||||
# Если назначается новый owner, текущий owner становится admin
|
||||
if role_data.role == "owner":
|
||||
for user in users.values():
|
||||
if user.get("role") == "owner":
|
||||
user["role"] = "admin" # Понижение роли
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
```python
|
||||
# Разрешаем несколько владельцев
|
||||
# Просто назначаем роль без изменения других владельцев
|
||||
users[username]["role"] = role_data.role
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Удаление владельца
|
||||
|
||||
**Было:**
|
||||
```python
|
||||
if users[username].get("role") == "owner":
|
||||
raise HTTPException(400, "Нельзя удалить владельца")
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
```python
|
||||
if users[username].get("role") == "owner":
|
||||
owners_count = sum(1 for u in users.values() if u.get("role") == "owner")
|
||||
if owners_count <= 1:
|
||||
raise HTTPException(400, "Нельзя удалить последнего владельца")
|
||||
```
|
||||
|
||||
**Логика:**
|
||||
- Если владельцев больше одного → можно удалить
|
||||
- Если владелец последний → нельзя удалить
|
||||
|
||||
---
|
||||
|
||||
### Блокировка владельца
|
||||
|
||||
**Было:**
|
||||
```python
|
||||
if users[username].get("role") == "owner":
|
||||
raise HTTPException(400, "Нельзя заблокировать владельца")
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
```python
|
||||
if users[username].get("role") == "owner":
|
||||
owners_count = sum(1 for u in users.values() if u.get("role") == "owner")
|
||||
if owners_count <= 1:
|
||||
raise HTTPException(400, "Нельзя заблокировать последнего владельца")
|
||||
```
|
||||
|
||||
**Логика:**
|
||||
- Если владельцев больше одного → можно заблокировать
|
||||
- Если владелец последний → нельзя заблокировать
|
||||
|
||||
---
|
||||
|
||||
## 💡 Примеры использования
|
||||
|
||||
### Сценарий 1: Назначить второго владельца
|
||||
|
||||
**Текущее состояние:**
|
||||
```json
|
||||
{
|
||||
"Root": {"role": "owner"},
|
||||
"Admin1": {"role": "admin"}
|
||||
}
|
||||
```
|
||||
|
||||
**Действие:** Назначить Admin1 владельцем
|
||||
|
||||
**Результат:**
|
||||
```json
|
||||
{
|
||||
"Root": {"role": "owner"},
|
||||
"Admin1": {"role": "owner"} // Теперь тоже владелец!
|
||||
}
|
||||
```
|
||||
|
||||
**Оба пользователя имеют полные права!** ✅
|
||||
|
||||
---
|
||||
|
||||
### Сценарий 2: Удалить одного из владельцев
|
||||
|
||||
**Текущее состояние:**
|
||||
```json
|
||||
{
|
||||
"Root": {"role": "owner"},
|
||||
"Admin1": {"role": "owner"},
|
||||
"User1": {"role": "user"}
|
||||
}
|
||||
```
|
||||
|
||||
**Действие:** Удалить Admin1
|
||||
|
||||
**Результат:**
|
||||
```json
|
||||
{
|
||||
"Root": {"role": "owner"},
|
||||
"User1": {"role": "user"}
|
||||
}
|
||||
```
|
||||
|
||||
**Успешно!** Root остался владельцем ✅
|
||||
|
||||
---
|
||||
|
||||
### Сценарий 3: Попытка удалить последнего владельца
|
||||
|
||||
**Текущее состояние:**
|
||||
```json
|
||||
{
|
||||
"Root": {"role": "owner"},
|
||||
"User1": {"role": "user"}
|
||||
}
|
||||
```
|
||||
|
||||
**Действие:** Удалить Root
|
||||
|
||||
**Результат:**
|
||||
```
|
||||
❌ Ошибка: "Нельзя удалить последнего владельца. Должен остаться хотя бы один владелец."
|
||||
```
|
||||
|
||||
**Защита от потери контроля!** 🔒
|
||||
|
||||
---
|
||||
|
||||
### Сценарий 4: Три владельца
|
||||
|
||||
**Возможно:**
|
||||
```json
|
||||
{
|
||||
"Root": {"role": "owner"},
|
||||
"Admin1": {"role": "owner"},
|
||||
"Admin2": {"role": "owner"},
|
||||
"User1": {"role": "user"}
|
||||
}
|
||||
```
|
||||
|
||||
**Все три владельца имеют:**
|
||||
- ✅ Управление пользователями
|
||||
- ✅ Изменение ролей
|
||||
- ✅ Удаление пользователей
|
||||
- ✅ Просмотр всех ресурсов
|
||||
- ✅ Все права
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Как использовать
|
||||
|
||||
### Назначить нового владельца
|
||||
|
||||
1. Войдите как владелец
|
||||
2. Нажмите "Управление"
|
||||
3. Найдите пользователя
|
||||
4. Нажмите "Роль"
|
||||
5. Выберите "Владелец"
|
||||
6. Готово! Теперь два владельца
|
||||
|
||||
### Понизить владельца до админа
|
||||
|
||||
1. Войдите как владелец
|
||||
2. Нажмите "Управление"
|
||||
3. Найдите другого владельца
|
||||
4. Нажмите "Роль"
|
||||
5. Выберите "Администратор"
|
||||
6. Владелец понижен до админа
|
||||
|
||||
### Удалить владельца
|
||||
|
||||
1. Убедитесь что владельцев больше одного
|
||||
2. Войдите как владелец
|
||||
3. Нажмите "Управление"
|
||||
4. Найдите владельца для удаления
|
||||
5. Нажмите кнопку удаления (красная)
|
||||
6. Подтвердите
|
||||
7. Владелец удалён (если их было больше одного)
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Правила безопасности
|
||||
|
||||
### Что НЕЛЬЗЯ сделать:
|
||||
|
||||
- ❌ Удалить последнего владельца
|
||||
- ❌ Заблокировать последнего владельца
|
||||
- ❌ Изменить свою роль
|
||||
- ❌ Удалить самого себя
|
||||
- ❌ Заблокировать самого себя
|
||||
|
||||
### Что МОЖНО:
|
||||
|
||||
- ✅ Назначить несколько владельцев
|
||||
- ✅ Удалить владельца (если их больше одного)
|
||||
- ✅ Заблокировать владельца (если их больше одного)
|
||||
- ✅ Понизить владельца до админа
|
||||
- ✅ Повысить админа до владельца
|
||||
|
||||
---
|
||||
|
||||
## 📊 Сравнение ролей
|
||||
|
||||
### Owner (Владелец) - Несколько человек ✅
|
||||
|
||||
**Права:**
|
||||
- ✅ Управление пользователями
|
||||
- ✅ Изменение ролей (включая назначение других владельцев)
|
||||
- ✅ Удаление пользователей
|
||||
- ✅ Управление серверами
|
||||
- ✅ Просмотр всех ресурсов
|
||||
- ✅ Все права
|
||||
|
||||
**Ограничения:**
|
||||
- Должен быть хотя бы один владелец
|
||||
- Нельзя удалить/заблокировать себя
|
||||
|
||||
### Admin (Администратор) - Несколько человек ✅
|
||||
|
||||
**Права:**
|
||||
- ✅ Управление пользователями
|
||||
- ✅ Управление серверами
|
||||
- ✅ Просмотр всех ресурсов
|
||||
- ❌ Изменение ролей
|
||||
- ❌ Удаление пользователей
|
||||
|
||||
**Отличие от Owner:**
|
||||
- Не может назначать владельцев
|
||||
- Не может удалять пользователей
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI изменения
|
||||
|
||||
### Модальное окно изменения роли
|
||||
|
||||
Теперь при выборе "Владелец" не будет предупреждения о понижении текущего владельца:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ Изменить роль: Admin1 │
|
||||
├─────────────────────────────────┤
|
||||
│ [👑 Владелец] │
|
||||
│ Полный контроль над панелью │
|
||||
│ ⚠️ Может быть несколько │ ← Новое
|
||||
│ │
|
||||
│ [🛡️ Администратор] ← Текущая │
|
||||
│ Управление без изменения │
|
||||
│ ролей │
|
||||
│ │
|
||||
│ [Отмена] │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Список пользователей
|
||||
|
||||
Теперь может быть несколько пользователей с меткой "👑 Владелец":
|
||||
|
||||
```
|
||||
1. Root [👑 Владелец]
|
||||
2. Admin1 [👑 Владелец] ← Новое
|
||||
3. Admin2 [🛡️ Админ]
|
||||
4. User1 [✅ Пользователь]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Технические детали
|
||||
|
||||
### Изменённые файлы
|
||||
|
||||
**Файл:** `backend/main.py`
|
||||
|
||||
**Изменённые эндпоинты:**
|
||||
1. `PUT /api/users/{username}/role` - Убрано автоматическое понижение
|
||||
2. `DELETE /api/users/{username}` - Добавлена проверка количества владельцев
|
||||
3. `POST /api/users/{username}/ban` - Добавлена проверка количества владельцев
|
||||
|
||||
**Добавленная логика:**
|
||||
```python
|
||||
# Подсчёт владельцев
|
||||
owners_count = sum(1 for u in users.values() if u.get("role") == "owner")
|
||||
|
||||
# Проверка перед удалением/блокировкой
|
||||
if owners_count <= 1:
|
||||
raise HTTPException(400, "Нельзя удалить/заблокировать последнего владельца")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Рекомендации
|
||||
|
||||
### Когда использовать несколько владельцев
|
||||
|
||||
**Хорошие случаи:**
|
||||
- ✅ Несколько администраторов проекта
|
||||
- ✅ Команда разработчиков
|
||||
- ✅ Резервный владелец на случай отсутствия основного
|
||||
- ✅ Разделение ответственности
|
||||
|
||||
**Плохие случаи:**
|
||||
- ❌ Слишком много владельцев (риск безопасности)
|
||||
- ❌ Назначение владельцем ненадёжных пользователей
|
||||
- ❌ Владелец "на пробу"
|
||||
|
||||
### Рекомендуемая структура
|
||||
|
||||
```
|
||||
👑 Owner (2-3 человека) - Основные администраторы
|
||||
↓
|
||||
🛡️ Admin (3-5 человек) - Помощники администраторов
|
||||
↓
|
||||
💬 Support (5-10 человек) - Техническая поддержка
|
||||
↓
|
||||
✅ User (неограниченно) - Обычные пользователи
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Перезапуск
|
||||
|
||||
После изменений перезапустите панель:
|
||||
|
||||
```bash
|
||||
RESTART_ALL.bat
|
||||
```
|
||||
|
||||
Или вручную:
|
||||
```bash
|
||||
cd backend
|
||||
python main.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Проверка
|
||||
|
||||
### Тест 1: Назначить второго владельца
|
||||
|
||||
1. Войдите как Root
|
||||
2. Управление → MihailPrud → Роль → Владелец
|
||||
3. Проверьте что оба имеют роль owner
|
||||
|
||||
### Тест 2: Попытка удалить единственного владельца
|
||||
|
||||
1. Понизьте всех владельцев кроме одного
|
||||
2. Попытайтесь удалить последнего
|
||||
3. Должна быть ошибка
|
||||
|
||||
### Тест 3: Удаление одного из нескольких владельцев
|
||||
|
||||
1. Назначьте двух владельцев
|
||||
2. Удалите одного
|
||||
3. Должно пройти успешно
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Итог
|
||||
|
||||
**Теперь можно иметь несколько владельцев!** ✅
|
||||
|
||||
### Преимущества:
|
||||
|
||||
- ✅ Гибкость в управлении
|
||||
- ✅ Резервирование доступа
|
||||
- ✅ Разделение ответственности
|
||||
- ✅ Защита от потери контроля (всегда остаётся хотя бы один владелец)
|
||||
|
||||
### Безопасность:
|
||||
|
||||
- 🔒 Нельзя удалить последнего владельца
|
||||
- 🔒 Нельзя заблокировать последнего владельца
|
||||
- 🔒 Нельзя изменить свою роль
|
||||
- 🔒 Нельзя удалить себя
|
||||
|
||||
---
|
||||
|
||||
**Версия:** 1.1.0
|
||||
**Дата:** 15 января 2026
|
||||
**Статус:** РАБОТАЕТ ✅
|
||||
|
||||
**Несколько владельцев - больше контроля!** 👑👑👑
|
||||
|
||||
24
README.md
24
README.md
@@ -91,6 +91,30 @@ Comprehensive overview всего проекта:
|
||||
|
||||
**Полный контроль над всеми ресурсами!** 🖥️
|
||||
|
||||
### 👑 [MULTIPLE_OWNERS.md](MULTIPLE_OWNERS.md)
|
||||
**Несколько владельцев**
|
||||
|
||||
Возможность назначить несколько владельцев:
|
||||
- 🎯 Что изменилось
|
||||
- 📊 Новая логика
|
||||
- 💡 Примеры использования
|
||||
- 🔒 Правила безопасности
|
||||
- 🎯 Рекомендации
|
||||
|
||||
**Больше владельцев - больше контроля!** 👑👑
|
||||
|
||||
### 🚀 [DRONE_SIMPLIFIED.md](DRONE_SIMPLIFIED.md)
|
||||
**Упрощённый CI/CD**
|
||||
|
||||
Упрощение Drone конфигурации:
|
||||
- 🎯 Что изменилось (4→2 пайплайна)
|
||||
- 📋 Оставшиеся пайплайны
|
||||
- 🗑️ Удалённые компоненты
|
||||
- 🔧 Настройка
|
||||
- ✅ Преимущества
|
||||
|
||||
**Меньше сложности - больше контроля!** 🔧
|
||||
|
||||
### 📝 [CHANGELOG.md](CHANGELOG.md)
|
||||
**История изменений**
|
||||
|
||||
|
||||
@@ -445,9 +445,11 @@ async def delete_user(username: str, user: dict = Depends(get_current_user)):
|
||||
if username not in users:
|
||||
raise HTTPException(404, "Пользователь не найден")
|
||||
|
||||
# Нельзя удалить другого владельца
|
||||
# Проверяем, что не удаляем последнего владельца
|
||||
if users[username]["role"] == "owner":
|
||||
raise HTTPException(400, "Нельзя удалить владельца")
|
||||
owners_count = sum(1 for u in users.values() if u.get("role") == "owner")
|
||||
if owners_count <= 1:
|
||||
raise HTTPException(400, "Нельзя удалить последнего владельца")
|
||||
|
||||
del users[username]
|
||||
save_users(users)
|
||||
@@ -1672,11 +1674,8 @@ async def change_user_role(username: str, role_data: RoleChange, current_user: d
|
||||
if role_data.role not in valid_roles:
|
||||
raise HTTPException(status_code=400, detail=f"Неверная роль")
|
||||
|
||||
# Если назначается новый owner, текущий owner становится admin
|
||||
if role_data.role == "owner":
|
||||
for user in users.values():
|
||||
if user.get("role") == "owner":
|
||||
user["role"] = "admin"
|
||||
# Разрешаем несколько владельцев (убрано ограничение на одного)
|
||||
# Теперь можно назначить несколько пользователей с ролью owner
|
||||
|
||||
old_role = users[username].get("role", "user")
|
||||
users[username]["role"] = role_data.role
|
||||
@@ -1733,8 +1732,11 @@ async def ban_user(username: str, ban_data: BanRequest, current_user: dict = Dep
|
||||
if username == current_user.get("username"):
|
||||
raise HTTPException(status_code=400, detail="Нельзя заблокировать самого себя")
|
||||
|
||||
# Проверяем, что не блокируем последнего владельца
|
||||
if users[username].get("role") == "owner":
|
||||
raise HTTPException(status_code=400, detail="Нельзя заблокировать владельца")
|
||||
owners_count = sum(1 for u in users.values() if u.get("role") == "owner")
|
||||
if owners_count <= 1:
|
||||
raise HTTPException(status_code=400, detail="Нельзя заблокировать последнего владельца. Должен остаться хотя бы один владелец.")
|
||||
|
||||
users[username]["role"] = "banned"
|
||||
users[username]["permissions"] = {
|
||||
@@ -1786,8 +1788,11 @@ async def delete_user(username: str, current_user: dict = Depends(get_current_us
|
||||
if username == current_user.get("username"):
|
||||
raise HTTPException(status_code=400, detail="Нельзя удалить самого себя")
|
||||
|
||||
# Проверяем, что не удаляем последнего владельца
|
||||
if users[username].get("role") == "owner":
|
||||
raise HTTPException(status_code=400, detail="Нельзя удалить владельца")
|
||||
owners_count = sum(1 for u in users.values() if u.get("role") == "owner")
|
||||
if owners_count <= 1:
|
||||
raise HTTPException(status_code=400, detail="Нельзя удалить последнего владельца. Должен остаться хотя бы один владелец.")
|
||||
|
||||
del users[username]
|
||||
save_users_dict(users)
|
||||
|
||||
@@ -1,74 +1 @@
|
||||
{
|
||||
"Root": {
|
||||
"username": "Root",
|
||||
"password": "$2b$12$PAaomoUWn3Ip5ov.S/uYPeTIRiDMq7DbA57ahyYQnw3QHT2zuYMlG",
|
||||
"role": "owner",
|
||||
"servers": [],
|
||||
"permissions": {
|
||||
"manage_users": true,
|
||||
"manage_roles": true,
|
||||
"manage_servers": true,
|
||||
"manage_tickets": true,
|
||||
"manage_files": true,
|
||||
"delete_users": true,
|
||||
"view_all_resources": true
|
||||
},
|
||||
"resource_access": {
|
||||
"servers": [],
|
||||
"tickets": [],
|
||||
"files": []
|
||||
}
|
||||
},
|
||||
"MihailPrud": {
|
||||
"username": "MihailPrud",
|
||||
"password": "$2b$12$GfbQN4scE.b.mtUHofWWE.Dn1tQpT1zwLAxeICv90sHP4zGv0dc2G",
|
||||
"role": "user",
|
||||
"servers": [
|
||||
"test",
|
||||
"nya"
|
||||
],
|
||||
"permissions": {
|
||||
"manage_users": false,
|
||||
"manage_roles": false,
|
||||
"manage_servers": true,
|
||||
"manage_tickets": true,
|
||||
"manage_files": true,
|
||||
"delete_users": false,
|
||||
"view_all_resources": false
|
||||
},
|
||||
"resource_access": {
|
||||
"servers": [
|
||||
"test",
|
||||
"nya"
|
||||
],
|
||||
"tickets": [],
|
||||
"files": []
|
||||
}
|
||||
},
|
||||
"arkonsad": {
|
||||
"username": "arkonsad",
|
||||
"password": "$2b$12$z.AYkfa/MlTYFd9rLNfBmu9JHOFKUe8YdddnqCmRqAxc7vGQeo392",
|
||||
"role": "user",
|
||||
"servers": [
|
||||
"123",
|
||||
"sdfsdf"
|
||||
],
|
||||
"permissions": {
|
||||
"manage_users": false,
|
||||
"manage_roles": false,
|
||||
"manage_servers": true,
|
||||
"manage_tickets": true,
|
||||
"manage_files": true,
|
||||
"delete_users": false,
|
||||
"view_all_resources": false
|
||||
},
|
||||
"resource_access": {
|
||||
"servers": [
|
||||
"123",
|
||||
"sdfsdf"
|
||||
],
|
||||
"tickets": [],
|
||||
"files": []
|
||||
}
|
||||
}
|
||||
}
|
||||
{}
|
||||
75
backend/users.json1
Normal file
75
backend/users.json1
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"Root": {
|
||||
"username": "Root",
|
||||
"password": "$2b$12$PAaomoUWn3Ip5ov.S/uYPeTIRiDMq7DbA57ahyYQnw3QHT2zuYMlG",
|
||||
"role": "owner",
|
||||
"servers": [],
|
||||
"permissions": {
|
||||
"manage_users": true,
|
||||
"manage_roles": true,
|
||||
"manage_servers": true,
|
||||
"manage_tickets": true,
|
||||
"manage_files": true,
|
||||
"delete_users": true,
|
||||
"view_all_resources": true
|
||||
},
|
||||
"resource_access": {
|
||||
"servers": [],
|
||||
"tickets": [],
|
||||
"files": []
|
||||
}
|
||||
},
|
||||
"MihailPrud": {
|
||||
"username": "MihailPrud",
|
||||
"password": "$2b$12$GfbQN4scE.b.mtUHofWWE.Dn1tQpT1zwLAxeICv90sHP4zGv0dc2G",
|
||||
"role": "owner",
|
||||
"servers": [
|
||||
"test",
|
||||
"nya"
|
||||
],
|
||||
"permissions": {
|
||||
"manage_users": true,
|
||||
"manage_roles": true,
|
||||
"manage_servers": true,
|
||||
"manage_tickets": true,
|
||||
"manage_files": true,
|
||||
"delete_users": true,
|
||||
"view_all_resources": true
|
||||
},
|
||||
"resource_access": {
|
||||
"servers": [
|
||||
"test",
|
||||
"nya"
|
||||
],
|
||||
"tickets": [],
|
||||
"files": []
|
||||
}
|
||||
},
|
||||
"arkonsad": {
|
||||
"username": "arkonsad",
|
||||
"password": "$2b$12$z.AYkfa/MlTYFd9rLNfBmu9JHOFKUe8YdddnqCmRqAxc7vGQeo392",
|
||||
"role": "banned",
|
||||
"servers": [
|
||||
"123",
|
||||
"sdfsdf"
|
||||
],
|
||||
"permissions": {
|
||||
"manage_users": false,
|
||||
"manage_roles": false,
|
||||
"manage_servers": false,
|
||||
"manage_tickets": false,
|
||||
"manage_files": false,
|
||||
"delete_users": false,
|
||||
"view_all_resources": false
|
||||
},
|
||||
"resource_access": {
|
||||
"servers": [
|
||||
"123",
|
||||
"sdfsdf"
|
||||
],
|
||||
"tickets": [],
|
||||
"files": []
|
||||
},
|
||||
"ban_reason": "Заблокирован администратором"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user