Files
wiki/tasks/apps-portal/docs/TZ.md

185 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ТЗ: Портал приложений (apps.mva154.duckdns.org)
## Общее описание
Лендинг-портал с карточками веб-приложений. Светлая тема, автогенерация аватарок, JSON-конфиг.
**URL:** `apps.mva154.duckdns.org`
**Стек:** Flask (порт 5560) + HTML/CSS/JS (один файл) + Pillow (аватарки)
**Бизнес-требования:** `docs/BRD.md`
---
## Файлы
```
tasks/apps-portal/
├── docs/
│ └── BRD.md — бизнес-требования
├── config/
│ └── apps.json — конфиг приложений
├── static/
│ └── avatars/ — сгенерированные аватарки (PNG 200×200)
├── templates/
│ └── index.html — главная страница
├── server.py — Flask сервер
└── requirements.txt — flask, pillow
```
---
## Конфиг: config/apps.json
```json
[
{
"id": "noisemap",
"name": "Карта шума",
"description": "Карта шумового загрязнения от авиации",
"icon": "🛩️",
"url": "https://openclaw.mva154.duckdns.org/noisemap/",
"enabled": true,
"order": 1
},
{
"id": "snowbike-rag",
"name": "Snowbike Поиск",
"description": "Семантический поиск по 155K сообщений сноубайков",
"icon": "🏔️",
"url": "https://openclaw.mva154.duckdns.org/snowbike-rag/",
"enabled": true,
"order": 2
}
]
```
---
## Аватарки: автогенерация
При старте Flask проверяет `static/avatars/`. Для каждого приложения из конфига без файла аватарки — генерирует PNG 200×200.
**Алгоритм:**
1. Хэш от `name` → два цвета для градиента
2. Градиентный фон (линейный, 135°)
3. По центру — emoji из поля `icon` (масштабируется через Pillow, если поддерживается) или первая буква `name`
4. Сохранить в `static/avatars/{id}.png`
**Примеры градиентов:**
- «Карта шума» → синий → тёмно-синий
- «Snowbike Поиск» → зелёный → тёмно-зелёный
- Разные названия → разные цвета (детерминированно)
---
## server.py
```python
from flask import Flask, render_template, send_from_directory
import json
from pathlib import Path
app = Flask(__name__)
CONFIG_FILE = Path(__file__).parent / 'config' / 'apps.json'
def load_apps():
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
apps = json.load(f)
return sorted([a for a in apps if a.get('enabled', True)], key=lambda x: x.get('order', 99))
@app.route('/')
def index():
apps = load_apps()
return render_template('index.html', apps=apps)
@app.route('/api/apps')
def api_apps():
return load_apps()
@app.route('/static/avatars/<path:filename>')
def avatar(filename):
return send_from_directory('static/avatars', filename)
if __name__ == '__main__':
generate_avatars() # автогенерация при старте
app.run(host='0.0.0.0', port=5560)
```
---
## index.html
### Структура
```
┌──────────────────────────────────────┐
│ Заголовок: «Мои приложения» │
│ Подзаголовок: «N активных» │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ аватар │ │ аватар │ │ аватар │ │
│ │ Название│ │ Название│ │ Название│ │
│ │ Опис. │ │ Опис. │ │ Опис. │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└──────────────────────────────────────┘
```
### Дизайн
• Светлая тема: фон #F8FAFC, карточки #FFFFFF
• Шрифт: Inter (Google Fonts CDN)
• Tailwind CSS через CDN
• Карточка: 80×80 аватарка по центру, название, описание
• Hover: border синий + lift-тень + scale 1.02
• Адаптивно: 4 → 2 → 1 колонка
### Зависимости (CDN)
```html
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
```
---
## Nginx (добавить в основной конфиг)
```nginx
server {
server_name apps.mva154.duckdns.org;
location / {
proxy_pass http://172.19.0.2:5560/;
proxy_http_version 1.1;
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;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/mva154.duckdns.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mva154.duckdns.org/privkey.pem;
}
```
---
## Критерии приёмки
- [ ] `http://localhost:5560/` — портал с карточками
- [ ] Клик по карточке — переход на URL приложения
- [ ] Аватарки сгенерированы в `static/avatars/`
- [ ] Светлая тема, шрифт Inter
- [ ] Адаптивно на мобильном
- [ ] Добавил приложение в `apps.json` — портал показывает его с аватаркой
- [ ] GET `/api/apps` — JSON-список приложений
---
## Важно
• Всё в `tasks/apps-portal/`
• HTML — один файл (inline CSS + JS)
• Аватарки — Pillow, без внешних API
• Порт 5560 (не пересекается с 5555, 5556, 5557)