ваше сообщение коммита
This commit is contained in:
@@ -1553,4 +1553,109 @@ router.get('/public/:id', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Endpoint для robots.txt
|
||||
router.get('/public/robots.txt', async (req, res) => {
|
||||
try {
|
||||
const domain = req.get('host') || req.headers.host || 'localhost';
|
||||
const protocol = req.protocol || 'https';
|
||||
const baseUrl = `${protocol}://${domain}`;
|
||||
|
||||
const robotsContent = `User-agent: *
|
||||
Allow: /
|
||||
Allow: /content/published
|
||||
Disallow: /api/
|
||||
Disallow: /ws
|
||||
Disallow: /admin/
|
||||
Disallow: /content/create
|
||||
Disallow: /content/edit
|
||||
|
||||
Sitemap: ${baseUrl}/sitemap.xml
|
||||
`;
|
||||
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.send(robotsContent);
|
||||
} catch (error) {
|
||||
console.error('Ошибка генерации robots.txt:', error);
|
||||
res.status(500).send('Error generating robots.txt');
|
||||
}
|
||||
});
|
||||
|
||||
// Endpoint для sitemap.xml
|
||||
router.get('/public/sitemap.xml', async (req, res) => {
|
||||
try {
|
||||
const tableName = `admin_pages_simple`;
|
||||
const domain = req.get('host') || req.headers.host || 'localhost';
|
||||
const protocol = req.protocol || 'https';
|
||||
const baseUrl = `${protocol}://${domain}`;
|
||||
|
||||
// Проверяем, есть ли таблица
|
||||
const existsRes = await db.getQuery()(
|
||||
`SELECT to_regclass($1) as exists`, [tableName]
|
||||
);
|
||||
|
||||
let pages = [];
|
||||
if (existsRes.rows[0].exists) {
|
||||
// Получаем все опубликованные публичные страницы
|
||||
const { rows } = await db.getQuery()(`
|
||||
SELECT id, title, updated_at, created_at
|
||||
FROM ${tableName}
|
||||
WHERE status = 'published' AND visibility = 'public'
|
||||
ORDER BY created_at DESC
|
||||
`);
|
||||
pages = rows;
|
||||
}
|
||||
|
||||
// Генерируем XML sitemap
|
||||
let sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>${baseUrl}/</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>${baseUrl}/content/published</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
`;
|
||||
|
||||
// Добавляем страницы документов
|
||||
for (const page of pages) {
|
||||
const lastmod = page.updated_at || page.created_at || new Date().toISOString();
|
||||
const pageUrl = `${baseUrl}/content/published?page=${page.id}`;
|
||||
|
||||
sitemap += ` <url>
|
||||
<loc>${escapeXml(pageUrl)}</loc>
|
||||
<lastmod>${lastmod.split('T')[0]}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>0.6</priority>
|
||||
</url>
|
||||
`;
|
||||
}
|
||||
|
||||
sitemap += `</urlset>`;
|
||||
|
||||
res.setHeader('Content-Type', 'application/xml');
|
||||
res.send(sitemap);
|
||||
} catch (error) {
|
||||
console.error('Ошибка генерации sitemap.xml:', error);
|
||||
res.status(500).send('Error generating sitemap.xml');
|
||||
}
|
||||
});
|
||||
|
||||
// Вспомогательная функция для экранирования XML
|
||||
function escapeXml(unsafe) {
|
||||
return unsafe.replace(/[<>&'"]/g, (c) => {
|
||||
switch (c) {
|
||||
case '<': return '<';
|
||||
case '>': return '>';
|
||||
case '&': return '&';
|
||||
case '\'': return ''';
|
||||
case '"': return '"';
|
||||
default: return c;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,884 +0,0 @@
|
||||
<!--
|
||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||
All rights reserved.
|
||||
|
||||
This software is proprietary and confidential.
|
||||
Unauthorized copying, modification, or distribution is prohibited.
|
||||
|
||||
For licensing inquiries: info@hb3-accelerator.com
|
||||
Website: https://hb3-accelerator.com
|
||||
GitHub: https://github.com/VC-HB3-Accelerator
|
||||
-->
|
||||
|
||||
# Обзор приложения Digital Legal Entity (DLE)
|
||||
|
||||
## 📋 Содержание
|
||||
|
||||
1. [Установка приложения](#установка-приложения)
|
||||
2. [Настройка безопасности](#настройка-безопасности)
|
||||
3. [Модули и микросервисная архитектура](#модули-и-микросервисная-архитектура)
|
||||
4. [Корпоративный мессенджер с ИИ ассистентом](#корпоративный-мессенджер-с-ии-ассистентом)
|
||||
5. [Настройки приложения для работы с интернет пользователями](#настройки-приложения-для-работы-с-интернет-пользователями)
|
||||
6. [Требования регулятора](#требования-регулятора)
|
||||
7. [Обновления софта](#обновления-софта)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Установка приложения
|
||||
|
||||
### Быстрый старт
|
||||
|
||||
Digital Legal Entity (DLE) разворачивается с помощью Docker Compose, что обеспечивает простую и надежную установку на любой платформе.
|
||||
|
||||
### Системные требования
|
||||
|
||||
**Минимальные требования**:
|
||||
- **CPU**: 4 ядра
|
||||
- **RAM**: 12 GB (4 GB приложение + 6 GB AI + 2 GB Vector Search)
|
||||
- **Хранилище**: 100 GB SSD
|
||||
- **ОС**: Ubuntu 20.04+, Debian 11+, CentOS 8+, любая Linux с Docker
|
||||
- **Docker**: версия 20.10+
|
||||
- **Docker Compose**: версия 2.0+
|
||||
|
||||
### Процесс установки
|
||||
|
||||
Для Linux/macOS/WSL:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/VC-HB3-Accelerator/DLE/main/setup.sh | bash
|
||||
```
|
||||
|
||||
Скрипт автоматически:
|
||||
- Скачивает последние артефакты из релиза
|
||||
- Разворачивает `docker-data`
|
||||
- Настраивает необходимые конфигурации
|
||||
|
||||
|
||||
### Компоненты системы
|
||||
|
||||
После установки запускаются следующие сервисы:
|
||||
|
||||
| Сервис | Описание | Порт |
|
||||
|--------|----------|------|
|
||||
| **PostgreSQL** | База данных с расширением pgvector | Внутренний |
|
||||
| **Ollama** | Локальный AI сервер | Внутренний (11434) |
|
||||
| **Vector Search** | Сервис векторного поиска (RAG) | Внутренний (8001) |
|
||||
| **Backend** | Node.js API сервер | Внутренний (8000) |
|
||||
| **Frontend (Nginx)** | Веб-интерфейс | 9000 (HTTP)
|
||||
|
||||
### Доступ к приложению
|
||||
|
||||
После успешного запуска приложение доступно по адресу:
|
||||
- **Production**: `http://localhost:9000` (HTTP)
|
||||
|
||||
### Первоначальная настройка
|
||||
|
||||
1. Откройте приложение в браузере
|
||||
2. Подключите крипто-кошелек (MetaMask, WalletConnect)
|
||||
3. Настройте RPC провайдеры в разделе **Настройки → Безопасность**
|
||||
4. Настройте смарт-контракты в разделе **Настройки → Блокчейн**
|
||||
|
||||
> 💡 **Подробная инструкция**: См. [Инструкция по установке](./setup-instruction.md) для пошаговой настройки всех компонентов.
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Настройка безопасности
|
||||
|
||||
### Многоуровневая модель безопасности
|
||||
|
||||
DLE использует комплексный подход к безопасности на всех уровнях архитектуры:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Уровни защиты DLE │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Уровень 1: Блокчейн (Неизменяемая база) │
|
||||
│ • Смарт-контракт DLE (проверен, иммутабельный) │
|
||||
│ • Токены управления (ERC20Votes) │
|
||||
│ • История всех операций на блокчейне │
|
||||
│ • Невозможность изменения правил без голосования │
|
||||
│ │
|
||||
│ Уровень 2: Веб-приложение (Backend) │
|
||||
│ • Проверка токенов в реальном времени │
|
||||
│ • Аутентификация через кошелек (SIWE) │
|
||||
│ • Шифрование данных (AES-256) │
|
||||
│ • Rate limiting и защита от DDoS │
|
||||
│ │
|
||||
│ Уровень 3: Frontend (Vue.js) │
|
||||
│ • Подключение к кошельку │
|
||||
│ • Подпись транзакций │
|
||||
│ • XSS защита (DOMPurify) │
|
||||
│ • CSRF токены │
|
||||
│ │
|
||||
│ Уровень 4: Пользователь │
|
||||
│ • Приватный ключ кошелька (MetaMask, WalletConnect) │
|
||||
│ • Подтверждение каждой операции │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Настройка безопасности
|
||||
|
||||
#### 1. Настройка RPC провайдеров
|
||||
|
||||
**Путь**: Настройки → Безопасность → RPC провайдеры
|
||||
|
||||
Для каждой блокчейн-сети необходимо добавить RPC провайдера:
|
||||
- **Network Name**: Название сети (Ethereum, Polygon, BSC и т.д.)
|
||||
- **RPC URL**: URL подключения (например: `https://eth-mainnet.g.alchemy.com/v2/YOUR-API-KEY`)
|
||||
- **Network ID**: Chain ID сети
|
||||
|
||||
> ⚠️ **Важно**: Получите API ключи от провайдеров (Alchemy, Infura, Quicknode и т.д.) перед добавлением.
|
||||
|
||||
#### 2. Настройка смарт-контрактов
|
||||
|
||||
**Путь**: Настройки → Блокчейн
|
||||
|
||||
Настройте адреса смарт-контрактов для каждой сети:
|
||||
- **Factory Address**: Адрес фабрики контрактов
|
||||
- **Core Contract Address**: Адрес основного контракта DLE
|
||||
|
||||
#### 3. Настройка шифрования
|
||||
|
||||
Все чувствительные данные шифруются с использованием AES-256:
|
||||
- Персональные данные пользователей
|
||||
- Идентификаторы гостей
|
||||
- Приватные сообщения
|
||||
- Ключи шифрования хранятся в защищенном хранилище
|
||||
|
||||
#### 4. Управление доступом
|
||||
|
||||
Система ролей и разрешений:
|
||||
- **Guest**: Базовый доступ, чат с AI
|
||||
- **User**: Полный доступ к коммуникациям
|
||||
- **ReadOnly**: Просмотр данных без редактирования
|
||||
- **Editor**: Полный доступ ко всем функциям
|
||||
|
||||
#### 5. Аудит и мониторинг
|
||||
|
||||
- Все действия логируются
|
||||
- Audit trail для критичных операций
|
||||
- Мониторинг подозрительной активности
|
||||
- Уведомления о важных событиях
|
||||
|
||||
> 💡 **Подробная информация**: См. [Безопасность DLE](./security.md) для детального описания всех уровней защиты, сценариев атак и рекомендаций.
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Модули и микросервисная архитектура
|
||||
|
||||
### Архитектура системы
|
||||
|
||||
DLE построен на принципах микросервисной архитектуры, что обеспечивает:
|
||||
- ✅ Легкое масштабирование отдельных компонентов
|
||||
- ✅ Независимое развертывание сервисов
|
||||
- ✅ Высокую отказоустойчивость
|
||||
- ✅ Гибкость в разработке и поддержке
|
||||
|
||||
### Основные сервисы
|
||||
|
||||
#### 1. PostgreSQL (База данных)
|
||||
- **Назначение**: Хранение всех данных приложения
|
||||
- **Особенности**: Расширение pgvector для векторного поиска
|
||||
- **Масштабирование**: Репликация, шардирование
|
||||
|
||||
#### 2. Ollama (AI сервер)
|
||||
- **Назначение**: Локальный AI для генерации ответов
|
||||
- **Модель**: qwen2.5:7b (настраивается)
|
||||
- **Особенности**: 100% конфиденциальность, работа offline
|
||||
- **Ресурсы**: 6 GB RAM, 2 CPU cores
|
||||
|
||||
#### 3. Vector Search (RAG сервис)
|
||||
- **Назначение**: Векторный поиск для контекстных ответов AI
|
||||
- **Технология**: FAISS, pgvector
|
||||
- **Модель эмбеддингов**: mxbai-embed-large:latest
|
||||
- **Особенности**: Семантический поиск по документам
|
||||
|
||||
#### 4. Backend (Node.js API)
|
||||
- **Назначение**: Основной API сервер
|
||||
- **Технологии**: Express.js, WebSocket
|
||||
- **Функции**:
|
||||
- Обработка запросов
|
||||
- Управление сообщениями
|
||||
- Интеграция с блокчейном
|
||||
- Управление пользователями
|
||||
|
||||
#### 5. Frontend (Vue.js)
|
||||
- **Назначение**: Пользовательский интерфейс
|
||||
- **Технологии**: Vue 3, Vite, Element Plus
|
||||
- **Особенности**: SPA, адаптивный дизайн
|
||||
|
||||
### Модульная система
|
||||
|
||||
DLE поддерживает расширение функциональности через модули:
|
||||
|
||||
#### Доступные модули
|
||||
|
||||
1. **HierarchicalVotingModule**
|
||||
- Иерархическое голосование
|
||||
- Делегирование голосов
|
||||
- Сложные сценарии управления
|
||||
|
||||
2. **Custom Modules**
|
||||
- Возможность добавления собственных модулей
|
||||
- Интеграция через смарт-контракты
|
||||
- Управление через голосование токен-холдеров
|
||||
|
||||
#### Добавление модулей
|
||||
|
||||
Модули добавляются через:
|
||||
1. Голосование токен-холдеров
|
||||
2. Деплой смарт-контракта модуля
|
||||
3. Регистрация в основном контракте DLE
|
||||
|
||||
### Коммуникация между сервисами
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ Frontend │
|
||||
│ (Vue.js) │
|
||||
└──────┬──────┘
|
||||
│ HTTP/WebSocket
|
||||
▼
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ Backend │──────│ PostgreSQL │ │ Ollama │
|
||||
│ (Node.js) │ │ (pgvector) │ │ (AI Server)│
|
||||
└──────┬──────┘ └──────────────┘ └─────────────┘
|
||||
│
|
||||
│ HTTP
|
||||
▼
|
||||
┌─────────────┐
|
||||
│Vector Search│
|
||||
│ (RAG) │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
### Масштабирование
|
||||
|
||||
Каждый сервис может масштабироваться независимо:
|
||||
- **Горизонтальное масштабирование**: Добавление инстансов
|
||||
- **Вертикальное масштабирование**: Увеличение ресурсов
|
||||
- **Балансировка нагрузки**: Nginx, HAProxy
|
||||
|
||||
> 💡 **Подробная информация**: См. [Техническая документация по блокчейну](./blockchain-integration-technical.md) для информации о модульной системе смарт-контрактов.
|
||||
|
||||
---
|
||||
|
||||
## 💬 Корпоративный мессенджер с ИИ ассистентом
|
||||
|
||||
### Обзор функциональности
|
||||
|
||||
DLE включает полнофункциональный корпоративный мессенджер с интегрированным AI ассистентом, который обеспечивает:
|
||||
- Единый интерфейс для всех каналов коммуникации
|
||||
- Автоматические ответы с помощью AI
|
||||
- Контекстное понимание истории общения
|
||||
- Многоканальную поддержку клиентов
|
||||
|
||||
### Каналы коммуникации
|
||||
|
||||
#### 1. Веб-чат
|
||||
- Публичный чат на сайте
|
||||
- Приватные сообщения между пользователями
|
||||
- Административный чат для связи с клиентами
|
||||
|
||||
#### 2. Telegram бот
|
||||
- Интеграция с Telegram
|
||||
- Автоматические ответы через AI
|
||||
- Синхронизация с веб-интерфейсом
|
||||
|
||||
#### 3. Email
|
||||
- Обработка входящих писем
|
||||
- Автоматические ответы
|
||||
- Интеграция с CRM
|
||||
|
||||
|
||||
### ИИ ассистент
|
||||
|
||||
#### Возможности AI ассистента
|
||||
|
||||
**Локальный AI на вашем сервере**:
|
||||
- Модель: qwen2.5:7b (настраивается)
|
||||
- Технология: Ollama + Vector Search (RAG)
|
||||
- Конфиденциальность: 100% (данные не покидают сервер)
|
||||
- Стоимость: $0 (без лимитов на запросы)
|
||||
|
||||
#### Функции AI ассистента
|
||||
|
||||
1. **Автоматические ответы**
|
||||
- Генерация ответов на основе контекста
|
||||
- Обучение на ваших документах
|
||||
- Персонализация под ваш бизнес
|
||||
|
||||
2. **Анализ настроения**
|
||||
- Определение эмоционального тона сообщений
|
||||
- Приоритизация запросов
|
||||
- Эскалация критичных ситуаций
|
||||
|
||||
3. **Контекстный поиск**
|
||||
- Поиск информации в базе знаний
|
||||
- Использование истории общения
|
||||
- Ссылки на релевантные документы
|
||||
|
||||
4. **Многозадачность**
|
||||
- Обработка нескольких запросов одновременно
|
||||
- Очередь запросов
|
||||
- Приоритизация важных сообщений
|
||||
|
||||
#### Настройка AI ассистента
|
||||
|
||||
**Путь**: Настройки → AI Ассистент
|
||||
|
||||
1. **Базовая настройка**
|
||||
- Выбор модели AI
|
||||
- Настройка температуры генерации
|
||||
- Лимиты токенов
|
||||
|
||||
2. **Правила и контекст**
|
||||
- Добавление правил поведения
|
||||
- Загрузка документов для обучения
|
||||
- Настройка тона общения
|
||||
|
||||
3. **Векторный поиск**
|
||||
- Индексация документов
|
||||
- Настройка релевантности
|
||||
- Обновление базы знаний
|
||||
|
||||
### Типы чатов
|
||||
|
||||
#### 1. Публичный чат
|
||||
- Доступен всем пользователям
|
||||
- AI может отвечать автоматически
|
||||
- Модерация сообщений
|
||||
|
||||
#### 2. Приватный чат
|
||||
- Личные сообщения между пользователями
|
||||
- Шифрование контента
|
||||
- История переписки
|
||||
|
||||
#### 3. Административный чат
|
||||
- Связь администраторов с клиентами
|
||||
- Управление через веб-интерфейс
|
||||
- Интеграция с CRM
|
||||
|
||||
#### 4. Гостевой чат
|
||||
- Для неавторизованных пользователей
|
||||
- Ограниченный функционал
|
||||
- Требуется согласие на обработку данных
|
||||
|
||||
### Управление сообщениями
|
||||
|
||||
- **История**: Полная история всех сообщений
|
||||
- **Поиск**: Поиск по содержимому, отправителю, дате
|
||||
- **Фильтры**: По каналам, типам, статусам
|
||||
- **Экспорт**: Выгрузка переписки в различных форматах
|
||||
|
||||
> 💡 **Подробная информация**: См. [AI Ассистент](./ai-assistant.md) для полного описания возможностей и [Настройка AI ассистента](./setup-ai-assistant.md) для инструкций по настройке.
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Настройки приложения для работы с интернет пользователями
|
||||
|
||||
### Работа с гостями (неавторизованными пользователями)
|
||||
|
||||
DLE поддерживает работу с интернет-пользователями без обязательной регистрации, обеспечивая при этом соответствие требованиям регуляторов.
|
||||
|
||||
#### Роли и права доступа
|
||||
|
||||
**Guest (Гость)**:
|
||||
- ✅ Просмотр главной страницы
|
||||
- ✅ Чат с AI ассистентом
|
||||
- ❌ Отправка сообщений пользователям
|
||||
- ❌ Доступ к CRM и данным
|
||||
|
||||
**User (Пользователь)**:
|
||||
- ✅ Все права гостя
|
||||
- ✅ Получение сообщений
|
||||
- ✅ Отправка сообщений другим пользователям
|
||||
- ✅ Просмотр контактов
|
||||
- ✅ Приватный чат с администраторами
|
||||
- ✅ Просмотр базовых документов
|
||||
|
||||
#### Настройка гостевого доступа
|
||||
|
||||
**Путь**: Настройки → Пользователи → Гостевой доступ
|
||||
|
||||
1. **Включение гостевого режима**
|
||||
- Разрешить доступ без регистрации
|
||||
- Настройка ограничений
|
||||
- Лимиты на использование
|
||||
|
||||
2. **Сбор согласий**
|
||||
- Автоматический запрос согласия на обработку ПД
|
||||
- Политика конфиденциальности
|
||||
- Пользовательское соглашение
|
||||
- Политика использования cookies
|
||||
|
||||
3. **Идентификация гостей**
|
||||
- Уникальные идентификаторы
|
||||
- Шифрование данных
|
||||
- Анонимизация при необходимости
|
||||
|
||||
### Обработка персональных данных
|
||||
|
||||
#### Согласие на обработку ПД
|
||||
|
||||
При первом обращении гостя система:
|
||||
1. Показывает политику конфиденциальности
|
||||
2. Запрашивает согласие на обработку ПД
|
||||
3. Сохраняет факт согласия с временной меткой
|
||||
4. Предоставляет доступ к функциям
|
||||
|
||||
#### Управление согласиями
|
||||
|
||||
**Путь**: Настройки → Контент → Юридические документы
|
||||
|
||||
- Просмотр всех согласий
|
||||
- Экспорт данных по запросу
|
||||
- Удаление данных при отзыве согласия
|
||||
- История изменений согласий
|
||||
|
||||
### Публичные документы
|
||||
|
||||
#### Обязательные документы для публичного доступа
|
||||
|
||||
1. **Политика конфиденциальности**
|
||||
- Описание обработки ПД
|
||||
- Права пользователей
|
||||
- Контакты для обращений
|
||||
|
||||
2. **Пользовательское соглашение**
|
||||
- Условия использования сервиса
|
||||
- Права и обязанности
|
||||
- Ограничения ответственности
|
||||
|
||||
3. **Согласие на обработку персональных данных**
|
||||
- Форма согласия
|
||||
- Информация о целях обработки
|
||||
- Возможность отзыва
|
||||
|
||||
4. **Политика использования cookies**
|
||||
- Типы используемых cookies
|
||||
- Цели использования
|
||||
- Управление настройками
|
||||
|
||||
#### Публикация документов
|
||||
|
||||
**Путь**: Контент → Шаблоны
|
||||
|
||||
1. Выберите необходимые шаблоны
|
||||
2. Предварительный просмотр с автозаполнением
|
||||
3. Редактирование специфичных параметров
|
||||
4. Публикация:
|
||||
- **Публичное использование**: Доступно на сайте
|
||||
- **Внутреннее использование**: Только в CRM
|
||||
- **Печать**: Экспорт в PDF
|
||||
|
||||
### Настройки веб-интерфейса
|
||||
|
||||
#### Публичные страницы
|
||||
|
||||
- **Главная страница**: Информация о сервисе
|
||||
- **Чат**: Публичный чат с AI
|
||||
- **Контакты**: Форма обратной связи
|
||||
- **Документы**: Публичные юридические документы
|
||||
|
||||
#### Интеграция с сайтом
|
||||
|
||||
DLE может быть интегрирован на ваш сайт:
|
||||
- Виджет чата
|
||||
- Формы обратной связи
|
||||
- API для кастомных решений
|
||||
|
||||
### Управление контактами
|
||||
|
||||
#### Автоматическое создание контактов
|
||||
|
||||
При обращении гостя система:
|
||||
1. Создает контакт в CRM
|
||||
2. Присваивает уникальный идентификатор
|
||||
3. Сохраняет канал коммуникации
|
||||
4. Записывает историю взаимодействий
|
||||
|
||||
#### Объединение контактов
|
||||
|
||||
Если один пользователь обращается через разные каналы:
|
||||
- Автоматическое определение дубликатов
|
||||
- Объединение в единый контакт
|
||||
- Единая история коммуникаций
|
||||
|
||||
> 💡 **Важно**: Все настройки для работы с интернет-пользователями должны соответствовать требованиям регуляторов (см. раздел "Требования регулятора").
|
||||
|
||||
---
|
||||
|
||||
## ⚖️ Требования регулятора
|
||||
|
||||
### Соответствие законодательству
|
||||
|
||||
DLE разработан с учетом требований основных регуляторов по защите персональных данных и обеспечению прозрачности работы с пользователями.
|
||||
|
||||
### GDPR (General Data Protection Regulation)
|
||||
|
||||
**Европейское законодательство о защите данных**
|
||||
|
||||
#### Основные требования и их реализация:
|
||||
|
||||
1. **Право на информацию** ✅
|
||||
- Политика конфиденциальности доступна на сайте
|
||||
- Прозрачная информация о целях обработки данных
|
||||
- Контакты контролера данных
|
||||
|
||||
2. **Право на доступ** ✅
|
||||
- Пользователи могут запросить копию своих данных
|
||||
- Экспорт данных в структурированном формате
|
||||
- История обработки данных
|
||||
|
||||
3. **Право на исправление** ✅
|
||||
- Редактирование персональных данных
|
||||
- Обновление информации в профиле
|
||||
- Корректировка неточных данных
|
||||
|
||||
4. **Право на удаление ("право быть забытым")** ✅
|
||||
- Удаление данных по запросу
|
||||
- Отзыв согласия на обработку
|
||||
- Полное удаление из системы
|
||||
|
||||
5. **Право на ограничение обработки** ✅
|
||||
- Временная блокировка обработки
|
||||
- Сохранение данных без использования
|
||||
- Уведомление о снятии ограничений
|
||||
|
||||
6. **Право на переносимость данных** ✅
|
||||
- Экспорт данных в машиночитаемом формате
|
||||
- Передача данных другому контролеру
|
||||
- Структурированные форматы (JSON, CSV)
|
||||
|
||||
7. **Право на возражение** ✅
|
||||
- Отказ от обработки данных
|
||||
- Отзыв согласия
|
||||
- Остановка маркетинговых рассылок
|
||||
|
||||
8. **Автоматизированное принятие решений** ✅
|
||||
- Прозрачность использования AI
|
||||
- Возможность человеческого вмешательства
|
||||
- Объяснение логики решений
|
||||
|
||||
### CCPA (California Consumer Privacy Act)
|
||||
|
||||
**Калифорнийский закон о защите конфиденциальности**
|
||||
|
||||
#### Основные требования:
|
||||
|
||||
1. **Право знать** ✅
|
||||
- Информация о собираемых данных
|
||||
- Цели использования данных
|
||||
- Категории третьих сторон
|
||||
|
||||
2. **Право на удаление** ✅
|
||||
- Удаление персональных данных
|
||||
- Исключения для бизнес-нужд
|
||||
- Подтверждение удаления
|
||||
|
||||
3. **Право на отказ от продажи** ✅
|
||||
- DLE не продает данные пользователей
|
||||
- Прозрачная политика
|
||||
- Механизм отказа
|
||||
|
||||
4. **Недискриминация** ✅
|
||||
- Равный доступ к сервису
|
||||
- Отсутствие штрафов за использование прав
|
||||
- Справедливое ценообразование
|
||||
|
||||
### 152-ФЗ (Российское законодательство)
|
||||
|
||||
**Федеральный закон "О персональных данных"**
|
||||
|
||||
#### Основные требования:
|
||||
|
||||
1. **Согласие на обработку** ✅
|
||||
- Явное согласие субъекта ПД
|
||||
- Информированное согласие
|
||||
- Возможность отзыва
|
||||
|
||||
2. **Уведомление Роскомнадзора** ✅
|
||||
- Документация для уведомления
|
||||
- Описание целей обработки
|
||||
- Меры безопасности
|
||||
|
||||
3. **Локализация данных** ✅
|
||||
- Хранение на серверах в РФ (опционально)
|
||||
- Контроль местоположения данных
|
||||
- Соответствие требованиям
|
||||
|
||||
4. **Права субъектов ПД** ✅
|
||||
- Доступ к данным
|
||||
- Исправление данных
|
||||
- Удаление данных
|
||||
- Отзыв согласия
|
||||
|
||||
5. **Меры безопасности** ✅
|
||||
- Шифрование данных
|
||||
- Контроль доступа
|
||||
- Аудит действий
|
||||
- Резервное копирование
|
||||
|
||||
### Готовые документы для регулятора
|
||||
|
||||
DLE включает готовый пакет документов, необходимых для соответствия требованиям:
|
||||
|
||||
#### 1. Политика конфиденциальности
|
||||
- Описание обработки ПД
|
||||
- Права пользователей
|
||||
- Контакты контролера
|
||||
- Механизмы реализации прав
|
||||
|
||||
#### 2. Пользовательское соглашение
|
||||
- Условия использования
|
||||
- Права и обязанности сторон
|
||||
- Ограничения ответственности
|
||||
- Разрешение споров
|
||||
|
||||
#### 3. Согласие на обработку персональных данных
|
||||
- Форма согласия
|
||||
- Цели обработки
|
||||
- Сроки хранения
|
||||
- Право отзыва
|
||||
|
||||
#### 4. Политика использования cookies
|
||||
- Типы cookies
|
||||
- Цели использования
|
||||
- Управление настройками
|
||||
- Отключение cookies
|
||||
|
||||
#### 5. Документация для Роскомнадзора
|
||||
- Описание системы обработки ПД
|
||||
- Меры безопасности
|
||||
- Технические характеристики
|
||||
- Процедуры обработки запросов
|
||||
|
||||
### Аудит и соответствие
|
||||
|
||||
#### Регулярные проверки
|
||||
|
||||
- Проверка актуальности документов
|
||||
- Обновление политик при изменении законодательства
|
||||
- Аудит обработки данных
|
||||
- Тестирование механизмов реализации прав
|
||||
|
||||
#### Рекомендации
|
||||
|
||||
1. **Регулярное обновление**
|
||||
- Следите за изменениями в законодательстве
|
||||
- Обновляйте документы при необходимости
|
||||
- Проводите аудит соответствия
|
||||
|
||||
2. **Консультации**
|
||||
- Рекомендуется консультация с юристом
|
||||
- Проверка соответствия локальному законодательству
|
||||
- Адаптация под специфику вашего региона
|
||||
|
||||
3. **Документирование**
|
||||
- Ведите журнал обработки данных
|
||||
- Сохраняйте историю согласий
|
||||
- Документируйте запросы пользователей
|
||||
|
||||
> 💡 **Подробная информация**: См. [Безопасность DLE](./security.md) для детального описания мер безопасности и соответствия требованиям регуляторов.
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Обновления софта
|
||||
|
||||
### Политика обновлений
|
||||
|
||||
DLE предоставляет бесплатные обновления в течение 5 лет для держателей лицензионных токенов. Это включает:
|
||||
- ✅ Исправления ошибок (bug fixes)
|
||||
- ✅ Новые функции (features)
|
||||
- ✅ Улучшения безопасности (security updates)
|
||||
- ✅ Обновления зависимостей (dependencies)
|
||||
- ✅ Оптимизация производительности
|
||||
|
||||
### Типы обновлений
|
||||
|
||||
#### 1. Патч-обновления (Patch)
|
||||
- **Формат версии**: X.Y.**Z** (например, 1.0.1 → 1.0.2)
|
||||
- **Содержание**: Исправления ошибок, мелкие улучшения
|
||||
- **Частота**: По мере необходимости
|
||||
- **Критичность**: Обычно низкая-средняя
|
||||
|
||||
#### 2. Минорные обновления (Minor)
|
||||
- **Формат версии**: X.**Y**.Z (например, 1.0.2 → 1.1.0)
|
||||
- **Содержание**: Новые функции, улучшения
|
||||
- **Частота**: Ежеквартально или по мере готовности
|
||||
- **Критичность**: Средняя
|
||||
|
||||
#### 3. Мажорные обновления (Major)
|
||||
- **Формат версии**: **X**.Y.Z (например, 1.1.0 → 2.0.0)
|
||||
- **Содержание**: Крупные изменения, breaking changes
|
||||
- **Частота**: Раз в год или реже
|
||||
- **Критичность**: Высокая (требуется миграция)
|
||||
|
||||
### Процесс обновления
|
||||
|
||||
#### Автоматическое обновление (рекомендуется)
|
||||
|
||||
```bash
|
||||
# 1. Остановка текущей версии
|
||||
docker-compose down
|
||||
|
||||
# 2. Резервное копирование данных
|
||||
docker run --rm -v dle_postgres_data:/data -v $(pwd):/backup \
|
||||
alpine tar czf /backup/postgres_backup.tar.gz -C /data .
|
||||
|
||||
# 3. Обновление кода
|
||||
git pull origin main
|
||||
|
||||
# 4. Обновление образов
|
||||
docker-compose pull
|
||||
|
||||
# 5. Пересборка (если необходимо)
|
||||
docker-compose build
|
||||
|
||||
# 6. Запуск обновленной версии
|
||||
docker-compose up -d
|
||||
|
||||
# 7. Проверка статуса
|
||||
docker-compose ps
|
||||
docker-compose logs -f
|
||||
```
|
||||
|
||||
#### Ручное обновление
|
||||
|
||||
1. **Подготовка**
|
||||
- Проверьте текущую версию
|
||||
- Изучите changelog обновления
|
||||
- Создайте резервную копию
|
||||
|
||||
2. **Резервное копирование**
|
||||
```bash
|
||||
# База данных
|
||||
docker-compose exec postgres pg_dump -U dapp_user dapp_db > backup.sql
|
||||
|
||||
# Файлы загрузок
|
||||
tar -czf uploads_backup.tar.gz backend/uploads/
|
||||
|
||||
# Конфигурации
|
||||
cp .env .env.backup
|
||||
```
|
||||
|
||||
3. **Обновление**
|
||||
- Скачайте новую версию
|
||||
- Обновите зависимости
|
||||
- Примените миграции БД (если есть)
|
||||
|
||||
4. **Проверка**
|
||||
- Проверьте работоспособность
|
||||
- Протестируйте ключевые функции
|
||||
- Проверьте логи на ошибки
|
||||
|
||||
### Миграции базы данных
|
||||
|
||||
При обновлении могут потребоваться миграции БД:
|
||||
|
||||
```bash
|
||||
# Автоматическое применение миграций
|
||||
docker-compose exec backend npm run migrate
|
||||
|
||||
# Или вручную через SQL
|
||||
docker-compose exec postgres psql -U dapp_user -d dapp_db -f migrations/version_X.X.X.sql
|
||||
```
|
||||
|
||||
### Откат обновления
|
||||
|
||||
В случае проблем можно откатиться к предыдущей версии:
|
||||
|
||||
```bash
|
||||
# 1. Остановка текущей версии
|
||||
docker-compose down
|
||||
|
||||
# 2. Восстановление из резервной копии
|
||||
docker run --rm -v dle_postgres_data:/data -v $(pwd):/backup \
|
||||
alpine tar xzf /backup/postgres_backup.tar.gz -C /data
|
||||
|
||||
# 3. Переключение на предыдущую версию
|
||||
git checkout <previous-version-tag>
|
||||
|
||||
# 4. Запуск предыдущей версии
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Уведомления об обновлениях
|
||||
|
||||
#### Каналы уведомлений
|
||||
|
||||
1. **GitHub Releases**
|
||||
- Официальные релизы
|
||||
- Changelog изменений
|
||||
- Инструкции по обновлению
|
||||
|
||||
2. **Email рассылка**
|
||||
- Уведомления о важных обновлениях
|
||||
- Информация о breaking changes
|
||||
- Рекомендации по обновлению
|
||||
|
||||
3. **В приложении**
|
||||
- Уведомления о доступных обновлениях
|
||||
- Информация о новых функциях
|
||||
- Ссылки на документацию
|
||||
|
||||
### Рекомендации по обновлениям
|
||||
|
||||
1. **Регулярность**
|
||||
- Обновляйтесь регулярно для получения исправлений безопасности
|
||||
- Не откладывайте критичные обновления
|
||||
- Следите за changelog
|
||||
|
||||
2. **Тестирование**
|
||||
- Тестируйте обновления на тестовой среде
|
||||
- Проверяйте совместимость с вашими данными
|
||||
- Убедитесь в работоспособности интеграций
|
||||
|
||||
3. **Резервное копирование**
|
||||
- Всегда создавайте резервные копии перед обновлением
|
||||
- Храните несколько версий бэкапов
|
||||
- Тестируйте восстановление из бэкапа
|
||||
|
||||
4. **Документирование**
|
||||
- Ведите журнал обновлений
|
||||
- Записывайте возникшие проблемы
|
||||
- Документируйте кастомные изменения
|
||||
|
||||
### Долгосрочная поддержка (LTS)
|
||||
|
||||
Для стабильных версий может быть доступна долгосрочная поддержка:
|
||||
- Расширенный период поддержки
|
||||
- Приоритетные исправления безопасности
|
||||
- Гарантированная совместимость
|
||||
|
||||
> 💡 **Важно**: Обновления предоставляются бесплатно в течение 5 лет для держателей лицензионных токенов. После этого периода обновления могут быть платными или доступны через сообщество.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Дополнительные ресурсы
|
||||
|
||||
### Документация
|
||||
|
||||
- [Описание приложения](./application-description.md) - Полный обзор возможностей и преимуществ
|
||||
- [Инструкция по установке](./setup-instruction.md) - Пошаговая настройка
|
||||
- [Настройка AI ассистента](./setup-ai-assistant.md) - Конфигурация AI
|
||||
- [Безопасность DLE](./security.md) - Детальная информация о безопасности
|
||||
- [FAQ](./FAQ.md) - Ответы на частые вопросы
|
||||
|
||||
### Поддержка
|
||||
|
||||
- **Email**: info@hb3-accelerator.com
|
||||
- **Сайт**: https://hb3-accelerator.com
|
||||
- **GitHub**: https://github.com/VC-HB3-Accelerator
|
||||
|
||||
---
|
||||
|
||||
**© 2024-2025 Тарабанов Александр Викторович. Все права защищены.**
|
||||
|
||||
**Digital Legal Entity (DLE)** - комплексное решение для управления бизнесом с блокчейн и AI.
|
||||
|
||||
**Версия документа**: 1.0.0
|
||||
**Последнее обновление**: January 2025
|
||||
|
||||
@@ -81,12 +81,31 @@ http {
|
||||
return 404;
|
||||
}
|
||||
|
||||
# Защита от доступа к чувствительным файлам
|
||||
location ~* /(\.htaccess|\.htpasswd|\.env|\.git|\.svn|\.DS_Store|Thumbs\.db|web\.config|robots\.txt|sitemap\.xml)$ {
|
||||
# Защита от доступа к чувствительным файлам (исключаем robots.txt и sitemap.xml для SEO)
|
||||
location ~* /(\.htaccess|\.htpasswd|\.env|\.git|\.svn|\.DS_Store|Thumbs\.db|web\.config)$ {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
|
||||
# Разрешаем доступ к robots.txt и sitemap.xml для поисковых систем
|
||||
location = /robots.txt {
|
||||
proxy_pass http://${BACKEND_CONTAINER}:8000/api/pages/public/robots.txt;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
location = /sitemap.xml {
|
||||
proxy_pass http://${BACKEND_CONTAINER}:8000/api/pages/public/sitemap.xml;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
add_header Content-Type application/xml;
|
||||
}
|
||||
|
||||
# Защита от доступа к конфигурационным файлам
|
||||
location ~* /\.(env|config|ini|conf|cfg|yml|yaml|json|xml|sql|db|bak|backup|old|tmp|temp|log)$ {
|
||||
deny all;
|
||||
|
||||
@@ -420,6 +420,35 @@ const isResizing = ref(false);
|
||||
const resizeStartX = ref(0);
|
||||
const resizeStartWidth = ref(0);
|
||||
|
||||
// Определяем, является ли устройство мобильным
|
||||
const isMobile = ref(false);
|
||||
|
||||
// Функция для проверки мобильного устройства
|
||||
const checkMobile = () => {
|
||||
isMobile.value = window.innerWidth <= 1024;
|
||||
if (isMobile.value) {
|
||||
// На мобильных устройствах устанавливаем ширину в 100%
|
||||
messagesWidth.value = 100;
|
||||
inputWidth.value = 100;
|
||||
} else {
|
||||
// На десктопе используем стандартные значения
|
||||
if (messagesWidth.value === 100) {
|
||||
messagesWidth.value = 70;
|
||||
inputWidth.value = 30;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Отслеживаем изменение размера окна
|
||||
onMounted(() => {
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', checkMobile);
|
||||
});
|
||||
|
||||
const startResize = (e) => {
|
||||
isResizing.value = true;
|
||||
|
||||
@@ -535,6 +564,10 @@ const updateChatInputHeight = () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// Проверяем мобильное устройство и устанавливаем ширину
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
|
||||
// Начальная установка высоты textarea и блока ввода
|
||||
adjustTextareaHeight();
|
||||
updateChatInputHeight();
|
||||
@@ -548,6 +581,8 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', checkMobile);
|
||||
|
||||
if (resizeObserver && chatInputRef.value) {
|
||||
resizeObserver.unobserve(chatInputRef.value);
|
||||
}
|
||||
@@ -709,8 +744,8 @@ async function handleAiReply() {
|
||||
/* На мобильных устройствах блок ввода занимает всё пространство внизу */
|
||||
@media (max-width: 1024px) {
|
||||
.chat-input {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
height: auto;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
@@ -737,6 +772,7 @@ async function handleAiReply() {
|
||||
color: var(--color-dark);
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.chat-input textarea:focus {
|
||||
@@ -748,12 +784,15 @@ async function handleAiReply() {
|
||||
/* На мобильных устройствах поле ввода меньше */
|
||||
@media (max-width: 1024px) {
|
||||
.chat-input textarea {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
border-radius: 20px;
|
||||
padding: 12px 16px;
|
||||
min-height: var(--chat-input-min-height, 40px);
|
||||
max-height: var(--chat-input-max-height, 120px);
|
||||
overflow-y: hidden;
|
||||
resize: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,11 +981,25 @@ async function handleAiReply() {
|
||||
|
||||
.chat-messages {
|
||||
padding: var(--spacing-md) var(--spacing-md) 8px;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-area textarea {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat-icon-btn {
|
||||
@@ -958,10 +1011,16 @@ async function handleAiReply() {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.resizer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.chat-input {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
position: sticky !important;
|
||||
bottom: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
@@ -969,14 +1028,27 @@ async function handleAiReply() {
|
||||
background: #f8f8f8 !important;
|
||||
border-top: 1px solid #eee !important;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
width: 100% !important;
|
||||
padding: var(--spacing-md) var(--spacing-md) 8px !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.input-area textarea {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.chat-input {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
position: sticky !important;
|
||||
bottom: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
@@ -984,7 +1056,9 @@ async function handleAiReply() {
|
||||
background: #f8f8f8 !important;
|
||||
border-top: 1px solid #eee !important;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
width: 100% !important;
|
||||
padding: var(--spacing-md) var(--spacing-md) 8px !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
<router-link to="/" class="nav-link-btn" active-class="active">
|
||||
<span>Чат</span>
|
||||
</router-link>
|
||||
<router-link to="/content/published" class="nav-link-btn" active-class="active">
|
||||
<span>Блог</span>
|
||||
</router-link>
|
||||
<router-link to="/crm" class="nav-link-btn" active-class="active">
|
||||
<span>CRM</span>
|
||||
</router-link>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<!-- Заголовок страницы -->
|
||||
<header v-if="page" class="page-header">
|
||||
<div class="page-header-top">
|
||||
<button v-if="breadcrumbs.length > 0" class="back-btn" @click="$emit('back')" title="Вернуться к списку">
|
||||
<button class="back-btn" @click="$emit('back')" title="Вернуться к списку">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
<span>Назад</span>
|
||||
</button>
|
||||
@@ -186,6 +186,68 @@ const navigation = ref(null);
|
||||
const breadcrumbs = ref([]);
|
||||
const isLoading = ref(false);
|
||||
|
||||
// Установка мета-тегов для SEO
|
||||
function updateMetaTags(pageData) {
|
||||
if (!pageData) return;
|
||||
|
||||
// Парсим seo, если это строка (может прийти из базы как JSON строка)
|
||||
let seoData = pageData.seo;
|
||||
if (typeof seoData === 'string') {
|
||||
try {
|
||||
seoData = JSON.parse(seoData);
|
||||
} catch (e) {
|
||||
console.warn('Ошибка парсинга SEO данных:', e);
|
||||
seoData = null;
|
||||
}
|
||||
}
|
||||
|
||||
const title = seoData?.title || pageData.title || 'Документ';
|
||||
const description = seoData?.description || pageData.summary || '';
|
||||
const keywords = seoData?.keywords || '';
|
||||
const canonicalUrl = `${window.location.origin}/content/published?page=${pageData.id}`;
|
||||
|
||||
// Обновляем title
|
||||
document.title = title;
|
||||
|
||||
// Обновляем или создаем meta теги
|
||||
const updateOrCreateMeta = (name, content, attribute = 'name') => {
|
||||
if (!content) return;
|
||||
let meta = document.querySelector(`meta[${attribute}="${name}"]`);
|
||||
if (!meta) {
|
||||
meta = document.createElement('meta');
|
||||
meta.setAttribute(attribute, name);
|
||||
document.head.appendChild(meta);
|
||||
}
|
||||
meta.setAttribute('content', content);
|
||||
};
|
||||
|
||||
// Meta description
|
||||
updateOrCreateMeta('description', description);
|
||||
|
||||
// Meta keywords
|
||||
if (keywords) {
|
||||
updateOrCreateMeta('keywords', keywords);
|
||||
}
|
||||
|
||||
// Canonical URL
|
||||
let canonical = document.querySelector('link[rel="canonical"]');
|
||||
if (!canonical) {
|
||||
canonical = document.createElement('link');
|
||||
canonical.setAttribute('rel', 'canonical');
|
||||
document.head.appendChild(canonical);
|
||||
}
|
||||
canonical.setAttribute('href', canonicalUrl);
|
||||
|
||||
// Open Graph теги для социальных сетей
|
||||
updateOrCreateMeta('og:title', title, 'property');
|
||||
updateOrCreateMeta('og:description', description, 'property');
|
||||
updateOrCreateMeta('og:type', 'article', 'property');
|
||||
updateOrCreateMeta('og:url', canonicalUrl, 'property');
|
||||
|
||||
// Robots meta
|
||||
updateOrCreateMeta('robots', 'index, follow');
|
||||
}
|
||||
|
||||
// Загрузка страницы
|
||||
async function loadPage() {
|
||||
if (!props.pageId) return;
|
||||
@@ -194,6 +256,11 @@ async function loadPage() {
|
||||
isLoading.value = true;
|
||||
page.value = await pagesService.getPublicPage(props.pageId);
|
||||
|
||||
// Устанавливаем мета-теги для SEO
|
||||
if (page.value) {
|
||||
updateMetaTags(page.value);
|
||||
}
|
||||
|
||||
// Загружаем навигацию
|
||||
try {
|
||||
navigation.value = await pagesService.getPublicPageNavigation(props.pageId);
|
||||
@@ -959,12 +1026,44 @@ onMounted(() => {
|
||||
@media (max-width: 768px) {
|
||||
.docs-content {
|
||||
padding: 20px;
|
||||
min-height: auto;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.page-article {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.file-preview {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pdf-embed {
|
||||
width: 100%;
|
||||
height: 60vh;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.page-navigation {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -199,4 +199,55 @@ onMounted(() => {
|
||||
padding: 1rem;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.admin-chat-header {
|
||||
padding: 0.75rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
font-size: 1.25rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
height: calc(100vh - 100px);
|
||||
}
|
||||
|
||||
:deep(.chat-messages) {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
:deep(.chat-input) {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.loading {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.admin-chat-header {
|
||||
padding: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
height: calc(100vh - 80px);
|
||||
}
|
||||
|
||||
:deep(.chat-messages) {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
:deep(.chat-input) {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -377,5 +377,98 @@ h1 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.connect-wallet-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.connect-wallet-card {
|
||||
padding: 30px 24px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.provider-info {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.connect-button,
|
||||
.go-chat-button {
|
||||
padding: 12px 24px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.connect-wallet-container {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.connect-wallet-card {
|
||||
padding: 24px 16px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 40px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.provider-info {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.connect-button,
|
||||
.go-chat-button {
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.expires-info {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
font-size: 13px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -94,4 +94,24 @@ function goBack() {
|
||||
margin-left: 7px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.contacts-header {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 16px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.85em;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.contacts-header {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -19,26 +19,6 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="content-create-page">
|
||||
<!-- Заголовок страницы -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>{{ isEditMode ? 'Редактирование страницы' : 'Создание страницы' }}</h1>
|
||||
<p>{{ isEditMode ? 'Редактируйте существующую страницу' : 'Создайте новую страницу для вашего DLE' }}</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button
|
||||
v-if="isEditMode && canManageLegalDocs && address"
|
||||
class="btn btn-danger"
|
||||
@click="deletePage"
|
||||
type="button"
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
Удалить
|
||||
</button>
|
||||
<button class="close-btn" @click="goBack">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Основной контент с тенью -->
|
||||
<div class="content-block">
|
||||
<form class="content-form" @submit.prevent="handleSubmit">
|
||||
|
||||
@@ -19,13 +19,6 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="crm-management">
|
||||
<!-- Заголовок -->
|
||||
<div class="management-header">
|
||||
<div class="header-content">
|
||||
<h1>CRM Система</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Блоки CRM -->
|
||||
<div class="management-blocks">
|
||||
<!-- Столбец 1 -->
|
||||
|
||||
@@ -19,13 +19,6 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="home-management">
|
||||
<!-- Заголовок -->
|
||||
<div class="management-header">
|
||||
<div class="header-content">
|
||||
<h1>Сообщения</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Чат интерфейс -->
|
||||
<div class="chat-wrapper">
|
||||
<template v-if="auth.userAccessLevel.value && auth.userAccessLevel.value.hasAccess">
|
||||
|
||||
@@ -19,12 +19,6 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="management-container">
|
||||
<!-- Заголовок -->
|
||||
<div class="management-header">
|
||||
<h1>Управление DLE</h1>
|
||||
<button class="close-btn" @click="router.push('/')">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Деплоированные DLE -->
|
||||
<div class="deployed-dles-section">
|
||||
<div class="section-header">
|
||||
|
||||
@@ -308,4 +308,65 @@ onUnmounted(() => {
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.personal-messages-header {
|
||||
padding: 0.75rem;
|
||||
font-size: 1rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
font-size: 1.25rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
}
|
||||
|
||||
.personal-messages-list {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.message-info {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.admin-name {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.message-preview {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.message-date {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.personal-messages-header {
|
||||
padding: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.admin-name {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 1.5rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,10 +19,8 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="settings-view-container">
|
||||
<div v-if="route.name !== 'settings-index'" class="page-header">
|
||||
<h1>{{ pageTitle }}</h1>
|
||||
<div v-if="route.name === 'settings-blockchain-dle-deploy' || route.name === 'settings-dle-v2-deploy'" class="page-header">
|
||||
<button
|
||||
v-if="route.name === 'settings-blockchain-dle-deploy' || route.name === 'settings-dle-v2-deploy'"
|
||||
class="close-btn"
|
||||
@click="router.push('/settings')"
|
||||
>×</button>
|
||||
|
||||
@@ -19,9 +19,7 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="vds-management-container">
|
||||
<!-- Заголовок -->
|
||||
<div class="vds-header">
|
||||
<h1>Управление VDS</h1>
|
||||
<div class="status-badge" :class="{ online: isOnline }">
|
||||
<div class="status-indicator" :class="{ online: isOnline }"></div>
|
||||
<span>{{ isOnline ? 'Онлайн' : 'Офлайн' }}</span>
|
||||
|
||||
@@ -19,18 +19,6 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="content-management-page">
|
||||
<!-- Заголовок страницы -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>Управление контентом</h1>
|
||||
<p v-if="canEditData && address">Создавайте и управляйте страницами вашего DLE</p>
|
||||
<p v-else>Просмотр опубликованных страниц DLE</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="close-btn" @click="goBack">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Основной контент с тенью -->
|
||||
<div class="content-block">
|
||||
<!-- Быстрые разделы -->
|
||||
@@ -248,8 +236,33 @@ async function deletePage(id) {
|
||||
.details-btn { background: var(--color-primary); color: #fff; border: none; border-radius: 8px; padding: 0.75rem 1.5rem; cursor: pointer; font-size: 1rem; font-weight: 600; transition: all 0.2s; min-width: 120px; margin-top: auto; }
|
||||
.details-btn:hover { background: var(--color-primary-dark); transform: translateY(-1px); }
|
||||
|
||||
@media (max-width: 1024px) { .management-blocks { grid-template-columns: repeat(2, 1fr); } }
|
||||
@media (max-width: 768px) { .management-blocks { grid-template-columns: 1fr; } }
|
||||
@media (max-width: 1024px) {
|
||||
.management-blocks {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.management-blocks {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.blocks-column {
|
||||
gap: 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.management-blocks {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.blocks-column {
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.content-section {
|
||||
background: #f8f9fa;
|
||||
@@ -496,6 +509,12 @@ async function deletePage(id) {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -505,6 +524,11 @@ async function deletePage(id) {
|
||||
padding: 5px;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: background 0.2s;
|
||||
min-width: 32px;
|
||||
min-height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
@@ -513,9 +537,37 @@ async function deletePage(id) {
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.content-management-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.header-content h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.header-content p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
font-size: 1.3rem;
|
||||
min-width: 28px;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
@@ -536,9 +588,59 @@ async function deletePage(id) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
.settings-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.management-block {
|
||||
padding: 1.5rem;
|
||||
height: auto;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.management-block h3 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.management-block p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.details-btn {
|
||||
padding: 0.6rem 1.2rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.content-management-page {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.header-content h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.management-block {
|
||||
padding: 1.2rem;
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.management-block h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.management-block p {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -13,12 +13,6 @@
|
||||
<template>
|
||||
<BaseLayout>
|
||||
<div class="list-page">
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>Настройки контента</h1>
|
||||
<p>Юр. реквизиты и параметры подстановки переменных</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-block">
|
||||
<div class="section-header">
|
||||
<h2>Юр. реквизиты (переменные)</h2>
|
||||
@@ -38,13 +32,136 @@ import BaseLayout from '../../components/BaseLayout.vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.list-page { padding: 20px; width: 100%; }
|
||||
.page-header { display:flex; justify-content: space-between; align-items: flex-start; margin-bottom: 30px; padding-bottom: 20px; border-bottom: 2px solid #f0f0f0; }
|
||||
.header-content h1 { color: var(--color-primary); font-size: 2.2rem; margin: 0 0 8px 0; }
|
||||
.header-content p { color: var(--color-grey-dark); margin: 0; }
|
||||
.content-block { background: #f8f9fa; border-radius: var(--radius-lg); padding: 25px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
||||
.section-header { display:flex; justify-content: space-between; align-items:center; margin-bottom: 20px; }
|
||||
.section-header h2 { color: var(--color-primary); margin: 0; }
|
||||
.empty-state { text-align:center; padding: 60px 20px; }
|
||||
.empty-icon { font-size: 3rem; color: var(--color-grey-dark); margin-bottom: 10px; }
|
||||
.list-page {
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.header-content h1 {
|
||||
color: var(--color-primary);
|
||||
font-size: 2.2rem;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.header-content p {
|
||||
color: var(--color-grey-dark);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
background: #f8f9fa;
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 25px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
color: var(--color-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 3rem;
|
||||
color: var(--color-grey-dark);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.list-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.header-content h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.header-content p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 40px 16px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.list-page {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.header-content h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 30px 12px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -16,13 +16,7 @@
|
||||
<template>
|
||||
<BaseLayout :is-authenticated="isAuthenticated" :identities="identities" :token-balances="tokenBalances" :is-loading-tokens="isLoadingTokens" @auth-action-completed="$emit('auth-action-completed')">
|
||||
<div class="list-page">
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>Внутренние документы</h1>
|
||||
<p>Документы, доступные только пользователям с ролями</p>
|
||||
</div>
|
||||
<button class="close-btn" @click="goBack">×</button>
|
||||
</div>
|
||||
<button class="close-btn" @click="goBack" style="margin-bottom: 20px;">×</button>
|
||||
<div class="content-block">
|
||||
<div class="section-header">
|
||||
<h2>Список документов</h2>
|
||||
|
||||
@@ -19,38 +19,6 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="page-view-container">
|
||||
<!-- Заголовок страницы -->
|
||||
<div v-if="page" class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>📄 {{ page.title }}</h1>
|
||||
<div class="page-meta">
|
||||
<span class="page-status" :class="page.status">
|
||||
<i class="fas fa-circle"></i>
|
||||
{{ getStatusText(page.status) }}
|
||||
</span>
|
||||
<span class="page-date">
|
||||
<i class="fas fa-calendar"></i>
|
||||
Создано: {{ formatDate(page.createdAt) }}
|
||||
</span>
|
||||
<span v-if="page.updatedAt" class="page-date">
|
||||
<i class="fas fa-edit"></i>
|
||||
Обновлено: {{ formatDate(page.updatedAt) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button v-if="canEditPage" class="btn btn-outline" @click="goToEdit">
|
||||
<i class="fas fa-edit"></i>
|
||||
Редактировать
|
||||
</button>
|
||||
<button v-if="canManageLegalDocs && address" class="btn btn-danger" @click="deletePage" type="button">
|
||||
<i class="fas fa-trash"></i>
|
||||
Удалить
|
||||
</button>
|
||||
<button class="close-btn" @click="goBack">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Контент страницы -->
|
||||
<div v-if="page" class="page-content-block">
|
||||
<div class="page-content">
|
||||
|
||||
@@ -19,13 +19,6 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="public-pages-page">
|
||||
<!-- Заголовок страницы -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>📄 Публичные страницы</h1>
|
||||
<p>Просмотр опубликованных страниц DLE</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Основной контент -->
|
||||
<div class="content-block">
|
||||
|
||||
@@ -16,16 +16,12 @@
|
||||
<template>
|
||||
<BaseLayout :is-authenticated="isAuthenticated" :identities="identities" :token-balances="tokenBalances" :is-loading-tokens="isLoadingTokens" @auth-action-completed="$emit('auth-action-completed')">
|
||||
<div class="docs-page">
|
||||
<!-- Заголовок страницы -->
|
||||
<div class="docs-header">
|
||||
<div class="header-content">
|
||||
<h1>Публичные документы</h1>
|
||||
</div>
|
||||
<button class="close-btn" @click="goBack" title="Закрыть">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Основной контент: сайдбар + контент -->
|
||||
<div class="docs-layout">
|
||||
<div class="docs-layout" :class="{ 'has-content': currentPageId }">
|
||||
<!-- Сайдбар навигации -->
|
||||
<DocsSidebar :current-page-id="currentPageId" />
|
||||
|
||||
@@ -734,7 +730,8 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.docs-main {
|
||||
overflow-y: visible;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,6 +951,32 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.docs-page {
|
||||
height: auto;
|
||||
min-height: calc(100vh - 40px);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.docs-layout {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: visible;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* В мобильной версии, когда выбран документ, скрываем сайдбар */
|
||||
.docs-layout.has-content .docs-sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.docs-main {
|
||||
overflow-y: visible;
|
||||
min-height: auto;
|
||||
width: 100%;
|
||||
display: block;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.docs-header {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
@@ -5,14 +5,8 @@
|
||||
<template>
|
||||
<BaseLayout :is-authenticated="isAuthenticated" :identities="identities" :token-balances="tokenBalances" :is-loading-tokens="isLoadingTokens" @auth-action-completed="$emit('auth-action-completed')">
|
||||
<div class="list-page">
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>Шаблоны документов</h1>
|
||||
<p>Системные шаблоны для персонализации и публикации.</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="close-btn" @click="goBack">×</button>
|
||||
</div>
|
||||
<div class="header-actions" style="margin-bottom: 20px;">
|
||||
<button class="close-btn" @click="goBack">×</button>
|
||||
</div>
|
||||
|
||||
<div class="content-block">
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="system-messages-page">
|
||||
<div class="page-header">
|
||||
<div class="page-header" v-if="canManageSystemMessages">
|
||||
<div class="header-content">
|
||||
<h1>Системные сообщения</h1>
|
||||
<p v-if="canManageSystemMessages">
|
||||
Создавайте и управляйте уведомлениями, которые видят пользователи в чате и интерфейсе DLE
|
||||
</p>
|
||||
|
||||
@@ -19,14 +19,6 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="groups-management">
|
||||
<div class="management-header">
|
||||
<div class="header-content">
|
||||
<h1>Группы</h1>
|
||||
<p class="groups-description">
|
||||
Создание и управление группами
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Блоки управления группами -->
|
||||
<div class="management-blocks">
|
||||
|
||||
@@ -14,13 +14,6 @@
|
||||
<div class="security-settings">
|
||||
<button class="close-btn" @click="goBack">×</button>
|
||||
|
||||
<!-- Заголовок в стиле основной страницы настроек -->
|
||||
<div class="management-header">
|
||||
<div class="header-content">
|
||||
<h1>Настройки безопасности</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Блоки настроек в едином стиле -->
|
||||
<div class="management-blocks">
|
||||
<!-- Столбец 1 -->
|
||||
|
||||
@@ -12,13 +12,6 @@
|
||||
|
||||
<template>
|
||||
<div class="settings-management">
|
||||
<!-- Заголовок -->
|
||||
<div class="management-header">
|
||||
<div class="header-content">
|
||||
<h1>Настройки системы</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Блоки настроек -->
|
||||
<div class="management-blocks">
|
||||
<!-- Столбец 1 -->
|
||||
|
||||
@@ -19,18 +19,19 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="add-module-page">
|
||||
<!-- Заголовок -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>Добавление модуля в DLE</h1>
|
||||
<p v-if="selectedDle">{{ selectedDle.name }} ({{ selectedDle.symbol }}) - {{ selectedDle.dleAddress }}</p>
|
||||
<p v-else-if="isLoadingDle">Загрузка...</p>
|
||||
<p v-else>DLE не выбран (Адрес: {{ dleAddress }})</p>
|
||||
<!-- Информация для неавторизованных пользователей -->
|
||||
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div v-if="selectedDle?.dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
{{ selectedDle.dleAddress }}
|
||||
</div>
|
||||
<div v-else-if="dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
{{ dleAddress }}
|
||||
</div>
|
||||
<div v-else-if="isLoadingDle" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
Загрузка...
|
||||
</div>
|
||||
<button class="close-btn" @click="goBackToProposals">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Информация для неавторизованных пользователей -->
|
||||
<div v-if="!props.isAuthenticated" class="auth-notice">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
|
||||
@@ -19,18 +19,16 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="analytics-container">
|
||||
<!-- Заголовок -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>Аналитика DLE</h1>
|
||||
<p v-if="selectedDle">{{ selectedDle.name }} ({{ selectedDle.symbol }}) - {{ formatAddress(dleAddress) }}</p>
|
||||
<p v-else-if="isLoadingDle">Загрузка...</p>
|
||||
<p v-else>Подробная аналитика и статистика DLE</p>
|
||||
<!-- Основная информация -->
|
||||
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div v-if="dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
{{ dleAddress }}
|
||||
</div>
|
||||
<div v-else-if="isLoadingDle" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
Загрузка...
|
||||
</div>
|
||||
<button class="close-btn" @click="goBackToBlocks">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Основная информация -->
|
||||
<div class="info-section">
|
||||
<h2>Основная информация</h2>
|
||||
<div class="info-grid">
|
||||
|
||||
@@ -19,18 +19,19 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="create-proposal-page">
|
||||
<!-- Заголовок -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>Создание предложения</h1>
|
||||
<p v-if="selectedDle">{{ selectedDle.name }} ({{ selectedDle.symbol }}) - {{ selectedDle.dleAddress }}</p>
|
||||
<p v-else-if="isLoadingDle">Загрузка...</p>
|
||||
<p v-else>DLE не выбран</p>
|
||||
<!-- Информация для неавторизованных пользователей -->
|
||||
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div v-if="selectedDle?.dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
{{ selectedDle.dleAddress }}
|
||||
</div>
|
||||
<div v-else-if="dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
{{ dleAddress }}
|
||||
</div>
|
||||
<div v-else-if="isLoadingDle" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
Загрузка...
|
||||
</div>
|
||||
<button class="close-btn" @click="goBackToBlocks">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Информация для неавторизованных пользователей -->
|
||||
<div v-if="!props.isAuthenticated" class="auth-notice">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
|
||||
@@ -19,18 +19,13 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="dle-blocks-management">
|
||||
<!-- Заголовок -->
|
||||
<div class="management-header">
|
||||
<div class="header-content">
|
||||
<h1>Управление DLE</h1>
|
||||
<p v-if="dleAddress" class="dle-address">
|
||||
<strong>DLE:</strong> {{ dleAddress }}
|
||||
</p>
|
||||
<!-- Блоки управления -->
|
||||
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div v-if="dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
{{ dleAddress }}
|
||||
</div>
|
||||
<button class="close-btn" @click="router.push('/management')">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Блоки управления -->
|
||||
<div class="management-blocks">
|
||||
<!-- Столбец 1 -->
|
||||
<div class="blocks-column">
|
||||
|
||||
@@ -19,11 +19,6 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="dle-management">
|
||||
<div class="management-header">
|
||||
<h3>🏢 Управление DLE</h3>
|
||||
<p>Добавление DLE контрактов администраторами</p>
|
||||
</div>
|
||||
|
||||
<!-- Форма добавления DLE -->
|
||||
<div class="add-dle-form">
|
||||
<div class="form-header">
|
||||
|
||||
@@ -29,11 +29,9 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="proposals-page">
|
||||
<!-- Заголовок -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>Предложения DLE</h1>
|
||||
<p v-if="dleAddress">Адрес DLE: {{ dleAddress }}</p>
|
||||
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div v-if="dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
{{ dleAddress }}
|
||||
</div>
|
||||
<button @click="goBack" class="close-btn">×</button>
|
||||
</div>
|
||||
|
||||
@@ -19,18 +19,16 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="history-container">
|
||||
<!-- Заголовок -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>История DLE</h1>
|
||||
<p v-if="selectedDle">{{ selectedDle.name }} ({{ selectedDle.symbol }}) - {{ selectedDle.dleAddress }}</p>
|
||||
<p v-else-if="isLoadingDle">Загрузка...</p>
|
||||
<p v-else>Лог операций, события и транзакции DLE</p>
|
||||
<!-- Фильтры -->
|
||||
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div v-if="selectedDle?.dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
{{ selectedDle.dleAddress }}
|
||||
</div>
|
||||
<div v-else-if="isLoadingDle" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
Загрузка...
|
||||
</div>
|
||||
<button class="close-btn" @click="goBackToBlocks">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Фильтры -->
|
||||
<div class="filters-section">
|
||||
<h2>Фильтры</h2>
|
||||
<div class="filters-form">
|
||||
|
||||
@@ -19,24 +19,22 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="modules-management">
|
||||
<!-- Заголовок -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="title-section">
|
||||
<h1>Модули DLE</h1>
|
||||
<div class="websocket-status" :class="{ connected: isWSConnected }" title="WebSocket соединение для обновления модулей">
|
||||
<i class="fas fa-circle" :class="isWSConnected ? 'fa-solid' : 'fa-light'"></i>
|
||||
<span>{{ isWSConnected ? 'Подключено' : 'Отключено' }}</span>
|
||||
</div>
|
||||
<!-- Модальное окно деплоя -->
|
||||
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div style="display: flex; align-items: center; gap: 20px;">
|
||||
<div v-if="selectedDle?.dleAddress" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
{{ selectedDle.dleAddress }}
|
||||
</div>
|
||||
<div v-else-if="isLoadingDle" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
Загрузка...
|
||||
</div>
|
||||
<div class="websocket-status" :class="{ connected: isWSConnected }" title="WebSocket соединение для обновления модулей">
|
||||
<i class="fas fa-circle" :class="isWSConnected ? 'fa-solid' : 'fa-light'"></i>
|
||||
<span>{{ isWSConnected ? 'Подключено' : 'Отключено' }}</span>
|
||||
</div>
|
||||
<p v-if="selectedDle">{{ selectedDle.name }} ({{ selectedDle.symbol }}) - {{ selectedDle.dleAddress }}</p>
|
||||
<p v-else-if="isLoadingDle">Загрузка...</p>
|
||||
<p v-else>DLE не выбран</p>
|
||||
</div>
|
||||
<button class="close-btn" @click="goBackToBlocks">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Модальное окно деплоя -->
|
||||
<div v-if="showDeploymentModal" class="modal-overlay" @click="moduleDeploymentStatus === 'error' || !isDeploying ? closeDeploymentModal() : null">
|
||||
<div class="modal-content" @click.stop>
|
||||
<div class="modal-header">
|
||||
@@ -171,11 +169,6 @@
|
||||
|
||||
<!-- Блоки для деплоя стандартных модулей -->
|
||||
<div class="standard-modules">
|
||||
<div class="modules-header">
|
||||
<h3>🚀 Деплой стандартных модулей</h3>
|
||||
<p>Быстрый деплой предустановленных модулей DLE</p>
|
||||
</div>
|
||||
|
||||
<div class="modules-grid">
|
||||
<!-- TreasuryModule -->
|
||||
<div class="module-deploy-card">
|
||||
|
||||
@@ -19,18 +19,19 @@
|
||||
@auth-action-completed="$emit('auth-action-completed')"
|
||||
>
|
||||
<div class="settings-container">
|
||||
<!-- Заголовок -->
|
||||
<div class="page-header">
|
||||
<div class="header-content">
|
||||
<h1>Настройки DLE</h1>
|
||||
<p v-if="dleInfo">{{ dleInfo.name }} ({{ dleInfo.symbol }}) - {{ dleInfo.address }}</p>
|
||||
<p v-else-if="address">Загрузка...</p>
|
||||
<p v-else>DLE не выбран</p>
|
||||
<!-- Основной контент -->
|
||||
<div style="margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div v-if="dleInfo?.address" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
{{ dleInfo.address }}
|
||||
</div>
|
||||
<div v-else-if="address" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
{{ address }}
|
||||
</div>
|
||||
<div v-else-if="isLoading" style="color: var(--color-grey-dark); font-size: 0.9rem;">
|
||||
Загрузка...
|
||||
</div>
|
||||
<button class="close-btn" @click="goBackToBlocks">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Основной контент -->
|
||||
<div v-if="dleInfo" class="main-content">
|
||||
<!-- Отображение в футере -->
|
||||
<div v-if="canSetFooterDle" class="footer-card">
|
||||
|
||||
@@ -27,7 +27,14 @@ export default defineConfig({
|
||||
build: {
|
||||
rollupOptions: {
|
||||
plugins: [polyfillNode()],
|
||||
output: {
|
||||
manualChunks: undefined,
|
||||
chunkFileNames: 'assets/[name]-[hash].js',
|
||||
entryFileNames: 'assets/[name]-[hash].js',
|
||||
assetFileNames: 'assets/[name]-[hash].[ext]',
|
||||
},
|
||||
},
|
||||
chunkSizeWarningLimit: 1000,
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
|
||||
Reference in New Issue
Block a user