72b6879f4b
- Refactored max_bot from nested packages to flat module structure - Q2: Extended BotUser model (patronymic, email, org, address, vcf_raw, contact_hash, phone_verified, email_verified, last_interaction, total_conversations, total_tickets) - Q2: VCF parser (FN, N, TEL, EMAIL, ORG, ADR), upsert on re-contact, NLP history context (_get_user_history_context -> YandexGPT) - Q1: Broadcast preview modal with 10s confirmation timer - Q3: CSS var(--white)->var(--bg-card), var(--text)->var(--text-primary) - Q4: bot_settings showNotification(), editable max_bot_id - Q5: Webhook secret passthrough via X-Max-Bot-Api-Secret - Masking sensitive keys, dialog_cleared handler, migrate via _add_column_if_not_exists() - Rate limit (asyncio.sleep 0.5 per 10), dead code removed, conv.intent context in contact.py - Portal pages: bot_consent, bot_kb (edit), bot_settings, bot_test, bot_tickets, portal_settings - Tests: 21/21 passing, added test_yandex_gpt.py, test_email_sender.py - Deploy: deploy_full.sh, schema.sql, seed_knowledge_base.sql
126 lines
6.3 KiB
HTML
126 lines
6.3 KiB
HTML
{% extends "page.html" %}
|
|
{% block page_content %}
|
|
<style>
|
|
.tab-bar { display:flex; gap:0; margin-bottom:24px; border-bottom:2px solid var(--border); }
|
|
.tab-bar .tab { padding:10px 20px; cursor:pointer; border-bottom:2px solid transparent;
|
|
margin-bottom:-2px; color:var(--text-muted); font-weight:500; }
|
|
.tab-bar .tab.active { color:var(--accent); border-bottom-color:var(--accent); }
|
|
.tab-content { display:none; }
|
|
.tab-content.active { display:block; }
|
|
.menu-group { margin-bottom:16px; }
|
|
.menu-group h4 { margin:0 0 8px; color:var(--text-muted); font-size:13px; text-transform:uppercase; }
|
|
.menu-item { display:flex; align-items:center; gap:8px; padding:8px 12px;
|
|
border:1px solid var(--border); border-radius:6px; margin-bottom:4px; cursor:pointer; }
|
|
.menu-item:hover { background:var(--bg-secondary); }
|
|
.menu-item input[type="checkbox"] { width:18px; height:18px; cursor:pointer; }
|
|
.menu-item .label { flex:1; }
|
|
</style>
|
|
<div class="card">
|
|
<div class="card-header"><h2>Настройка Ролей</h2></div>
|
|
<div class="tab-bar">
|
|
<div class="tab active" onclick="switchTab('engineer')">Инженер</div>
|
|
<div class="tab" onclick="switchTab('technician')">Техник</div>
|
|
</div>
|
|
|
|
<div class="tab-content active" id="tab-engineer">
|
|
<form id="role-form-engineer" class="menu-group">
|
|
<p style="margin:0 0 12px;color:var(--text-muted);">Отметьте пункты меню, доступные инженеру:</p>
|
|
<div id="menu-engineer"></div>
|
|
<div class="form-actions" style="margin-top:16px;">
|
|
<button type="submit" class="btn btn-primary">Сохранить</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="tab-content" id="tab-technician">
|
|
<form id="role-form-technician" class="menu-group">
|
|
<p style="margin:0 0 12px;color:var(--text-muted);">Отметьте пункты меню, доступные технику:</p>
|
|
<div id="menu-technician"></div>
|
|
<div class="form-actions" style="margin-top:16px;">
|
|
<button type="submit" class="btn btn-primary">Сохранить</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
var MENU_ITEMS = [
|
|
// Панель
|
|
{key: 'charts', label: 'Графики', section: 'Панель'},
|
|
{key: 'ceo', label: 'CEO дашборд', section: 'Панель'},
|
|
// Управление
|
|
{key: 'customers', label: 'Клиенты', section: 'Управление'},
|
|
{key: 'objects', label: 'Объекты', section: 'Управление'},
|
|
{key: 'sla', label: 'SLA контракты', section: 'Управление'},
|
|
{key: 'questionnaire', label: 'Опросник', section: 'Управление'},
|
|
{key: 'passports', label: 'Паспорта объектов', section: 'Управление'},
|
|
// Работа
|
|
{key: 'users', label: 'Сотрудники', section: 'Работа'},
|
|
{key: 'assignments', label: 'Назначение сотрудников', section: 'Работа'},
|
|
{key: 'docs', label: 'Просмотр документации', section: 'Работа'},
|
|
{key: 'tasks', label: 'Задачи', section: 'Работа'},
|
|
{key: 'reports', label: 'Отчёты', section: 'Работа'},
|
|
{key: 'incidents', label: 'Инциденты', section: 'Работа'},
|
|
{key: 'checklist', label: 'Чек-лист', section: 'Работа'},
|
|
{key: 'tech_access', label: 'Доступ техников к документам', section: 'Работа'},
|
|
// Контент
|
|
{key: 'blog', label: 'Управление блогом', section: 'Контент'},
|
|
{key: 'cases', label: 'Примеры из практики', section: 'Контент'},
|
|
];
|
|
|
|
function switchTab(name){
|
|
document.querySelectorAll('.tab-content').forEach(function(t){ t.classList.remove('active'); });
|
|
document.querySelectorAll('.tab').forEach(function(t){ t.classList.remove('active'); });
|
|
document.getElementById('tab-' + name).classList.add('active');
|
|
document.querySelector('.tab[onclick*="' + name + '"]').classList.add('active');
|
|
}
|
|
|
|
function loadPermissions(){
|
|
fetch('/service/api/role-permissions').then(function(r){ return r.json() }).then(function(data){
|
|
['engineer','technician'].forEach(function(role){
|
|
var container = document.getElementById('menu-' + role);
|
|
container.innerHTML = '';
|
|
var permMap = {};
|
|
(data[role] || []).forEach(function(p){ permMap[p.menu_key] = p.visible; });
|
|
var currentSection = '';
|
|
MENU_ITEMS.forEach(function(item){
|
|
if(item.section !== currentSection){
|
|
currentSection = item.section;
|
|
var hdr = document.createElement('h4');
|
|
hdr.textContent = item.section;
|
|
container.appendChild(hdr);
|
|
}
|
|
var div = document.createElement('div');
|
|
div.className = 'menu-item';
|
|
var checked = permMap[item.key] !== false;
|
|
div.innerHTML = '<input type="checkbox" name="' + item.key + '" ' + (checked?'checked':'') + '>' +
|
|
'<span class="label">' + item.label + '</span>';
|
|
div.querySelector('input[type="checkbox"]').addEventListener('change', function(){
|
|
div.style.opacity = this.checked ? '1' : '0.5';
|
|
});
|
|
div.style.opacity = checked ? '1' : '0.5';
|
|
container.appendChild(div);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
['engineer','technician'].forEach(function(role){
|
|
document.getElementById('role-form-' + role).addEventListener('submit', function(e){
|
|
e.preventDefault();
|
|
var perms = [];
|
|
this.querySelectorAll('input[type="checkbox"]').forEach(function(cb){
|
|
perms.push({menu_key: cb.name, visible: cb.checked});
|
|
});
|
|
fetch('/service/api/role-permissions/save', {
|
|
method: 'POST', headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({role: role, permissions: perms})
|
|
}).then(function(r){ return r.json() }).then(function(d){
|
|
if(d.ok) alert('Настройки роли "' + role + '" сохранены');
|
|
});
|
|
});
|
|
});
|
|
|
|
loadPermissions();
|
|
</script>
|
|
{% endblock %} |