Files
ayurishchevandClaude Opus 5.5 6b5c521461 Выпадающие списки в стиле меню действий
Все выпадающие списки (фильтры «Устройства», «Бэкапы», «Журнал», поле
«Группа» в окне устройства) оформлены как меню «⋯» (docs/changes/020):
- app.js строит меню поверх скрытого select: значение уходит с формой,
  без JavaScript работает стандартный список; выбор отправляет change —
  существующие onchange/hx-trigger не менялись;
- позиционирование — существующий placeMenu, выбранный пункт отмечен «✓»,
  клавиатура: Tab, Enter/Space, ↑/↓, Escape;
- Escape при открытом списке в окне закрывает только список.

Тесты: 24 из 24. Автотестов JS нет; ручная проверка UI пользователем на
момент коммита не подтверждена.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:18:32 +03:00

54 lines
5.0 KiB
HTML
Raw Permalink 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.
{# Форма устройства: одна разметка для окна (in_modal) и запасной страницы. v — значения полей. #}
{% import "_ui.html" as ui %}
{% set edit = device is not none %}
{% set action = ('/devices/' ~ device.id ~ '/edit') if edit else '/devices/new' %}
<form method="post" action="{{ action }}" {% if in_modal %}hx-post="{{ action }}" hx-target="#modal-body"{% endif %}>
<header class="modal-head">
<h2 id="modal-title">{{ "Изменить устройство" if edit else "Новое устройство" }}</h2>
{% if in_modal %}<button type="button" class="btn ghost-n icon" data-close aria-label="Закрыть">{{ ui.icon("x") }}</button>{% endif %}
</header>
<div class="modal-body">
{% if error %}<div class="form-err" role="alert">{{ error }}</div>{% endif %}
<label class="lbl"><span>Название <small>{% if edit %}задаётся при создании и не меняется — входит в ключи бэкапов{% else %}латиница, цифры, «.», «_», «-»; попадает в ключи бэкапов{% endif %}</small></span>
{% if edit %}<input class="input" value="{{ v.name }}" readonly aria-readonly="true" tabindex="-1">
{% else %}<input class="input" name="name" value="{{ v.name }}" maxlength="64" required autofocus>{% endif %}</label>
<label class="lbl"><span>Примечание <small>необязательно</small></span>
<textarea class="input" name="note" rows="3" maxlength="500" placeholder="Например: серверная, 2 этаж; ответственный — дежурный инженер">{{ v.note }}</textarea></label>
<div class="form-row host">
<label class="lbl">Адрес<input class="input" name="host" value="{{ v.host }}" required></label>
<label class="lbl">Порт<input class="input" name="port" type="number" min="1" max="65535" value="{{ v.port }}" required></label>
</div>
<div class="form-row">
<label class="lbl">Пользователь<input class="input" name="username" value="{{ v.username }}" required></label>
<label class="lbl"><span>Пароль {% if edit %}<small>пусто — не менять</small>{% endif %}</span>
<input class="input" name="password" type="password" autocomplete="new-password" {{ "" if edit else "required" }}></label>
</div>
{# div, а не label: поле-меню строит JS рядом со скрытым select — клик по подписи не должен форвардиться на него #}
<div class="lbl"><span id="group-lbl">Группа</span>
<select class="field" name="group_id" data-newgroup aria-labelledby="group-lbl" style="width:100%">
<option value="">— без группы —</option>
{% for g in groups %}<option value="{{ g.id }}" {{ "selected" if v.group_id == g.id }}>{{ g.name }}</option>{% endfor %}
<option value="__new__" {{ "selected" if v.group_id == "__new__" }}>+ Новая группа…</option>
</select>
</div>
<label class="lbl" id="new-group-field" {{ "" if v.group_id == "__new__" else "hidden" }}>Название новой группы
<input class="input" name="new_group" value="{{ v.new_group }}" maxlength="64"></label>
<label class="check"><input name="use_tls" type="checkbox" {{ "checked" if v.use_tls }}>
<span>HTTPS (сервис www-ssl)<small>Порт обычно 443; без HTTPS — сервис www, порт 80</small></span></label>
<div class="note" id="http-note" {{ "hidden" if v.use_tls }}>Без HTTPS логин и пароль передаются по сети открытым текстом — только для доверенной сети.</div>
<label class="check"><input name="verify_tls" type="checkbox" {{ "checked" if v.verify_tls }}>
<span>Проверять TLS-сертификат<small>Отключите для самоподписанных сертификатов</small></span></label>
</div>
<footer class="modal-foot">
{% if edit %}
{% if in_modal %}<button type="button" class="btn danger" hx-post="/devices/{{ device.id }}/delete"
hx-confirm="Удалить устройство {{ device.name }} из списка? Бэкапы в бакете сохранятся.">Удалить устройство</button>
{% else %}<button class="btn danger" formaction="/devices/{{ device.id }}/delete" formnovalidate
onclick="return confirm('Удалить устройство {{ device.name }} из списка? Бэкапы в бакете сохранятся.')">Удалить устройство</button>{% endif %}
{% endif %}
<span class="grow"></span>
{% if in_modal %}<button type="button" class="btn ghost-n" data-close>Отмена</button>{% else %}<a class="btn ghost-n" href="/">Отмена</a>{% endif %}
<button class="btn primary">{{ "Сохранить" if edit else "Добавить устройство" }}</button>
</footer>
</form>