work-license-server 1.0.6 → 1.0.8
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/landing.js +309 -228
- package/package.json +1 -1
- package/src/landing.js +123 -179
- package/src/server.js +86 -1
- package/src/store.js +110 -15
package/package.json
CHANGED
package/src/landing.js
CHANGED
|
@@ -180,24 +180,19 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
180
180
|
|
|
181
181
|
<nav class="tabs" id="tabs" aria-label="Разделы">
|
|
182
182
|
<span class="tab-ind" id="tabInd"></span>
|
|
183
|
-
<button class="tab active" data-stage="product"
|
|
184
|
-
<button class="tab" data-stage="
|
|
185
|
-
<button class="tab" data-stage="pricing">Тарифы</button>
|
|
186
|
-
<button class="tab" data-stage="order">Комплекс</button>
|
|
187
|
-
<button class="tab" id="tileAddons" data-stage="addons" style="display:none;">Докупка лицензий</button>
|
|
183
|
+
<button class="tab active" data-stage="product">О платформе</button>
|
|
184
|
+
<button class="tab" data-stage="clients">Клиентам</button>
|
|
188
185
|
<button class="tab" data-stage="franchise">Франшиза</button>
|
|
189
|
-
<button class="tab" data-stage="
|
|
186
|
+
<button class="tab" id="tileAddons" data-stage="addons" style="display:none;">Личный кабинет</button>
|
|
190
187
|
</nav>
|
|
191
188
|
|
|
192
189
|
<div class="top-actions">
|
|
193
190
|
<button class="icon-btn" id="btnTheme" onclick="toggleTheme()" title="Светлая / тёмная тема" aria-label="Переключить тему">${icon('sun')}</button>
|
|
194
|
-
<button class="btn btn-
|
|
195
|
-
<button class="btn btn-ghost btn-sm" id="btnAuth" data-action="auth">${icon('user')}<span id="btnAuthText">Войти / Регистрация</span></button>
|
|
196
|
-
<button class="btn btn-primary btn-sm" data-stage="order">Заказать</button>
|
|
191
|
+
<button class="btn btn-primary btn-sm" id="btnAuth" data-action="auth">${icon('user')}<span id="btnAuthText">Войти / Регистрация</span></button>
|
|
197
192
|
</div>
|
|
198
193
|
</header>
|
|
199
194
|
|
|
200
|
-
<!-- ============ ПЛАТФОРМА ============ -->
|
|
195
|
+
<!-- ============ ЛЕНДИНГ: ПЛАТФОРМА И ИИ-АГЕНТЫ (при клике на лого) ============ -->
|
|
201
196
|
<section class="stage active" id="stage-product">
|
|
202
197
|
<div class="hero">
|
|
203
198
|
<div>
|
|
@@ -205,8 +200,8 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
205
200
|
<h1 class="h1">Учёт, документооборот, процессы и <span class="grad">приватный ИИ</span> — в одном комплексе</h1>
|
|
206
201
|
<p class="lead">ODANT-WORK — вычислительный центр предприятия, который ставится в серверную и работает автономно. Встроенная система учёта, единое пространство документов и задач, ИИ-консультант в каждой форме и команда ИИ-агентов — без облаков и передачи данных наружу.</p>
|
|
207
202
|
<div class="hero-cta">
|
|
208
|
-
<button class="btn btn-primary btn-lg" data-stage="
|
|
209
|
-
<button class="btn btn-ghost btn-lg" data-stage="
|
|
203
|
+
<button class="btn btn-primary btn-lg" data-stage="clients">Клиентам · Условия и покупка</button>
|
|
204
|
+
<button class="btn btn-ghost btn-lg" data-stage="franchise">Партнёрская франшиза</button>
|
|
210
205
|
</div>
|
|
211
206
|
<div class="trust">
|
|
212
207
|
<span>${icon('shield')} Данные не покидают контур</span>
|
|
@@ -253,10 +248,9 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
253
248
|
<span class="kicker">Паспорт комплекса</span>
|
|
254
249
|
<h2 class="h2">ODANT-WORK · ${esc(P.sku)}</h2>
|
|
255
250
|
<div class="price-big">${rub(P.basePriceRub)}</div>
|
|
256
|
-
<p class="note">Вечная лицензия на ${P.baseSeats} одновременных рабочих
|
|
251
|
+
<p class="note">Вечная лицензия на ${P.baseSeats} одновременных рабочих сессий и ${P.includedSupportYears} год максимальной поддержки уровня Enterprise — в комплекте с машиной (или ${rub(P.baseLicensePriceRub || 4_000_000)} за базовую лицензию без оборудования).</p>
|
|
257
252
|
<div class="hero-cta" style="margin-top:20px">
|
|
258
|
-
<button class="btn btn-primary" data-stage="
|
|
259
|
-
<button class="btn btn-ghost" data-stage="pricing">Тарифы поддержки</button>
|
|
253
|
+
<button class="btn btn-primary" data-stage="clients">Условия и покупка</button>
|
|
260
254
|
</div>
|
|
261
255
|
</div>
|
|
262
256
|
<dl class="spec">
|
|
@@ -266,7 +260,7 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
266
260
|
<div><dt>Память</dt><dd>${esc(H.ram)}</dd></div>
|
|
267
261
|
<div><dt>Накопитель</dt><dd>${esc(H.storage)}</dd></div>
|
|
268
262
|
<div><dt>Защита</dt><dd>${esc(H.security)}</dd></div>
|
|
269
|
-
<div><dt>Лицензия</dt><dd>По одновременным сессиям,
|
|
263
|
+
<div><dt>Лицензия</dt><dd>По одновременным сессиям, файлы .lic.json в #system</dd></div>
|
|
270
264
|
</dl>
|
|
271
265
|
</div>
|
|
272
266
|
</div>
|
|
@@ -279,21 +273,10 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
279
273
|
<p>Интегрированные локальные модели предназначены для базовых ежедневных операций внутри контура. Если требуются более мощные модели ИИ, их можно подключить через API из любого источника.</p>
|
|
280
274
|
</div>
|
|
281
275
|
<div class="grid g4">${renderModels()}</div>
|
|
282
|
-
<div class="card reveal" style="display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap">
|
|
283
|
-
<div>
|
|
284
|
-
<span class="kicker">Подключение мощных моделей ИИ по API</span>
|
|
285
|
-
<h3 class="h3" style="margin:4px 0 6px">Нужны более мощные модели? Подключайте из любого источника</h3>
|
|
286
|
-
<p class="note" style="margin:0">Предустановленные на комплексе модели закрывают базовые операции учёта, голоса, иллюстраций и поиска без внешних каналов. Для сложной аналитики, больших контекстов и специализированных агентов вы можете в один клик подключить по стандартному API модели из любого источника: внешних облачных провайдеров, корпоративного GPU-кластера или коммерческого API регионального партнёра.</p>
|
|
287
|
-
</div>
|
|
288
|
-
<button class="btn btn-ghost btn-sm" data-stage="agents">Подробнее об ИИ-агентах →</button>
|
|
289
|
-
</div>
|
|
290
|
-
</section>
|
|
291
276
|
|
|
292
|
-
|
|
293
|
-
<section class="stage" id="stage-agents">
|
|
294
|
-
<div class="agents-hero">
|
|
277
|
+
<div class="agents-hero" style="margin-top:28px">
|
|
295
278
|
<div>
|
|
296
|
-
<span class="kicker"
|
|
279
|
+
<span class="kicker">ИИ-консультант и мультиагентная система</span>
|
|
297
280
|
<h2 class="h2" style="font-size:clamp(2rem,3.6vw,3.2rem)">Помощник, который <span class="grad">знает вашу организацию</span></h2>
|
|
298
281
|
<p class="lead">Консультант встроен в каждую форму системы. Он видит открытый документ, понимает структуру учёта и права пользователя — и не просто отвечает, а выполняет работу.</p>
|
|
299
282
|
<ul class="list">${AI_PLATFORM.consultant.features.map(f => `<li>${esc(f)}</li>`).join('')}</ul>
|
|
@@ -324,10 +307,6 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
324
307
|
</div>
|
|
325
308
|
</div>
|
|
326
309
|
|
|
327
|
-
<div class="sec-head reveal">
|
|
328
|
-
<div><span class="kicker">Оркестрация</span><h2 class="h2">Как агенты работают вместе</h2></div>
|
|
329
|
-
<p>Параллельность и скорость — без потери контроля: права, подтверждения и журнал действий заложены в код платформы, а не в текст подсказки.</p>
|
|
330
|
-
</div>
|
|
331
310
|
<div class="grid g3">${renderOrchestration()}</div>
|
|
332
311
|
|
|
333
312
|
<div class="card reveal">
|
|
@@ -339,110 +318,106 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
339
318
|
</div>
|
|
340
319
|
</section>
|
|
341
320
|
|
|
342
|
-
<!-- ============
|
|
343
|
-
<section class="stage" id="stage-
|
|
321
|
+
<!-- ============ КЛИЕНТАМ: УСЛОВИЯ И ПОКУПКА ============ -->
|
|
322
|
+
<section class="stage" id="stage-clients">
|
|
344
323
|
<div class="sec-head">
|
|
345
324
|
<div>
|
|
346
|
-
<span class="kicker"
|
|
347
|
-
<h2 class="h2"
|
|
348
|
-
</div>
|
|
349
|
-
<p>${P.baseSeats} рабочих мест входят в комплекс навсегда вместе с 1-м годом поддержки уровня Enterprise. Дальнейшее продление поддержки и докупка годовых мест — по желанию. Считаются одновременные активные сессии, а не учётные записи.</p>
|
|
350
|
-
</div>
|
|
351
|
-
<div class="tiers">${renderTiers()}</div>
|
|
352
|
-
<div class="card">
|
|
353
|
-
<div class="sec-head" style="margin-bottom:16px">
|
|
354
|
-
<div><span class="kicker">Скидка от количества</span><h3 class="h3" style="font-size:20px">Чем больше мест — тем ниже цена каждого</h3></div>
|
|
355
|
-
<p class="small">Скидка применяется ко всему пакету докупаемых мест.</p>
|
|
325
|
+
<span class="kicker">Клиентам · Условия и покупка</span>
|
|
326
|
+
<h2 class="h2">Выберите вариант поставки и количество пользователей</h2>
|
|
356
327
|
</div>
|
|
357
|
-
|
|
328
|
+
<p>Сразу после установки из <code>npm</code> система работает бесплатно в режиме настройки (1 сессия BOSS, ИИ-агенты отключены). Первичная постоянная лицензия открывает <b>${P.baseSeats} вечных мест</b> и включает агентскую ИИ-систему.</p>
|
|
358
329
|
</div>
|
|
359
|
-
</section>
|
|
360
330
|
|
|
361
|
-
<!-- ============ ЗАКАЗ КОМПЛЕКСА ============ -->
|
|
362
|
-
<section class="stage" id="stage-order">
|
|
363
|
-
<div class="sec-head">
|
|
364
|
-
<div><span class="kicker">Заказ</span><h2 class="h2">Программно-аппаратный комплекс ODANT-WORK</h2></div>
|
|
365
|
-
<p>Сервер AMD Ryzen™ AI Max+ PRO 495 · 192 ГБ · 4 ТБ с установленной платформой, вечной лицензией на ${P.baseSeats} мест и 1 годом поддержки Enterprise (дальше — по желанию). Оплата через ЮKassa с чеком по 54-ФЗ.</p>
|
|
366
|
-
</div>
|
|
367
|
-
<div class="card oferta-banner" data-oferta-banner></div>
|
|
368
331
|
<div class="split">
|
|
369
332
|
<div class="card">
|
|
370
333
|
<div class="field">
|
|
371
|
-
<label
|
|
334
|
+
<label>1. Базовая постоянная лицензия (10 вечных пользователей + ИИ-агенты)</label>
|
|
372
335
|
<div class="seg" id="segPakVariant">
|
|
373
|
-
<button data-variant="pak" class="on"
|
|
374
|
-
<button data-variant="license_only"
|
|
336
|
+
<button data-variant="pak" class="on">С машинкой (ПАК)<small>${rub(P.basePriceRub)} · сервер + 10 вечных мест</small></button>
|
|
337
|
+
<button data-variant="license_only">Без машинки (лицензия)<small>${rub(P.baseLicensePriceRub || 4_000_000)} · 10 вечных мест</small></button>
|
|
338
|
+
<button data-variant="addons_only">Только доп. юзеры<small>0 ₽ · базовая уже есть</small></button>
|
|
375
339
|
</div>
|
|
376
340
|
</div>
|
|
377
|
-
<div class="row2">
|
|
378
|
-
<div class="field"><label for="inpPakOrg">Организация</label><input type="text" id="inpPakOrg" value="ООО «Маслоторг»"></div>
|
|
379
|
-
<div class="field"><label for="inpPakInn">ИНН</label><input type="text" id="inpPakInn" value="7701234567" inputmode="numeric"></div>
|
|
380
|
-
</div>
|
|
381
|
-
<div class="field"><label for="inpPakEmail">Email для счёта и личного кабинета</label><input type="email" id="inpPakEmail" value="admin@maslotorg.ru"></div>
|
|
382
341
|
<div class="field">
|
|
383
|
-
<label
|
|
384
|
-
<
|
|
342
|
+
<label>2. Дополнительные временные пользователи (сверх 10 вечных)</label>
|
|
343
|
+
<div style="display:flex;align-items:center;gap:16px">
|
|
344
|
+
<input type="range" id="inpPakExtraSeats" min="0" max="150" value="0" style="flex:1">
|
|
345
|
+
<input type="number" id="inpPakExtraSeatsNum" min="0" max="2000" value="0" style="width:110px">
|
|
346
|
+
</div>
|
|
385
347
|
</div>
|
|
386
|
-
<div class="
|
|
387
|
-
<
|
|
388
|
-
|
|
348
|
+
<div class="row2">
|
|
349
|
+
<div class="field">
|
|
350
|
+
<label>Уровень поддержки доп. мест</label>
|
|
351
|
+
<div class="seg" id="segPakTier">${Object.values(SLA_TIERS).map(t => `<button data-tier="${t.id}" class="${t.id === 'business' ? 'on' : ''}">${esc(t.name)}<small>${rub(t.pricePerUserYearRub)}/год</small></button>`).join('')}</div>
|
|
352
|
+
</div>
|
|
353
|
+
<div class="field">
|
|
354
|
+
<label for="inpPakYears">Срок временных мест, лет (1–5)</label>
|
|
355
|
+
<input type="number" id="inpPakYears" min="1" max="5" value="1">
|
|
356
|
+
</div>
|
|
389
357
|
</div>
|
|
390
|
-
<
|
|
358
|
+
<div class="field" style="margin-top:6px"><label>Скидка от количества дополнительных мест</label>${renderLadder('ladderPricing')}</div>
|
|
391
359
|
</div>
|
|
360
|
+
|
|
392
361
|
<div class="card summary card-glow">
|
|
393
|
-
<span class="kicker"
|
|
362
|
+
<span class="kicker">Ваш выбор</span>
|
|
394
363
|
<div class="sum-total" id="lblPakTotal">${rub(P.basePriceRub)}</div>
|
|
364
|
+
<div class="small" id="lblPakDiscount" style="color:var(--ok);font-weight:650">Включает 10 вечных мест и 1 год поддержки Enterprise</div>
|
|
395
365
|
<div class="sum-lines" id="lblPakBreakdown"></div>
|
|
396
|
-
<button class="btn btn-primary btn-lg" style="width:100%" data-action="
|
|
397
|
-
<
|
|
366
|
+
<button class="btn btn-primary btn-lg" style="width:100%" data-action="buy-client-config">Купить</button>
|
|
367
|
+
<p class="small muted" style="margin:8px 0 0;text-align:center">При нажатии «Купить» заказ добавится в ваш Личный кабинет, где вы сможете оплатить его через ЮKassa или выставить счёт-договор для юр. лица.</p>
|
|
398
368
|
<div class="status" id="pakOrderStatus"></div>
|
|
399
369
|
</div>
|
|
400
370
|
</div>
|
|
371
|
+
|
|
372
|
+
<div class="sec-head" style="margin-top:12px">
|
|
373
|
+
<div>
|
|
374
|
+
<span class="kicker">Тарифы поддержки и конкурентных мест</span>
|
|
375
|
+
<h2 class="h2">Условия годовых лицензий и SLA</h2>
|
|
376
|
+
</div>
|
|
377
|
+
<p>Считаются одновременные активные сессии, а не число заведённых сотрудников в базе.</p>
|
|
378
|
+
</div>
|
|
379
|
+
<div class="tiers">${renderTiers()}</div>
|
|
401
380
|
</section>
|
|
402
381
|
|
|
403
|
-
<!-- ============
|
|
382
|
+
<!-- ============ ЛИЧНЫЙ КАБИНЕТ (открывается после входа / регистрации) ============ -->
|
|
404
383
|
<section class="stage" id="stage-addons">
|
|
405
384
|
<div class="sec-head">
|
|
406
|
-
<div><span class="kicker">Личный кабинет владельца</span><h2 class="h2"
|
|
385
|
+
<div><span class="kicker">Личный кабинет владельца</span><h2 class="h2">Заказы, токен привязки и лицензии</h2></div>
|
|
407
386
|
<p id="userBadgeBox"></p>
|
|
408
387
|
</div>
|
|
409
|
-
|
|
388
|
+
|
|
410
389
|
<div class="card" style="margin-bottom:20px">
|
|
411
|
-
<
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
<
|
|
390
|
+
<div style="display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap">
|
|
391
|
+
<div>
|
|
392
|
+
<span class="kicker">Заказы и оплата</span>
|
|
393
|
+
<h3 class="h3" style="margin:4px 0 0">Ваши заказы в Личном кабинете</h3>
|
|
394
|
+
</div>
|
|
395
|
+
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
396
|
+
<button class="btn btn-ghost btn-sm" data-stage="clients">+ Добавить лицензию / ПАК</button>
|
|
397
|
+
<button class="btn btn-ghost btn-sm" data-stage="franchise">+ Зарезервировать франшизу</button>
|
|
398
|
+
<button class="btn btn-ghost btn-sm" data-action="refresh-cabinet">Обновить</button>
|
|
399
|
+
</div>
|
|
417
400
|
</div>
|
|
401
|
+
<div id="cabinetOrdersBox" style="margin-top:14px"></div>
|
|
418
402
|
</div>
|
|
403
|
+
|
|
419
404
|
<div class="split">
|
|
420
405
|
<div class="card">
|
|
421
|
-
<
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
<div class="field">
|
|
429
|
-
<label>Уровень поддержки</label>
|
|
430
|
-
<div class="seg" id="segTier">${Object.values(SLA_TIERS).map(t => `<button data-tier="${t.id}" class="${t.id === 'business' ? 'on' : ''}">${esc(t.name)}<small>${rub(t.pricePerUserYearRub)} · ${esc(t.reactionLabel)}</small></button>`).join('')}</div>
|
|
431
|
-
</div>
|
|
432
|
-
<div class="row2">
|
|
433
|
-
<div class="field"><label for="inpYears">Срок, лет (кратно годам)</label><input type="number" id="inpYears" min="1" max="5" value="1"></div>
|
|
434
|
-
<div class="field"><label for="inpLicId">Лицензия комплекса</label><input type="text" id="inpLicId" readonly></div>
|
|
406
|
+
<span class="kicker">Привязка локального сервера WORK</span>
|
|
407
|
+
<h3 class="h3" style="margin:4px 0 8px">Токен владельца (содержит URL сервера поддержки)</h3>
|
|
408
|
+
<p class="small" style="margin:0 0 12px">Скопируйте этот токен и вставьте его в разделе <b>WORK → Лицензия</b> вашей локальной системы. По этому токену система сама скачивает и обновляет оплаченные файлы лицензий в папку <code>#system</code>.</p>
|
|
409
|
+
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center">
|
|
410
|
+
<input type="text" id="inpCabinetHubToken" readonly placeholder="owk1..." style="flex:1;min-width:220px;font-family:monospace">
|
|
411
|
+
<button class="btn btn-primary btn-sm" data-action="copy-hub-token">Скопировать токен</button>
|
|
412
|
+
<button class="btn btn-ghost btn-sm" data-action="gen-hub-token">Перевыпустить</button>
|
|
435
413
|
</div>
|
|
436
|
-
<div class="
|
|
414
|
+
<div class="small" id="tokenCopyStatus" style="color:var(--ok);min-height:18px;margin-top:6px"></div>
|
|
437
415
|
</div>
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
<
|
|
441
|
-
<
|
|
442
|
-
<div class="
|
|
443
|
-
<button class="btn btn-primary btn-lg" style="width:100%" data-action="order-addons">Оплатить через ЮKassa</button>
|
|
444
|
-
<button class="btn btn-ghost btn-sm" style="width:100%;margin-top:8px" data-action="order-addons-invoice">Выставить счёт-договор (для юр. лиц)</button>
|
|
445
|
-
<div class="status" id="orderStatus"></div>
|
|
416
|
+
|
|
417
|
+
<div class="card">
|
|
418
|
+
<span class="kicker">Действующие лицензии (.lic.json) и франшизы</span>
|
|
419
|
+
<h3 class="h3" style="margin:4px 0 6px">Выпущенные файлы лицензий</h3>
|
|
420
|
+
<div id="cabinetLicensesBox" class="small" style="margin-top:8px;line-height:1.55"></div>
|
|
446
421
|
</div>
|
|
447
422
|
</div>
|
|
448
423
|
</section>
|
|
@@ -508,12 +483,8 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
508
483
|
<div><span>Индекс экономики</span><b id="mapRegIndex">—</b></div>
|
|
509
484
|
<div><span>Остаток (в течение года)</span><b id="mapRegRest">—</b></div>
|
|
510
485
|
</div>
|
|
511
|
-
<
|
|
512
|
-
<
|
|
513
|
-
<div class="field"><label for="inpPartnerInn">ИНН</label><input type="text" id="inpPartnerInn" value="6670123456" inputmode="numeric"></div>
|
|
514
|
-
<div class="field"><label for="inpPartnerEmail">Email</label><input type="email" id="inpPartnerEmail" value="partner@uraltech.ru"></div>
|
|
515
|
-
</div>
|
|
516
|
-
<button class="btn btn-primary" style="width:100%" id="btnReserveSelected" data-action="reserve-selected" disabled>Зарезервировать · залог ${rub(FRANCHISE_PROGRAM.reservationDepositRub)}</button>
|
|
486
|
+
<button class="btn btn-primary" style="width:100%;margin-top:8px" id="btnReserveSelected" data-action="reserve-selected" disabled>Зарезервировать · залог ${rub(FRANCHISE_PROGRAM.reservationDepositRub)}</button>
|
|
487
|
+
<p class="small muted" style="margin:8px 0 0;text-align:center">При нажатии «Зарезервировать» заявка на регион добавится как заказ в ваш Личный кабинет.</p>
|
|
517
488
|
<div class="status" id="partnerActionMsg"></div>
|
|
518
489
|
</aside>
|
|
519
490
|
</div>
|
|
@@ -566,56 +537,6 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
566
537
|
<div class="grid g4">${renderTracks()}</div>
|
|
567
538
|
</section>
|
|
568
539
|
|
|
569
|
-
<!-- ============ ПОДДЕРЖКА ============ -->
|
|
570
|
-
<section class="stage" id="stage-support">
|
|
571
|
-
<div class="sec-head">
|
|
572
|
-
<div><span class="kicker">Сервис</span><h2 class="h2">Поддержка в мессенджерах MAX, Telegram и VK</h2></div>
|
|
573
|
-
<p>Боты принимают обращения, показывают статус лицензии, выставляют счета и передают вопросы инженеру по SLA вашего тарифа.</p>
|
|
574
|
-
</div>
|
|
575
|
-
<div class="grid g2">
|
|
576
|
-
<div class="card">
|
|
577
|
-
<h3 class="h3">Консоль бота</h3>
|
|
578
|
-
<div class="row2" style="margin-top:12px">
|
|
579
|
-
<div class="field"><label for="botChannel">Канал</label><select id="botChannel"><option value="max">MAX</option><option value="telegram">Telegram</option><option value="vk">VK</option></select></div>
|
|
580
|
-
<div class="field"><label for="botChatId">Chat ID</label><input type="text" id="botChatId" value="client-101"></div>
|
|
581
|
-
</div>
|
|
582
|
-
<div class="field"><label for="botText">Команда или текст</label>
|
|
583
|
-
<div style="display:flex;gap:8px"><input type="text" id="botText" value="/start"><button class="btn btn-primary" data-action="bot-send">${icon('send')}</button></div>
|
|
584
|
-
</div>
|
|
585
|
-
<div class="chips" style="margin-bottom:12px">
|
|
586
|
-
<button class="chip" data-bot="/status">/status</button>
|
|
587
|
-
<button class="chip" data-bot="/buy business 10">/buy business 10</button>
|
|
588
|
-
<button class="chip" data-bot="/help">/help</button>
|
|
589
|
-
</div>
|
|
590
|
-
<div class="console" id="botReplyBox">Ответ бота появится здесь…</div>
|
|
591
|
-
</div>
|
|
592
|
-
<div class="card">
|
|
593
|
-
<div style="display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap">
|
|
594
|
-
<h3 class="h3" style="margin:0">Ваша лицензия и привязка мессенджеров</h3>
|
|
595
|
-
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
|
596
|
-
<button class="btn btn-ghost btn-sm" data-action="auth">Личный кабинет</button>
|
|
597
|
-
<button class="btn btn-ghost btn-sm" data-action="open-oferta">Договор-оферта</button>
|
|
598
|
-
</div>
|
|
599
|
-
</div>
|
|
600
|
-
<div class="console" id="clientLicenseBox" style="margin-top:14px">Войдите в личный кабинет и подпишите договор-оферту, чтобы получить код привязки вашего комплекса к ботам MAX, Telegram и VK.</div>
|
|
601
|
-
</div>
|
|
602
|
-
</div>
|
|
603
|
-
<div class="card">
|
|
604
|
-
<div class="spec-wrap">
|
|
605
|
-
<div>
|
|
606
|
-
<span class="kicker">Разработчик платформы</span>
|
|
607
|
-
<h2 class="h2">${esc(VENDOR_CONTACTS.company)}</h2>
|
|
608
|
-
<p class="note">${esc(VENDOR_CONTACTS.description)}. Головной офис ODANT — в Рязани.</p>
|
|
609
|
-
</div>
|
|
610
|
-
<div>
|
|
611
|
-
<div class="contact-row">${icon('pin')}<div><b>${esc(VENDOR_CONTACTS.address)}</b><br><a href="https://yandex.ru/maps/?pt=${VENDOR_CONTACTS.geo.lon},${VENDOR_CONTACTS.geo.lat}&z=17&l=map" target="_blank" rel="noopener">Открыть на карте</a></div></div>
|
|
612
|
-
<div class="contact-row">${icon('phone')}<div>${VENDOR_CONTACTS.phones.map(p => `<a href="tel:${p.replace(/[^\d+]/g, '')}">${esc(p)}</a>`).join('<br>')}</div></div>
|
|
613
|
-
<div class="contact-row">${icon('globe')}<div><a href="${esc(VENDOR_CONTACTS.site)}" target="_blank" rel="noopener">${esc(VENDOR_CONTACTS.site.replace(/^https?:\/\//, ''))}</a> · <a href="https://${esc(P.domain)}">${esc(P.domain)}</a></div></div>
|
|
614
|
-
</div>
|
|
615
|
-
</div>
|
|
616
|
-
</div>
|
|
617
|
-
</section>
|
|
618
|
-
|
|
619
540
|
<footer>
|
|
620
541
|
<div class="footer">
|
|
621
542
|
<div>
|
|
@@ -642,39 +563,57 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
642
563
|
</footer>
|
|
643
564
|
</div>
|
|
644
565
|
|
|
645
|
-
<!--
|
|
566
|
+
<!-- Вход / Регистрация в Личный кабинет (без ИНН, ФИО и ПЭП — по Email, через мессенджеры или OAuth) -->
|
|
646
567
|
<div class="modal" id="authModal" role="dialog" aria-modal="true" aria-labelledby="authTitle">
|
|
647
|
-
<div class="card" style="max-width:
|
|
568
|
+
<div class="card" style="max-width:480px">
|
|
648
569
|
<div class="modal-head">
|
|
649
570
|
<img src="/assets/odant-logo.png" alt="">
|
|
650
571
|
<div>
|
|
651
|
-
<h3 class="h3" id="authTitle" style="margin:0"
|
|
652
|
-
<div class="small muted" id="authSub"
|
|
572
|
+
<h3 class="h3" id="authTitle" style="margin:0">Вход / Регистрация</h3>
|
|
573
|
+
<div class="small muted" id="authSub">Войдите в Личный кабинет по Email, через мессенджер или OAuth</div>
|
|
653
574
|
</div>
|
|
654
575
|
</div>
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
576
|
+
|
|
577
|
+
<div id="authIntentNotice" class="small" style="display:none;padding:10px 12px;border-radius:10px;background:rgba(0,163,224,.12);border:1px solid rgba(0,163,224,.35);margin-bottom:4px"></div>
|
|
578
|
+
|
|
579
|
+
<div class="field">
|
|
580
|
+
<label for="authEmail">Ваш Email</label>
|
|
581
|
+
<div style="display:flex;gap:8px">
|
|
582
|
+
<input type="email" id="authEmail" value="admin@maslotorg.ru" placeholder="name@company.ru" style="flex:1" required>
|
|
583
|
+
<button class="btn btn-primary" data-action="login">${icon('check')} Войти по Email</button>
|
|
584
|
+
</div>
|
|
658
585
|
</div>
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
<
|
|
586
|
+
|
|
587
|
+
<div class="field" style="margin-top:6px">
|
|
588
|
+
<label>Быстрый вход через мессенджеры</label>
|
|
589
|
+
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px">
|
|
590
|
+
<button class="btn btn-ghost btn-sm" data-action="oauth-login" data-provider="telegram">Telegram</button>
|
|
591
|
+
<button class="btn btn-ghost btn-sm" data-action="oauth-login" data-provider="max">MAX</button>
|
|
592
|
+
<button class="btn btn-ghost btn-sm" data-action="oauth-login" data-provider="vk">VK Мессенджер</button>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
|
|
596
|
+
<div class="field">
|
|
597
|
+
<label>Вход через OAuth-провайдеров</label>
|
|
598
|
+
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px">
|
|
599
|
+
<button class="btn btn-ghost btn-sm" data-action="oauth-login" data-provider="yandex">Яндекс ID</button>
|
|
600
|
+
<button class="btn btn-ghost btn-sm" data-action="oauth-login" data-provider="vk_id">VK ID</button>
|
|
601
|
+
<button class="btn btn-ghost btn-sm" data-action="oauth-login" data-provider="google">Google</button>
|
|
602
|
+
</div>
|
|
662
603
|
</div>
|
|
663
|
-
<div class="field"><label for="authLicId">Номер лицензии ПАК (если уже выдан)</label><input type="text" id="authLicId" placeholder="Будет привязан или выпущен автоматически"></div>
|
|
664
604
|
|
|
665
|
-
<div class="oferta-check-box">
|
|
605
|
+
<div class="oferta-check-box" style="margin-top:6px">
|
|
666
606
|
<label class="oferta-check-label">
|
|
667
607
|
<input type="checkbox" id="authOfertaCheck" checked>
|
|
668
608
|
<span>
|
|
669
|
-
|
|
609
|
+
Регистрируясь и входя в Личный кабинет, я принимаю условия <a href="#" data-action="open-oferta">Публичного договора-оферты ODANT-WORK (${esc(PUBLIC_OFERTA.version)})</a>.
|
|
670
610
|
</span>
|
|
671
611
|
</label>
|
|
672
612
|
</div>
|
|
673
|
-
<div class="status" id="authErrorMsg" style="color:var(--danger);min-height:18px;margin-top:
|
|
674
|
-
<div style="display:flex;gap:10px;margin-top:
|
|
675
|
-
<button class="btn btn-
|
|
676
|
-
<button class="btn btn-ghost
|
|
677
|
-
<button class="btn btn-ghost" data-action="auth-close">Закрыть</button>
|
|
613
|
+
<div class="status" id="authErrorMsg" style="color:var(--danger);min-height:18px;margin-top:4px"></div>
|
|
614
|
+
<div style="display:flex;gap:10px;margin-top:6px;justify-content:flex-end;flex-wrap:wrap">
|
|
615
|
+
<button class="btn btn-ghost btn-sm" id="btnLogoutModal" data-action="logout" style="display:none">Выйти из аккаунта</button>
|
|
616
|
+
<button class="btn btn-ghost btn-sm" data-action="auth-close">Закрыть</button>
|
|
678
617
|
</div>
|
|
679
618
|
</div>
|
|
680
619
|
</div>
|
|
@@ -702,7 +641,7 @@ export function renderLandingPage({ publicKey = '', regions = null, assetVersion
|
|
|
702
641
|
</div>
|
|
703
642
|
</div>
|
|
704
643
|
<div style="display:flex;gap:10px;margin-top:14px;justify-content:flex-end;flex-wrap:wrap">
|
|
705
|
-
<button class="btn btn-primary" data-action="accept-from-oferta">${icon('check')} Принять условия
|
|
644
|
+
<button class="btn btn-primary" data-action="accept-from-oferta">${icon('check')} Принять условия оферты</button>
|
|
706
645
|
<button class="btn btn-ghost" data-action="oferta-close">Закрыть</button>
|
|
707
646
|
</div>
|
|
708
647
|
</div>
|
|
@@ -809,7 +748,12 @@ export function renderSandboxPaymentPage(order) {
|
|
|
809
748
|
});
|
|
810
749
|
const data = await res.json();
|
|
811
750
|
document.getElementById('st').textContent = data.status || event;
|
|
812
|
-
document.getElementById('msg').
|
|
751
|
+
document.getElementById('msg').innerHTML = data.status === 'paid'
|
|
752
|
+
? 'Оплата успешно проведена! Файлы лицензий выпущены в вашем Личном кабинете.<br><button class="btn cancel" style="margin-top:10px" onclick="if(window.opener){window.opener.postMessage({type:\\'odant:order-paid\\'},\\'*\\');window.close();}else{location.href=\\'/#addons\\';}">Вернуться в Личный кабинет →</button>'
|
|
753
|
+
: 'Платёж отменён.';
|
|
754
|
+
if (data.status === 'paid' && window.opener) {
|
|
755
|
+
try { window.opener.postMessage({ type: 'odant:order-paid', orderId: ORDER.orderId }, '*'); } catch {}
|
|
756
|
+
}
|
|
813
757
|
}
|
|
814
758
|
</script>
|
|
815
759
|
</body>
|
package/src/server.js
CHANGED
|
@@ -327,7 +327,8 @@ export function createLicenseServerApp(options = {}) {
|
|
|
327
327
|
const user = ensureCustomerOferta(req, body);
|
|
328
328
|
const reserved = store.reserveFranchiseSlot({
|
|
329
329
|
...body,
|
|
330
|
-
|
|
330
|
+
ownerAccountId: user?.userId || body.ownerAccountId || '',
|
|
331
|
+
companyName: body.companyName || user?.companyName || user?.email || '',
|
|
331
332
|
inn: body.inn || user?.inn || '',
|
|
332
333
|
email: body.email || user?.email || '',
|
|
333
334
|
});
|
|
@@ -340,6 +341,90 @@ export function createLicenseServerApp(options = {}) {
|
|
|
340
341
|
});
|
|
341
342
|
}
|
|
342
343
|
|
|
344
|
+
// 2.6.1 Добавление позиции («Купить» или «Зарезервировать») как неоплаченного заказа в Личный кабинет
|
|
345
|
+
if (req.method === 'POST' && pathname === '/api/cabinet/orders/draft') {
|
|
346
|
+
const body = await readJsonBody(req);
|
|
347
|
+
const user = ensureCustomerOferta(req, body);
|
|
348
|
+
let order;
|
|
349
|
+
let reservation = null;
|
|
350
|
+
if (body.kind === 'franchise_reservation' || body.regionCode) {
|
|
351
|
+
const reserved = store.reserveFranchiseSlot({
|
|
352
|
+
regionCode: body.regionCode,
|
|
353
|
+
ownerAccountId: user?.userId || '',
|
|
354
|
+
companyName: body.companyName || user?.companyName || user?.email || 'Партнёр ODANT-WORK',
|
|
355
|
+
inn: body.inn || user?.inn || '',
|
|
356
|
+
email: body.email || user?.email || '',
|
|
357
|
+
});
|
|
358
|
+
order = reserved.order;
|
|
359
|
+
reservation = reserved.reservation;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
order = store.createOrder({
|
|
363
|
+
...body,
|
|
364
|
+
licenseId: body.licenseId || user?.licenseId || '',
|
|
365
|
+
ownerAccountId: user?.userId || '',
|
|
366
|
+
customer: {
|
|
367
|
+
name: body.customer?.name || user?.companyName || user?.email || '',
|
|
368
|
+
inn: body.customer?.inn || user?.inn || '',
|
|
369
|
+
email: body.customer?.email || user?.email || '',
|
|
370
|
+
phone: body.customer?.phone || user?.phone || '',
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
const updatedUser = store.findUserBySessionKey(user?.sessionKey);
|
|
375
|
+
return sendJson(res, 200, {
|
|
376
|
+
ok: true,
|
|
377
|
+
order,
|
|
378
|
+
reservation,
|
|
379
|
+
user: updatedUser,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// 2.6.2 Оплата существующего заказа из Личного кабинета (через ЮKassa или по счёту-договору для юрлиц)
|
|
384
|
+
if (req.method === 'POST' && pathname === '/api/cabinet/orders/pay') {
|
|
385
|
+
const body = await readJsonBody(req);
|
|
386
|
+
const user = ensureCustomerOferta(req, body);
|
|
387
|
+
const order = store.prepareOrderPayment(body.orderId, {
|
|
388
|
+
paymentMethod: body.paymentMethod || 'yookassa',
|
|
389
|
+
companyName: body.companyName || user?.companyName || '',
|
|
390
|
+
inn: body.inn || user?.inn || '',
|
|
391
|
+
});
|
|
392
|
+
const updatedUser = store.findUserBySessionKey(user?.sessionKey);
|
|
393
|
+
if (order.paymentMethod === 'invoice') {
|
|
394
|
+
return sendJson(res, 200, {
|
|
395
|
+
ok: true,
|
|
396
|
+
orderId: order.orderId,
|
|
397
|
+
paymentMethod: 'invoice',
|
|
398
|
+
invoiceNumber: order.invoiceNumber,
|
|
399
|
+
contractNumber: order.contractNumber,
|
|
400
|
+
invoiceUrl: `${reqOrigin}${order.invoiceUrl}`,
|
|
401
|
+
confirmationUrl: `${reqOrigin}${order.invoiceUrl}`,
|
|
402
|
+
order,
|
|
403
|
+
user: updatedUser,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
const payment = await yookassa.createPaymentForOrder(order, { originOverride: reqOrigin });
|
|
407
|
+
return sendJson(res, 200, {
|
|
408
|
+
ok: true,
|
|
409
|
+
...payment,
|
|
410
|
+
order,
|
|
411
|
+
user: updatedUser,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// 2.6.3 Отмена (удаление) неоплаченного заказа из Личного кабинета
|
|
416
|
+
if (req.method === 'POST' && pathname === '/api/cabinet/orders/cancel') {
|
|
417
|
+
const body = await readJsonBody(req);
|
|
418
|
+
const user = ensureCustomerOferta(req, body);
|
|
419
|
+
store.cancelOrder(body.orderId);
|
|
420
|
+
const updatedUser = store.findUserBySessionKey(user?.sessionKey);
|
|
421
|
+
return sendJson(res, 200, {
|
|
422
|
+
ok: true,
|
|
423
|
+
orderId: body.orderId,
|
|
424
|
+
user: updatedUser,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
343
428
|
// 2.7 Собственный ИИ-агент лицензионного сервера ODANT-WORK
|
|
344
429
|
if (req.method === 'POST' && pathname === '/api/agent/chat') {
|
|
345
430
|
const body = await readJsonBody(req);
|