431 lines
22 KiB
HTML
431 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>ButtonTask — конфигуратор</title>
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js" defer></script>
|
|
<script src="{{ url_for('static', filename='app.js') }}" defer></script>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<div class="brand">
|
|
<span class="logo">●</span>
|
|
<div>
|
|
<h1>ButtonTask</h1>
|
|
<span class="meta">Веб-конфигуратор · версия {{ version.version }}</span>
|
|
</div>
|
|
</div>
|
|
<a href="{{ url_for('auth.logout') }}" class="logout"><button type="button" class="ghost">Выйти</button></a>
|
|
</header>
|
|
|
|
<nav class="tabs" role="tablist">
|
|
<button class="tab active" data-tab="buttons">Кнопки</button>
|
|
<button class="tab" data-tab="appearance">Внешний вид</button>
|
|
<button class="tab" data-tab="network">Сеть</button>
|
|
<button class="tab" data-tab="system">Система</button>
|
|
<button class="tab" data-tab="update">Обновление</button>
|
|
<button class="tab" data-tab="security">Безопасность</button>
|
|
</nav>
|
|
|
|
<main>
|
|
<!-- ============ Кнопки ============ -->
|
|
<section class="panel active" id="tab-buttons">
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<h2>Кнопки</h2>
|
|
<button id="add-btn" class="primary">+ Добавить</button>
|
|
</div>
|
|
<p class="hint">Перетаскивайте за «≡» для изменения порядка.</p>
|
|
<ul id="buttons-list" class="buttons-list">
|
|
{% for b in config.buttons %}
|
|
<li data-id="{{ b.id }}">
|
|
<span class="handle">≡</span>
|
|
<img class="btn-icon" src="{{ url_for('api.serve_icon', fname=b.iconPath) if b.iconPath else '' }}" alt="">
|
|
<div class="btn-info">
|
|
<strong>{{ b.label }}</strong>
|
|
<small>{{ b.action.type }}: {{ b.action.url }}</small>
|
|
</div>
|
|
<span class="swatch" style="background: {{ b.color or '#7b007b' }}"></span>
|
|
<button class="edit icon-only" data-id="{{ b.id }}" title="Редактировать">✎</button>
|
|
<button class="del icon-only" data-id="{{ b.id }}" title="Удалить">✕</button>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% if not config.buttons %}
|
|
<p class="empty">Кнопок пока нет. Нажмите «Добавить».</p>
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ Внешний вид ============ -->
|
|
<section class="panel" id="tab-appearance">
|
|
<div class="card">
|
|
<h2>Расположение</h2>
|
|
<div class="grid-form">
|
|
<label>Режим
|
|
<select id="layout-mode">
|
|
<option value="grid" {% if config.layout.mode == 'grid' %}selected{% endif %}>Сетка</option>
|
|
<option value="list" {% if config.layout.mode == 'list' %}selected{% endif %}>Список</option>
|
|
</select>
|
|
</label>
|
|
<label>Колонок
|
|
<input type="number" id="layout-columns" min="1" max="8" value="{{ config.layout.columns }}">
|
|
</label>
|
|
<label>Отступ
|
|
<input type="number" id="layout-spacing" min="0" max="40" value="{{ config.layout.spacing }}">
|
|
</label>
|
|
<label class="check">
|
|
<input type="checkbox" id="layout-show-labels" {% if config.layout.showLabels %}checked{% endif %}>
|
|
Показывать подписи
|
|
</label>
|
|
</div>
|
|
<div class="card-actions">
|
|
<button id="save-layout" class="primary">Сохранить расположение</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Фон и обводка</h2>
|
|
|
|
<h3 class="subhead">Фон приложения</h3>
|
|
<div class="grid-form appearance-bg" id="bg-form">
|
|
<label>Тип
|
|
<select id="bg-type">
|
|
<option value="gradient" {% if config.background.type == 'gradient' %}selected{% endif %}>Градиент</option>
|
|
<option value="solid" {% if config.background.type == 'solid' %}selected{% endif %}>Цвет</option>
|
|
<option value="image" {% if config.background.type == 'image' %}selected{% endif %}>Картинка</option>
|
|
</select>
|
|
</label>
|
|
<label class="bg-solid-row bg-gradient-row">Основной цвет
|
|
<input type="color" id="bg-c1" value="{{ config.background.color1 }}">
|
|
</label>
|
|
<label class="bg-gradient-row">Второй цвет
|
|
<input type="color" id="bg-c2" value="{{ config.background.color2 }}">
|
|
</label>
|
|
<label class="check bg-gradient-row">
|
|
<input type="checkbox" id="bg-animated" {% if config.background.animated %}checked{% endif %}>
|
|
Анимация градиента
|
|
</label>
|
|
<label class="bg-image-row">Картинка
|
|
<select id="bg-image">
|
|
<option value="">— нет —</option>
|
|
{% for ic in icons %}
|
|
<option value="{{ ic }}" {% if config.background.imagePath == ic %}selected{% endif %}>{{ ic }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</label>
|
|
</div>
|
|
<div class="card-actions compact">
|
|
<button id="save-bg" class="primary">Сохранить фон</button>
|
|
</div>
|
|
|
|
<hr class="section-divider">
|
|
|
|
<h3 class="subhead">Обводка при нажатии</h3>
|
|
<p class="hint">Цвет и ширина свечения вокруг кнопки при успехе и ошибке.</p>
|
|
<div class="feedback-panel">
|
|
<div class="grid-form feedback-form">
|
|
<label>Цвет «ОК»
|
|
<input type="color" id="fb-ok-color" value="{{ config.feedback.okColor }}">
|
|
</label>
|
|
<label>Ширина «ОК»: <span id="fb-ok-width-val">{{ config.feedback.okWidth }}</span> px
|
|
<input type="range" id="fb-ok-width" min="0" max="30" value="{{ config.feedback.okWidth }}">
|
|
</label>
|
|
<label>Цвет «Ошибка»
|
|
<input type="color" id="fb-error-color" value="{{ config.feedback.errorColor }}">
|
|
</label>
|
|
<label>Ширина «Ошибка»: <span id="fb-error-width-val">{{ config.feedback.errorWidth }}</span> px
|
|
<input type="range" id="fb-error-width" min="0" max="30" value="{{ config.feedback.errorWidth }}">
|
|
</label>
|
|
<label class="span-2">Радиус свечения: <span id="fb-glow-val">{{ config.feedback.glowRadius }}</span> px
|
|
<input type="range" id="fb-glow" min="0" max="60" value="{{ config.feedback.glowRadius }}">
|
|
</label>
|
|
</div>
|
|
<div class="feedback-preview">
|
|
<div class="preview-label">Превью</div>
|
|
<div class="preview-row">
|
|
<div class="preview-dot" id="fb-preview-ok">OK</div>
|
|
<div class="preview-dot" id="fb-preview-error">ERR</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-actions compact">
|
|
<button id="save-feedback" class="primary">Сохранить обводку</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Иконки</h2>
|
|
<div class="row">
|
|
<input type="file" id="icon-file" accept=".png,.jpg,.jpeg,.bmp,.svg,.gif">
|
|
<button id="upload-icon">Загрузить</button>
|
|
</div>
|
|
<div id="icon-grid" class="icon-grid">
|
|
{% for ic in icons %}
|
|
<div class="icon-tile" data-name="{{ ic }}">
|
|
<img src="{{ url_for('api.serve_icon', fname=ic) }}" alt="{{ ic }}">
|
|
<span>{{ ic }}</span>
|
|
<button class="del icon-only" data-name="{{ ic }}">✕</button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ Сеть ============ -->
|
|
<section class="panel" id="tab-network">
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<h2>Ethernet</h2>
|
|
<button id="net-refresh" class="ghost">↻ Обновить</button>
|
|
</div>
|
|
<div id="net-status" class="net-status">Загрузка состояния…</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Настройка интерфейса</h2>
|
|
<p class="warn">Изменение настроек может разорвать текущее соединение и сменить IP-адрес устройства.</p>
|
|
<div class="grid-form">
|
|
<label>Интерфейс
|
|
<select id="net-iface"></select>
|
|
</label>
|
|
<label>Режим
|
|
<select id="net-mode">
|
|
<option value="dhcp">DHCP (автоматически)</option>
|
|
<option value="static">Статический IP</option>
|
|
</select>
|
|
</label>
|
|
<label class="static-only">IP-адрес <input type="text" id="net-address" placeholder="192.168.1.50"></label>
|
|
<label class="static-only">Маска (префикс) <input type="number" id="net-prefix" min="1" max="32" value="24"></label>
|
|
<label class="static-only">Шлюз <input type="text" id="net-gateway" placeholder="192.168.1.1"></label>
|
|
<label class="static-only">DNS <input type="text" id="net-dns" placeholder="8.8.8.8, 1.1.1.1"></label>
|
|
</div>
|
|
<div class="card-actions">
|
|
<button id="net-apply" class="primary">Применить</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ Система ============ -->
|
|
<section class="panel" id="tab-system">
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<h2>Состояние устройства</h2>
|
|
<button id="sys-refresh" class="ghost">↻ Обновить</button>
|
|
</div>
|
|
<div id="sys-stats" class="sys-stats">Загрузка…</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Дата и время</h2>
|
|
<p class="hint">Без сети NTP не работает — задайте время вручную. Сохраняется в системные часы и RTC (если есть).</p>
|
|
<div id="sys-clock" class="sys-clock">Текущее время: …</div>
|
|
<div class="row">
|
|
<label>Установить
|
|
<input type="datetime-local" id="sys-datetime" step="1">
|
|
</label>
|
|
<button id="sys-set-time" class="primary">Применить</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Перезагрузка</h2>
|
|
<p class="warn">Полная перезагрузка устройства. Используйте только если что-то зависло.</p>
|
|
<button id="sys-reboot" class="del">Перезагрузить устройство</button>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<h2>Доступ к устройству</h2>
|
|
<button id="access-refresh" class="ghost">↻ Обновить</button>
|
|
</div>
|
|
<p class="hint">ICMP управляет ответом на ping. Изменение веб-порта перезапустит веб-сервис.</p>
|
|
<div id="access-status" class="access-status">Загрузка…</div>
|
|
<div class="access-grid">
|
|
<div class="access-row">
|
|
<span>Ping / ICMP</span>
|
|
<button id="icmp-on">Включить</button>
|
|
<button id="icmp-off" class="del">Отключить</button>
|
|
</div>
|
|
<div class="access-row">
|
|
<span>SSH</span>
|
|
<button id="ssh-on">Включить</button>
|
|
<button id="ssh-off" class="del">Отключить</button>
|
|
</div>
|
|
<label>Порт SSH
|
|
<div class="row">
|
|
<input type="number" id="ssh-port" min="1" max="65535" value="22">
|
|
<button id="save-ssh-port">Сохранить</button>
|
|
</div>
|
|
</label>
|
|
<label>Порт веб-конфигуратора
|
|
<div class="row">
|
|
<input type="number" id="web-port" min="1" max="65535" value="8080">
|
|
<button id="save-web-port">Сохранить и перезапустить веб</button>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Журнал нажатий</h2>
|
|
<p class="hint">Какая кнопка нажата, куда ушёл запрос и чем завершился.</p>
|
|
<div class="log-wrap">
|
|
<table class="req-log" id="sys-log">
|
|
<thead>
|
|
<tr>
|
|
<th>Время</th>
|
|
<th>Кнопка</th>
|
|
<th>Метод</th>
|
|
<th>URL</th>
|
|
<th>Результат</th>
|
|
<th>мс</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="sys-log-body">
|
|
<tr><td colspan="6" class="empty-cell">Загрузка…</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ Обновление ============ -->
|
|
<section class="panel" id="tab-update">
|
|
<div class="update-layout">
|
|
<aside class="version-sidebar card">
|
|
<div class="card-head">
|
|
<h2>Версии</h2>
|
|
<button id="versions-refresh" class="ghost" title="Обновить">↻</button>
|
|
</div>
|
|
<p class="hint">Установленные на устройстве. Активную и версию для отката удалить нельзя.</p>
|
|
<ul id="version-list" class="version-list">
|
|
<li class="empty">Загрузка…</li>
|
|
</ul>
|
|
</aside>
|
|
|
|
<div class="update-main">
|
|
<div class="card">
|
|
<h2>Текущая версия</h2>
|
|
<div id="update-current" class="kv">
|
|
<div><span>Версия</span><strong id="update-version">{{ version.version }}</strong></div>
|
|
<div><span>Путь</span><code id="update-path">{{ version.path }}</code></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h2>Установить обновление</h2>
|
|
<p class="hint">Загрузите пакет <code>.tar.gz</code> (бинарь ButtonTask + webconfig + manifest.json). После применения выполняется проверка работоспособности; при ошибке запуска произойдёт автоматический откат.</p>
|
|
<div class="row">
|
|
<input type="file" id="update-file" accept=".tar.gz,.tgz">
|
|
<button id="update-upload" class="primary">Загрузить и применить</button>
|
|
</div>
|
|
<div id="update-status" class="update-status">Статус: проверка…</div>
|
|
<div class="card-actions">
|
|
<button id="update-rollback" class="del">Откатить на предыдущую версию</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ Безопасность ============ -->
|
|
<section class="panel" id="tab-security">
|
|
<div class="card">
|
|
<h2>Пароль</h2>
|
|
<p class="hint">Используется и для входа в веб-конфигуратор, и для настроек в приложении.</p>
|
|
<div class="row">
|
|
<label>Новый пароль
|
|
<input type="password" id="new-password" placeholder="Оставьте пустым чтобы не менять">
|
|
</label>
|
|
<button id="save-password" class="primary">Сохранить пароль</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- Edit dialog -->
|
|
<dialog id="btn-dialog">
|
|
<form method="dialog" id="btn-form">
|
|
<h3 id="btn-dialog-title">Кнопка</h3>
|
|
<input type="hidden" id="b-id">
|
|
<label>Подпись <input type="text" id="b-label" required></label>
|
|
<label>Иконка
|
|
<select id="b-icon">
|
|
<option value="">— нет —</option>
|
|
{% for ic in icons %}
|
|
<option value="{{ ic }}">{{ ic }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</label>
|
|
<label>Метод
|
|
<select id="b-type">
|
|
<option value="http_get">http_get</option>
|
|
<option value="http_post">http_post</option>
|
|
</select>
|
|
</label>
|
|
<label>URL <input type="text" id="b-url" required></label>
|
|
<label>Текст успеха <input type="text" id="b-success"></label>
|
|
<label>Текст ошибки <input type="text" id="b-error" value="Ошибка"></label>
|
|
<label>Текст ожидания <input type="text" id="b-pending" value="..."></label>
|
|
<label>Цвет кнопки <input type="color" id="b-color" value="#7b007b"></label>
|
|
<label>Срабатывание
|
|
<select id="b-trigger-mode">
|
|
<option value="hold">Удержание</option>
|
|
<option value="click">Клик</option>
|
|
</select>
|
|
</label>
|
|
<label id="b-hold-row">Длительность удержания: <span id="b-hold-val">800</span> мс
|
|
<input type="range" id="b-hold" min="200" max="3000" step="50" value="800">
|
|
</label>
|
|
|
|
<div class="response-check-block" id="b-response-check-block">
|
|
<label class="check">
|
|
<input type="checkbox" id="b-rc-enabled">
|
|
Расширенная проверка ответа (JSON)
|
|
</label>
|
|
<div id="b-rc-advanced" class="response-check-advanced">
|
|
<p class="hint">Пример ответа: <code>{"success":"ok"}</code>. Без этого блока проверяется только HTTP-код. В колонке результата правила выберите <strong>Ошибка</strong> / <strong>Ожидание</strong> / <strong>Принято</strong> — не оставляйте «Принято» для условий ошибки.</p>
|
|
<label class="check">
|
|
<input type="checkbox" id="b-rc-require-http" checked>
|
|
Требовать HTTP < 400
|
|
</label>
|
|
<label>Если ни одно правило не подошло
|
|
<select id="b-rc-default">
|
|
<option value="error">Ошибка</option>
|
|
<option value="ok">Принято</option>
|
|
<option value="pending">Ожидание</option>
|
|
</select>
|
|
</label>
|
|
<div class="response-rules-head">
|
|
<strong>Правила</strong>
|
|
<button type="button" id="b-rc-add-rule" class="ghost">+ Правило</button>
|
|
</div>
|
|
<div id="b-rc-rules"></div>
|
|
<h4 class="subhead">Polling при «Ожидание»</h4>
|
|
<div class="grid-form response-check-poll">
|
|
<label>Интервал, мс
|
|
<input type="number" id="b-rc-poll-interval" min="500" max="60000" value="3000">
|
|
</label>
|
|
<label>Макс. попыток
|
|
<input type="number" id="b-rc-poll-attempts" min="1" max="100" value="20">
|
|
</label>
|
|
<label>Общий таймаут, мс
|
|
<input type="number" id="b-rc-poll-timeout" min="1000" max="600000" value="120000">
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<menu>
|
|
<button value="cancel">Отмена</button>
|
|
<button id="b-save" value="ok" type="submit" class="primary">Сохранить</button>
|
|
</menu>
|
|
</form>
|
|
</dialog>
|
|
|
|
</body>
|
|
</html>
|