UI rework: modal dialogs, inline lab editing, manual creation, content management
- Dashboard: real stats (groups, students, labs, average grade), labs/groups summary lists, no placeholder tile - Lab page: view + edit combined — metadata form in modal, condition rendered as markdown, per-level variant tables with add/edit/delete dialogs, level rename/delete, student level/variant selectors with group filter - Labs list: create-lab dialog with manual level entry (code; title lines) - Groups: create-group dialog, add-student dialog (auto password, toast card) - Content: public /learn reader is the single place; admin buttons (new article with chapter/section create-or-select, rename/delete chapters and sections, move articles between sections, import zip) appear for admins; removed separate 'Контент' admin section - Buttons use btn classes instead of nav-link - Favicon: generated SVG book icon - Security: CSRF synchronizer token interceptor (replaced spring-security in web which broke logback under nested jars), session regenerated on login, hardened session cookie, gateway actuator limited to health,info - Jackson 3: nullable-safe request DTOs for labs (FAIL_ON_NULL_FOR_PRIMITIVES) - academic: POST /labs (manual create with levels), level add/rename/delete, content chapters/sections CRUD, article create/move/delete endpoints
This commit is contained in:
@@ -3,18 +3,14 @@
|
||||
<head><title>Лабораторные — LMS</title></head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<h1 class="page-title">Лабораторные</h1>
|
||||
<div class="flex items-center justify-between gap-4 mb-6">
|
||||
<h1 class="page-title">Лабораторные</h1>
|
||||
<button class="btn btn-primary" type="button" onclick="openModal('modal-create-lab')">+ Создать</button>
|
||||
</div>
|
||||
<div th:if="${notice}" class="alert alert-success" th:text="${notice}"></div>
|
||||
<div th:if="${error}" class="alert alert-error" th:text="${error}"></div>
|
||||
<div class="grid grid-2">
|
||||
<form class="card form-card form-stack" th:action="@{/admin/labs/import}" method="post" enctype="multipart/form-data">
|
||||
<h2 class="section-title">Импорт лабораторной</h2>
|
||||
<p class="text-sm text-muted">Принимаются .zip (lab.yaml + README.md + variants/*.md) или .json.</p>
|
||||
<div><label class="label" for="labFile">Файл</label><input class="input" id="labFile" name="file" type="file" accept=".zip,.json" required></div>
|
||||
<button class="btn btn-primary" type="submit">Импортировать</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card card-flush mt-8">
|
||||
|
||||
<div class="card card-flush">
|
||||
<table>
|
||||
<thead><tr><th>№</th><th>Название</th><th>Вариантов</th><th>Статус</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
@@ -23,12 +19,49 @@
|
||||
<td><a th:href="@{/admin/labs/{id}(id=${lab.id})}" th:text="${lab.title}">—</a></td>
|
||||
<td th:text="${lab.variantCount}">0</td>
|
||||
<td><span class="badge" th:classappend="${lab.status == 'published'} ? 'badge-ok' : 'badge-warn'" th:text="${lab.status}">—</span></td>
|
||||
<td class="text-center"><form th:action="@{/admin/labs/{id}/delete(id=${lab.id})}" method="post"><button class="nav-link text-sm" type="submit">Удалить</button></form></td>
|
||||
<td class="text-center"><form th:action="@{/admin/labs/{id}/delete(id=${lab.id})}" method="post"><input type="hidden" name="_csrf" th:value="${csrfToken}"><button class="btn btn-danger btn-sm" type="submit">Удалить</button></form></td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(labs)}"><td colspan="5" class="empty">Лабораторных пока нет.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dialog id="modal-create-lab" class="modal">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title">Новая лабораторная</h2>
|
||||
<button class="modal-close" type="button" onclick="closeModal('modal-create-lab')">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-stack" th:action="@{/admin/labs/create}" method="post">
|
||||
<div class="grid grid-2">
|
||||
<div><label class="label" for="labNumber">Номер</label>
|
||||
<input class="input" id="labNumber" name="number" type="number" min="1" required></div>
|
||||
<div><label class="label" for="labPoints">Максимум баллов</label>
|
||||
<input class="input" id="labPoints" name="pointsMax" type="number" step="0.1" min="0" value="5" required></div>
|
||||
</div>
|
||||
<div><label class="label" for="labTitle">Название</label>
|
||||
<input class="input" id="labTitle" name="title" required></div>
|
||||
<div class="grid grid-2">
|
||||
<div><label class="label" for="labDeadline">Срок сдачи</label>
|
||||
<input class="input" id="labDeadline" name="deadlineAt" type="datetime-local"></div>
|
||||
<div><label class="label" for="labDefense">Дата защиты</label>
|
||||
<input class="input" id="labDefense" name="defenseAt" type="datetime-local"></div>
|
||||
</div>
|
||||
<div><label class="label" for="labStatus">Статус</label>
|
||||
<select class="input" id="labStatus" name="status">
|
||||
<option value="draft">draft</option>
|
||||
<option value="published">published</option>
|
||||
</select></div>
|
||||
<div><label class="label" for="labLevels">Уровни (по одному на строку: код; название)</label>
|
||||
<textarea class="input" id="labLevels" name="levels" rows="4" placeholder="basic; Основная группа advanced; Повышенная сложность"></textarea>
|
||||
<p class="hint">Если оставить пустым, будет создан уровень «Основная группа». Варианты добавите на странице лабы.</p></div>
|
||||
<div><label class="label" for="labDesc">Условие (markdown, можно позже)</label>
|
||||
<textarea class="input" id="labDesc" name="descriptionMd" rows="6"></textarea></div>
|
||||
<button class="btn btn-primary" type="submit">Создать</button>
|
||||
<input type="hidden" name="_csrf" th:value="${csrfToken}">
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user