work-license-server 1.0.14 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/admin.js +603 -311
- package/assets/landing.css +150 -0
- package/assets/landing.js +504 -71
- package/package.json +1 -1
- package/src/admin.js +241 -165
- package/src/crypto.js +4 -0
- package/src/landing.js +32 -36
- package/src/server.js +120 -4
- package/src/store.js +771 -57
package/package.json
CHANGED
package/src/admin.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Админ-панель центра лицензирования и франшиз ODANT-WORK (/admin).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Единая шапка с основным сайтом (О платформе · Клиентам · Франшиза · Личный кабинет · Админ-панель)
|
|
5
|
+
* и двухколоночная навигация со счётчиками в скобках (по эталону Скрина 1).
|
|
6
6
|
*/
|
|
7
7
|
import {
|
|
8
8
|
PAK_PRODUCT,
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
import { icon } from './landing.js';
|
|
16
16
|
|
|
17
17
|
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, ch => ({ '&': '&', '<': '<', '>': '>', '"': '"', '\'': ''' }[ch]));
|
|
18
|
-
const rub = (n) => Number(n || 0).toLocaleString('ru-RU').replace(/\u00a0/g, ' ') + ' ₽';
|
|
19
18
|
const jsonForScript = (v) => JSON.stringify(v).replace(/</g, '\\u003c').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
|
20
19
|
|
|
21
20
|
export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
|
|
@@ -38,7 +37,7 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
|
|
|
38
37
|
<head>
|
|
39
38
|
<meta charset="utf-8">
|
|
40
39
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
41
|
-
<title>ODANT-WORK ·
|
|
40
|
+
<title>ODANT-WORK · Админ-панель</title>
|
|
42
41
|
<meta name="robots" content="noindex, nofollow">
|
|
43
42
|
<meta name="theme-color" content="#04060b">
|
|
44
43
|
<link rel="icon" type="image/png" href="/favicon.ico">
|
|
@@ -91,234 +90,311 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
|
|
|
91
90
|
<div style="margin-top:8px;text-align:center">
|
|
92
91
|
<a href="/" class="small muted">← Вернуться на главную страницу work.odant.org</a>
|
|
93
92
|
</div>
|
|
94
|
-
<div class="small muted" style="margin-top:14px;padding-top:12px;border-top:1px solid var(--line)">
|
|
95
|
-
Ключ ЦС Ed25519: <code>${esc(publicKey.slice(0, 26))}…</code>
|
|
96
|
-
</div>
|
|
97
93
|
</div>
|
|
98
94
|
</div>
|
|
99
95
|
|
|
100
|
-
<!-- Основная
|
|
96
|
+
<!-- Основная оболочка с единой шапкой сайта и двухколоночным меню разделов -->
|
|
101
97
|
<div class="shell" id="adminAppShell" style="display:none">
|
|
102
|
-
<header class="topbar"
|
|
103
|
-
<a href="/" class="brand" style="text-decoration:none">
|
|
98
|
+
<header class="topbar">
|
|
99
|
+
<a href="/" class="brand" style="text-decoration:none" aria-label="ODANT-WORK — на главную">
|
|
104
100
|
<img src="/assets/odant-logo.png" alt="ODANT">
|
|
105
101
|
<span>
|
|
106
|
-
<span class="brand-name">ODANT<b>·</b>WORK
|
|
107
|
-
<span class="brand-sub"
|
|
102
|
+
<span class="brand-name">ODANT<b>·</b>WORK</span><br>
|
|
103
|
+
<span class="brand-sub">учёт · документооборот · процессы · приватный ИИ</span>
|
|
108
104
|
</span>
|
|
109
105
|
</a>
|
|
110
|
-
|
|
106
|
+
|
|
107
|
+
<nav class="tabs" id="tabs" aria-label="Разделы">
|
|
108
|
+
<span class="tab-ind" id="tabInd"></span>
|
|
109
|
+
<a class="tab" href="/#product" style="text-decoration:none">О платформе</a>
|
|
110
|
+
<a class="tab" href="/#clients" style="text-decoration:none">Клиентам</a>
|
|
111
|
+
<a class="tab" href="/#franchise" style="text-decoration:none">Франшиза</a>
|
|
112
|
+
<a class="tab" href="/#addons" style="text-decoration:none">Личный кабинет</a>
|
|
113
|
+
<a class="tab active" href="/admin" id="tileAdmin" style="text-decoration:none">Админ-панель</a>
|
|
114
|
+
</nav>
|
|
115
|
+
|
|
111
116
|
<div class="top-actions">
|
|
112
|
-
<a href="/#addons" class="btn btn-ghost btn-sm" style="text-decoration:none">← Личный кабинет</a>
|
|
113
117
|
<button class="icon-btn" id="btnTheme" title="Светлая / тёмная тема" aria-label="Переключить тему">${icon('sun')}</button>
|
|
114
|
-
<button class="btn btn-
|
|
115
|
-
<button class="btn btn-ok btn-sm" data-action="demo-pak">+ Демо-лицензия ПАК</button>
|
|
116
|
-
<button class="btn btn-ghost btn-sm" data-action="admin-logout" title="Завершить сессию администратора">${icon('lock')}<span id="admUserLabel">Выйти</span></button>
|
|
118
|
+
<button class="btn btn-primary btn-sm" data-action="admin-logout" title="Нажмите, чтобы выйти из сессии">${icon('user')}<span id="admUserLabel">BOSS</span></button>
|
|
117
119
|
</div>
|
|
118
120
|
</header>
|
|
119
121
|
|
|
120
|
-
<section class="stage active" style="padding-top:
|
|
121
|
-
<div class="
|
|
122
|
-
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
<!-- Левая панель навигации и ИИ-агента администратора -->
|
|
132
|
-
<aside class="admin-left">
|
|
133
|
-
<nav class="admin-nav" aria-label="Вкладки админ-панели">
|
|
134
|
-
<button class="adm-tab on" data-adm-tab="all">${icon('ai')}<span>ИИ-агент + Все разделы</span><i class="live-dot"></i></button>
|
|
135
|
-
<button class="adm-tab" data-adm-tab="agent">${icon('agents')}<span>Только ИИ-агент</span></button>
|
|
136
|
-
<button class="adm-tab" data-adm-tab="licenses">${icon('key')}<span>Лицензии (${P.baseSeats} вечн. + доп.)</span></button>
|
|
137
|
-
<button class="adm-tab" data-adm-tab="orders">${icon('cart')}<span>Заказы ЮKassa</span></button>
|
|
138
|
-
<button class="adm-tab" data-adm-tab="partners">${icon('map')}<span>Франшизы и партнёры</span></button>
|
|
139
|
-
<button class="adm-tab" data-adm-tab="tickets">${icon('chat')}<span>Тикеты SLA (MAX·TG·VK)</span></button>
|
|
140
|
-
<button class="adm-tab" data-adm-tab="users" style="grid-column:1 / -1">${icon('user')}<span>Зарегистрированные клиенты и подписанные оферты (ПЭП)</span></button>
|
|
141
|
-
</nav>
|
|
122
|
+
<section class="stage active" style="padding-top:24px">
|
|
123
|
+
<div class="sec-layout">
|
|
124
|
+
<!-- Левое вертикальное меню разделов со счётчиками в скобках (как на Скрине 1) -->
|
|
125
|
+
<aside class="sec-nav" aria-label="Разделы администрирования">
|
|
126
|
+
<button type="button" class="sec-nav-item active" data-adm-tab="servers" id="navAdmServers">Серверы и лицензии (0)</button>
|
|
127
|
+
<button type="button" class="sec-nav-item" data-adm-tab="franchises" id="navAdmFranchises">Франшизы (0)</button>
|
|
128
|
+
<div class="sec-nav-sep"></div>
|
|
129
|
+
<button type="button" class="sec-nav-item" data-adm-tab="orders" id="navAdmOrders">Заказы и счета (0)</button>
|
|
130
|
+
<button type="button" class="sec-nav-item" data-adm-tab="users" id="navAdmUsers">Клиенты и роли (0)</button>
|
|
131
|
+
<button type="button" class="sec-nav-item" data-adm-tab="tickets" id="navAdmTickets">Тикеты поддержки (0)</button>
|
|
132
|
+
</aside>
|
|
142
133
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
134
|
+
<!-- Правая область выбранного раздела -->
|
|
135
|
+
<div class="sec-main">
|
|
136
|
+
<!-- 1. СЕРВЕРЫ И ЛИЦЕНЗИИ -->
|
|
137
|
+
<div data-adm-sec="servers">
|
|
138
|
+
<div class="sec-bar" style="margin-bottom:12px">
|
|
139
|
+
<h2 class="sec-title">Серверы (устройства) WORK и лицензии клиентов</h2>
|
|
140
|
+
<div class="sec-controls">
|
|
141
|
+
<input type="search" id="inpFilterServers" placeholder="Поиск по клиенту, Email, HWID…">
|
|
142
|
+
<select id="selFilterServers">
|
|
143
|
+
<option value="all">Все серверы</option>
|
|
144
|
+
<option value="base_active">Базовая активна</option>
|
|
145
|
+
<option value="base_inactive">Без базовой лицензии</option>
|
|
146
|
+
<option value="hwid_bound">HWID привязан</option>
|
|
147
|
+
</select>
|
|
148
|
+
<button type="button" class="btn btn-primary btn-sm" data-action="open-create-server-modal">+ Добавить сервер</button>
|
|
149
149
|
</div>
|
|
150
150
|
</div>
|
|
151
|
-
<div class="
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
<button class="btn btn-primary" data-action="adm-ai-send" aria-label="Отправить">${icon('send')}</button>
|
|
151
|
+
<div class="sec-box">
|
|
152
|
+
<table class="sec-table">
|
|
153
|
+
<thead><tr>
|
|
154
|
+
<th>Сервер / ID</th>
|
|
155
|
+
<th>Владелец / Организация</th>
|
|
156
|
+
<th>Железо (HWID)</th>
|
|
157
|
+
<th>Базовая лицензия</th>
|
|
158
|
+
<th>Доп. места и SLA</th>
|
|
159
|
+
<th style="text-align:right">Файлы</th>
|
|
160
|
+
</tr></thead>
|
|
161
|
+
<tbody id="admLicensesBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
162
|
+
</table>
|
|
164
163
|
</div>
|
|
165
164
|
</div>
|
|
166
|
-
</aside>
|
|
167
165
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
<
|
|
175
|
-
|
|
166
|
+
<!-- 2. ФРАНШИЗЫ И ГРАФИКИ ПОГАШЕНИЯ (12 МЕС.) -->
|
|
167
|
+
<div data-adm-sec="franchises" style="display:none">
|
|
168
|
+
<div class="sec-bar" style="margin-bottom:12px">
|
|
169
|
+
<h2 class="sec-title">Региональные франшизы и графики погашения (12 мес.)</h2>
|
|
170
|
+
<div class="sec-controls">
|
|
171
|
+
<input type="search" id="inpFilterFranchises" placeholder="Поиск по региону, компании, ИНН…">
|
|
172
|
+
<select id="selFilterFranchises">
|
|
173
|
+
<option value="all">Все активные</option>
|
|
174
|
+
<option value="pending_deposit">Ожидают залога (15 мин)</option>
|
|
175
|
+
<option value="deposit_paid">Погашение по графику</option>
|
|
176
|
+
<option value="overdue">С просрочкой</option>
|
|
177
|
+
<option value="paid_in_full">Полностью погашены</option>
|
|
178
|
+
<option value="closed">Отменённые / Истёкшие</option>
|
|
179
|
+
</select>
|
|
176
180
|
</div>
|
|
177
181
|
</div>
|
|
178
|
-
<div class="
|
|
179
|
-
<
|
|
180
|
-
<div class="field" style="margin:0"><label for="admLicInn">ИНН</label><input type="text" id="admLicInn" value="6234109876"></div>
|
|
181
|
-
<div class="field" style="margin:0"><label for="admLicEmail">Email клиента</label><input type="email" id="admLicEmail" value="it@ryazan-agro.ru"></div>
|
|
182
|
-
<div class="field" style="margin:0"><label for="admLicHwid">HWID комплекса (необязательно)</label><input type="text" id="admLicHwid" placeholder="PAK-..."></div>
|
|
183
|
-
</div>
|
|
184
|
-
<div style="display:flex;align-items:center;gap:14px;margin-top:14px;flex-wrap:wrap">
|
|
185
|
-
<button class="btn btn-primary btn-sm" data-action="adm-issue-lic">${icon('key')} Выпустить и подписать сертификат</button>
|
|
186
|
-
<span class="small muted" id="admIssueStatus"></span>
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
|
|
190
|
-
<div class="card" data-adm-sec="licenses">
|
|
191
|
-
<h3 class="h3" style="margin:0 0 12px">Выпущенные лицензии комплексов ODANT-WORK</h3>
|
|
192
|
-
<div class="table-wrap">
|
|
193
|
-
<table class="t">
|
|
182
|
+
<div class="sec-box">
|
|
183
|
+
<table class="sec-table">
|
|
194
184
|
<thead><tr>
|
|
195
|
-
<th
|
|
196
|
-
<th
|
|
197
|
-
<th
|
|
198
|
-
<th
|
|
199
|
-
<th
|
|
200
|
-
<th
|
|
185
|
+
<th>Субъект РФ / ID</th>
|
|
186
|
+
<th>Франчайзи</th>
|
|
187
|
+
<th>Залог (100 тыс. ₽)</th>
|
|
188
|
+
<th>График (12 мес.)</th>
|
|
189
|
+
<th>Оплачено / Остаток</th>
|
|
190
|
+
<th style="text-align:right">Управление</th>
|
|
201
191
|
</tr></thead>
|
|
202
|
-
<tbody id="
|
|
192
|
+
<tbody id="admFranchisesBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
203
193
|
</table>
|
|
204
194
|
</div>
|
|
205
195
|
</div>
|
|
206
196
|
|
|
207
|
-
<!--
|
|
208
|
-
<div
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
<
|
|
197
|
+
<!-- 3. ЗАКАЗЫ И СЧЕТА -->
|
|
198
|
+
<div data-adm-sec="orders" style="display:none">
|
|
199
|
+
<div class="sec-bar" style="margin-bottom:12px">
|
|
200
|
+
<h2 class="sec-title">Заказы ЮKassa и безналичные счета-договоры</h2>
|
|
201
|
+
<div class="sec-controls">
|
|
202
|
+
<input type="search" id="inpFilterOrders" placeholder="Поиск по заказу, счёту, ИНН…">
|
|
203
|
+
<select id="selFilterOrders">
|
|
204
|
+
<option value="all">Все заказы</option>
|
|
205
|
+
<option value="pending">Ожидают оплаты</option>
|
|
206
|
+
<option value="paid">Оплачены</option>
|
|
207
|
+
<option value="canceled">Отменены</option>
|
|
208
|
+
</select>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
<div class="sec-box">
|
|
212
|
+
<table class="sec-table">
|
|
212
213
|
<thead><tr>
|
|
213
|
-
<th>Заказ /
|
|
214
|
+
<th>Заказ / Счёт</th>
|
|
214
215
|
<th>Назначение</th>
|
|
215
216
|
<th>Плательщик</th>
|
|
216
|
-
<th
|
|
217
|
+
<th>Сумма</th>
|
|
217
218
|
<th>Статус</th>
|
|
218
|
-
<th>Действия</th>
|
|
219
|
+
<th style="text-align:right">Действия</th>
|
|
219
220
|
</tr></thead>
|
|
220
221
|
<tbody id="admOrdersBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
221
222
|
</table>
|
|
222
223
|
</div>
|
|
223
224
|
</div>
|
|
224
225
|
|
|
225
|
-
<!--
|
|
226
|
-
<div
|
|
227
|
-
<div class="sec-
|
|
228
|
-
<
|
|
229
|
-
|
|
230
|
-
<
|
|
231
|
-
</div>
|
|
232
|
-
</div>
|
|
233
|
-
<div class="grid g4" style="align-items:end">
|
|
234
|
-
<div class="field" style="margin:0">
|
|
235
|
-
<label for="admPartRegion">Субъект РФ</label>
|
|
236
|
-
<select id="admPartRegion">
|
|
237
|
-
${visibleRegions.map(r => `<option value="${esc(r.code)}" ${r.code === 'RU-RYA' ? 'selected' : ''}>${esc(r.name)} (${esc(r.district)})</option>`).join('')}
|
|
238
|
-
</select>
|
|
226
|
+
<!-- 4. КЛИЕНТЫ И РОЛИ BOSS -->
|
|
227
|
+
<div data-adm-sec="users" style="display:none">
|
|
228
|
+
<div class="sec-bar" style="margin-bottom:12px">
|
|
229
|
+
<h2 class="sec-title">Зарегистрированные клиенты и права администраторов (BOSS)</h2>
|
|
230
|
+
<div class="sec-controls">
|
|
231
|
+
<input type="search" id="inpFilterUsers" placeholder="Поиск по Email, организации, ИНН…">
|
|
239
232
|
</div>
|
|
240
|
-
<div class="field" style="margin:0"><label for="admPartCompany">Компания-партнёр</label><input type="text" id="admPartCompany" value="ООО «ЦентрИнтеграция»"></div>
|
|
241
|
-
<div class="field" style="margin:0"><label for="admPartInn">ИНН</label><input type="text" id="admPartInn" value="6234556677"></div>
|
|
242
|
-
<div class="field" style="margin:0"><label for="admPartEmail">Email партнёра</label><input type="email" id="admPartEmail" value="partner@center-it.ru"></div>
|
|
243
|
-
</div>
|
|
244
|
-
<div style="display:flex;align-items:center;gap:14px;margin-top:14px;flex-wrap:wrap">
|
|
245
|
-
<button class="btn btn-primary btn-sm" data-action="adm-register-partner">${icon('map')} Зарегистрировать партнёра</button>
|
|
246
|
-
<span class="small muted" id="admPartnerStatus"></span>
|
|
247
233
|
</div>
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
<div class="card" data-adm-sec="partners">
|
|
251
|
-
<h3 class="h3" style="margin:0 0 12px">Резервы региональных франшиз и действующие партнёры</h3>
|
|
252
|
-
<div class="table-wrap">
|
|
253
|
-
<table class="t">
|
|
234
|
+
<div class="sec-box">
|
|
235
|
+
<table class="sec-table">
|
|
254
236
|
<thead><tr>
|
|
255
|
-
<th>
|
|
256
|
-
<th
|
|
257
|
-
<th
|
|
258
|
-
<th
|
|
259
|
-
<th
|
|
260
|
-
<th
|
|
237
|
+
<th>Email / ID</th>
|
|
238
|
+
<th>Роль</th>
|
|
239
|
+
<th>Организация / ИНН</th>
|
|
240
|
+
<th>Реестры клиента</th>
|
|
241
|
+
<th>Оферта (ПЭП)</th>
|
|
242
|
+
<th style="text-align:right">Вход</th>
|
|
261
243
|
</tr></thead>
|
|
262
|
-
<tbody id="
|
|
244
|
+
<tbody id="admUsersBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
263
245
|
</table>
|
|
264
246
|
</div>
|
|
265
247
|
</div>
|
|
266
248
|
|
|
267
|
-
<!--
|
|
268
|
-
<div
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
<
|
|
249
|
+
<!-- 5. ТИКЕТЫ ПОДДЕРЖКИ SLA -->
|
|
250
|
+
<div data-adm-sec="tickets" style="display:none">
|
|
251
|
+
<div class="sec-bar" style="margin-bottom:12px">
|
|
252
|
+
<h2 class="sec-title">Тикеты техподдержки SLA (MAX · Telegram · VK)</h2>
|
|
253
|
+
<div class="sec-controls">
|
|
254
|
+
<select id="selFilterTickets">
|
|
255
|
+
<option value="all">Все обращения</option>
|
|
256
|
+
<option value="open">Открытые</option>
|
|
257
|
+
<option value="answered">Отвеченные</option>
|
|
258
|
+
<option value="closed">Закрытые</option>
|
|
259
|
+
</select>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
<div class="sec-box">
|
|
263
|
+
<table class="sec-table">
|
|
272
264
|
<thead><tr>
|
|
273
265
|
<th>Тикет / Канал</th>
|
|
274
|
-
<th>Клиент /
|
|
266
|
+
<th>Клиент / Сервер</th>
|
|
275
267
|
<th>SLA</th>
|
|
276
268
|
<th>Переписка</th>
|
|
277
|
-
<th>Ответ инженера</th>
|
|
269
|
+
<th style="text-align:right">Ответ инженера</th>
|
|
278
270
|
</tr></thead>
|
|
279
271
|
<tbody id="admTicketsBody"><tr><td colspan="5" class="muted">Загрузка…</td></tr></tbody>
|
|
280
272
|
</table>
|
|
281
273
|
</div>
|
|
282
274
|
</div>
|
|
283
|
-
|
|
284
|
-
<!-- 5. КЛИЕНТЫ И РОЛИ BOSS -->
|
|
285
|
-
<div class="card" data-adm-sec="users">
|
|
286
|
-
<h3 class="h3" style="margin:0 0 12px">Зарегистрированные клиенты, администраторы (BOSS) и договоры-оферты</h3>
|
|
287
|
-
<div class="table-wrap">
|
|
288
|
-
<table class="t">
|
|
289
|
-
<thead><tr>
|
|
290
|
-
<th>ID / Email</th>
|
|
291
|
-
<th>Роль на сервере</th>
|
|
292
|
-
<th>Организация / ИНН</th>
|
|
293
|
-
<th>Лицензия комплекса</th>
|
|
294
|
-
<th>Статус оферты (ПЭП)</th>
|
|
295
|
-
<th>Дата регистрации</th>
|
|
296
|
-
</tr></thead>
|
|
297
|
-
<tbody id="admUsersBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
298
|
-
</table>
|
|
299
|
-
</div>
|
|
300
|
-
</div>
|
|
301
275
|
</div>
|
|
302
276
|
</div>
|
|
303
277
|
</section>
|
|
304
278
|
|
|
305
279
|
<footer>
|
|
306
280
|
<div class="footer-bottom">
|
|
307
|
-
<span>© ODANT ·
|
|
308
|
-
<span>Ключ центра сертификации Ed25519: <code>${esc(publicKey)}
|
|
281
|
+
<span>© ODANT · ${esc(P.domain)}</span>
|
|
282
|
+
<span>Ключ центра сертификации Ed25519: <code>${esc(publicKey.slice(0, 28))}…</code></span>
|
|
309
283
|
</div>
|
|
310
284
|
</footer>
|
|
311
285
|
</div>
|
|
312
286
|
|
|
313
|
-
<!--
|
|
287
|
+
<!-- Модальное окно: Создание сервера / Выпуск лицензии клиенту -->
|
|
288
|
+
<div class="modal" id="admCreateServerModal" role="dialog" aria-modal="true">
|
|
289
|
+
<div class="card" style="max-width:540px">
|
|
290
|
+
<div class="modal-head">
|
|
291
|
+
<img src="/assets/odant-logo.png" alt="">
|
|
292
|
+
<div>
|
|
293
|
+
<h3 class="h3" style="margin:0">Добавить сервер / выпустить лицензию</h3>
|
|
294
|
+
<div class="small muted">Создание записи сервера в реестре с подписью Ed25519</div>
|
|
295
|
+
</div>
|
|
296
|
+
<button class="icon-btn" data-action="close-create-server-modal" aria-label="Закрыть">${icon('close')}</button>
|
|
297
|
+
</div>
|
|
298
|
+
<div class="field"><label for="admNewSrvName">Название сервера (устройства)</label><input type="text" id="admNewSrvName" placeholder="Основной сервер WORK"></div>
|
|
299
|
+
<div class="row2">
|
|
300
|
+
<div class="field"><label for="admNewSrvOrg">Организация клиента</label><input type="text" id="admNewSrvOrg" placeholder="ООО «Компания»"></div>
|
|
301
|
+
<div class="field"><label for="admNewSrvInn">ИНН</label><input type="text" id="admNewSrvInn" placeholder="7701234567"></div>
|
|
302
|
+
</div>
|
|
303
|
+
<div class="row2">
|
|
304
|
+
<div class="field"><label for="admNewSrvEmail">Email владельца *</label><input type="email" id="admNewSrvEmail" placeholder="client@company.ru"></div>
|
|
305
|
+
<div class="field"><label for="admNewSrvHwid">HWID (необязательно)</label><input type="text" id="admNewSrvHwid" placeholder="PAK-..."></div>
|
|
306
|
+
</div>
|
|
307
|
+
<label style="display:flex;align-items:center;gap:10px;font-size:13.5px;margin:8px 0 14px;cursor:pointer">
|
|
308
|
+
<input type="checkbox" id="admNewSrvActivateBase" checked style="width:17px;height:17px">
|
|
309
|
+
<span>Сразу активировать базовую постоянную лицензию (10 вечных мест + ИИ-агенты)</span>
|
|
310
|
+
</label>
|
|
311
|
+
<div class="status" id="admCreateServerStatus" style="color:var(--danger);min-height:18px;margin-bottom:8px"></div>
|
|
312
|
+
<div style="display:flex;gap:10px;justify-content:flex-end">
|
|
313
|
+
<button class="btn btn-ghost" data-action="close-create-server-modal">Отмена</button>
|
|
314
|
+
<button class="btn btn-primary" data-action="submit-create-server">${icon('key')} Создать и подписать</button>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
<!-- Модальное окно: Начисление дополнительных мест серверу -->
|
|
320
|
+
<div class="modal" id="admGrantSeatsModal" role="dialog" aria-modal="true">
|
|
321
|
+
<div class="card" style="max-width:480px">
|
|
322
|
+
<div class="modal-head">
|
|
323
|
+
<img src="/assets/odant-logo.png" alt="">
|
|
324
|
+
<div>
|
|
325
|
+
<h3 class="h3" id="admGrantTitle" style="margin:0">Начислить доп. рабочие места</h3>
|
|
326
|
+
<div class="small muted" id="admGrantSub">Сервер WORK</div>
|
|
327
|
+
</div>
|
|
328
|
+
<button class="icon-btn" data-action="close-grant-modal" aria-label="Закрыть">${icon('close')}</button>
|
|
329
|
+
</div>
|
|
330
|
+
<input type="hidden" id="admGrantLicId" value="">
|
|
331
|
+
<div class="row2">
|
|
332
|
+
<div class="field">
|
|
333
|
+
<label for="admGrantSeatsCount">Количество доп. мест</label>
|
|
334
|
+
<input type="number" id="admGrantSeatsCount" min="1" max="2000" value="5">
|
|
335
|
+
</div>
|
|
336
|
+
<div class="field">
|
|
337
|
+
<label for="admGrantYears">Срок действия (лет)</label>
|
|
338
|
+
<input type="number" id="admGrantYears" min="1" max="5" value="1">
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
<div class="field">
|
|
342
|
+
<label for="admGrantTier">Уровень поддержки SLA</label>
|
|
343
|
+
<select id="admGrantTier">
|
|
344
|
+
<option value="start">Start (10 000 ₽/место в год)</option>
|
|
345
|
+
<option value="business" selected>Business (15 000 ₽/место в год)</option>
|
|
346
|
+
<option value="enterprise">Enterprise (20 000 ₽/место в год)</option>
|
|
347
|
+
</select>
|
|
348
|
+
</div>
|
|
349
|
+
<div style="display:flex;gap:10px;justify-content:flex-end;margin-top:14px">
|
|
350
|
+
<button class="btn btn-ghost" data-action="close-grant-modal">Отмена</button>
|
|
351
|
+
<button class="btn btn-primary" data-action="submit-grant-seats">Начислить и выпустить .lic.json</button>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
|
|
356
|
+
<!-- Модальное окно: 12-месячный график платежей по франшизе -->
|
|
357
|
+
<div class="modal" id="admFranchiseScheduleModal" role="dialog" aria-modal="true">
|
|
358
|
+
<div class="card" style="max-width:780px">
|
|
359
|
+
<div class="modal-head">
|
|
360
|
+
<img src="/assets/odant-logo.png" alt="">
|
|
361
|
+
<div>
|
|
362
|
+
<h3 class="h3" id="admSchedTitle" style="margin:0">График погашения франшизы (12 месяцев)</h3>
|
|
363
|
+
<div class="small muted" id="admSchedSub"></div>
|
|
364
|
+
</div>
|
|
365
|
+
<button class="icon-btn" data-action="close-sched-modal" aria-label="Закрыть">${icon('close')}</button>
|
|
366
|
+
</div>
|
|
367
|
+
<div id="admSchedSummary" style="margin-bottom:12px"></div>
|
|
368
|
+
<div style="max-height:420px;overflow-y:auto">
|
|
369
|
+
<table class="sec-table">
|
|
370
|
+
<thead><tr>
|
|
371
|
+
<th>Месяц</th>
|
|
372
|
+
<th>Дата платежа</th>
|
|
373
|
+
<th>Сумма</th>
|
|
374
|
+
<th>Статус</th>
|
|
375
|
+
<th style="text-align:right">Действие</th>
|
|
376
|
+
</tr></thead>
|
|
377
|
+
<tbody id="admSchedBody"></tbody>
|
|
378
|
+
</table>
|
|
379
|
+
</div>
|
|
380
|
+
<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:14px;flex-wrap:wrap">
|
|
381
|
+
<div id="admSchedEarlyBox"></div>
|
|
382
|
+
<button class="btn btn-ghost" data-action="close-sched-modal">Закрыть</button>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<!-- Модальное окно: Просмотр сертификата и файлов лицензий (.lic.json) -->
|
|
314
388
|
<div class="modal" id="certModal" role="dialog" aria-modal="true" aria-labelledby="certTitle">
|
|
315
|
-
<div class="card" style="max-width:
|
|
389
|
+
<div class="card" style="max-width:680px">
|
|
316
390
|
<div class="modal-head">
|
|
317
391
|
<img src="/assets/odant-logo.png" alt="">
|
|
318
|
-
<div><h3 class="h3" id="certTitle" style="margin:0">Сертификат
|
|
392
|
+
<div><h3 class="h3" id="certTitle" style="margin:0">Сертификат и файлы лицензий (.lic.json)</h3><div class="small muted" id="certSub">WORK-LIC-v1</div></div>
|
|
393
|
+
<button class="icon-btn" data-action="cert-close" aria-label="Закрыть">${icon('close')}</button>
|
|
319
394
|
</div>
|
|
320
|
-
<
|
|
321
|
-
<
|
|
395
|
+
<div id="certFilesList" style="margin-bottom:12px"></div>
|
|
396
|
+
<pre class="console" id="certJsonBox" style="max-height:320px;margin:0"></pre>
|
|
397
|
+
<div style="display:flex;gap:10px;margin-top:14px;flex-wrap:wrap;justify-content:flex-end">
|
|
322
398
|
<button class="btn btn-primary" data-action="cert-copy">Скопировать JSON</button>
|
|
323
399
|
<button class="btn btn-ok" data-action="cert-download">Скачать .json</button>
|
|
324
400
|
<button class="btn btn-ghost" data-action="cert-close">Закрыть</button>
|
package/src/crypto.js
CHANGED
|
@@ -289,6 +289,8 @@ export function createOwnerAccessToken({
|
|
|
289
289
|
hubUrl = 'https://work.odant.org',
|
|
290
290
|
accountId = '',
|
|
291
291
|
email = '',
|
|
292
|
+
licenseId = '',
|
|
293
|
+
serverName = '',
|
|
292
294
|
tokenId = '',
|
|
293
295
|
secret = '',
|
|
294
296
|
issuedAt = Date.now(),
|
|
@@ -298,6 +300,8 @@ export function createOwnerAccessToken({
|
|
|
298
300
|
hubUrl: String(hubUrl || 'https://work.odant.org').replace(/\/+$/, ''),
|
|
299
301
|
accountId: String(accountId || ''),
|
|
300
302
|
email: String(email || '').toLowerCase(),
|
|
303
|
+
...(licenseId ? { licenseId: String(licenseId) } : {}),
|
|
304
|
+
...(serverName ? { serverName: String(serverName) } : {}),
|
|
301
305
|
tokenId: String(tokenId || ('TOK-' + crypto.randomBytes(4).toString('hex').toUpperCase())),
|
|
302
306
|
secret: String(secret || crypto.randomBytes(16).toString('hex')),
|
|
303
307
|
issuedAt: Number(issuedAt || Date.now()),
|