Files
site_aegisone/py_service/app/templates/pages/role_settings.html
T
angel 72b6879f4b v1.7.0: refactor max_bot to flat structure, add VCF+UserModel+NLP history context, portal pages and proxy fixes
- 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
2026-05-29 02:30:30 +03:00

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 %}