mirror of
https://github.com/avecms/AVE.cms.git
synced 2026-11-13 23:25:06 +00:00
Publish the verified clean core runtime with editor relation, paste and highlighting fixes, session maintenance and consistent database backups. No private storefront, commerce or AI modules are included.
667 lines
46 KiB
Twig
667 lines
46 KiB
Twig
{% extends '@adminx/main.twig' %}
|
|
{% block title %}{{ settings_title }}{% endblock %}
|
|
{% block content %}
|
|
<div data-settings-page="{{ settings_page }}" data-base="{{ ADMINX_BASE }}">
|
|
|
|
<nav class="breadcrumbs" aria-label="Хлебные крошки">
|
|
<a href="{{ ADMINX_BASE }}/">Главная</a><i class="ti ti-chevron-right"></i>
|
|
<a href="{{ ADMINX_BASE }}/settings/main">Настройки</a><i class="ti ti-chevron-right"></i><span>{{ settings_title }}</span>
|
|
</nav>
|
|
|
|
<div class="page-header">
|
|
<div class="between">
|
|
<div>
|
|
<h1>{{ settings_title }}</h1>
|
|
{% set settings_subtitles = {'main':'Системные параметры сайта и почты','interface':'Порядок и видимость элементов панели управления','security':'Подтверждение критических действий администратором','constants':'Типизированные значения системной конфигурации','paginations':'Шаблоны постраничной навигации','maintenance':'Кеш и служебные данные','diagnostics':'Проверка окружения перед production','files':'Редактируемые системные PHP-файлы'} %}
|
|
<p class="text-secondary" style="margin-top:5px">{{ settings_subtitles[settings_page]|default('Системные настройки') }}</p>
|
|
</div>
|
|
<div class="cluster">
|
|
{% if can_manage %}
|
|
{% if settings_page == 'main' %}<button class="btn btn-primary" type="submit" form="settingsForm"><i class="ti ti-check"></i>Сохранить</button>{% endif %}
|
|
{% if settings_page == 'interface' %}<button class="btn btn-primary" type="submit" form="interfaceSettingsForm"><i class="ti ti-device-floppy"></i>Сохранить порядок</button>{% endif %}
|
|
{% if settings_page == 'security' %}<button class="btn btn-primary" type="submit" form="securitySettingsForm"><i class="ti ti-device-floppy"></i>Сохранить</button>{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if settings_page == 'main' %}
|
|
<section class="tab-panel active" data-settings-panel="main">
|
|
<div class="section-header settings-panel-header">
|
|
<div class="section-icon"><i class="ti ti-adjustments"></i></div>
|
|
<div>
|
|
<div class="section-eyebrow">Настройки</div>
|
|
<h2>Основные</h2>
|
|
<p class="section-desc">Системные параметры сайта, почты, пагинации и хлебных крошек.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ax-summary settings-main-summary">
|
|
{% for code, group in groups %}
|
|
<div class="card stat-tile"><span class="icon-tile" style="--tile-bg:{{ group.tile_bg|default('var(--color-primary-soft)') }};--tile-fg:{{ group.tile_fg|default('var(--color-primary)') }}"><i class="{{ group.icon }}"></i></span><div><div class="stat-val">{{ group.fields|length }}</div><div class="stat-label">{{ group.label }}</div></div></div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<form id="settingsForm" class="settings-form" data-base="{{ ADMINX_BASE }}">
|
|
<input type="hidden" name="_csrf" value="{{ csrf_token }}">
|
|
|
|
<div class="settings-stack">
|
|
{% for code, group in groups %}
|
|
<div class="card settings-card" id="settings-group-{{ code }}">
|
|
<div class="settings-section-head">
|
|
<div class="settings-section-title">
|
|
<span class="icon-tile settings-head-icon" style="--tile-bg:{{ group.tile_bg|default('var(--color-primary-soft)') }};--tile-fg:{{ group.tile_fg|default('var(--color-primary)') }}"><i class="{{ group.icon }}"></i></span>
|
|
<div>
|
|
<h2>{{ group.label }}</h2>
|
|
<p class="text-secondary">{{ group.description }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="settings-section-meta">
|
|
<span class="badge badge-blue">{{ group.fields|length }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-grid">
|
|
{% for field in group.fields %}
|
|
{% if field.section|default('') %}<div class="settings-grid-section"><span>{{ field.section }}</span></div>{% endif %}
|
|
{% set wide = field.type in ['text', 'section_order'] %}
|
|
{% set depends = field.depends is defined ? field.depends : null %}
|
|
<div class="field settings-field{{ wide ? ' settings-field-wide' : '' }}{{ field.type == 'bool' ? ' settings-field-bool' : '' }}" {% if depends %}{% for depKey, depValue in depends %}data-depends-key="{{ depKey }}" data-depends-value="{{ depValue }}"{% endfor %}{% endif %}>
|
|
<label class="field-label" for="settings-{{ field.key }}">
|
|
{{ field.label }}
|
|
{% if field.sensitive is defined and field.sensitive %}<span class="badge badge-amber">секрет</span>{% endif %}
|
|
</label>
|
|
|
|
{% if field.type == 'section_order' %}
|
|
<input type="hidden" id="settings-{{ field.key }}" name="{{ field.key }}" value="{{ field.order_items|json_encode|e('html_attr') }}" data-section-order-value>
|
|
<div class="settings-section-order" data-section-order>
|
|
{% for item in field.order_items %}
|
|
<article class="settings-section-order-row{{ item.visible ? '' : ' is-disabled' }}" draggable="{{ can_manage ? 'true' : 'false' }}" data-section-order-item data-code="{{ item.code }}">
|
|
<button class="btn btn-ghost btn-icon settings-section-order-handle" type="button" data-section-order-handle data-tooltip="Перетащить" aria-label="Перетащить"{{ can_manage ? '' : ' disabled' }}><i class="ti ti-grip-vertical"></i></button>
|
|
<span class="icon-tile"><i class="ti {{ item.code == 'hero' ? 'ti-photo' : (item.code == 'categories' ? 'ti-category' : (item.code in ['popular', 'new'] ? 'ti-package' : (item.code == 'news' ? 'ti-news' : 'ti-file-text'))) }}"></i></span>
|
|
<span><b>{{ item.label }}</b><small>{{ item.code }}</small></span>
|
|
<span class="settings-layout-move"><button class="btn btn-ghost btn-icon btn-sm" type="button" data-section-order-move="up" data-tooltip="Выше" aria-label="Выше"{{ can_manage ? '' : ' disabled' }}><i class="ti ti-chevron-up"></i></button><button class="btn btn-ghost btn-icon btn-sm" type="button" data-section-order-move="down" data-tooltip="Ниже" aria-label="Ниже"{{ can_manage ? '' : ' disabled' }}><i class="ti ti-chevron-down"></i></button></span>
|
|
<label class="switch" data-tooltip="Показывать на главной"><input type="checkbox" data-section-order-visible{{ item.visible ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></label>
|
|
</article>
|
|
{% endfor %}
|
|
</div>
|
|
{% elseif field.type == 'text' %}
|
|
<textarea class="textarea" id="settings-{{ field.key }}" name="{{ field.key }}" rows="6" {{ can_manage ? '' : 'disabled' }}>{{ field.value }}</textarea>
|
|
{% elseif field.type == 'select' %}
|
|
<select class="select" id="settings-{{ field.key }}" name="{{ field.key }}" {{ can_manage ? '' : 'disabled' }}>
|
|
{% for optValue, optLabel in field.options %}
|
|
<option value="{{ optValue }}"{{ optValue == field.value ? ' selected' : '' }}>{{ optLabel }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
{% elseif field.type == 'bool' %}
|
|
<label class="settings-switch-line" for="settings-{{ field.key }}">
|
|
<input type="hidden" name="{{ field.key }}" value="0">
|
|
<span><b>{{ field.value ? 'Включено' : 'Выключено' }}</b><small>Изменение применяется после сохранения настроек.</small></span>
|
|
<span class="switch"><input id="settings-{{ field.key }}" type="checkbox" name="{{ field.key }}" value="1" {{ field.value ? 'checked' : '' }} {{ can_manage ? '' : 'disabled' }}><span></span></span>
|
|
</label>
|
|
{% else %}
|
|
<input class="input" id="settings-{{ field.key }}" type="{{ field.type == 'password' ? 'password' : (field.type == 'email' ? 'email' : 'text') }}" name="{{ field.key }}" value="{{ field.value }}" {{ can_manage ? '' : 'disabled' }}>
|
|
{% endif %}
|
|
|
|
{% if field.hint|default('') %}<span class="field-hint">{{ field.hint }}</span>{% elseif field.theme_key is not defined %}<span class="field-hint mono">{{ field.key }}</span>{% endif %}
|
|
<span class="field-error" data-error="{{ field.key }}"></span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</form>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if settings_page == 'security' %}
|
|
<section class="tab-panel active" data-settings-panel="security">
|
|
<div class="section-header settings-panel-header" style="--section-accent:var(--red-600);--section-accent-soft:var(--red-100)">
|
|
<div class="section-icon"><i class="ti ti-shield-lock"></i></div>
|
|
<div><div class="section-eyebrow">Безопасность панели</div><h2>Подтверждение действий</h2><p class="section-desc">Дополнительная проверка перед изменением исполняемого кода и установкой модулей.</p></div>
|
|
</div>
|
|
|
|
<div class="alert alert-warning settings-security-note"><i class="ti ti-alert-triangle"></i><div><b>На production защиту лучше оставить включённой</b><p>Одно успешное подтверждение действует 5 минут. Отключение не меняет права ролей и CSRF-защиту.</p></div></div>
|
|
|
|
<form id="securitySettingsForm" method="post" action="{{ ADMINX_BASE }}/settings/security" data-ax-ajax-form>
|
|
<input type="hidden" name="_csrf" value="{{ csrf_token }}">
|
|
<div class="card settings-card">
|
|
<div class="settings-section-head">
|
|
<div class="settings-section-title"><span class="icon-tile settings-head-icon" style="--tile-bg:var(--amber-100);--tile-fg:var(--amber-600)"><i class="ti ti-password-user"></i></span><div><h2>Повторный вход</h2><p class="text-secondary">Пароль перед операциями, которые могут изменить PHP/шаблоны или добавить исполняемые файлы.</p></div></div>
|
|
<span class="badge {{ reauth_enabled ? 'badge-green' : 'badge-gray' }}">{{ reauth_enabled ? 'Включено' : 'Выключено' }}</span>
|
|
</div>
|
|
<div class="settings-security-control">
|
|
<label class="settings-switch-line" for="adminxReauthEnabled">
|
|
<input type="hidden" name="adminx_reauth_enabled" value="0">
|
|
<span><b>Запрашивать пароль</b><small>Блоки, шаблоны, рубрики, запросы, навигация и операции с кодом.</small></span>
|
|
<span class="switch"><input id="adminxReauthEnabled" type="checkbox" name="adminx_reauth_enabled" value="1"{{ reauth_enabled ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></span>
|
|
</label>
|
|
</div>
|
|
{% if can_manage %}<div class="card-actions"><button class="btn btn-primary" type="submit"><i class="ti ti-device-floppy"></i>Сохранить</button></div>{% endif %}
|
|
</div>
|
|
</form>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if settings_page == 'interface' %}
|
|
<section class="tab-panel active" data-settings-panel="interface">
|
|
<div class="section-header settings-panel-header">
|
|
<div class="section-icon"><i class="ti ti-layout-sidebar"></i></div>
|
|
<div><div class="section-eyebrow">Интерфейс</div><h2>Размещение элементов</h2><p class="section-desc">Скрытые элементы продолжают работать по прямому адресу и по-прежнему проверяют права доступа.</p></div>
|
|
</div>
|
|
|
|
<form id="interfaceSettingsForm" data-interface-layout-form>
|
|
<input type="hidden" name="_csrf" value="{{ csrf_token }}">
|
|
<div class="settings-stack">
|
|
<div class="card settings-card settings-layout-card">
|
|
<div class="settings-section-head">
|
|
<div class="settings-section-title"><span class="icon-tile settings-head-icon" style="--tile-bg:var(--blue-100);--tile-fg:var(--blue-600)"><i class="ti ti-layout-sidebar"></i></span><div><h2>Левое меню</h2><p class="text-secondary">Порядок разделов и вложенных пунктов панели.</p></div></div>
|
|
<div class="settings-section-meta"><span class="badge badge-blue">{{ navigation_layout|length }}</span></div>
|
|
</div>
|
|
<div class="settings-layout-list" data-layout-list="navigation">
|
|
{% for item in navigation_layout %}
|
|
<article class="settings-layout-row{{ item.layout_visible ? '' : ' is-disabled' }}" data-layout-item data-code="{{ item.layout_code }}">
|
|
<button class="btn btn-ghost btn-icon settings-layout-handle" type="button" draggable="{{ can_manage ? 'true' : 'false' }}" data-layout-handle data-tooltip="Перетащить" aria-label="Перетащить"{{ can_manage ? '' : ' disabled' }}><i class="ti ti-grip-vertical"></i></button>
|
|
<span class="icon-tile" style="--tile-bg:var(--surface-muted);--tile-fg:var(--text-secondary)"><i class="{{ item.icon ?: 'ti ti-point' }}"></i></span>
|
|
<span class="settings-layout-copy"><b>{{ item.label }}</b><small>{{ item.parent ? 'Вложен в ' ~ item.parent : (item.group ?: 'Без группы') }} · <span class="mono">{{ item.layout_code }}</span></small></span>
|
|
<span class="settings-layout-move"><button class="btn btn-ghost btn-icon btn-sm" type="button" data-layout-move="up" data-tooltip="Выше" aria-label="Выше"{{ can_manage ? '' : ' disabled' }}><i class="ti ti-chevron-up"></i></button><button class="btn btn-ghost btn-icon btn-sm" type="button" data-layout-move="down" data-tooltip="Ниже" aria-label="Ниже"{{ can_manage ? '' : ' disabled' }}><i class="ti ti-chevron-down"></i></button></span>
|
|
<label class="switch" data-tooltip="Показывать в меню"><input type="checkbox" data-layout-visible{{ item.layout_visible ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></label>
|
|
</article>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card settings-card settings-layout-card">
|
|
<div class="settings-section-head">
|
|
<div class="settings-section-title"><span class="icon-tile settings-head-icon" style="--tile-bg:var(--green-100);--tile-fg:var(--green-600)"><i class="ti ti-layout-dashboard"></i></span><div><h2>Виджеты дашборда</h2><p class="text-secondary">Системные блоки и виджеты установленных модулей.</p></div></div>
|
|
<div class="settings-section-meta"><span class="badge badge-green">{{ dashboard_layout|length }}</span></div>
|
|
</div>
|
|
<div class="settings-layout-list" data-layout-list="dashboard">
|
|
{% for item in dashboard_layout %}
|
|
<article class="settings-layout-row{{ item.layout_visible ? '' : ' is-disabled' }}" data-layout-item data-code="{{ item.layout_code }}">
|
|
<button class="btn btn-ghost btn-icon settings-layout-handle" type="button" draggable="{{ can_manage ? 'true' : 'false' }}" data-layout-handle data-tooltip="Перетащить" aria-label="Перетащить"{{ can_manage ? '' : ' disabled' }}><i class="ti ti-grip-vertical"></i></button>
|
|
<span class="icon-tile" style="--tile-bg:var(--surface-muted);--tile-fg:var(--text-secondary)"><i class="{{ item.icon ?: 'ti ti-layout-dashboard' }}"></i></span>
|
|
<span class="settings-layout-copy"><b>{{ item.label }}</b><small>{{ item.description ?: item.layout_code }}{% if item.description %} · <span class="mono">{{ item.layout_code }}</span>{% endif %}</small></span>
|
|
<span class="settings-layout-move"><button class="btn btn-ghost btn-icon btn-sm" type="button" data-layout-move="up" data-tooltip="Выше" aria-label="Выше"{{ can_manage ? '' : ' disabled' }}><i class="ti ti-chevron-up"></i></button><button class="btn btn-ghost btn-icon btn-sm" type="button" data-layout-move="down" data-tooltip="Ниже" aria-label="Ниже"{{ can_manage ? '' : ' disabled' }}><i class="ti ti-chevron-down"></i></button></span>
|
|
<label class="switch" data-tooltip="Показывать на дашборде"><input type="checkbox" data-layout-visible{{ item.layout_visible ? ' checked' : '' }}{{ can_manage ? '' : ' disabled' }}><span></span></label>
|
|
</article>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if settings_page == 'constants' %}
|
|
<section class="tab-panel active" data-settings-panel="constants">
|
|
<div class="section-header settings-panel-header">
|
|
<div class="section-icon"><i class="ti ti-code"></i></div>
|
|
<div>
|
|
<div class="section-eyebrow">Настройки</div>
|
|
<h2>Константы</h2>
|
|
<p class="section-desc">Типизированные значения публичного runtime, применяемые со следующего запроса.</p>
|
|
</div>
|
|
{% if can_manage %}
|
|
<div class="section-header-right">
|
|
<div class="cluster">
|
|
<button class="btn btn-primary" type="button" data-constant-new data-open-drawer="constantDrawer"><i class="ti ti-plus"></i>Добавить</button>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="settings-stack">
|
|
{% for group in constant_groups %}
|
|
<div class="card settings-card" data-constant-group="{{ group.code }}">
|
|
<div class="settings-section-head">
|
|
<div class="settings-section-title">
|
|
<span class="icon-tile settings-head-icon" style="--tile-bg:{{ cycle(['var(--blue-100)', 'var(--green-100)', 'var(--violet-100)', 'var(--amber-100)', 'var(--teal-100)', 'var(--cyan-100)', 'var(--pink-100)', 'var(--orange-100)', 'var(--indigo-100)', 'var(--gray-100)'], loop.index0) }};--tile-fg:{{ cycle(['var(--blue-600)', 'var(--green-600)', 'var(--violet-600)', 'var(--amber-600)', 'var(--teal-600)', 'var(--cyan-600)', 'var(--pink-600)', 'var(--orange-600)', 'var(--indigo-600)', 'var(--gray-600)'], loop.index0) }}"><i class="ti ti-folder"></i></span>
|
|
<div>
|
|
<h2>{{ group.label }}</h2>
|
|
<p class="text-secondary">{{ group.code }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="settings-section-meta">
|
|
<span class="badge badge-blue">{{ group.items|length }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="table-scroll">
|
|
<table class="table table-compact constants-table">
|
|
<colgroup>
|
|
<col class="constants-col-name">
|
|
<col class="constants-col-status">
|
|
<col class="constants-col-value">
|
|
<col class="constants-col-type">
|
|
<col class="constants-col-description">
|
|
{% if can_manage %}<col class="constants-col-actions">{% endif %}
|
|
</colgroup>
|
|
<thead>
|
|
<tr>
|
|
<th>Имя</th>
|
|
<th>Статус</th>
|
|
<th>Значение</th>
|
|
<th>Тип</th>
|
|
<th>Описание</th>
|
|
{% if can_manage %}<th></th>{% endif %}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for c in group.items %}
|
|
<tr data-constant-row data-name="{{ c.name }}">
|
|
<td class="mono"><span class="constant-name"><b>{{ c.name }}</b></span></td>
|
|
<td><span class="badge {{ c.usage_status == 'active' ? 'badge-green' : (c.usage_status == 'compatibility' ? 'badge-amber' : 'badge-gray') }}" title="{{ c.usage_reason }}">{{ c.usage_label }}</span></td>
|
|
<td class="mono text-sm constant-value">{{ c.value_display }}</td>
|
|
<td><span class="badge badge-blue" title="{{ c.type }}">{{ c.type_label }}</span></td>
|
|
<td class="text-secondary text-sm">{{ c.description }}</td>
|
|
{% if can_manage %}
|
|
<td>
|
|
<div class="cluster" style="gap:4px">
|
|
<button class="btn btn-ghost btn-icon btn-sm" type="button" data-constant-edit data-open-drawer="constantDrawer" data-tooltip="Изменить" aria-label="Изменить"><i class="ti ti-pencil"></i></button>
|
|
{% if not c.is_system %}
|
|
<button class="btn btn-ghost btn-icon btn-sm" type="button" data-constant-delete data-tooltip="Удалить" aria-label="Удалить"><i class="ti ti-trash"></i></button>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="card settings-status">
|
|
<span class="stat-ico"><i class="ti ti-code-plus"></i></span>
|
|
<div>
|
|
<div class="settings-status-title">Константы ещё не созданы</div>
|
|
<div class="text-secondary text-sm">Добавьте первое типизированное значение. Оно станет доступно публичному runtime со следующего запроса.</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if settings_page == 'paginations' %}
|
|
<section class="tab-panel active" data-settings-panel="paginations">
|
|
<div class="section-header settings-panel-header">
|
|
<div class="section-icon"><i class="ti ti-list-numbers"></i></div>
|
|
<div>
|
|
<div class="section-eyebrow">Настройки</div>
|
|
<h2>Шаблоны пагинации</h2>
|
|
<p class="section-desc">Шаблоны из <span class="mono">_paginations</span>, используемые запросами и документами.</p>
|
|
</div>
|
|
{% if can_manage %}
|
|
<div class="section-header-right">
|
|
<button class="btn btn-primary" type="button" data-pagination-new data-open-drawer="paginationDrawer"><i class="ti ti-plus"></i>Создать</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="card settings-card">
|
|
<div class="settings-section-head">
|
|
<div class="settings-section-title">
|
|
<span class="icon-tile settings-head-icon" style="--tile-bg:var(--violet-100);--tile-fg:var(--violet-600)"><i class="ti ti-table"></i></span>
|
|
<div>
|
|
<h2>Список шаблонов</h2>
|
|
<p class="text-secondary">Единая таблица шаблонов пагинации.</p>
|
|
</div>
|
|
</div>
|
|
<div class="settings-section-meta">
|
|
<span class="badge badge-blue">{{ paginations|length }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="table-scroll">
|
|
<table class="table table-compact">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:72px">ID</th>
|
|
<th>Название</th>
|
|
{% if can_manage %}<th style="width:120px"></th>{% endif %}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for p in paginations %}
|
|
<tr data-pagination-row data-id="{{ p.id }}" data-name="{{ p.pagination_name }}">
|
|
<td class="mono text-muted">{{ p.id }}</td>
|
|
<td><b>{{ p.pagination_name }}</b></td>
|
|
{% if can_manage %}
|
|
<td>
|
|
<div class="cluster" style="gap:4px">
|
|
<button class="btn btn-ghost btn-icon btn-sm" type="button" data-pagination-edit data-open-drawer="paginationDrawer" data-tooltip="Изменить" aria-label="Изменить"><i class="ti ti-pencil"></i></button>
|
|
{% if p.id != 1 %}
|
|
<button class="btn btn-ghost btn-icon btn-sm" type="button" data-pagination-delete data-tooltip="Удалить" aria-label="Удалить"><i class="ti ti-trash"></i></button>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
{% endif %}
|
|
</tr>
|
|
{% else %}
|
|
<tr><td colspan="3" class="text-muted" style="text-align:center;padding:24px">Шаблоны ещё не перенесены</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if settings_page == 'files' %}
|
|
<section class="tab-panel active" data-settings-panel="files">
|
|
<div class="section-header settings-panel-header"><div class="section-icon"><i class="ti ti-file-code"></i></div><div><div class="section-eyebrow">Настройки</div><h2>Системные файлы</h2><p class="section-desc">Редактирование разрешённых файлов без доступа к произвольным путям.</p></div></div>
|
|
<div class="settings-stack">
|
|
{% for code, file in system_files %}
|
|
<div class="card settings-card" data-system-file-row="{{ code }}">
|
|
<div class="settings-section-head"><div class="settings-section-title"><span class="icon-tile settings-head-icon" style="--tile-bg:{{ code == 'custom' ? 'var(--violet-100)' : 'var(--cyan-100)' }};--tile-fg:{{ code == 'custom' ? 'var(--violet-600)' : 'var(--cyan-600)' }}"><i class="ti ti-{{ code == 'custom' ? 'brand-php' : 'robot' }}"></i></span><div><h2>{{ file.label }}</h2><p class="text-secondary">{{ file.description }}</p></div></div><div class="cluster"><span class="badge badge-{{ file.writable ? 'green' : 'red' }}">{{ file.writable ? 'доступен' : 'только чтение' }}</span>{% if can_manage %}<button class="btn btn-secondary btn-sm" type="button" data-system-file-edit="{{ code }}" data-open-drawer="systemFileDrawer"><i class="ti ti-pencil"></i>Редактировать</button>{% endif %}</div></div>
|
|
<div class="settings-file-meta"><span class="mono">{{ file.path }}</span><span>{{ file.bytes }} Б</span></div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if settings_page == 'maintenance' %}
|
|
<section class="tab-panel active" data-settings-panel="maintenance">
|
|
<div class="section-header settings-panel-header">
|
|
<div class="section-icon"><i class="ti ti-tool"></i></div>
|
|
<div>
|
|
<div class="section-eyebrow">Настройки</div>
|
|
<h2>Обслуживание</h2>
|
|
<p class="section-desc">Очистка актуальных cache-директорий и служебных данных.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="settings-stack">
|
|
<div class="card settings-card">
|
|
<div class="settings-section-head">
|
|
<div class="settings-section-title">
|
|
<span class="icon-tile settings-head-icon" style="--tile-bg:var(--amber-100);--tile-fg:var(--amber-600)"><i class="ti ti-trash"></i></span>
|
|
<div>
|
|
<h2>Источники кеша</h2>
|
|
<p class="text-secondary">Только каталоги, которые используются текущим runtime.</p>
|
|
</div>
|
|
</div>
|
|
<div class="settings-section-meta">
|
|
<span class="badge badge-blue">{{ cache_rows|length }}</span>
|
|
</div>
|
|
</div>
|
|
<div class="table-scroll">
|
|
<table class="table table-compact">
|
|
<thead>
|
|
<tr>
|
|
<th>Источник</th>
|
|
<th>Путь</th>
|
|
<th style="width:120px">Размер</th>
|
|
{% if can_manage %}<th style="width:110px"></th>{% endif %}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in cache_rows %}
|
|
<tr data-cache-row="{{ row.code }}">
|
|
<td><b>{{ row.label }}</b><div class="mono text-muted text-xs">{{ row.code }}</div></td>
|
|
<td class="mono text-xs">{{ row.path }}</td>
|
|
<td class="mono" data-cache-size>{{ row.size }}</td>
|
|
{% if can_manage %}
|
|
<td><button class="btn btn-ghost btn-icon btn-sm ax-act ax-act-danger" type="button" data-cache-clear="{{ row.code }}" data-tooltip="Очистить кеш" aria-label="Очистить кеш {{ row.label }}"><i class="ti ti-trash"></i></button></td>
|
|
{% endif %}
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card settings-card" data-session-maintenance>
|
|
<div class="settings-section-head">
|
|
<div class="settings-section-title">
|
|
<span class="icon-tile settings-head-icon" style="--tile-bg:var(--blue-100);--tile-fg:var(--blue-600)"><i class="ti ti-clock-x"></i></span>
|
|
<div><h2>Публичные сессии</h2><p class="text-secondary">Удаляются только просроченные сессии. Действующие сессии, заказы и сохранённые брошенные корзины не затрагиваются.</p></div>
|
|
</div>
|
|
{% if can_manage and public_sessions.available %}
|
|
<button class="btn btn-danger-soft btn-sm" type="button" data-session-cleanup><i class="ti ti-trash"></i>Очистить просроченные</button>
|
|
{% endif %}
|
|
</div>
|
|
<div class="settings-session-body">
|
|
{% if public_sessions.available %}
|
|
<dl class="settings-session-stats">
|
|
<div><dt>Просроченные</dt><dd data-session-expired>{{ public_sessions.expired }}</dd></div>
|
|
<div><dt>Действующие</dt><dd>{{ public_sessions.active }}</dd></div>
|
|
</dl>
|
|
<div class="settings-session-progress" data-session-progress hidden>
|
|
<div class="progress" role="progressbar" aria-label="Очистка просроченных сессий" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div class="progress-bar" data-session-progress-bar style="width:0%"></div></div>
|
|
<div class="settings-session-progress-row"><span role="status" aria-live="polite" data-session-progress-status></span>{% if can_manage %}<button class="btn btn-ghost btn-sm" type="button" data-session-stop hidden><i class="ti ti-player-pause"></i>Остановить</button>{% endif %}</div>
|
|
</div>
|
|
<div class="alert {{ public_sessions.last and public_sessions.last.error ? 'alert-danger' : 'alert-info' }}">
|
|
<i class="ti ti-info-circle"></i>
|
|
<div>
|
|
{% if not public_sessions.database %}
|
|
Сейчас используются не сессии БД. Автоочистка этой таблицы не запускается.
|
|
{% elseif public_sessions.after_response %}
|
|
Веб-очистка после отправки ответа доступна. Лимит: 1000 записей в минуту. Для запуска без посещений используйте Планировщик и cron.
|
|
{% else %}
|
|
Очистка после ответа недоступна в этом PHP-окружении. Для регулярной очистки настройте cron в модуле «Планировщик».
|
|
{% endif %}
|
|
{% if public_sessions.last %}
|
|
<div>Последняя автоматическая попытка: {{ public_sessions.last.at|date('d.m.Y H:i:s') }}. Удалено: {{ public_sessions.last.deleted }}.</div>
|
|
{% if public_sessions.last.error %}<div class="settings-session-error">{{ public_sessions.last.error }}</div>{% endif %}
|
|
{% else %}
|
|
<div>После обновления автоматические запуски пока не зарегистрированы.</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<p class="text-secondary text-xs">Режим PHP: {{ public_sessions.sapi }}. PHP GC: {{ public_sessions.gc_enabled ? 'включён' : 'выключен' }}. Время жизни сессии, секунд: {{ public_sessions.lifetime }}.</p>
|
|
<p class="text-secondary text-xs">Размер таблицы в мегабайтах может не уменьшиться сразу: БД повторно использует освобождённое место. Повторная оптимизация для очистки записей не требуется.</p>
|
|
{% else %}
|
|
<div class="alert alert-danger"><i class="ti ti-alert-triangle"></i><div>Не удалось проверить публичные сессии.<div class="settings-session-error">{{ public_sessions.error }}</div></div></div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card settings-card">
|
|
<div class="settings-section-head"><div class="settings-section-title"><span class="icon-tile settings-head-icon" style="--tile-bg:var(--red-100);--tile-fg:var(--red-600)"><i class="ti ti-database-minus"></i></span><div><h2>Служебные данные</h2><p class="text-secondary">Глобальная очистка ревизий редакторов и подневных счётчиков.</p></div></div></div>
|
|
<div class="settings-maintenance-actions">
|
|
<div class="settings-maintenance-revisions"><b>Все ревизии</b><span class="text-secondary">{{ maintenance_counts.revisions }} записей во всех редакторах</span>{% if can_manage %}<button class="btn btn-danger-soft btn-sm" type="button" data-maintenance-clear="revisions"><i class="ti ti-trash"></i>Очистить</button>{% endif %}<div class="settings-maintenance-breakdown">{% for source in maintenance_counts.revision_sources %}<span><b>{{ source.label }}</b> {{ source.count }}</span>{% endfor %}</div></div>
|
|
<div><b>Подневные просмотры</b><span class="text-secondary">{{ maintenance_counts.views }} записей</span>{% if can_manage %}<button class="btn btn-danger-soft btn-sm" type="button" data-maintenance-clear="views"><i class="ti ti-trash"></i>Очистить</button>{% endif %}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if settings_page == 'diagnostics' %}
|
|
<section class="tab-panel active" data-settings-panel="diagnostics">
|
|
<div class="section-header settings-panel-header"><div class="section-icon"><i class="ti ti-stethoscope"></i></div><div><div class="section-eyebrow">Система</div><h2>Диагностика</h2><p class="section-desc">Проверка окружения и готовности сайта без изменения данных.</p></div></div>
|
|
|
|
<div class="settings-diagnostics-overview">
|
|
{% for item in production_diagnostics.overview %}
|
|
{% set tile_bg = item.state == 'ok' ? 'var(--green-100)' : (item.state == 'error' ? 'var(--red-100)' : 'var(--amber-100)') %}
|
|
{% set tile_fg = item.state == 'ok' ? 'var(--green-600)' : (item.state == 'error' ? 'var(--red-600)' : 'var(--amber-600)') %}
|
|
<article class="card settings-diagnostics-overview-card is-{{ item.state }}">
|
|
<span class="icon-tile" style="--tile-bg:{{ tile_bg }};--tile-fg:{{ tile_fg }}"><i class="ti {{ item.icon }}"></i></span>
|
|
<div>
|
|
<span>{{ item.label }}</span>
|
|
<b>{{ item.value }}</b>
|
|
<small>{{ item.detail }}</small>
|
|
</div>
|
|
</article>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<div class="settings-diagnostics-groups">
|
|
{% for group in production_diagnostics.groups %}
|
|
<section class="settings-diagnostics-group">
|
|
<div class="settings-diagnostics-group-head">
|
|
<div class="settings-section-title">
|
|
<span class="icon-tile settings-head-icon" style="--tile-bg:var(--blue-100);--tile-fg:var(--blue-600)"><i class="ti {{ group.icon }}"></i></span>
|
|
<div><h2>{{ group.title }}</h2><p class="text-secondary">{{ group.description }}</p></div>
|
|
</div>
|
|
<span class="badge badge-blue">{{ group.checks|length }}</span>
|
|
</div>
|
|
|
|
<div class="settings-diagnostics-grid">
|
|
{% for check in group.checks %}
|
|
<article class="card settings-diagnostic-card is-{{ check.state }}">
|
|
<span class="settings-diagnostic-icon"><i class="ti ti-{{ check.state == 'ok' ? 'check' : (check.state == 'error' ? 'x' : 'alert-triangle') }}"></i></span>
|
|
<div><b>{{ check.label }}</b><span>{{ check.detail }}</span></div>
|
|
</article>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
|
|
{% if can_manage and settings_page == 'files' %}
|
|
<aside class="drawer drawer-right drawer-lg settings-file-drawer" id="systemFileDrawer" role="dialog" aria-modal="true" aria-labelledby="systemFileDrawerTitle" hidden>
|
|
<div class="drawer-header"><div><h3 id="systemFileDrawerTitle">Системный файл</h3><p class="text-secondary text-sm" data-system-file-path></p></div><button class="btn btn-ghost btn-icon" type="button" data-close-drawer aria-label="Закрыть"><i class="ti ti-x"></i></button></div>
|
|
<form data-system-file-form><input type="hidden" name="code"><input type="hidden" name="_csrf" value="{{ csrf_token }}"><div class="drawer-body settings-file-editor"><textarea class="textarea mono" name="content" rows="30" data-code-editor data-code-mode="application/x-httpd-php"></textarea></div><div class="drawer-footer"><button class="btn btn-ghost" type="button" data-close-drawer>Закрыть</button><button class="btn btn-primary" type="submit" style="margin-left:auto"><i class="ti ti-device-floppy"></i>Сохранить</button></div></form>
|
|
</aside>
|
|
{% endif %}
|
|
|
|
{% if can_manage and settings_page == 'constants' %}
|
|
<aside class="drawer drawer-right drawer-lg" id="constantDrawer" role="dialog" aria-modal="true" aria-labelledby="constantDrawerTitle" hidden>
|
|
<div class="drawer-header">
|
|
<h3 id="constantDrawerTitle">Константа</h3>
|
|
<button class="btn btn-ghost btn-icon" data-close-drawer aria-label="Закрыть"><i class="ti ti-x"></i></button>
|
|
</div>
|
|
<form id="constantForm" data-base="{{ ADMINX_BASE }}">
|
|
<input type="hidden" name="_csrf" value="{{ csrf_token }}">
|
|
<input type="hidden" name="is_system" value="0">
|
|
<input type="hidden" name="value" value="" data-constant-value>
|
|
<div class="drawer-body">
|
|
<div class="constant-editor-identity">
|
|
<label class="field">
|
|
<span class="field-label">Имя</span>
|
|
<input class="input mono" name="name" value="" placeholder="MY_CONSTANT" required>
|
|
</label>
|
|
</div>
|
|
<div class="tabs constant-editor-tabs" role="tablist" aria-label="Раздел редактора">
|
|
<button class="tab is-active" type="button" role="tab" aria-selected="true" data-constant-editor-tab="value"><i class="ti ti-adjustments"></i>Значение</button>
|
|
<button class="tab" type="button" role="tab" aria-selected="false" data-constant-editor-tab="settings"><i class="ti ti-settings"></i>Настройки</button>
|
|
</div>
|
|
<div class="constant-editor-panel" data-constant-editor-panel="value">
|
|
<div class="constant-value-editor" data-constant-control="string">
|
|
<label class="field"><span class="field-label">Значение</span><input class="input mono" type="text" data-constant-input="string"></label>
|
|
</div>
|
|
<div class="constant-value-editor" data-constant-control="path" hidden>
|
|
<label class="field"><span class="field-label">Путь</span><div class="input-wrap"><i class="ti ti-folder"></i><input class="input mono" type="text" data-constant-input="path" placeholder="/path/to/folder"></div></label>
|
|
</div>
|
|
<div class="constant-value-editor" data-constant-control="int" hidden>
|
|
<label class="field"><span class="field-label">Целое число</span><input class="input mono" type="number" step="1" data-constant-input="int"></label>
|
|
</div>
|
|
<div class="constant-value-editor" data-constant-control="bool" hidden>
|
|
<span class="field-label">Значение</span>
|
|
<div class="segmented constant-bool-control" role="group" aria-label="Значение константы">
|
|
<button type="button" aria-pressed="true" data-constant-bool="1"><i class="ti ti-check"></i>Да</button>
|
|
<button type="button" aria-pressed="false" data-constant-bool="0"><i class="ti ti-x"></i>Нет</button>
|
|
</div>
|
|
</div>
|
|
<div class="constant-value-editor" data-constant-control="select" hidden>
|
|
<label class="field"><span class="field-label">Выбранное значение</span><select class="select" data-constant-input="select"></select></label>
|
|
<div class="constant-value-empty" data-constant-select-empty hidden><i class="ti ti-alert-circle"></i><span>Сначала добавьте варианты во вкладке «Настройки».</span></div>
|
|
</div>
|
|
<div class="constant-value-editor" data-constant-control="tags" hidden>
|
|
<label class="field"><span class="field-label">Значения</span><textarea class="textarea mono" rows="5" data-constant-input="tags" placeholder="Одно значение на строку"></textarea></label>
|
|
</div>
|
|
<div class="constant-thumbnail-tools" data-thumbnail-preset-tools hidden>
|
|
<span class="icon-tile" style="--tile-bg:var(--blue-100);--tile-fg:var(--blue-600)"><i class="ti ti-photo-search"></i></span>
|
|
<div class="constant-thumbnail-tools-copy">
|
|
<b>Собрать используемые размеры</b>
|
|
<p>Проверит файлы темы, ядра, установленных модулей и текстовые данные текущей БД. Найденные пресеты заменят содержимое поля, но сохранятся только после нажатия «Сохранить».</p>
|
|
<div class="constant-thumbnail-result" data-thumbnail-preset-result hidden></div>
|
|
</div>
|
|
<button class="btn btn-secondary" type="button" data-thumbnail-preset-scan><i class="ti ti-scan"></i>Собрать</button>
|
|
</div>
|
|
<p class="constant-value-hint" data-constant-value-hint></p>
|
|
</div>
|
|
<div class="constant-editor-panel" data-constant-editor-panel="settings" hidden>
|
|
<div class="settings-pagination-grid">
|
|
<label class="field">
|
|
<span class="field-label">Тип значения</span>
|
|
<select class="select" name="type" data-constant-type>
|
|
{% for type, label in constant_type_labels %}<option value="{{ type }}">{{ label }}</option>{% endfor %}
|
|
</select>
|
|
</label>
|
|
<label class="field">
|
|
<span class="field-label">Группа</span>
|
|
<select class="select" name="group_code">
|
|
{% for code, label in constant_group_labels %}
|
|
<option value="{{ code }}">{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</label>
|
|
<label class="field"><span class="field-label">Порядок</span><input class="input" type="number" name="sort_order" value="100"></label>
|
|
<label class="field settings-field-wide"><span class="field-label">Описание</span><textarea class="textarea" name="description" rows="4"></textarea></label>
|
|
<label class="field settings-field-wide" data-constant-options-field hidden><span class="field-label">Варианты выбора</span><textarea class="textarea mono" name="options" rows="6" placeholder="one two three" data-constant-options></textarea><small class="field-help">По одному варианту на строку.</small></label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="drawer-footer">
|
|
<button class="btn btn-ghost" type="button" data-close-drawer>Отмена</button>
|
|
<button class="btn btn-primary" type="submit" style="margin-left:auto"><i class="ti ti-check"></i>Сохранить</button>
|
|
</div>
|
|
</form>
|
|
</aside>
|
|
{% endif %}
|
|
|
|
{% if can_manage and settings_page == 'paginations' %}
|
|
<aside class="drawer drawer-right drawer-lg" id="paginationDrawer" role="dialog" aria-modal="true" aria-labelledby="paginationDrawerTitle" hidden>
|
|
<div class="drawer-header">
|
|
<h3 id="paginationDrawerTitle">Шаблон пагинации</h3>
|
|
<button class="btn btn-ghost btn-icon" data-close-drawer aria-label="Закрыть"><i class="ti ti-x"></i></button>
|
|
</div>
|
|
<form id="paginationForm" data-base="{{ ADMINX_BASE }}">
|
|
<input type="hidden" name="_csrf" value="{{ csrf_token }}">
|
|
<input type="hidden" name="id" value="">
|
|
<div class="drawer-body">
|
|
<div class="settings-pagination-grid">
|
|
{% set pagination_fields = {
|
|
'pagination_name': 'Название',
|
|
'pagination_box': 'Контейнер навигации',
|
|
'pagination_link_box': 'Контейнер ссылки',
|
|
'pagination_active_link_box': 'Контейнер активной ссылки',
|
|
'pagination_link_template': 'Шаблон ссылки',
|
|
'pagination_link_active_template': 'Шаблон активной ссылки',
|
|
'pagination_separator_box': 'Контейнер разделителя',
|
|
'pagination_separator_label': 'Метка разделителя',
|
|
'pagination_start_label': 'Метка первой',
|
|
'pagination_end_label': 'Метка последней',
|
|
'pagination_next_label': 'Метка следующей',
|
|
'pagination_prev_label': 'Метка предыдущей'
|
|
} %}
|
|
{% for key, label in pagination_fields %}
|
|
<label class="field {{ key in ['pagination_box','pagination_link_template','pagination_link_active_template'] ? 'settings-field-wide' : '' }}">
|
|
<span class="field-label">{{ label }}</span>
|
|
{% if key in ['pagination_box','pagination_link_template','pagination_link_active_template'] %}
|
|
<textarea class="textarea" name="{{ key }}" rows="3"></textarea>
|
|
{% else %}
|
|
<input class="input" name="{{ key }}" value="">
|
|
{% endif %}
|
|
<span class="field-hint mono">{{ key }}</span>
|
|
</label>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
<div class="drawer-footer">
|
|
<button class="btn btn-ghost" type="button" data-close-drawer>Отмена</button>
|
|
<button class="btn btn-primary" type="submit" style="margin-left:auto"><i class="ti ti-check"></i>Сохранить</button>
|
|
</div>
|
|
</form>
|
|
</aside>
|
|
{% endif %}
|
|
|
|
{% if can_manage and settings_page == 'main' %}
|
|
<div class="settings-savebar" id="settingsSavebar" hidden>
|
|
<div class="text-secondary text-sm">Есть несохранённые изменения</div>
|
|
<button class="btn btn-ghost btn-sm" type="button" data-settings-reset>Сбросить</button>
|
|
<button class="btn btn-primary btn-sm" type="submit" form="settingsForm"><i class="ti ti-check"></i>Сохранить</button>
|
|
</div>
|
|
{% endif %}
|
|
|
|
</div>
|
|
{% endblock %}
|