Files
zuevav 5527a464ca НПР / УДС — отдельная служба bj-npr
Модуль вынесен в modules/npr + cmd/bj-npr (127.0.0.1:8083), обновляется
независимо от ядра. Служба читает подключение из setup.json и принимает
запросы только со служебным ключом модулей (/var/lib/bj/.bj/module.key,
создаёт bj-server). Полные страницы отдаются фрагментом (X-BJ-Layout: page).

bj-server хранит настройки и проверку подключения, проксирует /admin/npr
после входа, прав и лицензии и встраивает страницы модуля в общий макет
(admin.modulePage); если служба не отвечает — понятная страница.
Unit bj-npr.service встроен в установщик, установщик включает службы
установленных модулей; build-release.sh собирает bj-npr.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 01:00:31 +03:00

48 lines
5.8 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "npr_client"}}
{{template "npr_css"}}
<p style="margin:0 0 12px;display:flex;align-items:center;gap:12px"><a href="/admin/npr" class="btn">← К списку клиентов</a>{{if not .Custom}}<span class="muted" id="autoupd" style="margin-left:auto">⟳ Страница обновится автоматически через 60 сек</span>{{end}}</p>
{{if not .Custom}}<script>(function(){let left=60;const el=document.getElementById('autoupd');const t=setInterval(function(){left-=1;if(el)el.textContent='⟳ Обновление через '+left+' сек';if(left<=0){clearInterval(t);location.reload();}},1000);})();</script>{{end}}
<div class="card">
<div style="display:flex;align-items:baseline;gap:12px;flex-wrap:wrap"><h2 style="margin:0">Клиент <code style="font-size:1.1em">{{.C.ClientCode}}</code></h2><span class="muted">Тип: <strong>{{if .C.MarginType}}{{.C.MarginType}}{{else}}—{{end}}</strong> · Данные на {{.C.TS.Local.Format "02.01.2006 15:04:05"}}</span></div>
<section class="nmetrics">
<article class="{{if or (eq .C.UDSFlag "CALL") (and .C.UDS (lt (deref .C.UDS) 1.0))}}m-call{{else if eq .C.UDSFlag "INF"}}m-ok{{end}}"><header>Уровень достаточности средств (УДС)</header><strong>{{if .C.UDS}}{{nprF .C.UDS}}{{else if eq .C.UDSFlag "INF"}}∞{{else if eq .C.UDSFlag "CALL"}}маржин-колл{{else}}—{{end}}</strong>{{if .C.BadgeClass}}<div style="margin-top:4px"><span class="badge {{.C.BadgeClass}}">{{.C.BadgeText}}</span></div>{{end}}</article>
<article><header>НПР1 (запас до начальной маржи)</header><strong>{{nprF .C.RCV1}}</strong></article>
<article><header>НПР2 (запас до минимальной маржи)</header><strong>{{nprF .C.RCV2}}</strong></article>
<article><header>Стоимость портфеля</header><strong>{{nprF .C.PortfolioValue}}</strong></article>
<article><header>Маржинальное требование</header><strong>{{nprF .C.MarginReq}}</strong></article>
</section>
<section style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:12px 0">
<div class="nmini"><header>Начальная маржа</header><strong>{{nprF .C.InitMargin}}</strong></div>
<div class="nmini"><header>Минимальная маржа</header><strong>{{nprF .C.MinMargin}}</strong></div>
<div class="nmini"><header>Скорректированная маржа</header><strong>{{nprF .C.CorrMargin}}</strong></div>
</section>
</div>
<div class="card">
<h3 style="margin:0 0 8px">История изменений</h3>
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-bottom:8px">{{range .Periods}}<a class="btn{{if and (eq $.Hours .H) (not $.Custom)}} btn-ok{{end}}" href="?hours={{.H}}">{{.T}}</a>{{end}}<span class="muted" style="margin-left:auto">или свой диапазон (МСК):</span></div>
<form method="get" action="" style="display:flex;gap:8px;align-items:end;flex-wrap:wrap;margin-bottom:8px">
<label style="margin:0;font-size:12px">С<br><input type="datetime-local" name="dt_from" value="{{.DtFrom}}"></label>
<label style="margin:0;font-size:12px">По<br><input type="datetime-local" name="dt_to" value="{{.DtTo}}"></label>
<button type="submit" class="btn">Показать</button><a class="btn" href="?hours=24">Сбросить</a>
<a class="btn" style="margin-left:auto" href="/admin/npr/c/{{.C.ClientCode}}/export.csv?{{if .Custom}}dt_from={{.DtFrom}}&dt_to={{.DtTo}}{{else}}hours={{.Hours}}{{end}}">⤓ Скачать CSV (для регулятора)</a>
</form>
<p class="muted">Показано: с <strong>{{.TF.Local.Format "02.01.2006 15:04"}}</strong> по <strong>{{.TT.Local.Format "02.01.2006 15:04"}}</strong> (МСК).</p>
<canvas id="chart" style="max-height:340px;margin-bottom:14px"></canvas>
<h4 style="margin:0 0 6px">Последние {{len .History}} записей</h4>
<table class="ntable"><thead><tr><th>Время</th><th class="num">УДС</th><th>Статус</th><th class="num">НПР1</th><th class="num">НПР2</th><th class="num">Марж. требование</th></tr></thead>
<tbody>{{range .History}}<tr class="{{.Class}}"><td><small>{{.TS.Local.Format "02.01.2006 15:04:05"}}</small></td><td class="num">{{nprF .UDS}}</td><td>{{if .BadgeClass}}<span class="badge {{.BadgeClass}}">{{.BadgeText}}</span>{{else}}—{{end}}</td><td class="num">{{nprF .RCV1}}</td><td class="num">{{nprF .RCV2}}</td><td class="num">{{nprF .MarginReq}}</td></tr>{{end}}</tbody></table>
</div>
<script src="/admin/npr/static/chart.umd.min.js"></script>
<script>
fetch("/admin/npr/c/{{.C.ClientCode}}/series?{{if .Custom}}dt_from={{.DtFrom}}&dt_to={{.DtTo}}{{else}}hours={{.Hours}}{{end}}").then(r=>r.json()).then(d=>{
const labels=d.labels.map(s=>new Date(s).toLocaleString('ru-RU',{day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}));
new Chart(document.getElementById('chart'),{type:'line',data:{labels:labels,datasets:[
{label:'НПР1',data:d.rcv1,borderColor:'#059669',backgroundColor:'#05966920',tension:.2,spanGaps:true,yAxisID:'y',pointRadius:1.5},
{label:'НПР2',data:d.rcv2,borderColor:'#2563eb',backgroundColor:'#2563eb20',tension:.2,spanGaps:true,yAxisID:'y',pointRadius:1.5},
{label:'УДС',data:d.uds,borderColor:'#dc2626',backgroundColor:'#dc262620',tension:.2,spanGaps:true,yAxisID:'y2',pointRadius:1.5,borderDash:[5,3]}]},
options:{animation:false,responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},plugins:{legend:{position:'top'},tooltip:{callbacks:{label:c=>c.dataset.label+': '+(c.parsed.y===null?'—':c.parsed.y.toLocaleString('ru-RU',{maximumFractionDigits:2}))}}},
scales:{y:{position:'left',title:{display:true,text:'НПР'}},y2:{position:'right',title:{display:true,text:'УДС'},grid:{drawOnChartArea:false},suggestedMin:-1,suggestedMax:3}}}});
});
</script>
{{end}}