Files
Stend_mont/app/templates/login.html
T
ruslan 40de4d5a0f Bundles feature WIP + fix request-access products cap (20 -> 200)
- app/main.py: raise products_raw[:20] cap to [:200] in /api/request-access
  and /api/request-more-access - was silently truncating access requests
  with more than 20 selected products
- accumulated bundles/product-page WIP (config, models, runtime, templates)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 13:58:09 +00:00

561 lines
34 KiB
HTML

{% set _scheme = request.headers.get('x-forwarded-proto', request.url.scheme) %}
{% set base_url = _scheme + '://' + request.url.netloc + '/' %}
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Инфраструктурный полигон MONT — демо и пилоты российского ПО</title>
<meta name="description" content="Инфраструктурный полигон MONT: демонстрация и пилотное тестирование российского ПО для партнёров и заказчиков. Браузерный доступ к рабочим стендам — без установки и настройки." />
<meta name="keywords" content="инфраструктурный полигон MONT, пилоты MONT, демо MONT, партнёры MONT, демонстрация MONT, российское ПО демо, отечественное ПО тестирование, демостенд ПО" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="{{ base_url }}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{ base_url }}" />
<meta property="og:title" content="Инфраструктурный полигон MONT — демо и пилоты российского ПО" />
<meta property="og:description" content="Демонстрация и тестирование российского ПО для партнёров и заказчиков MONT. Доступ к рабочим стендам прямо в браузере." />
<meta property="og:image" content="{{ base_url }}static/logo.png?v=4" />
<meta property="og:locale" content="ru_RU" />
<meta property="og:site_name" content="Полигон MONT" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="Инфраструктурный полигон MONT" />
<meta name="twitter:description" content="Демо и пилоты российского ПО для партнёров и заказчиков MONT." />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Инфраструктурный полигон MONT",
"url": "{{ base_url }}",
"description": "Платформа для демонстрации и пилотного тестирования российского программного обеспечения. Партнеры MONT и их заказчики получают браузерный доступ к рабочим стендам.",
"publisher": {
"@type": "Organization",
"name": "MONT",
"url": "https://www.mont.ru/"
}
}
</script>
<link rel="stylesheet" href="/static/style.css?v=2" />
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon_32.png" />
<link rel="icon" type="image/png" sizes="192x192" href="/static/favicon_192.png" />
<link rel="apple-touch-icon" sizes="192x192" href="/static/favicon_192.png" />
<link rel="manifest" href="/static/manifest.json" />
<!-- 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 -->
<style>
:root{
--lg2-bg:#eef1f5; --lg2-panel:#ffffff; --lg2-raised:#ffffff;
--lg2-line:#d7dee6; --lg2-line-soft:#e3e8ee;
--lg2-fg:#16202c; --lg2-fg-dim:#5c6b7c; --lg2-fg-faint:#93a1b0;
--lg2-accent:#c96c14; --lg2-accent-dim:rgba(201,108,20,.10); --lg2-accent-ink:#2a1400;
--lg2-bad:#d3453f; --lg2-bad-dim:rgba(211,69,63,.08);
--lg2-info:#1f6fb2; --lg2-info-dim:rgba(31,111,178,.08);
--lg2-good:#1f9d63;
}
html{ background: var(--lg2-bg); }
body.login-page{
margin:0;min-height:100vh;background:var(--lg2-bg);color:var(--lg2-fg);
font-family:"Ubuntu","IBM Plex Sans",system-ui,sans-serif;overflow-x:hidden;
}
.lg2-wrap{display:grid;grid-template-columns:1.15fr 420px;min-height:100vh}
@media (max-width:980px){ .lg2-wrap{grid-template-columns:minmax(0,1fr)} }
.lg2-pitch{
position:relative;min-width:0;padding:56px clamp(28px,5vw,72px) 48px;border-right:1px solid var(--lg2-line);
background-image:linear-gradient(var(--lg2-line-soft) 1px, transparent 1px),linear-gradient(90deg, var(--lg2-line-soft) 1px, transparent 1px);
background-size:34px 34px;display:flex;flex-direction:column;
}
@media (max-width:980px){ .lg2-pitch{border-right:none;border-bottom:1px solid var(--lg2-line)} }
.lg2-brand{display:flex;flex-wrap:wrap;align-items:center;gap:12px 16px;margin-bottom:40px}
.lg2-brand img{height:59px;display:block}
.lg2-brand-mark{font:700 11px/1 "Ubuntu Mono",monospace;letter-spacing:.14em;color:var(--lg2-fg-faint)}
@media (max-width:520px){
.lg2-brand{flex-direction:column;align-items:flex-start;gap:10px}
.lg2-brand img{height:40px}
}
.lg2-eyebrow{font:700 11px/1 "Ubuntu Mono",monospace;letter-spacing:.18em;text-transform:uppercase;color:var(--lg2-accent);margin:0 0 16px}
.lg2-pitch h1{font:800 clamp(30px,3.6vw,46px)/1.06 "Ubuntu",sans-serif;letter-spacing:-.01em;margin:0 0 18px;text-wrap:balance;max-width:16ch;color:var(--lg2-fg);overflow-wrap:break-word;hyphens:auto}
.lg2-pitch, .lg2-pitch *{ overflow-wrap: break-word; }
.lg2-pitch p.lead{font:400 15px/1.65 "Ubuntu",sans-serif;color:var(--lg2-fg-dim);max-width:52ch;margin:0 0 32px}
.lg2-feat-row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;background:var(--lg2-line);border:1px solid var(--lg2-line);border-radius:10px;overflow:hidden;margin-bottom:40px}
@media (max-width:720px){ .lg2-feat-row{grid-template-columns:repeat(2,minmax(0,1fr))} }
@media (max-width:420px){ .lg2-feat-row{grid-template-columns:minmax(0,1fr)} }
.lg2-feat{position:relative;min-width:0;background:var(--lg2-panel);padding:18px 16px 16px;border-top:2px solid var(--lg2-accent)}
.lg2-feat-idx{font:700 10px/1 "Ubuntu Mono",monospace;letter-spacing:.08em;color:var(--lg2-fg-faint);display:block;margin-bottom:12px}
.lg2-feat-icon{width:26px;height:26px;margin-bottom:14px;color:var(--lg2-accent);display:block}
.lg2-feat-icon svg{width:100%;height:100%;display:block}
.lg2-feat-label{font:600 13px/1.35 "Ubuntu",sans-serif;color:var(--lg2-fg);margin-bottom:6px}
.lg2-feat-desc{font:400 11.5px/1.55 "Ubuntu",sans-serif;color:var(--lg2-fg-dim)}
.lg2-distrib-btn{
align-self:flex-start;margin:0 0 44px;padding:11px 22px;border:1px solid var(--lg2-line);border-radius:9px;
background:transparent;color:var(--lg2-fg-dim);font:600 12.5px/1 "Ubuntu",sans-serif;letter-spacing:.02em;cursor:pointer;
}
.lg2-distrib-btn:hover{border-color:var(--lg2-accent);color:var(--lg2-fg)}
.lg2-wall-head{display:flex;align-items:baseline;gap:10px;margin-bottom:14px}
.lg2-wall-head h2{font:700 11px/1 "Ubuntu Mono",monospace;letter-spacing:.14em;text-transform:uppercase;color:var(--lg2-fg-dim);margin:0}
.lg2-wall-head::after{content:"";flex:1;height:1px;background:var(--lg2-line)}
.lg2-wall-count{font:600 11px/1 "Ubuntu Mono",monospace;color:var(--lg2-fg-faint)}
.lg2-logo-wall{display:grid;grid-template-columns:repeat(auto-fill,minmax(84px,1fr));gap:10px;max-width:760px}
.lg2-wall-tile{aspect-ratio:1;background:var(--lg2-panel);border:1px solid var(--lg2-line);border-radius:9px;display:flex;align-items:center;justify-content:center;padding:8px}
a.lg2-wall-tile{text-decoration:none;cursor:pointer}
.lg2-wall-tile img{width:100%;height:100%;object-fit:contain;filter:grayscale(1) opacity(.75)}
.lg2-wall-tile:hover img{filter:none}
.lg2-wall-tile:hover{border-color:var(--lg2-accent)}
.lg2-pitch-footer{margin-top:auto;padding-top:36px;font:400 11px/1 "Ubuntu Mono",monospace;color:var(--lg2-fg-faint)}
.lg2-pitch-footer a{color:inherit;text-decoration:none}
.lg2-pitch-footer a:hover{color:var(--lg2-fg-dim)}
.lg2-auth{display:flex;flex-direction:column;justify-content:center;min-width:0;padding:48px 40px;background:var(--lg2-bg);
position:sticky;top:0;height:100vh;box-sizing:border-box}
@media (max-width:980px){ .lg2-auth{position:static;height:auto} }
.lg2-auth-card{width:100%;max-width:320px;margin:0 auto}
.lg2-auth-title{font:700 22px/1.2 "Ubuntu",sans-serif;letter-spacing:-.01em;margin:0 0 4px;color:var(--lg2-fg)}
.lg2-auth-sub{font:400 12px/1.4 "Ubuntu Mono",monospace;color:var(--lg2-fg-faint);letter-spacing:.02em;margin:0 0 18px;text-transform:uppercase}
.lg2-prompt{font:400 12.5px/1 "Ubuntu Mono",monospace;color:var(--lg2-fg-dim);margin:0 0 24px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lg2-prompt-user{color:var(--lg2-good)}
.lg2-prompt-path{color:var(--lg2-accent)}
.lg2-caret{display:inline-block;width:7px;height:13px;background:var(--lg2-accent);vertical-align:-2px;margin-left:3px;animation:lg2-blink 1.1s step-end infinite}
@keyframes lg2-blink{50%{opacity:0}}
@media (prefers-reduced-motion: reduce){ .lg2-caret{animation:none} }
.lg2-notice, .lg2-error{
border-radius:9px;padding:10px 13px;font:500 12.5px/1.5 "Ubuntu",sans-serif;margin-bottom:18px;
}
.lg2-notice{background:var(--lg2-info-dim);border:1px solid rgba(127,199,255,.35);color:var(--lg2-info)}
.lg2-error{background:var(--lg2-bad-dim);border:1px solid rgba(255,107,107,.35);color:var(--lg2-bad)}
.lg2-field{display:flex;flex-direction:column;gap:7px;margin-bottom:16px}
.lg2-field label{font:700 10.5px/1 "Ubuntu Mono",monospace;letter-spacing:.09em;text-transform:uppercase;color:var(--lg2-fg-faint)}
.lg2-field input{background:var(--lg2-panel);border:1px solid var(--lg2-line);border-radius:9px;padding:11px 13px;color:var(--lg2-fg);font:400 14px/1 "Ubuntu",sans-serif;outline:none;width:100%;box-sizing:border-box}
.lg2-field input::placeholder{color:var(--lg2-fg-faint)}
.lg2-field input:focus{border-color:var(--lg2-accent)}
.lg2-submit{width:100%;margin-top:6px;padding:12px 16px;border:none;border-radius:9px;cursor:pointer;background:var(--lg2-accent);color:var(--lg2-accent-ink);font:700 13.5px/1 "Ubuntu",sans-serif;letter-spacing:.02em}
.lg2-submit:hover{filter:brightness(1.06)}
.lg2-request-btn{width:100%;margin-top:12px;padding:10px 16px;border-radius:9px;border:1px solid var(--lg2-line);background:transparent;color:var(--lg2-fg-dim);font:600 12.5px/1 "Ubuntu",sans-serif;cursor:pointer}
.lg2-request-btn:hover{border-color:var(--lg2-accent);color:var(--lg2-fg)}
.lg2-auth-foot{margin-top:40px;text-align:center}
.lg2-auth-foot a{color:var(--lg2-fg-faint);font-size:11px;text-decoration:none}
.lg2-auth-foot a:hover{color:var(--lg2-fg-dim)}
/* Re-skin the shared access-modal component to match the new panel palette
(it's used by "Запросить доступ" here and on the dashboard alike, so we
override on top of style.css instead of editing it there). */
.access-modal-overlay{ background: rgba(6,9,14,.82) !important; }
.access-modal{
background: var(--lg2-panel) !important; border: 1px solid var(--lg2-line) !important;
box-shadow: 0 24px 80px rgba(0,0,0,.45) !important;
}
.access-modal-title{ color: var(--lg2-fg) !important; font-family:"Ubuntu",sans-serif; }
.access-modal-sub{ color: var(--lg2-fg-faint) !important; }
.access-field label{ color: var(--lg2-fg-dim) !important; font-family:"Ubuntu",sans-serif; }
.access-field .req{ color: var(--lg2-accent) !important; }
.access-field input[type=text],
.access-field input[type=email],
.access-field input[type=tel],
.access-textarea{
background: var(--lg2-raised) !important; border: 1px solid var(--lg2-line) !important;
color: var(--lg2-fg) !important; font-family:"Ubuntu",sans-serif;
}
.access-field input[type=text]::placeholder,
.access-field input[type=email]::placeholder,
.access-field input[type=tel]::placeholder,
.access-textarea::placeholder{ color: var(--lg2-fg-faint) !important; }
.access-field input[type=text]:focus,
.access-field input[type=email]:focus,
.access-field input[type=tel]:focus,
.access-textarea:focus{
border-color: var(--lg2-accent) !important; box-shadow: 0 0 0 3px var(--lg2-accent-dim) !important;
}
.access-products-wrap{ background: var(--lg2-raised) !important; border: 1px solid var(--lg2-line) !important; }
.access-products-loading{ color: var(--lg2-fg-faint) !important; }
.access-products-cat{ color: var(--lg2-fg-faint) !important; }
.access-product-item{ color: var(--lg2-fg-dim) !important; font-family:"Ubuntu",sans-serif; }
.access-product-item:hover span{ color: var(--lg2-fg) !important; }
.access-product-item input[type=checkbox]{ accent-color: var(--lg2-accent); }
.access-modal-error{
background: var(--lg2-bad-dim) !important; border: 1px solid rgba(255,107,107,.35) !important;
color: var(--lg2-bad) !important;
}
.access-btn-cancel{
background: transparent !important; border: 1px solid var(--lg2-line) !important; color: var(--lg2-fg-dim) !important;
font-family:"Ubuntu",sans-serif;
}
.access-btn-cancel:hover{ border-color: var(--lg2-fg-faint) !important; color: var(--lg2-fg) !important; background: transparent !important; }
.access-btn-submit{
background: var(--lg2-accent) !important; color: var(--lg2-accent-ink) !important; box-shadow: none !important;
font-family:"Ubuntu",sans-serif; font-weight: 700 !important;
}
.access-btn-submit:hover:not(:disabled){ opacity: 1 !important; filter: brightness(1.06); }
.access-consent-label span{ color: var(--lg2-fg-dim) !important; font-family:"Ubuntu",sans-serif; }
.access-consent-link{ color: var(--lg2-accent) !important; }
.am-success-title{ color: var(--lg2-fg) !important; }
.am-success-sub{ color: var(--lg2-fg-dim) !important; }
.am-success-icon{ color: var(--lg2-good) !important; }
@media (max-width:820px){
.lg2-pitch{padding:40px 22px 34px}
.lg2-auth{padding:36px 22px}
}
</style>
</head>
<body class="login-page">
<div class="lg2-wrap">
<aside class="lg2-pitch">
<div class="lg2-brand">
<a href="#" onclick="window.open(location.hostname==='stand.mont.ru'?'https://www.mont.ru':'https://4mont.ru','_blank');return false;">
<img src="/static/logo-color.png?v=1" alt="MONT" />
</a>
<span class="lg2-brand-mark">Инфраструктурный полигон</span>
</div>
<p class="lg2-eyebrow">Демо и пилоты российского ПО</p>
<h1>Полигон импортонезависимой инфраструктуры</h1>
<p class="lead">Платформа для демонстрации и пилотного тестирования российского ПО. Партнёры MONT
и их заказчики получают браузерный доступ к рабочим стендам с отечественными ОС, платформами
виртуализации, СРК и другими решениями — без установки и настройки.</p>
<div class="lg2-feat-row">
<div class="lg2-feat">
<span class="lg2-feat-idx">01</span>
<span class="lg2-feat-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="12" rx="1.5"/><path d="M8 20h8M12 16v4"/></svg></span>
<div class="lg2-feat-label">Доступ к рабочим столам ОС</div>
<div class="lg2-feat-desc">Полный рабочий стол Astra, RED OS, Uncom и других ОС прямо в браузере, без RDP-клиента.</div>
</div>
<div class="lg2-feat">
<span class="lg2-feat-idx">02</span>
<span class="lg2-feat-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.5 2.6 3.8 5.7 3.8 9s-1.3 6.4-3.8 9c-2.5-2.6-3.8-5.7-3.8-9s1.3-6.4 3.8-9z"/></svg></span>
<div class="lg2-feat-label">Веб-интерфейсы сервисов</div>
<div class="lg2-feat-desc">Админ-панели и порталы продуктов открываются как обычная вкладка браузера.</div>
</div>
<div class="lg2-feat">
<span class="lg2-feat-idx">03</span>
<span class="lg2-feat-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 4 14h7l-1 8 9-12h-7l1-8z"/></svg></span>
<div class="lg2-feat-label">Доступ в один клик</div>
<div class="lg2-feat-desc">Стенд поднимается автоматически — ничего разворачивать и настраивать не нужно.</div>
</div>
<div class="lg2-feat">
<span class="lg2-feat-idx">04</span>
<span class="lg2-feat-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="11" width="16" height="9" rx="1.5"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg></span>
<div class="lg2-feat-label">Защищённый контур</div>
<div class="lg2-feat-desc">Изолированная сеть с резервированием и регулярными бэкапами всех стендов.</div>
</div>
</div>
<button type="button" class="lg2-distrib-btn" onclick="window.open(location.hostname==='stand.mont.ru'?'https://maps.mont.ru':'https://maps.4mont.ru','_blank')">Продукты нашей дистрибуции</button>
{% set wall_services = public_services|rejectattr('slug', 'equalto', 'communigate-pro-user')|list %}
{% if wall_services %}
<div class="lg2-wall-head" id="products"><h2>Что на полигоне</h2><span class="lg2-wall-count">{{ wall_services|length }} продуктов</span></div>
<div class="lg2-logo-wall">
{% for service in wall_services %}
{% set wall_name = 'CommuniGate Pro' if service.slug == 'communigate-pro-admin' else service.name %}
<a class="lg2-wall-tile" href="/product/{{ service.slug }}" title="{{ wall_name }} — подробнее">
<img src="{{ service.icon_path or '/static/service-placeholder.svg' }}" alt="{{ wall_name }}" />
</a>
{% endfor %}
</div>
{% endif %}
<div class="lg2-pitch-footer">MONT PROVING GROUND</div>
</aside>
<main class="lg2-auth">
<div class="lg2-auth-card">
<div class="lg2-auth-title">Вход в систему</div>
<div class="lg2-auth-sub">Инфраструктурный полигон</div>
<div class="lg2-prompt"><span class="lg2-prompt-user">mont@polygon</span><span class="lg2-prompt-path">:~/access</span>$ ./login.sh<span class="lg2-caret"></span></div>
{% if session_notice %}<div class="lg2-notice">{{ session_notice }}</div>{% endif %}
{% if login_error %}<div class="lg2-error">{{ login_error }}</div>{% endif %}
<form method="post" action="/login">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<div class="lg2-field">
<label>Логин</label>
<input type="text" name="username" placeholder="Введите логин" required autocomplete="username" />
</div>
<div class="lg2-field">
<label>Пароль</label>
<input type="password" name="password" placeholder="Введите пароль" required autocomplete="current-password" />
</div>
<button type="submit" class="lg2-submit">Войти</button>
</form>
<button type="button" class="lg2-request-btn" id="btn-request-access" data-open-access-modal="1">Запросить доступ</button>
<div class="lg2-auth-foot"><a href="mailto:ruslan@ipcom.su">Made by Galyaviev</a></div>
</div>
</main>
</div>
<!-- Request Access Modal -->
<div id="access-modal" class="access-modal-overlay" style="display:none" aria-modal="true" role="dialog">
<div class="access-modal">
<div class="access-modal-header">
<div class="access-modal-title">Запрос на доступ</div>
</div>
<div class="access-modal-body">
<div class="access-field">
<label>Имя и фамилия <span class="req">*</span></label>
<input id="am-name" type="text" placeholder="Иван Иванов" />
</div>
<div class="access-field">
<label>Название компании <span class="req">*</span></label>
<input id="am-company" type="text" placeholder="ООО Компания" />
</div>
<div class="access-field">
<label>Email <span class="req">*</span></label>
<input id="am-email" type="email" placeholder="ivan@company.ru" />
</div>
<div class="access-field">
<label>Телефон <span class="req">*</span></label>
<input id="am-phone" type="tel" placeholder="+7 (999) 000-00-00" />
</div>
<div class="access-field">
<label>Ваш менеджер в MONT</label>
<input id="am-manager" type="text" placeholder="Если известно — укажите имя" />
</div>
<div class="access-field">
<label style="display:flex;align-items:center;justify-content:space-between">
Интересующие продукты
<label id="am-select-all-wrap" style="display:none;font-weight:400;font-size:13px;cursor:pointer;gap:6px;align-items:center">
<input type="checkbox" id="am-select-all" style="cursor:pointer" />
<span>Выбрать все</span>
</label>
</label>
<div id="am-products" class="access-products-wrap">
<div class="access-products-loading">Загрузка...</div>
</div>
</div>
<div class="access-consent-field">
<label class="access-consent-label">
<input type="checkbox" id="am-consent" />
<span>Согласен на <a href="/privacy" target="_blank" class="access-consent-link">обработку персональных данных</a></span>
</label>
</div>
<div id="am-error" class="access-modal-error" style="display:none"></div>
</div>
<div class="access-modal-footer">
<button type="button" class="access-btn-cancel" id="am-cancel">Отмена</button>
<button type="button" class="access-btn-submit" id="am-submit">Запросить доступ</button>
</div>
</div>
</div>
<script>
(function() {
const overlay = document.getElementById('access-modal');
const btnCancel = document.getElementById('am-cancel');
const btnSubmit = document.getElementById('am-submit');
const errEl = document.getElementById('am-error');
let productsLoaded = false;
function resetAccessForm() {
if (!document.getElementById('am-name')) {
document.querySelector('.access-modal-body').innerHTML = `
<div class="access-field"><label>Имя и фамилия <span class="req">*</span></label><input id="am-name" type="text" placeholder="Иван Иванов" /></div>
<div class="access-field"><label>Название компании <span class="req">*</span></label><input id="am-company" type="text" placeholder="ООО Компания" /></div>
<div class="access-field"><label>Email <span class="req">*</span></label><input id="am-email" type="email" placeholder="ivan@company.ru" /></div>
<div class="access-field"><label>Телефон <span class="req">*</span></label><input id="am-phone" type="tel" placeholder="+7 (999) 000-00-00" /></div>
<div class="access-field"><label>Ваш менеджер в MONT</label><input id="am-manager" type="text" placeholder="Если известно — укажите имя" /></div>
<div class="access-field"><label>Интересующие продукты</label><div id="am-products" class="access-products-wrap"><div class="access-products-loading">Загрузка...</div></div></div>
<div class="access-consent-field"><label class="access-consent-label"><input type="checkbox" id="am-consent" /><span>Согласен на <a href="/privacy" target="_blank" class="access-consent-link">обработку персональных данных</a></span></label></div>
<div id="am-error" class="access-modal-error" style="display:none"></div>`;
document.querySelector('.access-modal-footer').innerHTML = `<button type="button" class="access-btn-cancel" id="am-cancel">Отмена</button><button type="button" class="access-btn-submit" id="am-submit">Запросить доступ</button>`;
document.getElementById('am-cancel').addEventListener('click', closeModal);
document.getElementById('am-submit').addEventListener('click', submitForm);
document.querySelectorAll('#am-name,#am-company,#am-email,#am-phone').forEach(function(el){ el.addEventListener('input', function(){ el.classList.remove('am-invalid'); }); });
productsLoaded = false;
} else {
['am-name','am-company','am-email','am-phone','am-manager'].forEach(function(id){ var el=document.getElementById(id); if(el){el.value='';el.classList.remove('am-invalid');} });
document.querySelectorAll('#am-products input[type=checkbox]').forEach(function(cb){ cb.checked=false; });
var saBox = document.getElementById('am-select-all'); if (saBox) saBox.checked = false;
var err=document.getElementById('am-error'); if(err) err.style.display='none';
var btn=document.getElementById('am-submit'); if(btn){btn.disabled=false;btn.textContent='Запросить доступ';}
}
}
function openModal() {
resetAccessForm();
overlay.style.display = 'flex';
document.body.style.overflow = 'hidden';
if (!productsLoaded) loadProducts();
}
function closeModal() {
overlay.style.display = 'none';
document.body.style.overflow = '';
errEl.style.display = 'none';
document.querySelectorAll('.am-invalid').forEach(el => el.classList.remove('am-invalid'));
}
window._closeAccessModal = function() { closeModal(); productsLoaded = false; };
async function loadProducts() {
const wrap = document.getElementById('am-products');
try {
const res = await fetch('/api/public/services-by-category');
const data = await res.json();
wrap.innerHTML = '';
for (const [cat, svcs] of Object.entries(data)) {
const group = document.createElement('div');
group.className = 'access-products-group';
group.innerHTML = '<div class="access-products-cat">' + cat + '</div>';
for (const svc of svcs) {
const lbl = document.createElement('label');
lbl.className = 'access-product-item';
lbl.innerHTML = '<input type="checkbox" value="' + svc.name.replace(/"/g, '&quot;') + '" /><span>' + svc.name + '</span>';
group.appendChild(lbl);
}
wrap.appendChild(group);
}
productsLoaded = true;
// Show "select all" checkbox
const saWrap = document.getElementById('am-select-all-wrap');
if (saWrap) saWrap.style.display = 'flex';
const saBox = document.getElementById('am-select-all');
if (saBox) {
saBox.addEventListener('change', function() {
document.querySelectorAll('#am-products input[type=checkbox]').forEach(function(cb){ cb.checked = saBox.checked; });
});
// Keep "select all" in sync when individual boxes change
wrap.addEventListener('change', function(e) {
if (e.target === saBox) return;
const all = [...document.querySelectorAll('#am-products input[type=checkbox]')];
saBox.checked = all.length > 0 && all.every(function(cb){ return cb.checked; });
});
}
} catch(e) {
wrap.innerHTML = '<div class="access-products-loading">Не удалось загрузить список</div>';
}
}
async function submitForm() {
const nameEl = document.getElementById('am-name');
const companyEl = document.getElementById('am-company');
const emailEl = document.getElementById('am-email');
const phoneEl = document.getElementById('am-phone');
const managerEl = document.getElementById('am-manager');
const submitBtn = document.getElementById('am-submit');
const errorEl = document.getElementById('am-error');
const name = nameEl ? nameEl.value.trim() : '';
const company = companyEl ? companyEl.value.trim() : '';
const email = emailEl ? emailEl.value.trim() : '';
const phone = phoneEl ? phoneEl.value.trim() : '';
const manager = managerEl ? managerEl.value.trim() : '';
const checked = [...document.querySelectorAll('#am-products input[type=checkbox]:checked')];
const products = checked.map(c => c.value);
const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const phoneRe = /^[\+\d][\d\s\-\(\)]{6,18}$/;
const consentEl = document.getElementById('am-consent');
const fields = [
{ el: nameEl, check: () => !!name, msg: 'Введите имя и фамилию' },
{ el: companyEl, check: () => !!company, msg: 'Введите название компании' },
{ el: emailEl, check: () => emailRe.test(email), msg: 'Введите корректный email' },
{ el: phoneEl, check: () => phoneRe.test(phone), msg: 'Введите корректный номер телефона' },
];
const errors = [];
fields.forEach(f => {
if (f.el && !f.check()) { f.el.classList.add('am-invalid'); errors.push(f.msg); }
else if (f.el) f.el.classList.remove('am-invalid');
});
if (!consentEl || !consentEl.checked) {
errors.push('Необходимо согласие на обработку персональных данных');
const cf = document.querySelector('.access-consent-field');
if (cf) cf.classList.add('am-invalid-consent');
} else {
const cf = document.querySelector('.access-consent-field');
if (cf) cf.classList.remove('am-invalid-consent');
}
if (errors.length) {
if (errorEl) { errorEl.textContent = errors.join(' • '); errorEl.style.display = 'block'; }
return;
}
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Отправка...'; }
if (errorEl) errorEl.style.display = 'none';
try {
const res = await fetch('/api/request-access', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name, company, email, phone, manager, products}),
});
if (!res.ok) {
const d = await res.json().catch(() => ({}));
throw new Error(d.detail || 'Ошибка отправки');
}
const body = document.querySelector('#access-modal .access-modal-body');
const footer = document.querySelector('#access-modal .access-modal-footer');
body.innerHTML = '<div class="am-success-msg">' +
'<div class="am-success-icon">✓</div>' +
'<div class="am-success-title">Запрос отправлен</div>' +
'<div class="am-success-sub">После утверждения доступы придут на электронную почту <strong>' + email + '</strong></div>' +
'</div>';
footer.innerHTML = '<button type="button" class="access-btn-cancel" onclick="window._closeAccessModal()">Закрыть</button>';
} catch(e) {
const eb = document.getElementById('am-error');
if (eb) { eb.textContent = e.message || 'Ошибка отправки, попробуйте позже'; eb.style.display = 'block'; }
const sb = document.getElementById('am-submit');
if (sb) { sb.disabled = false; sb.textContent = 'Запросить доступ'; }
}
}
// Clear invalid highlight on input
document.querySelectorAll('#am-name,#am-company,#am-email,#am-phone').forEach(el => {
el.addEventListener('input', () => el.classList.remove('am-invalid'));
});
// Wire up request-access button
document.querySelectorAll('.login-request-btn, [data-open-access-modal]').forEach(el => {
el.addEventListener('click', function(e) {
e.preventDefault();
openModal();
});
});
btnCancel.addEventListener('click', closeModal);
btnSubmit.addEventListener('click', submitForm);
overlay.addEventListener('click', function(e) {
if (e.target === overlay) closeModal();
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeModal();
});
})();
</script>
<!-- Cookie Banner -->
<div id="cookie-banner" style="display:none;position:fixed;bottom:0;left:0;right:0;z-index:99999;background:rgba(7,15,28,0.97);border-top:1px solid rgba(255,255,255,0.1);backdrop-filter:blur(8px);padding:14px 24px;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;">
<p style="margin:0;font-size:0.85rem;color:#c8d8ea;line-height:1.5;flex:1;min-width:200px;">
Мы используем файлы cookie, чтобы сделать работу с сайтом удобнее. Нажмите «Принять», чтобы согласиться с использованием файлов cookie в соответствии с <a href="https://www.mont.ru/ru-ru/confidential" target="_blank" style="color:#ff9d3d;text-decoration:underline;">Политикой конфиденциальности</a>.
</p>
<button onclick="document.getElementById('cookie-banner').style.display='none';localStorage.setItem('cookie_accepted','1');" style="flex-shrink:0;padding:8px 22px;background:linear-gradient(135deg,#ffb15c,#e88420);color:#1a0f00;border:none;border-radius:8px;font-size:0.88rem;font-weight:700;cursor:pointer;white-space:nowrap;">Принять</button>
</div>
<script>
if (!localStorage.getItem('cookie_accepted')) {
document.getElementById('cookie-banner').style.display = 'flex';
}
</script>
</body>
</html>