Модули вне лицензии исчезают из интерфейса: меню, разделы настроек, прямые адреса отвечают «не входит в лицензию»

- moduleForPath: quik-mnp (QUIK, обмен с DEPO, ручной поиск в DEPO), quik-export, web-cabinet (bj-lk);
- в настройках каждый модуль по своему признаку (раньше Экспорт QUIK и Кабинет шли общим условием);
- разделы Fancy/QUIK открыты при лицензии QUIK МНП без пароля перспективных модулей.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-09-24 22:14:47 +03:00
parent e043e7c371
commit f1df6b529b
4 changed files with 44 additions and 11 deletions
+2 -1
View File
@@ -55,6 +55,7 @@ var templateFuncs = template.FuncMap{
"can": authTemplateFuncs["can"],
"devOn": authTemplateFuncs["devOn"],
"devLicensed": authTemplateFuncs["devLicensed"],
"mod": func(f string) bool { return globalRC == nil || moduleAllowed(globalRC, f) }, // модуль в лицензии
"authOpen": authOpen,
"internalDocs": func() bool { return internalDocsBuilt },
"roleTitle": roleTitle,
@@ -358,7 +359,7 @@ type page struct {
NPR bool // модуль «НПР / УДС» включён и разрешён лицензией
IGWDown string // ИШ НРД не отвечает: «N мин» (пусто — отвечает или не проверялся)
IGWDetail string
DevMode bool // режим разработки (сервер лицензий): видны служебные разделы
DevMode bool // режим разработки (сервер лицензий): видны служебные разделы
}
// globalRC — ссылка на runtime-конфиг для template-funcs/page helpers.
+28
View File
@@ -21,6 +21,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"html"
"html/template"
"log"
"net"
@@ -883,6 +884,29 @@ func devOnlyPath(p string) bool {
return false
}
// moduleForPath — лицензируемый модуль, которому принадлежит раздел ("" — ядро). Модуль, снятый
// с лицензии, пропадает из меню и настроек, а его адреса отвечают «не входит в лицензию».
func moduleForPath(p string) string {
has := func(pre string) bool { return p == pre || strings.HasPrefix(p, pre+"/") }
switch {
case has("/admin/quik"), has("/admin/setup/quik"), has("/admin/fancy-queue"), has("/admin/setup/fancy"), has("/admin/manual/depo"):
return "quik-mnp"
case has("/admin/setup/quikexport"):
return "quik-export"
case strings.HasPrefix(p, "/admin/setup/lk/"): // личный кабинет клиента (bj-lk); /admin/setup/lk* без «/» — ЕСИА Финанс, ядро
return "web-cabinet"
}
return ""
}
var moduleTitles = map[string]string{"quik-mnp": "QUIK: неторговые поручения и обмен с DEPO", "quik-export": "Экспорт биржевой информации QUIK", "web-cabinet": "Личный кабинет клиента", "npr": "Риск-монитор НПР / УДС"}
func moduleNotLicensed(w http.ResponseWriter, f string) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusForbidden)
fmt.Fprintf(w, `<!doctype html><meta charset="utf-8"><title>Модуль не входит в лицензию</title><body style="font-family:system-ui,sans-serif;max-width:640px;margin:60px auto;padding:0 16px"><h2>Модуль не входит в лицензию</h2><p>«%s» не входит в лицензию этого сервера. Модуль можно добавить в лицензию у ZETIT — после этого раздел появится сам.</p><p><a href="/admin/">На главную</a> · <a href="/admin/setup#sec-license">Лицензия</a></p></body>`, html.EscapeString(moduleTitles[f]))
}
// requiredPerm — какое право нужно для пути и метода.
func requiredPerm(p, method string) string {
if devOnlyPath(p) {
@@ -1024,6 +1048,10 @@ func authMiddleware(rc *RuntimeConfig, next http.Handler) http.Handler {
http.Redirect(w, r, "/login?next="+url.QueryEscape(next), http.StatusSeeOther)
return
}
if f := moduleForPath(p); f != "" && globalRC != nil && !moduleAllowed(globalRC, f) {
moduleNotLicensed(w, f)
return
}
perm := requiredPerm(p, r.Method)
if !u.Can(perm) {
// пользователь без права просмотра — отправляем на его стартовую страницу
@@ -6,13 +6,13 @@
<nav class="settings-nav">
<button data-sec="overview" class="active"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3"/></svg></span>Обзор</button>
<button data-sec="db"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5.5" rx="7" ry="2.6"/><path d="M5 5.5v13c0 1.4 3.1 2.6 7 2.6s7-1.2 7-2.6v-13"/><path d="M5 12c0 1.4 3.1 2.6 7 2.6s7-1.2 7-2.6"/></svg></span>База данных<span class="ind {{if .Settings.Postgres.DSN}}ok{{else}}err{{end}}"></span></button>
{{if or .AdvancedUnlocked (index .Modules "quik-mnp")}}<button data-sec="fancy"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 8h12M12 4l4 4-4 4"/><path d="M20 16H8M12 20l-4-4 4-4"/></svg></span>Обмен Fancy<span class="ind {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{end}}"></span></button>
<button data-sec="quik"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20V4M4 20h16"/><rect x="7" y="12" width="2.6" height="5"/><rect x="12" y="8" width="2.6" height="9"/><rect x="17" y="14" width="2.6" height="3"/></svg></span>QUIK МНП<span class="ind {{if .Settings.Quik.Verified}}ok{{else if .Settings.Quik.Server}}warn{{end}}"></span></button>
<button data-sec="quikexport"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17l5-5 4 4 7-8"/><path d="M14 8h6v6"/></svg></span>Экспорт QUIK<span class="ind {{.QuikExport.Ind}}"></span></button>{{end}}
{{if or .AdvancedUnlocked (mod "quik-mnp")}}<button data-sec="fancy"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 8h12M12 4l4 4-4 4"/><path d="M20 16H8M12 20l-4-4 4-4"/></svg></span>Обмен Fancy<span class="ind {{if .Settings.Fancy.DepoTablesCheckOK}}ok{{else if .Settings.Fancy.Server}}warn{{end}}"></span></button>
<button data-sec="quik"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20V4M4 20h16"/><rect x="7" y="12" width="2.6" height="5"/><rect x="12" y="8" width="2.6" height="9"/><rect x="17" y="14" width="2.6" height="3"/></svg></span>QUIK МНП<span class="ind {{if .Settings.Quik.Verified}}ok{{else if .Settings.Quik.Server}}warn{{end}}"></span></button>{{end}}
{{if or .AdvancedUnlocked (mod "quik-export") (mod "npr")}}<button data-sec="quikexport"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 17l5-5 4 4 7-8"/><path d="M14 8h6v6"/></svg></span>Экспорт QUIK<span class="ind {{.QuikExport.Ind}}"></span></button>{{end}}
<button data-sec="users"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="8" r="3.5"/><path d="M3 20c0-3.3 2.7-6 6-6s6 2.7 6 6"/><circle cx="17" cy="9" r="2.5"/><path d="M15.5 14.5c2.8.3 5.5 2.5 5.5 5.5"/></svg></span>Пользователи<span class="ind {{if .Users.Open}}warn{{else}}ok{{end}}"></span></button>
<button data-sec="crypto"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/></svg></span>Криптография<span class="ind {{if .Settings.Crypto.Profile}}ok{{else}}warn{{end}}"></span></button>
<button data-sec="nsd"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18M4 21V10M20 21V10M3 10l9-6 9 6M8 21v-7M12 21v-7M16 21v-7"/></svg></span>НРД<span class="ind {{if .Settings.NSD.IGWBaseURL}}ok{{else}}warn{{end}}"></span></button>
<button data-sec="cabinet"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="9" r="3.2"/><path d="M6 19c.6-3 3-4.6 6-4.6s5.4 1.6 6 4.6"/><rect x="3" y="4" width="18" height="16" rx="2"/></svg></span>Кабинет клиента<span class="ind {{if .Settings.LK.APIKey}}ok{{else}}warn{{end}}"></span></button>
{{if or .AdvancedUnlocked (mod "web-cabinet")}}<button data-sec="cabinet"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="9" r="3.2"/><path d="M6 19c.6-3 3-4.6 6-4.6s5.4 1.6 6 4.6"/><rect x="3" y="4" width="18" height="16" rx="2"/></svg></span>Кабинет клиента<span class="ind {{if .Settings.LK.APIKey}}ok{{else}}warn{{end}}"></span></button>{{end}}
<button data-sec="lk"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="11" r="2"/><path d="M6 16c.4-1.4 1.6-2.2 3-2.2s2.6.8 3 2.2"/><path d="M15 10h4M15 13h3"/></svg></span>ЕСИА Финанс<span class="ind {{if .Settings.LK.PublicURL}}ok{{else}}warn{{end}}"></span></button>
<button data-sec="tests"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M9 3h6M10 3v6l-5 8.5A2 2 0 0 0 6.7 21h10.6a2 2 0 0 0 1.7-3.5L14 9V3"/><path d="M8 15h8"/></svg></span>Диагностика</button>
<button data-sec="alerts"><span class="nico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg></span>Оповещения<span class="ind {{if or .Settings.Alerts.SMTP.Enabled .Settings.Alerts.Zabbix.Enabled}}ok{{end}}"></span></button>
@@ -81,7 +81,7 @@
</div>
</section>
{{if .AdvancedUnlocked}}
{{if or .AdvancedUnlocked (mod "quik-mnp")}}
{{/* ============ ОБМЕН FANCY (MSSQL) — перспективный модуль ============ */}}
<section class="settings-section" id="sec-fancy">
<h1>Обмен Fancy (MSSQL / PostgreSQL)</h1>
@@ -381,11 +381,12 @@
</section>
{{/* ============ ЭКСПОРТ БИРЖЕВОЙ ИНФОРМАЦИИ (QUIK) ============ */}}
{{if or .AdvancedUnlocked (mod "quik-export") (mod "npr")}}
<section class="settings-section" id="sec-quikexport">
{{.QuikExport.HTML}}
{{if or .AdvancedUnlocked (mod "quik-export")}}{{.QuikExport.HTML}}{{end}}
{{/* модуль НПР/УДС работает поверх базы экспорта QUIK — его настройки здесь же */}}
<div class="card" id="sec-npr">
{{if or .AdvancedUnlocked (mod "npr")}}<div class="card" id="sec-npr">
<h2><span class="dot {{if .Settings.NPR.Enabled}}ok{{else}}warn{{end}}"></span>Модуль «НПР / УДС» (риск-монитор по данным QUIK)</h2>
<p class="muted">Показывает НПР1 / НПР2 / УДС клиентов маржинальной торговли по базе экспорта QUIK (схема <code>npr</code>). Отдельно лицензируемый модуль (признак <code>npr</code>); при включении в меню появляется пункт «НПР / УДС». Подключение — логин только для чтения.</p>
{{template "verline" (.Versions.Get "bj-npr")}}
@@ -395,9 +396,10 @@
<label>Строка подключения к базе экспорта QUIK <input type="text" name="dsn" value="{{.Settings.NPR.DSN}}" placeholder="postgres://quik_ro:пароль@localhost:5432/quik_export" style="width:100%;font-family:ui-monospace,monospace;font-size:12px"></label>
<div style="display:flex;gap:8px;flex-wrap:wrap"><button type="submit" class="btn btn-ok">Сохранить</button><button type="submit" formaction="/admin/setup/npr/test" class="btn">Проверить подключение</button></div>
</form>
</div>
</div>{{end}}
<h1>Лицензия</h1>
</section>
{{end}}
{{/* ============ КРИПТОГРАФИЯ ============ */}}
<section class="settings-section" id="sec-crypto">
@@ -623,6 +625,7 @@ function hbBind(inp,box,list,onPick){
{{/* ============ ЛИЧНЫЙ КАБИНЕТ ============ */}}
{{/* ============ КАБИНЕТ КЛИЕНТА (наш модуль bj-lk) ============ */}}
{{if or .AdvancedUnlocked (mod "web-cabinet")}}
<section class="settings-section" id="sec-cabinet">
<h1>Кабинет клиента</h1>
<p class="muted" style="margin:-8px 0 16px">Наш личный кабинет для клиентов депозитария: модуль <code>bj-lk</code> на своём сервере или PHP-хостинге, связь с ядром, учётные записи клиентов и сотрудников. Интеграция с платформой ЕСИА Финанс — отдельный раздел.</p>
@@ -790,6 +793,7 @@ function hbBind(inp,box,list,onPick){
</div>
</section>
{{end}}
{{/* ============ ЕСИА ФИНАНС (внешняя платформа ЛК) ============ */}}
<section class="settings-section" id="sec-lk">
+2 -2
View File
@@ -334,8 +334,8 @@ label { font-size:13px; font-weight:500; }
{{if or (can "view") (can "quik")}}<span class="dd{{if or (eq .Active "fancy") (eq .Active "quik") (eq .Active "news") (eq .Active "handbook")}} active{{end}}">
<span class="ddbtn">Обмен ▾</span>
<span class="ddmenu">
{{if can "view"}}<a href="/admin/fancy-queue">Обмен с DEPO (Fancy)</a>{{end}}
{{if can "quik"}}<a href="/admin/quik">Поручения QUIK</a>{{end}}
{{if and (can "view") (mod "quik-mnp")}}<a href="/admin/fancy-queue">Обмен с DEPO (Fancy)</a>{{end}}
{{if and (can "quik") (mod "quik-mnp")}}<a href="/admin/quik">Поручения QUIK</a>{{end}}
{{if can "view"}}<a href="/admin/news">События</a>
<a href="/admin/handbook">Справочник НРД</a>{{end}}
</span>