<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Администрирование</title>
  <link rel="stylesheet" href="/static/style.css" />
  <link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
  <link rel="icon" type="image/png" href="/static/favicon.png" />
<!-- Yandex.Metrika counter -->
<script type="text/javascript">
    (function(m,e,t,r,i,k,a){
        m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
        m[i].l=1*new Date();
        for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
        k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
    })(window, document,"script","https://mc.yandex.ru/metrika/tag.js?id=109119977", "ym");
    ym(109119977, "init", {ssr:true, webvisor:true, clickmap:true, ecommerce:"dataLayer", referrer: document.referrer, url: location.href, accurateTrackBounce:true, trackLinks:true});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/109119977" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
</head>
<body>
  <header class="header">
    <div style="display:flex; align-items:center; gap:0.6rem;">
      <a href="https://4mont.ru"><img src="/static/logo.png?v=2" alt="MONT" class="header-logo" /></a>
      <div>MONT - инфрастуктурный полигон | Админ: {{ admin.username }}</div>
    </div>
    <a href="/" class="btn-link secondary">Главная панель</a>
  </header>

  <main class="admin-layout">
    <section class="panel">
      <div class="admin-intro">
        Основной режим: <b>WEB</b>. Пользователь выбирает сервис, а портал открывает нужный URL в заранее прогретом браузере.
        Для WEB используется <b>общий пул</b> горячих контейнеров с автодоращиванием по занятости.
      </div>
    </section>
    <section class="panel">
      <div class="tab-row">
        <button class="tab-btn" data-tab="users" onclick="showTab('users')">Users</button>
        <button class="tab-btn" data-tab="web" onclick="showTab('web')">WEB</button>
        <button class="tab-btn" data-tab="rdp" onclick="showTab('rdp')">RDP</button>
        <button class="tab-btn" data-tab="categories" onclick="showTab('categories')">Categories</button>
        <button class="tab-btn" data-tab="stats" onclick="showTab('stats')">Stats</button>
      </div>
    </section>

    <section id="tab-users" class="panel admin-tab">
      <h3>Пользователи</h3>
      <div class="split">
        <div>
          <div class="list-title">Список пользователей</div>
          <input class="list-search" id="users_search" placeholder="Поиск пользователя..." oninput="filterList('users_search', '#users_list .user-item')" />
          <div class="list-box" id="users_list">
            {% for u in users %}
              <button class="list-item user-item" data-user-id="{{u.id}}" data-filter="{{u.username|lower}}" onclick='selectUser({{u.id}}, {{u.username|tojson}}, {{u.active|tojson}}, {{u.is_admin|tojson}}, {{u.expires_at.isoformat()|tojson}}, {{u.first_name|tojson}}, {{u.last_name|tojson}})'>
                <div>{{u.username}}{% if u.first_name or u.last_name %} <small style="opacity:.6">— {{ (u.first_name + ' ' + u.last_name)|trim }}</small>{% endif %}</div>
                <small class="user-days" data-exp="{{u.expires_at.isoformat()}}"></small>
              </button>
            {% endfor %}
          </div>
        </div>

        <div>
          <div class="list-title">Редактирование пользователя</div>
          <div class="form-grid">
            <input id="u_id" type="hidden" />
            <input id="u_name" placeholder="username" />
            <input id="u_first_name" placeholder="Имя" />
            <input id="u_last_name" placeholder="Фамилия" />
            <input id="u_exp" type="date" required />
            <input id="u_pwd" placeholder="new password (optional)" type="password" />
            <select id="u_active"><option value="true">active</option><option value="false">inactive</option></select>
            <select id="u_admin"><option value="false">user</option><option value="true">admin</option></select>
          </div>
          <div class="actions">
            <button onclick="saveUser()">Save</button>
            <button onclick="deleteUser()">Delete</button>
            <button onclick="clearUserForm()">Clear</button>
          </div>

          <div style="margin-top:1rem;">
            <div class="list-title">ACL выбранного пользователя</div>
            <div class="acl-grid">
              {% for s in services %}
                <label><input type="checkbox" class="acl_service" value="{{s.id}}" data-stype="{{s.type.value}}" /> {{s.name}} ({{s.slug}})<span class="acl-owner"></span></label>
              {% endfor %}
            </div>
            <button onclick="saveAclForSelectedUser()">Save ACL</button>
          </div>

          <hr>
          <div class="list-title">Добавить пользователя</div>
          <div class="form-grid">
            <input id="new_u_name" placeholder="username" />
            <input id="new_u_first_name" placeholder="Имя" />
            <input id="new_u_last_name" placeholder="Фамилия" />
            <input id="new_u_pwd" placeholder="password" type="password" />
            <input id="new_u_exp" type="date" required />
            <select id="new_u_active"><option value="true">active</option><option value="false">inactive</option></select>
            <select id="new_u_admin"><option value="false">user</option><option value="true">admin</option></select>
          </div>
          <button onclick="createUser()">Add User</button>
        </div>
      </div>
    </section>

    <section id="tab-web" class="panel admin-tab" style="display:none;">
      <h3>WEB сервисы</h3>
      <div class="admin-intro">
        <b>Как читать статусы:</b> прогрето X / Y — это состояние общего WEB-пула, занято: N — активные сессии конкретного сервиса.
      </div>
      <div class="panel" style="padding:0.8rem;">
        <div class="list-title">Настройки общего WEB pool</div>
        <div class="actions">
          <input id="web_pool_size" type="number" min="0" value="{{ web_pool_size }}" style="max-width:220px;" />
          <button onclick="saveWebPoolSize()">Save WEB pool size</button>
        </div>
        <small>Автодоращивание: active + {{ web_pool_buffer }}</small>
      </div>
      <div class="summary-strip">
        <div class="summary-card">
          <div class="summary-label">WEB сервисов</div>
          <div class="summary-value">{{ web_totals.services }}</div>
        </div>
        <div class="summary-card">
          <div class="summary-label">Всего прогрето</div>
          <div class="summary-value">{{ web_totals.running }} / {{ web_totals.desired }}</div>
        </div>
        <div class="summary-card">
          <div class="summary-label">Сейчас занято</div>
          <div class="summary-value">{{ web_totals.active_sessions }}</div>
        </div>
      </div>
      <div class="split">
        <div>
          <div class="list-title">Список WEB</div>
          <input class="list-search" id="web_search" placeholder="Поиск WEB сервиса..." oninput="filterList('web_search', '#web_list .web-item')" />
          <div class="list-box" id="web_list">
            {% for s in web_services %}
              <button class="list-item service-row web-item" data-service-id="{{s.id}}" data-filter="{{(s.name ~ ' ' ~ s.slug)|lower}}" onclick='selectWebService({{s.id}}, {{s.name|tojson}}, {{s.slug|tojson}}, {{s.target|tojson}}, {{s.comment|tojson}}, {{s.icon_path|tojson}}, {{s.active|tojson}}, {{s.svc_login|tojson}}, {{s.svc_password|tojson}}, {{s.svc_cred_hint|tojson}})'>
                <img class="service-thumb" src="{{ s.icon_path or '/static/service-placeholder.svg' }}" alt="icon" />
                <div>
                  <div>{{s.name}}</div>
                  <small>
                    <span class="status-dot status-{{ service_health[s.id].health }}"></span>
                    {{service_health[s.id].health}} | прогрето: {{service_health[s.id].running}} / {{service_health[s.id].desired}} | занято: {{service_health[s.id].active_sessions}}
                  </small>
                </div>
              </button>
            {% endfor %}
          </div>
        </div>
        <div>
          <div class="list-title">Редактирование WEB</div>
          <div class="field-help">Для пользователей показывается только название и описание. Технический тип скрыт.</div>
          <div class="form-grid labelled-grid">
            <input id="w_id" type="hidden" />
            <input id="w_slug" type="hidden" />

            <label class="field-col">
              <span>Название сервиса</span>
              <input id="w_name" placeholder="Например: CRM, Router, Wiki" oninput="autogenSlug('w_name','w_slug')" />
            </label>

            <label class="field-col">
              <span>URL для открытия</span>
              <input id="w_target" placeholder="Например: https://crm.company.local" />
            </label>

            <label class="field-col">
              <span>Описание для пользователя</span>
              <textarea id="w_comment" placeholder="Коротко: что это за сервис"></textarea>
            </label>

            <label class="field-col">
              <span>Логин сервиса (показывается на карточке)</span>
              <input id="w_svc_login" placeholder="Например: admin" />
            </label>

            <label class="field-col">
              <span>Пароль сервиса (показывается на карточке)</span>
              <input id="w_svc_password" placeholder="Пароль для входа в сервис" />
            </label>

            <label class="field-col">
              <span>Подсказка к логину/паролю (необязательно)</span>
              <input id="w_svc_cred_hint" placeholder="Например: учётная запись гостя, сбрасывается раз в месяц" />
            </label>

            <label class="field-col">
              <span>Статус</span>
              <select id="w_active"><option value="true">active</option><option value="false">inactive</option></select>
            </label>
          </div>
          <div class="list-title">Категории</div>
          <div class="acl-grid compact-grid" id="w_categories">
            {% for c in categories %}
              <label><input type="checkbox" class="w_cat" value="{{c.id}}" /> {{c.name}}</label>
            {% endfor %}
          </div>
          <div class="actions">
            <button onclick="saveWebService()">Save</button>
            <button onclick="deleteService('w_id')">Delete</button>
            <button onclick="clearWebForm()">Clear</button>
          </div>

          <div class="health-box" id="w_health_box" style="display:none; margin-top:1rem;">
            <div class="list-title">Container Health</div>
            <div id="w_health_summary"></div>
            <div class="actions" style="margin-top:0.5rem;">
              <button onclick="refreshSelectedServiceStatus('web')">Refresh status</button>
              <button id="w_health_toggle" onclick="toggleHealthDetails('w')">Показать детали контейнеров</button>
            </div>
            <div class="container-table-wrap" id="w_health_table_wrap" style="display:none;">
              <table class="admin-table" id="w_health_table">
                <thead>
                  <tr>
                    <th>name</th>
                    <th>state</th>
                    <th>status</th>
                    <th>image</th>
                    <th>labels</th>
                  </tr>
                </thead>
                <tbody></tbody>
              </table>
            </div>
          </div>

          <div class="icon-box" style="margin-top:1rem;">
            <div class="list-title">Иконка сервиса</div>
            <div class="icon-row">
              <img id="w_icon_preview" class="service-icon-preview" src="/static/service-placeholder.svg" alt="icon" />
              <div>
                <input id="w_icon_file" type="file" accept="image/png,image/jpeg,image/webp" onchange="autoUploadIcon('w')" />
                <div class="actions" style="margin-top:0.4rem;">
                  <button onclick="pasteServiceIcon('w')">Paste</button>
                  <button onclick="removeServiceIcon('w')">Remove icon</button>
                </div>
              </div>
            </div>
          </div>

          <hr>
          <div class="list-title">Добавить WEB</div>
          <div class="field-help">Горячий пул задается сверху и общий для всех WEB сервисов.</div>
          <div class="form-grid labelled-grid">
            <input id="new_w_slug" type="hidden" />

            <label class="field-col">
              <span>Название сервиса</span>
              <input id="new_w_name" placeholder="Например: CRM, Router, Wiki" oninput="autogenSlug('new_w_name','new_w_slug')" />
            </label>

            <label class="field-col">
              <span>URL для открытия</span>
              <input id="new_w_target" placeholder="Например: https://crm.company.local" />
            </label>

            <label class="field-col">
              <span>Описание для пользователя</span>
              <textarea id="new_w_comment" placeholder="Коротко: что это за сервис"></textarea>
              <input id="new_w_svc_login" placeholder="Логин сервиса (необязательно)" />
              <input id="new_w_svc_password" placeholder="Пароль сервиса (необязательно)" />
              <input id="new_w_svc_cred_hint" placeholder="Подсказка к логину/паролю (необязательно)" />
            </label>

            <label class="field-col">
              <span>Статус</span>
              <select id="new_w_active"><option value="true">active</option><option value="false">inactive</option></select>
            </label>
          </div>
          <div class="list-title">Категории</div>
          <div class="acl-grid compact-grid" id="new_w_categories">
            {% for c in categories %}
              <label><input type="checkbox" class="new_w_cat" value="{{c.id}}" /> {{c.name}}</label>
            {% endfor %}
          </div>
          <button onclick="createWebService()">Add WEB</button>
        </div>
      </div>
    </section>

    <section id="tab-rdp" class="panel admin-tab" style="display:none;">
      <h3>RDP сервисы</h3>
      <div class="split">
        <div>
          <div class="list-title">Список RDP</div>
          <input class="list-search" id="rdp_search" placeholder="Поиск RDP сервиса..." oninput="filterList('rdp_search', '#rdp_list .rdp-item')" />
          <div class="list-box" id="rdp_list">
            {% for s in rdp_services %}
              <button class="list-item service-row rdp-item" data-service-id="{{s.id}}" data-filter="{{(s.name ~ ' ' ~ s.slug)|lower}}" onclick='selectRdpService({{s.id}}, {{s.name|tojson}}, {{s.slug|tojson}}, {{s.target|tojson}}, {{s.comment|tojson}}, {{s.icon_path|tojson}}, {{s.active|tojson}}, {{s.warm_pool_size}}, {{s.svc_login|tojson}}, {{s.svc_password|tojson}}, {{s.svc_cred_hint|tojson}})'>
                <img class="service-thumb" src="{{ s.icon_path or '/static/service-placeholder.svg' }}" alt="icon" />
                <div>
                  <div>{{s.name}}</div>
                  <small>
                    <span class="status-dot status-{{ service_health[s.id].health }}"></span>
                    {{service_health[s.id].health}} | {{service_health[s.id].running}} / {{service_health[s.id].desired}} | active: {{service_health[s.id].active_sessions}}
                  </small>
                </div>
              </button>
            {% endfor %}
          </div>
        </div>
        <div>
          <div class="list-title">Редактирование RDP</div>
          <div class="field-help">Используйте поля host/port/user. Поле target собирается автоматически.</div>
          <div class="form-grid">
            <input id="r_id" type="hidden" />
            <input id="r_name" placeholder="Название сервиса (что увидит user)" oninput="autogenSlug('r_name','r_slug')" />
            <input id="r_slug" placeholder="Системный slug" />
            <input id="r_host" placeholder="RDP host (например 192.168.1.60)" oninput="buildRdpTarget('r')" />
            <input id="r_port" type="number" min="1" max="65535" placeholder="RDP порт, обычно 3389" oninput="buildRdpTarget('r')" />
            <input id="r_domain" placeholder="Домен (опционально)" oninput="buildRdpTarget('r')" />
            <select id="r_sec" onchange="buildRdpTarget('r')">
              <option value="">auto</option>
              <option value="nla">nla</option>
              <option value="tls">tls</option>
              <option value="rdp">rdp</option>
            </select>
            <input id="r_target" placeholder="Собранный target (авто)" readonly style="background:rgba(255,255,255,.05);color:#888;cursor:default" />
            <textarea id="r_comment" placeholder="Описание для пользователя"></textarea>
            <input id="r_svc_login" placeholder="Логин сервиса (показывается на карточке)" />
            <input id="r_svc_password" placeholder="Пароль сервиса (показывается на карточке)" />
            <input id="r_svc_cred_hint" placeholder="Подсказка к логину/паролю (необязательно)" />
            <input id="r_pool" type="number" min="0" placeholder="Количество заранее прогретых слотов" />
            <select id="r_active"><option value="true">active</option><option value="false">inactive</option></select>
          </div>
          <div class="list-title">Категории</div>
          <div class="acl-grid compact-grid" id="r_categories">
            {% for c in categories %}
              <label><input type="checkbox" class="r_cat" value="{{c.id}}" /> {{c.name}}</label>
            {% endfor %}
          </div>
          <div class="actions">
            <button onclick="saveRdpService()">Save</button>
            <button onclick="prewarmNow('r_id')">Prewarm now</button>
            <button onclick="deleteService('r_id')">Delete</button>
            <button onclick="clearRdpForm()">Clear</button>
          </div>

          <div class="health-box" id="r_health_box" style="display:none; margin-top:1rem;">
            <div class="list-title">Container Health</div>
            <div id="r_health_summary"></div>
            <div class="actions" style="margin-top:0.5rem;">
              <button onclick="refreshSelectedServiceStatus('rdp')">Refresh status</button>
            </div>
            <div class="container-table-wrap">
              <table class="admin-table" id="r_health_table">
                <thead>
                  <tr>
                    <th>name</th>
                    <th>state</th>
                    <th>status</th>
                    <th>image</th>
                    <th>labels</th>
                  </tr>
                </thead>
                <tbody></tbody>
              </table>
            </div>
          </div>

          <div class="icon-box" style="margin-top:1rem;">
            <div class="list-title">Иконка сервиса</div>
            <div class="icon-row">
              <img id="r_icon_preview" class="service-icon-preview" src="/static/service-placeholder.svg" alt="icon" />
              <div>
                <input id="r_icon_file" type="file" accept="image/png,image/jpeg,image/webp" onchange="autoUploadIcon('r')" />
                <div class="actions" style="margin-top:0.4rem;">
                  <button onclick="pasteServiceIcon('r')">Paste</button>
                  <button onclick="removeServiceIcon('r')">Remove icon</button>
                </div>
              </div>
            </div>
          </div>

          <div id="rdp_slots_box" style="display:none; margin-top:1rem;">
            <div class="list-title">RDP пользователи (слоты пула)</div>
            <div class="field-help">Каждый пользователь — отдельный контейнер. Пользователи портала берут свободный слот.</div>
            <table class="admin-table" id="rdp_slots_table" style="margin-bottom:.7rem">
              <thead><tr><th>Логин RDP</th><th>Контейнер</th><th>Статус</th><th>Занят</th><th></th></tr></thead>
              <tbody></tbody>
            </table>
            <div style="display:flex;gap:.5rem;align-items:flex-end;flex-wrap:wrap;">
              <input id="new_slot_user" placeholder="Логин RDP" style="max-width:160px" />
              <input id="new_slot_pass" type="password" placeholder="Пароль RDP" style="max-width:160px" />
              <button onclick="addRdpSlot()">+ Добавить слот</button>
            </div>
          </div>

          <hr>
          <div class="list-title">Добавить RDP</div>
          <div class="field-help">Для большинства кейсов достаточно host + user + password.</div>
          <div class="form-grid">
            <input id="new_r_name" placeholder="Название сервиса" oninput="autogenSlug('new_r_name','new_r_slug')" />
            <input id="new_r_slug" placeholder="Системный slug" />
            <input id="new_r_host" placeholder="RDP host" oninput="buildRdpTarget('new_r')" />
            <input id="new_r_port" type="number" min="1" max="65535" placeholder="RDP порт (3389)" oninput="buildRdpTarget('new_r')" />
            <input id="new_r_domain" placeholder="Домен (опционально)" oninput="buildRdpTarget('new_r')" />
            <select id="new_r_sec" onchange="buildRdpTarget('new_r')">
              <option value="">auto</option>
              <option value="nla">nla</option>
              <option value="tls">tls</option>
              <option value="rdp">rdp</option>
            </select>
            <input id="new_r_target" placeholder="Собранный target (авто)" readonly style="background:rgba(255,255,255,.05);color:#888;cursor:default" />
            <textarea id="new_r_comment" placeholder="Описание для пользователя"></textarea>
            <input id="new_r_svc_login" placeholder="Логин сервиса (необязательно)" />
            <input id="new_r_svc_password" placeholder="Пароль сервиса (необязательно)" />
            <input id="new_r_svc_cred_hint" placeholder="Подсказка к логину/паролю (необязательно)" />
            <input id="new_r_pool" type="number" min="0" value="1" placeholder="Количество прогретых слотов" />
            <select id="new_r_active"><option value="true">active</option><option value="false">inactive</option></select>
          </div>
          <div class="list-title">Категории</div>
          <div class="acl-grid compact-grid" id="new_r_categories">
            {% for c in categories %}
              <label><input type="checkbox" class="new_r_cat" value="{{c.id}}" /> {{c.name}}</label>
            {% endfor %}
          </div>
          <button onclick="createRdpService()">Add RDP</button>
        </div>
      </div>
    </section>

    <section id="tab-categories" class="panel admin-tab" style="display:none;">
      <h3>Категории</h3>
      <div class="admin-intro">
        Добавляйте и удаляйте категории, чтобы аккуратно группировать сервисы на главной панели.
      </div>
      <div class="split">
        <div>
          <div class="list-title">Существующие категории</div>
          <div class="list-box" id="categories_list">
            {% for c in categories %}
              <div class="list-item category-item-row">
                <div>
                  <div>{{ c.name }}</div>
                  <small>{{ c.slug }}</small>
                </div>
                <button onclick='deleteCategory({{ c.id }}, {{ c.name|tojson }})'>Delete</button>
              </div>
            {% else %}
              <div class="list-item">Категории пока не созданы</div>
            {% endfor %}
          </div>
        </div>
        <div>
          <div class="list-title">Добавить категорию</div>
          <div class="form-grid">
            <input id="cat_name" placeholder="Название категории" oninput="autogenSlug('cat_name','cat_slug')" />
            <input id="cat_slug" placeholder="slug (латиницей)" />
          </div>
          <button onclick="createCategory()">Add Category</button>
        </div>
      </div>
    </section>

    <section id="tab-stats" class="panel admin-tab" style="display:none;">
      <h3>Статистика открытий</h3>
      <div class="admin-intro">
        Здесь видно кто, когда и какой сервис открывал, а также кто сейчас онлайн.
      </div>
      <div class="split">
        <div>
          <div class="list-title">Топ запусков (user x service)</div>
          <div class="container-table-wrap" style="max-height:520px;">
            <table class="admin-table">
              <thead>
                <tr>
                  <th>user</th>
                  <th>service</th>
                  <th>slug</th>
                  <th>opens</th>
                </tr>
              </thead>
              <tbody>
                {% for row in open_stats %}
                  <tr>
                    <td>{{ row.username }}</td>
                    <td>{{ row.service_name }}</td>
                    <td>{{ row.service_slug }}</td>
                    <td>{{ row.opens }}</td>
                  </tr>
                {% else %}
                  <tr><td colspan="4">Нет данных</td></tr>
                {% endfor %}
              </tbody>
            </table>
          </div>
        </div>
        <div>
          <div class="list-title">Последние открытия</div>
          <div class="container-table-wrap" style="max-height:520px;">
            <table class="admin-table">
              <thead>
                <tr>
                  <th>user</th>
                  <th>service</th>
                  <th>status</th>
                  <th>created</th>
                  <th>last_access</th>
                  <th>session_id</th>
                </tr>
              </thead>
              <tbody>
                {% for row in recent_sessions %}
                  <tr>
                    <td>{{ row.username }}</td>
                    <td>{{ row.service_name }} ({{ row.service_slug }})</td>
                    <td>{{ row.status }}</td>
                    <td>{{ row.created_at }}</td>
                    <td>{{ row.last_access_at }}</td>
                    <td>{{ row.id }}</td>
                  </tr>
                {% else %}
                  <tr><td colspan="6">Нет данных</td></tr>
                {% endfor %}
              </tbody>
            </table>
          </div>
        </div>
      </div>
      <div style="margin-top:0.8rem;">
        <div class="list-title">Сейчас онлайн (активные сессии)</div>
        <div class="container-table-wrap" style="max-height:420px;">
          <table class="admin-table">
            <thead>
              <tr>
                <th>user</th>
                <th>service</th>
                <th>type</th>
                <th>container</th>
                <th>last_access</th>
                <th>created</th>
                <th>session_id</th>
              </tr>
            </thead>
            <tbody>
              {% for row in online_sessions %}
                <tr>
                  <td>{{ row.username }}</td>
                  <td>{{ row.service_name }} ({{ row.service_slug }})</td>
                  <td>{{ row.service_type }}</td>
                  <td>{{ row.container_id }}</td>
                  <td>{{ row.last_access_at }}</td>
                  <td>{{ row.created_at }}</td>
                  <td>{{ row.id }}</td>
                </tr>
              {% else %}
                <tr><td colspan="7">Сейчас нет активных сессий</td></tr>
              {% endfor %}
            </tbody>
          </table>
        </div>
      </div>
    </section>
  </main>

  <script>
    const csrf = "{{ csrf_token }}";
    const aclMap = {{ acl | tojson }};
    const serviceCategoryMap = {{ service_category_map | tojson }};
    const rdpSlotsMap = {{ rdp_slots | tojson }};
    const placeholderIcon = '/static/service-placeholder.svg';
    let activeTab = 'users';

    function showTab(tab) {
      activeTab = tab;
      localStorage.setItem('admin_active_tab', tab);
      document.querySelectorAll('.admin-tab').forEach(x => x.style.display = 'none');
      document.getElementById(`tab-${tab}`).style.display = 'block';
      document.querySelectorAll('.tab-btn').forEach(x => x.classList.remove('active-tab'));
      document.querySelector(`.tab-btn[data-tab="${tab}"]`).classList.add('active-tab');
    }

    function filterList(inputId, itemsSelector) {
      const needle = (document.getElementById(inputId)?.value || '').trim().toLowerCase();
      document.querySelectorAll(itemsSelector).forEach((item) => {
        const hay = (item.dataset.filter || item.textContent || '').toLowerCase();
        item.style.display = hay.includes(needle) ? '' : 'none';
      });
    }

    function markSelected(selector, attr, value) {
      document.querySelectorAll(selector).forEach((el) => {
        el.classList.toggle('selected-item', String(el.getAttribute(attr)) === String(value));
      });
    }

    function checkedCategoryIds(selector) {
      return [...document.querySelectorAll(selector)]
        .filter((x) => x.checked)
        .map((x) => parseInt(x.value, 10));
    }

    function setCategoryChecks(selector, categoryIds) {
      const setIds = new Set((categoryIds || []).map((x) => parseInt(x, 10)));
      document.querySelectorAll(selector).forEach((box) => {
        box.checked = setIds.has(parseInt(box.value, 10));
      });
    }

    function slugifyRu(text) {
      const map = {
        'а':'a','б':'b','в':'v','г':'g','д':'d','е':'e','ё':'e','ж':'zh','з':'z','и':'i','й':'y','к':'k','л':'l','м':'m','н':'n','о':'o','п':'p','р':'r','с':'s','т':'t','у':'u','ф':'f','х':'h','ц':'ts','ч':'ch','ш':'sh','щ':'sch','ъ':'','ы':'y','ь':'','э':'e','ю':'yu','я':'ya'
      };
      const lower = (text || '').toLowerCase();
      let out = '';
      for (const ch of lower) out += map[ch] !== undefined ? map[ch] : ch;
      return out.replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').replace(/-{2,}/g, '-');
    }

    function autogenSlug(srcId, dstId) {
      const src = document.getElementById(srcId);
      const dst = document.getElementById(dstId);
      if (!dst.value || !dst.dataset.touched) dst.value = slugifyRu(src.value);
    }

    function dateFromIso(iso) {
      return (iso || '').slice(0, 10);
    }

    function expiryToApi(dateStr) {
      if (!dateStr) return '';
      return `${dateStr}T23:59:59+00:00`;
    }

    function daysLeft(expIso) {
      const now = new Date();
      const exp = new Date(expIso);
      const diff = Math.ceil((exp - now) / (1000 * 60 * 60 * 24));
      if (Number.isNaN(diff)) return 'дата некорректна';
      if (diff < 0) return `просрочен ${Math.abs(diff)} дн`;
      return `до деактивации: ${diff} дн`;
    }

    function renderUserDays() {
      document.querySelectorAll('.user-days').forEach(el => {
        el.textContent = daysLeft(el.dataset.exp || '');
      });
    }

    async function api(url, method, body) {
      const r = await fetch(url, {
        method,
        credentials: 'include',
        headers: {'Content-Type': 'application/json', 'X-CSRF-Token': csrf},
        body: body !== undefined ? JSON.stringify(body) : undefined,
      });
      if (!r.ok) {
        const t = await r.text();
        alert(`Request failed: ${r.status} ${t}`);
        throw new Error(t);
      }
      return r.status === 204 ? {} : r.json();
    }

    async function apiForm(url, formData) {
      const r = await fetch(url, {
        method: 'POST',
        credentials: 'include',
        headers: {'X-CSRF-Token': csrf},
        body: formData,
      });
      if (!r.ok) {
        const t = await r.text();
        alert(`Request failed: ${r.status} ${t}`);
        throw new Error(t);
      }
      return r.json();
    }

    function selectUser(id, username, active, isAdmin, expiresIso, firstName, lastName) {
      document.getElementById('u_id').value = id;
      document.getElementById('u_name').value = username;
      document.getElementById('u_first_name').value = firstName || '';
      document.getElementById('u_last_name').value = lastName || '';
      document.getElementById('u_exp').value = dateFromIso(expiresIso);
      document.getElementById('u_pwd').value = '';
      document.getElementById('u_active').value = String(active);
      document.getElementById('u_admin').value = String(isAdmin);
      markSelected('.user-item', 'data-user-id', id);
      syncAclForSelectedUser();
    }

    async function createUser() {
      const expDate = document.getElementById('new_u_exp').value;
      if (!expDate) return alert('Выберите дату деактивации');
      await api('/api/admin/users', 'POST', {
        username: document.getElementById('new_u_name').value,
        first_name: document.getElementById('new_u_first_name').value,
        last_name: document.getElementById('new_u_last_name').value,
        password: document.getElementById('new_u_pwd').value,
        expires_at: expiryToApi(expDate),
        active: document.getElementById('new_u_active').value === 'true',
        is_admin: document.getElementById('new_u_admin').value === 'true',
      });
      location.reload();
    }

    async function saveUser() {
      const id = document.getElementById('u_id').value;
      if (!id) return alert('Выберите пользователя');
      const expDate = document.getElementById('u_exp').value;
      if (!expDate) return alert('Выберите дату деактивации');
      const payload = {
        username: document.getElementById('u_name').value,
        first_name: document.getElementById('u_first_name').value,
        last_name: document.getElementById('u_last_name').value,
        expires_at: expiryToApi(expDate),
        active: document.getElementById('u_active').value === 'true',
        is_admin: document.getElementById('u_admin').value === 'true',
      };
      const pwd = document.getElementById('u_pwd').value.trim();
      if (pwd) payload.password = pwd;
      await api(`/api/admin/users/${id}`, 'PUT', payload);
      location.reload();
    }

    async function deleteUser() {
      const id = document.getElementById('u_id').value;
      if (!id) return alert('Выберите пользователя');
      if (!confirm('Удалить пользователя?')) return;
      await api(`/api/admin/users/${id}`, 'DELETE', {});
      location.reload();
    }

    function clearUserForm() {
      ['u_id','u_name','u_exp','u_pwd'].forEach(id => document.getElementById(id).value = '');
      document.getElementById('u_active').value = 'true';
      document.getElementById('u_admin').value = 'false';
      document.querySelectorAll('.acl_service').forEach(x => x.checked = false);
      document.querySelectorAll('.user-item').forEach((el) => el.classList.remove('selected-item'));
    }

    function syncAclForSelectedUser() {
      const userId = parseInt(document.getElementById('u_id').value || '0', 10);
      const allowed = new Set((aclMap[userId] || []));
      document.querySelectorAll('.acl_service').forEach((box) => {
        const sid = parseInt(box.value, 10);
        box.checked = allowed.has(sid);
        box.disabled = false;
        box.closest('label').style.opacity = '';
      });
    }

    async function saveAclForSelectedUser() {
      const userId = document.getElementById('u_id').value;
      if (!userId) return alert('Сначала выберите пользователя');
      const serviceIds = [...document.querySelectorAll('.acl_service:checked')].map(x => parseInt(x.value, 10));
      await api(`/api/admin/users/${userId}/acl`, 'PUT', {service_ids: serviceIds});
      location.reload();
    }

    function selectWebService(id, name, slug, target, comment, iconPath, active, svcLogin, svcPassword, svcCredHint) {
      document.getElementById('w_id').value = id;
      document.getElementById('w_name').value = name;
      document.getElementById('w_slug').value = slug;
      document.getElementById('w_target').value = target;
      document.getElementById('w_comment').value = comment || '';
      document.getElementById('w_svc_login').value = svcLogin || '';
      document.getElementById('w_svc_password').value = svcPassword || '';
      document.getElementById('w_svc_cred_hint').value = svcCredHint || '';
      document.getElementById('w_active').value = String(active);
      document.getElementById('w_icon_preview').src = iconPath || placeholderIcon;
      setCategoryChecks('.w_cat', serviceCategoryMap[id] || []);
      document.getElementById('w_health_box').style.display = 'block';
      markSelected('.web-item', 'data-service-id', id);
      refreshSelectedServiceStatus('web');
    }

    async function saveWebPoolSize() {
      const size = parseInt(document.getElementById('web_pool_size').value || '0', 10);
      await api('/api/admin/web-pool-size', 'PUT', {size});
      location.reload();
    }

    async function createWebService() {
      const slug = document.getElementById('new_w_slug').value || slugifyRu(document.getElementById('new_w_name').value);
      await api('/api/admin/services', 'POST', {
        name: document.getElementById('new_w_name').value,
        slug,
        type: 'WEB',
        target: document.getElementById('new_w_target').value,
        comment: document.getElementById('new_w_comment').value,
        svc_login: document.getElementById('new_w_svc_login').value,
        svc_password: document.getElementById('new_w_svc_password').value,
        svc_cred_hint: document.getElementById('new_w_svc_cred_hint').value,
        category_ids: checkedCategoryIds('.new_w_cat'),
        active: document.getElementById('new_w_active').value === 'true',
      });
      location.reload();
    }

    async function saveWebService() {
      const id = document.getElementById('w_id').value;
      if (!id) return alert('Выберите WEB сервис');
      const slug = document.getElementById('w_slug').value || slugifyRu(document.getElementById('w_name').value);
      await api(`/api/admin/services/${id}`, 'PUT', {
        name: document.getElementById('w_name').value,
        slug,
        type: 'WEB',
        target: document.getElementById('w_target').value,
        comment: document.getElementById('w_comment').value,
        svc_login: document.getElementById('w_svc_login').value,
        svc_password: document.getElementById('w_svc_password').value,
        svc_cred_hint: document.getElementById('w_svc_cred_hint').value,
        category_ids: checkedCategoryIds('.w_cat'),
        active: document.getElementById('w_active').value === 'true',
      });
      location.reload();
    }

    function clearWebForm() {
      ['w_id','w_name','w_slug','w_target','w_comment','w_svc_login','w_svc_password','w_svc_cred_hint'].forEach(id => document.getElementById(id).value = '');
      document.getElementById('w_active').value = 'true';
      setCategoryChecks('.w_cat', []);
      document.getElementById('w_icon_preview').src = placeholderIcon;
      document.getElementById('w_health_box').style.display = 'none';
      document.querySelectorAll('.web-item').forEach((el) => el.classList.remove('selected-item'));
    }

    function parseRdpTarget(target) {
      const raw = (target || '').trim();
      if (!raw) return {host: '', port: '3389', user: '', pass: '', domain: '', sec: ''};
      let url;
      try {
        url = raw.includes('://') ? new URL(raw) : new URL(`rdp://${raw}`);
      } catch (e) {
        return {host: '', port: '3389', user: '', pass: '', domain: '', sec: ''};
      }
      const params = url.searchParams;
      return {
        host: url.hostname || '',
        port: url.port || '3389',
        user: decodeURIComponent(url.username || params.get('u') || params.get('user') || ''),
        pass: decodeURIComponent(url.password || params.get('p') || params.get('password') || ''),
        domain: params.get('domain') || params.get('d') || '',
        sec: params.get('sec') || params.get('security') || '',
      };
    }

    function buildRdpTarget(prefix) {
      const host = (document.getElementById(`${prefix}_host`)?.value || '').trim();
      const port = (document.getElementById(`${prefix}_port`)?.value || '').trim() || '3389';
      const domain = (document.getElementById(`${prefix}_domain`)?.value || '').trim();
      const sec = (document.getElementById(`${prefix}_sec`)?.value || '').trim();
      const targetInput = document.getElementById(`${prefix}_target`);
      if (!host) return (targetInput?.value || '').trim();
      const query = new URLSearchParams();
      if (domain) query.set('domain', domain);
      if (sec) query.set('sec', sec);
      const q = query.toString();
      const target = `rdp://${host}:${port}${q ? `?${q}` : ''}`;
      if (targetInput) targetInput.value = target;
      return target;
    }

    function renderRdpSlots(serviceId) {
      const box = document.getElementById('rdp_slots_box');
      const tbody = document.querySelector('#rdp_slots_table tbody');
      const slots = rdpSlotsMap[serviceId] || [];
      box.style.display = 'block';
      tbody.innerHTML = '';
      if (!slots.length) {
        tbody.innerHTML = '<tr><td colspan="5" style="color:#888">Нет слотов. Добавьте RDP пользователей ниже.</td></tr>';
        return;
      }
      slots.forEach(s => {
        const statusBadge = s.running
          ? '<span style="color:#4caf50">&#9679; running</span>'
          : '<span style="color:#e07b39">&#9679; stopped</span>';
        const occupiedCell = s.occupied_username
          ? `<span style="color:#e07b39">${s.occupied_username}</span>`
          : '<span style="color:#888">свободен</span>';
        const tr = document.createElement('tr');
        tr.innerHTML = `<td>${s.rdp_username}</td><td style="font-size:.8em;color:#888">${s.container_name||'—'}</td><td>${statusBadge}</td><td>${occupiedCell}</td><td><button onclick="deleteRdpSlot(${s.id})">✕</button></td>`;
        tbody.appendChild(tr);
      });
    }

    async function addRdpSlot() {
      const serviceId = document.getElementById('r_id').value;
      if (!serviceId) return alert('Выберите RDP сервис');
      const rdp_username = document.getElementById('new_slot_user').value.trim();
      const rdp_password = document.getElementById('new_slot_pass').value.trim();
      if (!rdp_username) return alert('Введите логин RDP');
      await api(`/api/admin/services/${serviceId}/rdp-slots`, 'POST', {rdp_username, rdp_password});
      location.reload();
    }

    async function deleteRdpSlot(slotId) {
      if (!confirm('Удалить RDP слот и остановить контейнер?')) return;
      await api(`/api/admin/rdp-slots/${slotId}`, 'DELETE', {});
      location.reload();
    }

    function selectRdpService(id, name, slug, target, comment, iconPath, active, pool, svcLogin, svcPassword, svcCredHint) {
      const cfg = parseRdpTarget(target);
      document.getElementById('r_id').value = id;
      document.getElementById('r_name').value = name;
      document.getElementById('r_slug').value = slug;
      document.getElementById('r_target').value = target;
      document.getElementById('r_host').value = cfg.host;
      document.getElementById('r_port').value = cfg.port;
      document.getElementById('r_domain').value = cfg.domain;
      document.getElementById('r_sec').value = cfg.sec;
      document.getElementById('r_comment').value = comment || '';
      document.getElementById('r_svc_login').value = svcLogin || '';
      document.getElementById('r_svc_password').value = svcPassword || '';
      document.getElementById('r_svc_cred_hint').value = svcCredHint || '';
      document.getElementById('r_active').value = String(active);
      document.getElementById('r_pool').value = pool;
      setCategoryChecks('.r_cat', serviceCategoryMap[id] || []);
      document.getElementById('r_icon_preview').src = iconPath || placeholderIcon;
      document.getElementById('r_health_box').style.display = 'block';
      markSelected('.rdp-item', 'data-service-id', id);
      renderRdpSlots(id);
      refreshSelectedServiceStatus('rdp');
    }

    async function createRdpService() {
      const slug = document.getElementById('new_r_slug').value || slugifyRu(document.getElementById('new_r_name').value);
      const target = buildRdpTarget('new_r');
      await api('/api/admin/services', 'POST', {
        name: document.getElementById('new_r_name').value,
        slug,
        type: 'RDP',
        target,
        comment: document.getElementById('new_r_comment').value,
        svc_login: document.getElementById('new_r_svc_login').value,
        svc_password: document.getElementById('new_r_svc_password').value,
        svc_cred_hint: document.getElementById('new_r_svc_cred_hint').value,
        category_ids: checkedCategoryIds('.new_r_cat'),
        warm_pool_size: parseInt(document.getElementById('new_r_pool').value || '0', 10),
        active: document.getElementById('new_r_active').value === 'true',
      });
      location.reload();
    }

    async function saveRdpService() {
      const id = document.getElementById('r_id').value;
      if (!id) return alert('Выберите RDP сервис');
      const target = buildRdpTarget('r');
      await api(`/api/admin/services/${id}`, 'PUT', {
        name: document.getElementById('r_name').value,
        slug: document.getElementById('r_slug').value,
        type: 'RDP',
        target,
        comment: document.getElementById('r_comment').value,
        svc_login: document.getElementById('r_svc_login').value,
        svc_password: document.getElementById('r_svc_password').value,
        svc_cred_hint: document.getElementById('r_svc_cred_hint').value,
        category_ids: checkedCategoryIds('.r_cat'),
        warm_pool_size: parseInt(document.getElementById('r_pool').value || '0', 10),
        active: document.getElementById('r_active').value === 'true',
      });
      location.reload();
    }

    function clearRdpForm() {
      ['r_id','r_name','r_slug','r_target','r_host','r_port','r_domain','r_comment','r_pool','r_svc_login','r_svc_password','r_svc_cred_hint'].forEach(id => document.getElementById(id).value = '');
      document.getElementById('rdp_slots_box').style.display = 'none';
      document.getElementById('r_sec').value = '';
      document.getElementById('r_active').value = 'true';
      setCategoryChecks('.r_cat', []);
      document.getElementById('r_icon_preview').src = placeholderIcon;
      document.getElementById('r_health_box').style.display = 'none';
      document.querySelectorAll('.rdp-item').forEach((el) => el.classList.remove('selected-item'));
    }

    async function deleteService(sourceId) {
      const id = document.getElementById(sourceId).value;
      if (!id) return alert('Выберите сервис');
      if (!confirm('Удалить сервис?')) return;
      await api(`/api/admin/services/${id}`, 'DELETE', {});
      location.reload();
    }

    async function prewarmNow(sourceId) {
      const id = document.getElementById(sourceId).value;
      if (!id) return alert('Выберите сервис');
      await api(`/api/admin/services/${id}/prewarm`, 'POST', {});
      const kind = activeTab === 'rdp' ? 'rdp' : 'web';
      await refreshSelectedServiceStatus(kind);
      location.reload();
    }

    function renderStatusInto(prefix, data) {
      const summary = document.getElementById(`${prefix}_health_summary`);
      const tableBody = document.querySelector(`#${prefix}_health_table tbody`);
      summary.innerHTML = `
        <div><span class="status-dot status-${data.health}"></span> health: <b>${data.health}</b></div>
        <div>running/desired: <b>${data.running}</b> / <b>${data.desired}</b>, total: ${data.total}</div>
        <div>active sessions: <b>${data.active_sessions ?? 0}</b></div>
        <div class="muted">updated: ${data.updated_at}</div>
      `;
      tableBody.innerHTML = '';
      (data.containers || []).forEach((c) => {
        const tr = document.createElement('tr');
        tr.innerHTML = `
          <td>${c.name}</td>
          <td>${c.state}</td>
          <td>${c.status}</td>
          <td>${c.image || ''}</td>
          <td>${c.labels_ok ? 'ok' : 'mismatch'}</td>
        `;
        tableBody.appendChild(tr);
      });
    }

    function toggleHealthDetails(prefix) {
      const wrap = document.getElementById(`${prefix}_health_table_wrap`);
      const btn = document.getElementById(`${prefix}_health_toggle`);
      if (!wrap || !btn) return;
      const opened = wrap.style.display !== 'none';
      wrap.style.display = opened ? 'none' : 'block';
      btn.textContent = opened ? 'Показать детали контейнеров' : 'Скрыть детали контейнеров';
    }

    async function refreshSelectedServiceStatus(kind) {
      const kindToPrefix = {web: 'w', rdp: 'r'};
      const prefix = kindToPrefix[kind];
      if (!prefix) return;
      const serviceId = document.getElementById(`${prefix}_id`).value;
      if (!serviceId) return;
      const data = await api(`/api/admin/services/${serviceId}/containers/status`, 'GET');
      renderStatusInto(prefix, data);
    }

    async function autoUploadIcon(prefix) {
      const input = document.getElementById(`${prefix}_icon_file`);
      if (!input.files || !input.files[0]) return;
      await uploadServiceIcon(prefix);
    }

    async function uploadServiceIcon(prefix) {
      const serviceId = document.getElementById(`${prefix}_id`).value;
      if (!serviceId) return alert('Сначала выберите сервис');
      const input = document.getElementById(`${prefix}_icon_file`);
      if (!input.files || !input.files[0]) return alert('Выберите файл');
      const form = new FormData();
      form.append('file', input.files[0]);
      const data = await apiForm(`/api/admin/services/${serviceId}/icon`, form);
      document.getElementById(`${prefix}_icon_preview`).src = data.icon_path || placeholderIcon;
      input.value = '';
      location.reload();
    }

    async function removeServiceIcon(prefix) {
      const serviceId = document.getElementById(`${prefix}_id`).value;
      if (!serviceId) return alert('Сначала выберите сервис');
      await api(`/api/admin/services/${serviceId}/icon`, 'DELETE', {});
      document.getElementById(`${prefix}_icon_preview`).src = placeholderIcon;
      location.reload();
    }

    async function pasteServiceIcon(prefix) {
      const input = document.getElementById(`${prefix}_icon_file`);
      if (!navigator.clipboard || !navigator.clipboard.read) {
        return alert('Clipboard API недоступен в этом браузере');
      }
      try {
        const items = await navigator.clipboard.read();
        for (const item of items) {
          const imageType = item.types.find((t) => t.startsWith('image/'));
          if (!imageType) continue;
          const blob = await item.getType(imageType);
          const ext = imageType.split('/')[1] || 'png';
          const file = new File([blob], `clipboard.${ext}`, {type: imageType});
          const dt = new DataTransfer();
          dt.items.add(file);
          input.files = dt.files;
          await uploadServiceIcon(prefix);
          return;
        }
        alert('В буфере нет изображения');
      } catch (e) {
        alert(`Не удалось вставить изображение: ${e}`);
      }
    }

    async function createCategory() {
      const name = (document.getElementById('cat_name').value || '').trim();
      const slugInput = document.getElementById('cat_slug');
      const slug = (slugInput.value || slugifyRu(name)).trim();
      if (!name) return alert('Введите название категории');
      if (!slug) return alert('Введите slug категории');
      await api('/api/admin/categories', 'POST', {name, slug});
      location.reload();
    }

    async function deleteCategory(id, name) {
      if (!confirm(`Удалить категорию ${name}?`)) return;
      await api(`/api/admin/categories/${id}`, 'DELETE', {});
      location.reload();
    }

    setInterval(() => {
      if (activeTab === 'web' && document.getElementById('w_id').value) {
        refreshSelectedServiceStatus('web').catch(() => {});
      }
      if (activeTab === 'rdp' && document.getElementById('r_id').value) {
        refreshSelectedServiceStatus('rdp').catch(() => {});
      }
    }, 5000);

    const hashTab = (window.location.hash || '').replace('#', '');
    const savedTab = localStorage.getItem('admin_active_tab');
    const initialTab = ['users', 'web', 'rdp', 'categories', 'stats'].includes(hashTab)
      ? hashTab
      : (['users', 'web', 'rdp', 'categories', 'stats'].includes(savedTab || '') ? savedTab : 'users');
    showTab(initialTab);
    renderUserDays();
  </script>
</body>
</html>
