4b0d56ff0e
Этап 1 миграции TestingWebApp на целевой стек (Python/Flask/Jinja),
БД остаётся clinic_tests.
E1.0 — База Flask-приложения: SQLAlchemy/psycopg2 пул, Flask sessions,
фабрика create_app, blueprint main с / и /health, base.html в стиле
кабинета HR (Tailwind CDN + Manrope + Material Symbols), 404/500.
E1.1 — Auth + /api/me: Flask sessions (signed cookie) вместо JWT,
bcrypt + Werkzeug, опц. HR_AUTH=1 с UPSERT в clinic_tests.users по
staff_id. UI /login, JSON /api/auth/{login,logout,me}, декораторы
@login_required / @require_role.
E1.2 — Тесты: список + редактор. 10 эндпоинтов, сервисы test_draft,
test_access, test_chain, ai_editor, llm_client, draft_validator,
editor_content. UI /tests (каталог + создание) и /tests/<id>/edit
(редактор с AI). Полный мобильный UX (аккордеоны/drag-n-drop) — в E1.7.
E1.3 — Импорт документов: pypdf + python-docx, эндпоинт
POST /api/tests/import/document, кнопка «Импорт документа» в
AI-панели редактора, лимит 16 МБ.
E1.8 — AI v2: страница /settings (статус ENV-ключа + ping),
ai/generate-by-title (без сетки), ai/check (рецензия), ai/improve
(массовое было→стало с чекбоксами). Унифицированный ответ AI-ошибок:
{ error, code, settingsUrl }.
Docker:
- docker-compose.dev.yml: добавлены DATABASE_URL, HR_AUTH/HR_DATABASE_URL,
DEEPSEEK_API_KEY/OPENAI_API_KEY/LLM_BASE_URL/LLM_MODEL и сеть postgres
для testing-flask.
Документация:
- docs/migration-final.md — двух-этапный план (Этап 1: унификация
стека внутри TestingWebApp; Этап 2: слияние с tgFlaskForm).
- docs/migration-final-inventory.md — карта 22 эндпоинтов Express.
Made-with: Cursor
112 lines
4.2 KiB
HTML
112 lines
4.2 KiB
HTML
<!doctype html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
<title>{% block title %}Тестирование персонала{% endblock %}</title>
|
|
|
|
{# Tailwind CDN — на E1.0 этого достаточно. В Этапе 2/CI можно заменить на сборку. #}
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script>
|
|
// Минимальная палитра в стиле кабинета HR. Без зависимостей от HR-репо.
|
|
tailwind.config = {
|
|
theme: {
|
|
extend: {
|
|
fontFamily: {
|
|
sans: ['Manrope', 'Inter', 'system-ui', 'sans-serif'],
|
|
},
|
|
colors: {
|
|
brand: {
|
|
50: '#eef2ff',
|
|
100: '#e0e7ff',
|
|
500: '#6366f1',
|
|
600: '#4f46e5',
|
|
700: '#4338ca',
|
|
},
|
|
ink: {
|
|
900: '#0f172a',
|
|
700: '#334155',
|
|
500: '#64748b',
|
|
300: '#cbd5e1',
|
|
100: '#f1f5f9',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<link
|
|
href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&display=swap"
|
|
rel="stylesheet"
|
|
/>
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined"
|
|
/>
|
|
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}" />
|
|
{% block head %}{% endblock %}
|
|
</head>
|
|
<body class="min-h-screen bg-ink-100 text-ink-900 font-sans antialiased">
|
|
<header class="sticky top-0 z-30 bg-white/90 backdrop-blur border-b border-ink-300/60">
|
|
<div class="mx-auto max-w-6xl px-4 h-14 flex items-center justify-between">
|
|
<a href="{{ url_for('main.index') }}" class="flex items-center gap-2 font-semibold text-ink-900">
|
|
<span class="material-symbols-outlined text-brand-600">quiz</span>
|
|
<span>Тестирование</span>
|
|
</a>
|
|
<nav class="flex items-center gap-2 text-sm">
|
|
{% if current_user %}
|
|
<a href="{{ url_for('tests.tests_list_page') }}"
|
|
class="hidden sm:inline-flex items-center gap-1 px-2 py-1 rounded-lg
|
|
text-ink-700 hover:bg-ink-100">
|
|
<span class="material-symbols-outlined text-base">list_alt</span>
|
|
Тесты
|
|
</a>
|
|
<a href="{{ url_for('settings.settings_page') }}"
|
|
class="hidden sm:inline-flex items-center gap-1 px-2 py-1 rounded-lg
|
|
text-ink-700 hover:bg-ink-100">
|
|
<span class="material-symbols-outlined text-base">settings</span>
|
|
Настройки
|
|
</a>
|
|
{% endif %}
|
|
{% if current_user %}
|
|
<span class="hidden sm:inline text-ink-500">
|
|
{{ current_user.full_name or current_user.login }}
|
|
<span class="text-ink-300">·</span>
|
|
<span class="text-brand-700">{{ current_user.role }}</span>
|
|
</span>
|
|
<form method="post" action="{{ url_for('auth.logout') }}" class="inline">
|
|
<button type="submit"
|
|
class="inline-flex items-center gap-1 px-2 py-1 rounded-lg
|
|
text-ink-700 hover:bg-ink-100 transition">
|
|
<span class="material-symbols-outlined text-base">logout</span>
|
|
<span class="hidden sm:inline">Выйти</span>
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<a href="{{ url_for('auth.login_page') }}"
|
|
class="inline-flex items-center gap-1 px-2 py-1 rounded-lg
|
|
text-brand-700 hover:bg-brand-50 transition">
|
|
<span class="material-symbols-outlined text-base">login</span>
|
|
Войти
|
|
</a>
|
|
{% endif %}
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="mx-auto max-w-6xl px-4 py-6">
|
|
{% block content %}{% endblock %}
|
|
</main>
|
|
|
|
<footer class="mx-auto max-w-6xl px-4 py-8 text-xs text-ink-500">
|
|
{% block footer %}testing-flask-app · Этап 1{% endblock %}
|
|
</footer>
|
|
|
|
{% block scripts %}{% endblock %}
|
|
</body>
|
|
</html>
|