work-license-server 1.0.14 → 1.0.15
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 +602 -299
- package/assets/landing.js +541 -69
- package/package.json +1 -1
- package/src/admin.js +279 -182
- package/src/crypto.js +4 -0
- package/src/landing.js +38 -30
- package/src/server.js +120 -4
- package/src/store.js +771 -57
package/package.json
CHANGED
package/src/admin.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Рабочая консоль администратора центра лицензирования и биллинга ODANT-WORK (/admin).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Доступна пользователям с ролью BOSS (первый зарегистрированный по Email пользователь
|
|
5
|
+
* или назначенный администратор) по их штатной сессии без отдельных паролей.
|
|
6
|
+
*
|
|
7
|
+
* Разделы:
|
|
8
|
+
* 1. Реестр серверов (устройств) и лицензий клиентов
|
|
9
|
+
* 2. Реестр региональных франшиз и 12-месячных графиков погашения
|
|
10
|
+
* 3. Заказы ЮKassa и безналичные счета-договоры
|
|
11
|
+
* 4. Зарегистрированные клиенты, оферты (ПЭП) и роли BOSS
|
|
12
|
+
* 5. Тикеты техподдержки SLA (MAX · Telegram · VK)
|
|
6
13
|
*/
|
|
7
14
|
import {
|
|
8
15
|
PAK_PRODUCT,
|
|
@@ -15,7 +22,6 @@ import {
|
|
|
15
22
|
import { icon } from './landing.js';
|
|
16
23
|
|
|
17
24
|
const esc = (s) => String(s ?? '').replace(/[&<>"']/g, ch => ({ '&': '&', '<': '<', '>': '>', '"': '"', '\'': ''' }[ch]));
|
|
18
|
-
const rub = (n) => Number(n || 0).toLocaleString('ru-RU').replace(/\u00a0/g, ' ') + ' ₽';
|
|
19
25
|
const jsonForScript = (v) => JSON.stringify(v).replace(/</g, '\\u003c').replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
|
20
26
|
|
|
21
27
|
export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
|
|
@@ -38,7 +44,7 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
|
|
|
38
44
|
<head>
|
|
39
45
|
<meta charset="utf-8">
|
|
40
46
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
41
|
-
<title>ODANT-WORK ·
|
|
47
|
+
<title>ODANT-WORK · Админ-панель центра лицензирования и франшиз</title>
|
|
42
48
|
<meta name="robots" content="noindex, nofollow">
|
|
43
49
|
<meta name="theme-color" content="#04060b">
|
|
44
50
|
<link rel="icon" type="image/png" href="/favicon.ico">
|
|
@@ -97,208 +103,197 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
|
|
|
97
103
|
</div>
|
|
98
104
|
</div>
|
|
99
105
|
|
|
100
|
-
<!-- Основная консоль администратора
|
|
106
|
+
<!-- Основная операционная консоль администратора -->
|
|
101
107
|
<div class="shell" id="adminAppShell" style="display:none">
|
|
102
|
-
<header class="topbar"
|
|
108
|
+
<header class="topbar">
|
|
103
109
|
<a href="/" class="brand" style="text-decoration:none">
|
|
104
110
|
<img src="/assets/odant-logo.png" alt="ODANT">
|
|
105
111
|
<span>
|
|
106
|
-
<span class="brand-name">ODANT<b>·</b>WORK <span class="tag ok" style="margin-left:6px;vertical-align:middle">BOSS ·
|
|
107
|
-
<span class="brand-sub"
|
|
112
|
+
<span class="brand-name">ODANT<b>·</b>WORK <span class="tag ok" style="margin-left:6px;vertical-align:middle">BOSS · АДМИН</span></span><br>
|
|
113
|
+
<span class="brand-sub">реестр серверов · реестр франшиз · платежи и счета</span>
|
|
108
114
|
</span>
|
|
109
115
|
</a>
|
|
110
|
-
|
|
116
|
+
|
|
117
|
+
<nav class="tabs" id="admTopTabs" aria-label="Реестры администратора">
|
|
118
|
+
<button class="tab active" data-adm-tab="servers">Серверы и лицензии <span class="tag" id="tabCntServers" style="margin-left:4px">0</span></button>
|
|
119
|
+
<button class="tab" data-adm-tab="franchises">Франшизы и графики <span class="tag" id="tabCntFranchises" style="margin-left:4px">0</span></button>
|
|
120
|
+
<button class="tab" data-adm-tab="orders">Заказы и счета <span class="tag" id="tabCntOrders" style="margin-left:4px">0</span></button>
|
|
121
|
+
<button class="tab" data-adm-tab="users">Клиенты и роли BOSS <span class="tag" id="tabCntUsers" style="margin-left:4px">0</span></button>
|
|
122
|
+
<button class="tab" data-adm-tab="tickets">Тикеты SLA <span class="tag" id="tabCntTickets" style="margin-left:4px">0</span></button>
|
|
123
|
+
</nav>
|
|
124
|
+
|
|
111
125
|
<div class="top-actions">
|
|
112
126
|
<a href="/#addons" class="btn btn-ghost btn-sm" style="text-decoration:none">← Личный кабинет</a>
|
|
113
127
|
<button class="icon-btn" id="btnTheme" title="Светлая / тёмная тема" aria-label="Переключить тему">${icon('sun')}</button>
|
|
114
|
-
<button class="btn btn-ghost btn-sm" data-action="registry">${icon('server')}<span
|
|
115
|
-
<button class="btn btn-
|
|
116
|
-
<button class="btn btn-ghost btn-sm" data-action="admin-logout" title="Завершить сессию администратора">${icon('lock')}<span id="admUserLabel">Выйти</span></button>
|
|
128
|
+
<button class="btn btn-ghost btn-sm" data-action="registry">${icon('server')}<span>Обновить</span></button>
|
|
129
|
+
<button class="btn btn-ghost btn-sm" data-action="admin-logout" title="Завершить сессию">${icon('lock')}<span id="admUserLabel">Выйти</span></button>
|
|
117
130
|
</div>
|
|
118
131
|
</header>
|
|
119
132
|
|
|
120
|
-
<section class="stage active" style="padding-top:
|
|
121
|
-
|
|
122
|
-
|
|
133
|
+
<section class="stage active" style="padding-top:20px">
|
|
134
|
+
<!-- Сводные показатели -->
|
|
135
|
+
<div class="stats" style="grid-template-columns:repeat(6,1fr);margin-bottom:20px">
|
|
136
|
+
<div class="stat"><b id="admStatLic">0</b><span id="admStatLicSub">серверов · 0 мест</span></div>
|
|
137
|
+
<div class="stat"><b id="admStatFranchises">0</b><span id="admStatFranchisesSub">активных франшиз</span></div>
|
|
123
138
|
<div class="stat"><b id="admStatPaid">0 ₽</b><span id="admStatPaidSub">оплачено заказов: 0</span></div>
|
|
124
139
|
<div class="stat"><b id="admStatPending">0 ₽</b><span id="admStatPendingSub">ожидают оплаты: 0</span></div>
|
|
125
|
-
<div class="stat"><b id="
|
|
126
|
-
<div class="stat"><b id="admStatUsers">0</b><span id="admStatUsersSub">клиентов (оферт ПЭП: 0)</span></div>
|
|
140
|
+
<div class="stat"><b id="admStatUsers">0</b><span id="admStatUsersSub">клиентов в базе</span></div>
|
|
127
141
|
<div class="stat"><b id="admStatTickets">0</b><span id="admStatTicketsSub">открытых тикетов SLA</span></div>
|
|
128
142
|
</div>
|
|
129
143
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<
|
|
133
|
-
<
|
|
134
|
-
<
|
|
135
|
-
<
|
|
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>
|
|
142
|
-
|
|
143
|
-
<div class="card adm-agent-card" id="admAgentCard">
|
|
144
|
-
<div class="ai-head" style="padding:0 0 12px;margin-bottom:10px">
|
|
145
|
-
<img src="/assets/odant-logo.png" alt="">
|
|
146
|
-
<div>
|
|
147
|
-
<b>ИИ-агент администратора</b>
|
|
148
|
-
<span>● управление реестром · защищённый контур</span>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
<div class="ai-msgs adm-ai-msgs" id="admAiMessages">
|
|
152
|
-
<div class="msg bot">Здравствуйте! Я ИИ-агент консоли администратора ODANT-WORK. Помогу проанализировать реестр лицензий (${P.baseSeats} вечных мест + 1 год Enterprise), выпустить сертификат Ed25519, провести оплату заказов ЮKassa, активировать резервы франшиз или проверить квоты по регионам РФ.</div>
|
|
153
|
-
</div>
|
|
154
|
-
<div class="ai-sugg" style="padding:8px 0;border-top:1px solid var(--line)">
|
|
155
|
-
<button data-adm-ask="Сводка по серверу лицензий">Сводка реестра</button>
|
|
156
|
-
<button data-adm-ask="Выпусти базовую лицензию для ООО «АгроТех» ИНН 6234001122">+ Выпустить лицензию</button>
|
|
157
|
-
<button data-adm-ask="Проведи оплату ожидающих заказов">Провести оплаты</button>
|
|
158
|
-
<button data-adm-ask="Активируй резервы франшиз">Активировать резервы</button>
|
|
159
|
-
<button data-adm-ask="Сколько вакантных франшиз в Татарстане?">Квота Татарстана</button>
|
|
160
|
-
</div>
|
|
161
|
-
<div class="ai-input" style="padding:10px 0 0">
|
|
162
|
-
<input type="text" id="admAiInput" placeholder="Команда или вопрос ИИ-агенту админки…" autocomplete="off">
|
|
163
|
-
<button class="btn btn-primary" data-action="adm-ai-send" aria-label="Отправить">${icon('send')}</button>
|
|
164
|
-
</div>
|
|
144
|
+
<!-- 1. РЕЕСТР СЕРВЕРОВ (УСТРОЙСТВ) И ЛИЦЕНЗИЙ -->
|
|
145
|
+
<div class="card adm-section" data-adm-sec="servers">
|
|
146
|
+
<div style="display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:14px">
|
|
147
|
+
<div>
|
|
148
|
+
<span class="kicker">Реестр 1 · Устройства и лицензии</span>
|
|
149
|
+
<h3 class="h3" style="margin:2px 0 0">Серверы (устройства) WORK и лицензии клиентов</h3>
|
|
165
150
|
</div>
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<h3 class="h3" style="margin:4px 0 0">Выпустить базовую лицензию комплекса (${P.baseSeats} вечных лицензий + 1 год поддержки Enterprise)</h3>
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
<div class="grid g4" style="align-items:end">
|
|
179
|
-
<div class="field" style="margin:0"><label for="admLicOrg">Организация</label><input type="text" id="admLicOrg" value="АО «РязаньАгроПром»"></div>
|
|
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>
|
|
151
|
+
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
|
|
152
|
+
<input type="search" id="inpFilterServers" placeholder="Поиск по клиенту, Email, ИНН, серверу, HWID…" style="width:280px;padding:8px 12px;font-size:13px">
|
|
153
|
+
<select id="selFilterServers" style="padding:8px 12px;font-size:13px">
|
|
154
|
+
<option value="all">Все серверы</option>
|
|
155
|
+
<option value="base_active">Базовая активирована</option>
|
|
156
|
+
<option value="base_inactive">Без базовой лицензии</option>
|
|
157
|
+
<option value="hwid_bound">Железо (HWID) привязано</option>
|
|
158
|
+
</select>
|
|
159
|
+
<button class="btn btn-primary btn-sm" data-action="open-create-server-modal">${icon('plus')} Добавить сервер / лицензию</button>
|
|
188
160
|
</div>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="table-wrap">
|
|
163
|
+
<table class="t">
|
|
164
|
+
<thead><tr>
|
|
165
|
+
<th>Сервер / ID</th>
|
|
166
|
+
<th>Владелец / Организация</th>
|
|
167
|
+
<th>Привязка железа (HWID)</th>
|
|
168
|
+
<th>Базовая лицензия (10 вечн.)</th>
|
|
169
|
+
<th>Доп. места и SLA</th>
|
|
170
|
+
<th>Управление</th>
|
|
171
|
+
</tr></thead>
|
|
172
|
+
<tbody id="admLicensesBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
173
|
+
</table>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
189
176
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<th>Организация / ИНН</th>
|
|
197
|
-
<th>Привязка оборудования / Партнёр</th>
|
|
198
|
-
<th class="num">Сессии (вечные + доп.)</th>
|
|
199
|
-
<th>SLA и срок</th>
|
|
200
|
-
<th>Сертификат</th>
|
|
201
|
-
</tr></thead>
|
|
202
|
-
<tbody id="admLicensesBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
203
|
-
</table>
|
|
204
|
-
</div>
|
|
177
|
+
<!-- 2. РЕЕСТР ФРАНШИЗ И ГРАФИКОВ ПОГАШЕНИЯ (12 МЕС.) -->
|
|
178
|
+
<div class="card adm-section" data-adm-sec="franchises" style="display:none">
|
|
179
|
+
<div style="display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:14px">
|
|
180
|
+
<div>
|
|
181
|
+
<span class="kicker">Реестр 2 · Региональная партнёрская сеть</span>
|
|
182
|
+
<h3 class="h3" style="margin:2px 0 0">Региональные франшизы, резервы (15 мин) и годовые графики погашения</h3>
|
|
205
183
|
</div>
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
<
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
<th class="num">Сумма</th>
|
|
217
|
-
<th>Статус</th>
|
|
218
|
-
<th>Действия</th>
|
|
219
|
-
</tr></thead>
|
|
220
|
-
<tbody id="admOrdersBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
221
|
-
</table>
|
|
222
|
-
</div>
|
|
184
|
+
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
|
|
185
|
+
<input type="search" id="inpFilterFranchises" placeholder="Поиск по региону, компании, ИНН, Email…" style="width:280px;padding:8px 12px;font-size:13px">
|
|
186
|
+
<select id="selFilterFranchises" style="padding:8px 12px;font-size:13px">
|
|
187
|
+
<option value="all">Все активные франшизы и резервы</option>
|
|
188
|
+
<option value="pending_deposit">Ожидают залога (таймер 15 мин)</option>
|
|
189
|
+
<option value="deposit_paid">Погашение по графику (12 мес.)</option>
|
|
190
|
+
<option value="overdue">С просрочкой ежемесячного платежа</option>
|
|
191
|
+
<option value="paid_in_full">Полностью погашены</option>
|
|
192
|
+
<option value="closed">Отменённые / Истёкшие</option>
|
|
193
|
+
</select>
|
|
223
194
|
</div>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="table-wrap">
|
|
197
|
+
<table class="t">
|
|
198
|
+
<thead><tr>
|
|
199
|
+
<th>Субъект РФ / ID</th>
|
|
200
|
+
<th>Франчайзи / Контакты</th>
|
|
201
|
+
<th>Начальный взнос (залог 100 тыс. ₽)</th>
|
|
202
|
+
<th>График погашения (12 мес.)</th>
|
|
203
|
+
<th>Финансовая сводка</th>
|
|
204
|
+
<th>Управление</th>
|
|
205
|
+
</tr></thead>
|
|
206
|
+
<tbody id="admFranchisesBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
207
|
+
</table>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
224
210
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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>
|
|
239
|
-
</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
|
-
</div>
|
|
211
|
+
<!-- 3. ЗАКАЗЫ ЮKASSA И БЕЗНАЛИЧНЫЕ СЧЕТА-ДОГОВОРЫ -->
|
|
212
|
+
<div class="card adm-section" data-adm-sec="orders" style="display:none">
|
|
213
|
+
<div style="display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:14px">
|
|
214
|
+
<div>
|
|
215
|
+
<span class="kicker">Реестр 3 · Биллинг и расчёты</span>
|
|
216
|
+
<h3 class="h3" style="margin:2px 0 0">Заказы ЮKassa, счета-договоры юрлиц и взносы по франшизам</h3>
|
|
248
217
|
</div>
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
<
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
<th>Компания / ИНН</th>
|
|
258
|
-
<th>Условия / Скидка</th>
|
|
259
|
-
<th>Статус и аттестация</th>
|
|
260
|
-
<th>Действия</th>
|
|
261
|
-
</tr></thead>
|
|
262
|
-
<tbody id="admPartnersBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
263
|
-
</table>
|
|
264
|
-
</div>
|
|
218
|
+
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
|
|
219
|
+
<input type="search" id="inpFilterOrders" placeholder="Поиск по номеру заказа, счёту, плательщику, ИНН…" style="width:280px;padding:8px 12px;font-size:13px">
|
|
220
|
+
<select id="selFilterOrders" style="padding:8px 12px;font-size:13px">
|
|
221
|
+
<option value="all">Все заказы</option>
|
|
222
|
+
<option value="pending">Ожидают оплаты</option>
|
|
223
|
+
<option value="paid">Оплачены</option>
|
|
224
|
+
<option value="canceled">Отменены</option>
|
|
225
|
+
</select>
|
|
265
226
|
</div>
|
|
227
|
+
</div>
|
|
228
|
+
<div class="table-wrap">
|
|
229
|
+
<table class="t">
|
|
230
|
+
<thead><tr>
|
|
231
|
+
<th>Заказ / Счёт / Дата</th>
|
|
232
|
+
<th>Назначение и объект</th>
|
|
233
|
+
<th>Плательщик / ИНН</th>
|
|
234
|
+
<th class="num">Сумма</th>
|
|
235
|
+
<th>Статус</th>
|
|
236
|
+
<th>Действия</th>
|
|
237
|
+
</tr></thead>
|
|
238
|
+
<tbody id="admOrdersBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
239
|
+
</table>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
266
242
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
<th>Переписка</th>
|
|
277
|
-
<th>Ответ инженера</th>
|
|
278
|
-
</tr></thead>
|
|
279
|
-
<tbody id="admTicketsBody"><tr><td colspan="5" class="muted">Загрузка…</td></tr></tbody>
|
|
280
|
-
</table>
|
|
281
|
-
</div>
|
|
243
|
+
<!-- 4. КЛИЕНТЫ, ОФЕРТЫ ПЭП И РОЛИ BOSS -->
|
|
244
|
+
<div class="card adm-section" data-adm-sec="users" style="display:none">
|
|
245
|
+
<div style="display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:14px">
|
|
246
|
+
<div>
|
|
247
|
+
<span class="kicker">Реестр 4 · Аккаунты владельцев и права доступа</span>
|
|
248
|
+
<h3 class="h3" style="margin:2px 0 0">Зарегистрированные клиенты, подписанные оферты (ПЭП) и администраторы (BOSS)</h3>
|
|
249
|
+
</div>
|
|
250
|
+
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
|
|
251
|
+
<input type="search" id="inpFilterUsers" placeholder="Поиск по Email, организации, ИНН…" style="width:280px;padding:8px 12px;font-size:13px">
|
|
282
252
|
</div>
|
|
253
|
+
</div>
|
|
254
|
+
<div class="table-wrap">
|
|
255
|
+
<table class="t">
|
|
256
|
+
<thead><tr>
|
|
257
|
+
<th>Email / ID</th>
|
|
258
|
+
<th>Роль на сервере</th>
|
|
259
|
+
<th>Организация / ИНН</th>
|
|
260
|
+
<th>Серверов / Франшиз</th>
|
|
261
|
+
<th>Договор-оферта (ПЭП)</th>
|
|
262
|
+
<th>Регистрация / Вход</th>
|
|
263
|
+
</tr></thead>
|
|
264
|
+
<tbody id="admUsersBody"><tr><td colspan="6" class="muted">Загрузка…</td></tr></tbody>
|
|
265
|
+
</table>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
283
268
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
</table>
|
|
299
|
-
</div>
|
|
269
|
+
<!-- 5. ТИКЕТЫ SLA -->
|
|
270
|
+
<div class="card adm-section" data-adm-sec="tickets" style="display:none">
|
|
271
|
+
<div style="display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:14px">
|
|
272
|
+
<div>
|
|
273
|
+
<span class="kicker">Реестр 5 · Служба поддержки</span>
|
|
274
|
+
<h3 class="h3" style="margin:2px 0 0">Обращения техподдержки SLA (MAX · Telegram · VK)</h3>
|
|
275
|
+
</div>
|
|
276
|
+
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
|
|
277
|
+
<select id="selFilterTickets" style="padding:8px 12px;font-size:13px">
|
|
278
|
+
<option value="all">Все обращения</option>
|
|
279
|
+
<option value="open">Открытые (ожидают ответа)</option>
|
|
280
|
+
<option value="answered">Отвеченные</option>
|
|
281
|
+
<option value="closed">Закрытые</option>
|
|
282
|
+
</select>
|
|
300
283
|
</div>
|
|
301
284
|
</div>
|
|
285
|
+
<div class="table-wrap">
|
|
286
|
+
<table class="t">
|
|
287
|
+
<thead><tr>
|
|
288
|
+
<th>Тикет / Канал</th>
|
|
289
|
+
<th>Клиент / Сервер</th>
|
|
290
|
+
<th>Тариф SLA</th>
|
|
291
|
+
<th>Переписка</th>
|
|
292
|
+
<th>Ответ инженера</th>
|
|
293
|
+
</tr></thead>
|
|
294
|
+
<tbody id="admTicketsBody"><tr><td colspan="5" class="muted">Загрузка…</td></tr></tbody>
|
|
295
|
+
</table>
|
|
296
|
+
</div>
|
|
302
297
|
</div>
|
|
303
298
|
</section>
|
|
304
299
|
|
|
@@ -310,15 +305,117 @@ export function renderAdminPage({ publicKey = '', assetVersion = '' } = {}) {
|
|
|
310
305
|
</footer>
|
|
311
306
|
</div>
|
|
312
307
|
|
|
313
|
-
<!--
|
|
308
|
+
<!-- Модальное окно: Создание сервера / Выпуск лицензии клиенту -->
|
|
309
|
+
<div class="modal" id="admCreateServerModal" role="dialog" aria-modal="true">
|
|
310
|
+
<div class="card" style="max-width:540px">
|
|
311
|
+
<div class="modal-head">
|
|
312
|
+
<img src="/assets/odant-logo.png" alt="">
|
|
313
|
+
<div>
|
|
314
|
+
<h3 class="h3" style="margin:0">Добавить сервер / выпустить лицензию</h3>
|
|
315
|
+
<div class="small muted">Создание записи сервера в реестре с подписью Ed25519</div>
|
|
316
|
+
</div>
|
|
317
|
+
<button class="icon-btn" data-action="close-create-server-modal" aria-label="Закрыть">${icon('close')}</button>
|
|
318
|
+
</div>
|
|
319
|
+
<div class="field"><label for="admNewSrvName">Название сервера (устройства)</label><input type="text" id="admNewSrvName" placeholder="Основной сервер WORK"></div>
|
|
320
|
+
<div class="row2">
|
|
321
|
+
<div class="field"><label for="admNewSrvOrg">Организация клиента</label><input type="text" id="admNewSrvOrg" placeholder="ООО «Компания»"></div>
|
|
322
|
+
<div class="field"><label for="admNewSrvInn">ИНН</label><input type="text" id="admNewSrvInn" placeholder="7701234567"></div>
|
|
323
|
+
</div>
|
|
324
|
+
<div class="row2">
|
|
325
|
+
<div class="field"><label for="admNewSrvEmail">Email владельца *</label><input type="email" id="admNewSrvEmail" placeholder="client@company.ru"></div>
|
|
326
|
+
<div class="field"><label for="admNewSrvHwid">HWID (необязательно)</label><input type="text" id="admNewSrvHwid" placeholder="PAK-..."></div>
|
|
327
|
+
</div>
|
|
328
|
+
<label style="display:flex;align-items:center;gap:10px;font-size:13.5px;margin:8px 0 14px;cursor:pointer">
|
|
329
|
+
<input type="checkbox" id="admNewSrvActivateBase" checked style="width:17px;height:17px">
|
|
330
|
+
<span>Сразу активировать базовую постоянную лицензию (10 вечных мест + ИИ-агенты)</span>
|
|
331
|
+
</label>
|
|
332
|
+
<div class="status" id="admCreateServerStatus" style="color:var(--danger);min-height:18px;margin-bottom:8px"></div>
|
|
333
|
+
<div style="display:flex;gap:10px;justify-content:flex-end">
|
|
334
|
+
<button class="btn btn-ghost" data-action="close-create-server-modal">Отмена</button>
|
|
335
|
+
<button class="btn btn-primary" data-action="submit-create-server">${icon('key')} Создать и подписать</button>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
<!-- Модальное окно: Начисление дополнительных мест серверу -->
|
|
341
|
+
<div class="modal" id="admGrantSeatsModal" role="dialog" aria-modal="true">
|
|
342
|
+
<div class="card" style="max-width:480px">
|
|
343
|
+
<div class="modal-head">
|
|
344
|
+
<img src="/assets/odant-logo.png" alt="">
|
|
345
|
+
<div>
|
|
346
|
+
<h3 class="h3" id="admGrantTitle" style="margin:0">Начислить доп. рабочие места</h3>
|
|
347
|
+
<div class="small muted" id="admGrantSub">Сервер WORK</div>
|
|
348
|
+
</div>
|
|
349
|
+
<button class="icon-btn" data-action="close-grant-modal" aria-label="Закрыть">${icon('close')}</button>
|
|
350
|
+
</div>
|
|
351
|
+
<input type="hidden" id="admGrantLicId" value="">
|
|
352
|
+
<div class="row2">
|
|
353
|
+
<div class="field">
|
|
354
|
+
<label for="admGrantSeatsCount">Количество доп. мест</label>
|
|
355
|
+
<input type="number" id="admGrantSeatsCount" min="1" max="2000" value="5">
|
|
356
|
+
</div>
|
|
357
|
+
<div class="field">
|
|
358
|
+
<label for="admGrantYears">Срок действия (лет)</label>
|
|
359
|
+
<input type="number" id="admGrantYears" min="1" max="5" value="1">
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
<div class="field">
|
|
363
|
+
<label for="admGrantTier">Уровень поддержки SLA</label>
|
|
364
|
+
<select id="admGrantTier">
|
|
365
|
+
<option value="start">Start (10 000 ₽/место в год)</option>
|
|
366
|
+
<option value="business" selected>Business (15 000 ₽/место в год)</option>
|
|
367
|
+
<option value="enterprise">Enterprise (20 000 ₽/место в год)</option>
|
|
368
|
+
</select>
|
|
369
|
+
</div>
|
|
370
|
+
<div style="display:flex;gap:10px;justify-content:flex-end;margin-top:14px">
|
|
371
|
+
<button class="btn btn-ghost" data-action="close-grant-modal">Отмена</button>
|
|
372
|
+
<button class="btn btn-primary" data-action="submit-grant-seats">Начислить и выпустить .lic.json</button>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<!-- Модальное окно: 12-месячный график платежей по франшизе -->
|
|
378
|
+
<div class="modal" id="admFranchiseScheduleModal" role="dialog" aria-modal="true">
|
|
379
|
+
<div class="card" style="max-width:780px">
|
|
380
|
+
<div class="modal-head">
|
|
381
|
+
<img src="/assets/odant-logo.png" alt="">
|
|
382
|
+
<div>
|
|
383
|
+
<h3 class="h3" id="admSchedTitle" style="margin:0">График погашения франшизы (12 месяцев)</h3>
|
|
384
|
+
<div class="small muted" id="admSchedSub"></div>
|
|
385
|
+
</div>
|
|
386
|
+
<button class="icon-btn" data-action="close-sched-modal" aria-label="Закрыть">${icon('close')}</button>
|
|
387
|
+
</div>
|
|
388
|
+
<div id="admSchedSummary" style="margin-bottom:12px"></div>
|
|
389
|
+
<div class="table-wrap" style="max-height:420px">
|
|
390
|
+
<table class="t">
|
|
391
|
+
<thead><tr>
|
|
392
|
+
<th>Месяц</th>
|
|
393
|
+
<th>Дата платежа</th>
|
|
394
|
+
<th class="num">Сумма</th>
|
|
395
|
+
<th>Статус</th>
|
|
396
|
+
<th style="text-align:right">Действие администратора</th>
|
|
397
|
+
</tr></thead>
|
|
398
|
+
<tbody id="admSchedBody"></tbody>
|
|
399
|
+
</table>
|
|
400
|
+
</div>
|
|
401
|
+
<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:14px;flex-wrap:wrap">
|
|
402
|
+
<div id="admSchedEarlyBox"></div>
|
|
403
|
+
<button class="btn btn-ghost" data-action="close-sched-modal">Закрыть</button>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
<!-- Модальное окно: Просмотр сертификата и файлов лицензий (.lic.json) -->
|
|
314
409
|
<div class="modal" id="certModal" role="dialog" aria-modal="true" aria-labelledby="certTitle">
|
|
315
|
-
<div class="card" style="max-width:
|
|
410
|
+
<div class="card" style="max-width:680px">
|
|
316
411
|
<div class="modal-head">
|
|
317
412
|
<img src="/assets/odant-logo.png" alt="">
|
|
318
|
-
<div><h3 class="h3" id="certTitle" style="margin:0">Сертификат
|
|
413
|
+
<div><h3 class="h3" id="certTitle" style="margin:0">Сертификат и файлы лицензий (.lic.json)</h3><div class="small muted" id="certSub">WORK-LIC-v1</div></div>
|
|
414
|
+
<button class="icon-btn" data-action="cert-close" aria-label="Закрыть">${icon('close')}</button>
|
|
319
415
|
</div>
|
|
320
|
-
<
|
|
321
|
-
<
|
|
416
|
+
<div id="certFilesList" style="margin-bottom:12px"></div>
|
|
417
|
+
<pre class="console" id="certJsonBox" style="max-height:320px;margin:0"></pre>
|
|
418
|
+
<div style="display:flex;gap:10px;margin-top:14px;flex-wrap:wrap;justify-content:flex-end">
|
|
322
419
|
<button class="btn btn-primary" data-action="cert-copy">Скопировать JSON</button>
|
|
323
420
|
<button class="btn btn-ok" data-action="cert-download">Скачать .json</button>
|
|
324
421
|
<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()),
|