remove UI split (legacy/modern)

This commit is contained in:
Константин Лебединский
2026-04-30 21:53:49 +05:00
parent 1494b839f5
commit 0229bc250b
16 changed files with 107 additions and 384 deletions
+42 -104
View File
@@ -3,117 +3,55 @@
{% block content %}
{% set fio_as_login = dev_fio_enabled or hr_auth_enabled %}
{% if ui_variant == 'legacy' %}
<div class="login-page">
<div class="login-shell">
<div class="login-logo">
<img src="{{ url_for('static', filename='img/clinic-logo.png') }}"
alt="Логотип клиники" class="login-logo__img" />
<h1 class="font-headline">Тестирование</h1>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="callout callout--error" style="margin-bottom: 1rem;">
{% for category, msg in messages %}
{% if category == 'error' %}{{ msg }}{% endif %}
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="login-card">
{% if dev_fio_enabled %}
<p style="font-size:0.8rem; color:#4b7b78; margin-bottom:0.75rem; line-height:1.4;">
Введите <b>ФИО</b> из кадровой системы и общий dev-пароль — или обычный логин/пароль.
</p>
{% elif hr_auth_enabled %}
<p style="font-size:0.8rem; color:#4b7b78; margin-bottom:0.75rem; line-height:1.4;">
Можно ввести <b>логин</b> из HR или <b>ФИО</b> (как в кадровой системе), если совпадение одно, и пароль учётной записи HR.
</p>
{% endif %}
<form method="post" action="{{ url_for('auth.login_submit') }}" novalidate>
<input type="hidden" name="next" value="{{ next or '/' }}">
<div class="form-field">
<label class="form-label" for="login-username">
{% if fio_as_login %}ФИО или логин{% else %}Логин{% endif %}
</label>
<input id="login-username" class="form-input" type="text" name="login"
value="{{ login or '' }}" required autofocus autocomplete="username"
placeholder="{% if fio_as_login %}Иванов Иван Иванович{% endif %}" />
</div>
<div class="form-field">
<label class="form-label" for="login-password">Пароль</label>
<input id="login-password" class="form-input" type="password" name="password"
required autocomplete="current-password" />
</div>
<button type="submit" class="btn btn-primary">Войти</button>
</form>
</div>
<div class="login-page">
<div class="login-shell">
<div class="login-logo">
<img src="{{ url_for('static', filename='img/clinic-logo.png') }}"
alt="Логотип клиники" class="login-logo__img" />
<h1 class="font-headline">Тестирование</h1>
</div>
</div>
{% else %}
<section class="mx-auto max-w-md mt-8">
<div class="rounded-2xl bg-white shadow-sm border border-ink-300/60 p-6">
<div class="flex items-center gap-2">
<span class="material-symbols-outlined text-brand-600">login</span>
<h1 class="text-xl font-semibold">Вход в систему</h1>
</div>
<p class="mt-1 text-sm text-ink-500">
{% if dev_fio_enabled %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="callout callout--error" style="margin-bottom: 1rem;">
{% for category, msg in messages %}
{% if category == 'error' %}{{ msg }}{% endif %}
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="login-card">
{% if dev_fio_enabled %}
<p style="font-size:0.8rem; color:#4b7b78; margin-bottom:0.75rem; line-height:1.4;">
Введите <b>ФИО</b> из кадровой системы и общий dev-пароль — или обычный логин/пароль.
{% elif hr_auth_enabled %}
Учётная запись HR: можно ввести <b>логин</b> или <b>ФИО</b> (если в базе только один такой сотрудник), и пароль.
{% else %}
Используйте логин и пароль.
{% endif %}
</p>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="mt-4 space-y-2">
{% for category, msg in messages %}
<div class="px-3 py-2 rounded-lg text-sm
{% if category == 'error' %}bg-red-50 text-red-700 border border-red-200
{% else %}bg-brand-50 text-brand-700 border border-brand-100{% endif %}">
{{ msg }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<form method="post" action="{{ url_for('auth.login_submit') }}" class="mt-5 space-y-4" novalidate>
</p>
{% elif hr_auth_enabled %}
<p style="font-size:0.8rem; color:#4b7b78; margin-bottom:0.75rem; line-height:1.4;">
Можно ввести <b>логин</b> из HR или <b>ФИО</b> (как в кадровой системе), если совпадение одно, и пароль учётной записи HR.
</p>
{% endif %}
<form method="post" action="{{ url_for('auth.login_submit') }}" novalidate>
<input type="hidden" name="next" value="{{ next or '/' }}">
<label class="block">
<span class="text-sm font-medium text-ink-700">
<div class="form-field">
<label class="form-label" for="login-username">
{% if fio_as_login %}ФИО или логин{% else %}Логин{% endif %}
</span>
<input type="text" name="login" value="{{ login or '' }}" required autofocus autocomplete="username"
placeholder="{% if fio_as_login %}Иванов Иван Иванович{% endif %}"
class="mt-1 w-full rounded-lg border border-ink-300 bg-white px-3 py-2 text-ink-900
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20" />
</label>
</label>
<input id="login-username" class="form-input" type="text" name="login"
value="{{ login or '' }}" required autofocus autocomplete="username"
placeholder="{% if fio_as_login %}Иванов Иван Иванович{% endif %}" />
</div>
<label class="block">
<span class="text-sm font-medium text-ink-700">Пароль</span>
<input type="password" name="password" required autocomplete="current-password"
class="mt-1 w-full rounded-lg border border-ink-300 bg-white px-3 py-2 text-ink-900
focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20" />
</label>
<div class="form-field">
<label class="form-label" for="login-password">Пароль</label>
<input id="login-password" class="form-input" type="password" name="password"
required autocomplete="current-password" />
</div>
<button type="submit"
class="w-full inline-flex items-center justify-center gap-2 rounded-lg
bg-brand-600 hover:bg-brand-700 text-white font-medium px-4 py-2 transition">
<span class="material-symbols-outlined text-base">login</span>
Войти
</button>
<button type="submit" class="btn btn-primary">Войти</button>
</form>
</div>
</section>
{% endif %}
</div>
</div>
{% endblock %}
+19 -79
View File
@@ -51,96 +51,36 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}" />
{% block head %}{% endblock %}
</head>
<body data-ui-variant="{{ ui_variant }}"
class="min-h-screen bg-ink-100 text-ink-900 font-sans antialiased ui-{{ ui_variant }}">
{% if ui_variant == 'legacy' %}
<div class="cabinet-app">
<header class="cabinet-header">
<div class="cabinet-header__inner">
<a href="{{ url_for('tests.tests_list_page') }}" class="cabinet-brand">
<img src="{{ url_for('static', filename='img/clinic-logo.png') }}"
alt="Логотип клиники" class="cabinet-brand__logo" />
<div>
<div class="cabinet-brand__title">Тестирование</div>
</div>
</a>
<div class="cabinet-header__actions">
{% if current_user %}
<span class="cabinet-user" title="{{ (current_user.full_name or current_user.login) ~ (' · ' ~ format_role(current_user.role) if format_role(current_user.role) else '') }}">
{{ format_name_short(current_user.full_name, current_user.login) }}
{% if format_role(current_user.role) %}<span class="cabinet-user__role"> · {{ format_role(current_user.role) }}</span>{% endif %}
</span>
<form method="post" action="{{ url_for('auth.logout') }}" class="inline">
<button type="submit" class="btn btn-ghost">Выйти</button>
</form>
{% else %}
<a href="{{ url_for('auth.login_page') }}" class="btn btn-ghost">Войти</a>
{% endif %}
</div>
</div>
</header>
<main class="cabinet-main">
{% block content scoped %}{% endblock %}
</main>
</div>
{% else %}
<header class="sticky top-0 z-30 bg-white/90 backdrop-blur border-b border-ink-300/50">
<div class="mx-auto max-w-2xl 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">
<body class="min-h-screen bg-ink-100 text-ink-900 font-sans antialiased ui-legacy">
<div class="cabinet-app">
<header class="cabinet-header">
<div class="cabinet-header__inner">
<a href="{{ url_for('tests.tests_list_page') }}" class="cabinet-brand">
<img src="{{ url_for('static', filename='img/clinic-logo.png') }}"
alt="Логотип клиники" class="h-7 w-7 object-contain" />
<span>Тестирование</span>
alt="Логотип клиники" class="cabinet-brand__logo" />
<div>
<div class="cabinet-brand__title">Тестирование</div>
</div>
</a>
<nav class="flex items-center gap-1 sm:gap-2 text-sm">
<div class="cabinet-header__actions">
{% if current_user %}
<a href="{{ url_for('tests.tests_list_page') }}"
class="inline-flex items-center justify-center gap-1
min-w-10 min-h-10 px-2 sm:px-3 rounded-lg
text-ink-700 hover:bg-ink-100"
title="Каталог тестов" aria-label="Каталог тестов">
<span class="material-symbols-outlined text-base">list_alt</span>
<span class="hidden sm:inline">Тесты</span>
</a>
<a href="{{ url_for('settings.settings_page') }}"
class="inline-flex items-center justify-center gap-1
min-w-10 min-h-10 px-2 sm:px-3 rounded-lg
text-ink-700 hover:bg-ink-100"
title="Настройки" aria-label="Настройки">
<span class="material-symbols-outlined text-base">settings</span>
</a>
<span class="hidden md:inline text-ink-500">
{{ current_user.full_name or current_user.login }}
<span class="text-ink-300">·</span>
<span class="text-brand-700">{{ format_role(current_user.role) }}</span>
<span class="cabinet-user" title="{{ (current_user.full_name or current_user.login) ~ (' · ' ~ format_role(current_user.role) if format_role(current_user.role) else '') }}">
{{ format_name_short(current_user.full_name, current_user.login) }}
{% if format_role(current_user.role) %}<span class="cabinet-user__role"> · {{ format_role(current_user.role) }}</span>{% endif %}
</span>
<form method="post" action="{{ url_for('auth.logout') }}" class="inline">
<button type="submit"
class="inline-flex items-center justify-center gap-1
min-w-10 min-h-10 px-2 sm:px-3 rounded-lg
text-ink-700 hover:bg-ink-100 transition"
title="Выйти" aria-label="Выйти">
<span class="material-symbols-outlined text-base">logout</span>
<span class="hidden sm:inline">Выйти</span>
</button>
<button type="submit" class="btn btn-ghost">Выйти</button>
</form>
{% else %}
<a href="{{ url_for('auth.login_page') }}"
class="inline-flex items-center gap-1 px-3 py-2 rounded-lg
text-brand-700 hover:bg-brand-50 transition min-h-10">
<span class="material-symbols-outlined text-base">login</span>
Войти
</a>
<a href="{{ url_for('auth.login_page') }}" class="btn btn-ghost">Войти</a>
{% endif %}
</nav>
</div>
</div>
</header>
<main class="mx-auto max-w-2xl px-4 py-6">
{{ self.content() }}
<main class="cabinet-main">
{% block content scoped %}{% endblock %}
</main>
<footer class="mx-auto max-w-2xl px-4 py-8 text-xs text-ink-500">
{% block footer %}testing-flask-app · Этап 1{% endblock %}
</footer>
{% endif %}
</div>
{% block scripts %}{% endblock %}
</body>
+3 -3
View File
@@ -2,13 +2,13 @@
{% block title %}Настройки — LLM{% endblock %}
{% block content %}
<section class="{% if ui_variant == 'legacy' %}surface-card{% else %}rounded-2xl bg-white shadow-sm border border-ink-300/60{% endif %} p-6 max-w-2xl">
<section class="surface-card p-6 max-w-2xl">
<div class="flex items-center gap-2">
<span class="material-symbols-outlined text-brand-600">settings</span>
<h1 class="text-2xl font-semibold">Настройки</h1>
</div>
<h2 class="mt-5 font-semibold {% if ui_variant == 'legacy' %}font-headline{% endif %}">Подключение к LLM</h2>
<h2 class="mt-5 font-semibold font-headline">Подключение к LLM</h2>
<p class="mt-1 text-sm text-ink-500">
Ключ задаётся в <code class="px-1 py-0.5 rounded bg-ink-100">.env</code> сервера
(общий, не на пользователя). Поддерживаются DeepSeek и OpenAI-совместимые API.
@@ -53,7 +53,7 @@ OPENAI_API_KEY=sk-...
<div class="mt-5 flex items-center gap-3">
<button id="btn-ping"
class="{% if ui_variant == 'legacy' %}btn btn-primary{% else %}inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-brand-600 hover:bg-brand-700 text-white text-sm{% endif %}">
class="btn btn-primary">
<span class="material-symbols-outlined text-base">cable</span>
Проверить подключение
</button>
+3 -3
View File
@@ -3,7 +3,7 @@
{% block content %}
<div id="editor-root"
class="space-y-4 sm:space-y-5 pb-24 {% if ui_variant == 'legacy' %}test-detail-page test-detail-page--with-fixed-actions{% endif %}"
class="space-y-4 sm:space-y-5 pb-24 test-detail-page test-detail-page--with-fixed-actions"
data-test-id="{{ test_id }}"
data-initial='{{ content | tojson | safe }}'>
@@ -277,7 +277,7 @@
{# ── Sticky-footer: «Цепочка активна» + «Сохранить» ────────────── #}
<div class="fixed bottom-0 inset-x-0 z-30 bg-white/95 backdrop-blur border-t border-ink-300/60
pb-[env(safe-area-inset-bottom)]">
<div class="mx-auto {% if ui_variant == 'legacy' %}max-w-2xl{% else %}max-w-6xl{% endif %} px-4 py-3
<div class="mx-auto max-w-2xl px-4 py-3
flex items-center justify-between gap-3">
<div id="intro-fork-banner" class="callout callout--warning text-xs sm:text-sm"
data-fork-risk="{{ '1' if content.test.hasForkRisk else '0' }}"
@@ -297,7 +297,7 @@
</button>
</div>
</div>
<p id="save-status" class="mx-auto {% if ui_variant == 'legacy' %}max-w-2xl{% else %}max-w-6xl{% endif %} px-4 pb-2 text-xs text-ink-500"></p>
<p id="save-status" class="mx-auto max-w-2xl px-4 pb-2 text-xs text-ink-500"></p>
</div>
{# ── Шаблон вопроса ─────────────────────────────────────────────── #}
+1 -67
View File
@@ -17,7 +17,6 @@
{%- endmacro %}
{% block content %}
{% if ui_variant == 'legacy' %}
<section class="legacy-list-shell">
<h1 class="font-headline legacy-list-title">Тесты</h1>
<div class="legacy-list-toolbar">
@@ -77,71 +76,6 @@
</ul>
{% endif %}
</section>
{% else %}
<section class="rounded-2xl bg-white shadow-sm border border-ink-300/60 p-4 sm:p-6">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
<div>
<h1 class="text-xl sm:text-2xl font-semibold">Каталог тестов</h1>
<p class="mt-1 text-sm text-ink-500">Все активные тесты.</p>
</div>
<button id="btn-create-test"
class="inline-flex items-center justify-center gap-2 px-4 py-3 rounded-lg
bg-brand-600 hover:bg-brand-700 text-white font-medium transition
min-h-11 w-full sm:w-auto">
<span class="material-symbols-outlined text-base">add</span>
Создать тест
</button>
</div>
{% if visible %}
<ul class="mt-5 grid gap-3 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
{% for t in visible %}
<li class="rounded-xl border border-ink-300/60 hover:border-brand-300 hover:shadow-sm transition bg-white">
<a href="{{ url_for('tests.tests_editor_page', test_id=t.id) }}"
class="block p-4 active:bg-ink-100/40">
<div class="flex items-start justify-between gap-2">
<h3 class="font-semibold text-ink-900 line-clamp-2 min-w-0">{{ t.title }}</h3>
<span class="text-xs text-ink-500 shrink-0 mt-0.5 whitespace-nowrap">Версия {{ t.version }}</span>
</div>
{% if t.description %}
<p class="mt-1 text-sm text-ink-500 line-clamp-3">{{ t.description }}</p>
{% endif %}
<div class="mt-3 flex items-center justify-between gap-2 text-xs text-ink-500">
<span class="truncate">{{ t.author_full_name or '—' }}</span>
<span class="inline-flex items-center gap-1 text-brand-700">
<span class="material-symbols-outlined text-sm">edit_note</span>
Открыть
</span>
</div>
<p class="mt-1.5 text-xs text-ink-500 leading-snug">{{ catalog_test_params_line(t) }}</p>
</a>
</li>
{% endfor %}
</ul>
{% else %}
<p class="mt-5 text-ink-500 text-sm">Доступных тестов пока нет.</p>
{% endif %}
{% if hidden %}
<details class="mt-6 rounded-xl border border-ink-300/60 bg-ink-100/50 p-4">
<summary class="cursor-pointer font-medium text-ink-700">
Скрытые из каталога ({{ hidden|length }})
</summary>
<ul class="mt-3 space-y-2">
{% for t in hidden %}
<li class="flex flex-col gap-1 sm:flex-row sm:items-start sm:justify-between text-sm">
<span class="min-w-0">{{ t.title }} <span class="text-ink-500">· v{{ t.version }}</span>
<span class="block text-xs text-ink-500 mt-0.5 leading-snug">{{ catalog_test_params_line(t) }}</span>
</span>
<a href="{{ url_for('tests.tests_editor_page', test_id=t.id) }}"
class="text-brand-700 hover:underline shrink-0">Открыть</a>
</li>
{% endfor %}
</ul>
</details>
{% endif %}
</section>
{% endif %}
<dialog id="dlg-create"
class="m-0 p-0 w-full sm:w-full sm:max-w-md
@@ -235,7 +169,7 @@ class="m-0 p-0 w-full sm:w-full sm:max-w-md
let data = {};
try { data = await r.json(); } catch (_) {}
if (!r.ok || !data.attempt || !data.attempt.id) {
// В Flask legacy контуре пока может отсутствовать отдельная UI-страница попытки.
// Если нет страницы попытки, уводим в редактор.
// Тогда ведём в карточку теста, чтобы пользователь не попадал на not_found.
window.location.href = `/tests/${testId}/edit`;
return;