This commit is contained in:
mac 2026-07-15 16:33:08 +03:00
parent 0c2106c1df
commit be5e0fc1d1
3 changed files with 580 additions and 0 deletions

View File

@ -0,0 +1,92 @@
<?php
require_once($_SERVER['DOCUMENT_ROOT']."/config.php");
require_once($_SERVER['DOCUMENT_ROOT']."/engine/classes/BiAnalytics.php");
if (!isset($_SESSION['id']) || !$_SESSION['id']) {
http_response_code(401);
echo json_encode(array('error' => 'Unauthorized'));
exit;
}
$company_id = isset($_SESSION['agency_id']) ? (int)$_SESSION['agency_id'] : 1;
$user_id = (int)$_SESSION['id'];
//$_SESSION['role_superset'] = 'manager';
if (isset($_SESSION['role_superset']) && !empty($_SESSION['role_superset'])) {
$user_role = $_SESSION['role_superset'];
} else {
if (!empty($_SESSION['agency']) || !empty($_SESSION['users_admin'])) {
$user_role = 'company_head';
} elseif (!empty($_SESSION['manager'])) {
$user_role = 'manager';
} else {
$user_role = 'agent';
}
$_SESSION['role_superset'] = $user_role;
}
$dashboard_uuid = isset($_GET['dashboard_id']) ? $_GET['dashboard_id'] : '';
// Проверка доступа к дашборду
$dashboard_uuid_esc = mysql_real_escape_string($dashboard_uuid);
$company_id_esc = $company_id;
$user_id_esc = $user_id;
if ($user_id == $company_id) {
$sql_check = "
SELECT 1 FROM bi_analytics_dashboard_access
WHERE dashboard_uuid = '$dashboard_uuid_esc'
AND company_id = $company_id_esc
AND is_active = 1
LIMIT 1
";
} else {
$role_level = 0;
if ($user_role == 'agent') $role_level = 1;
elseif ($user_role == 'manager') $role_level = 2;
elseif ($user_role == 'company_head') $role_level = 3;
$sql_check = "
SELECT 1 FROM bi_analytics_dashboard_access
WHERE dashboard_uuid = '$dashboard_uuid_esc'
AND company_id = $company_id_esc
AND is_active = 1
AND (user_id = $user_id_esc OR user_id = 0)
AND role_required <= $role_level
LIMIT 1
";
}
$result_check = mysql_query($sql_check);
if (!$result_check || mysql_num_rows($result_check) == 0) {
http_response_code(403);
echo json_encode(array(
'error' => 'Dashboard access denied',
'role_level' => $user_role
));
exit;
}
// Информация о пользователе
$userInfo = array(
'first_name' => isset($_SESSION['first_name']) ? $_SESSION['first_name'] : 'User',
'last_name' => isset($_SESSION['last_name']) ? $_SESSION['last_name'] : (string)$user_id,
'email' => isset($_SESSION['email']) ? $_SESSION['email'] : null,
);
$bi = new BiAnalytics();
$result = $bi->getGuestToken($dashboard_uuid, $company_id, $user_id, $user_role, $userInfo);
if (isset($result['token'])) {
header('Content-Type: application/json');
echo json_encode(array('token' => $result['token']));
} else {
http_response_code(500);
echo json_encode(array('error' => 'Failed to get guest token', 'details' => $result['error']));
}
file_put_contents($_SERVER['DOCUMENT_ROOT'] . '/log/superset_guest.log',
date('Y-m-d H:i:s') . "\n" . json_encode($result) . "\n\n", FILE_APPEND);

304
bi-analytics-test.php Normal file
View File

@ -0,0 +1,304 @@
<?php
require_once($_SERVER['DOCUMENT_ROOT'] . "/config.php");
// Проверка авторизации
if (!isset($_SESSION['id']) || !$_SESSION['id']) {
header("location: index.php");
die("Доступ запрещён!");
}
// Разрешить тестирование только определённым пользователям (опционально)
$allowed_users = [117, 12093, 6635]; // ID пользователей, кому разрешён тест
if (!in_array((int)$_SESSION['id'], $allowed_users)) {
die("⚠️ Доступ ограничен для тестирования. Обратитесь к администратору.");
}
$user_id = (int)$_SESSION['id'];
$company_id = (int)$_SESSION['agency_id'];
// Получаем доступные дашборды для тестирования
$sql_dashboards = "
SELECT dashboard_uuid, dashboard_name, role_required
FROM bi_analytics_dashboard_access
WHERE company_id = $company_id
AND is_active = 1
ORDER BY dashboard_name
";
$q_dashboards = mysql_query($sql_dashboards);
$available_dashboards = [];
while ($row = mysql_fetch_assoc($q_dashboards)) {
$user_role = isset($_SESSION['role']) ? $_SESSION['role'] : 'agent';
$role_order = ['agent' => 1, 'manager' => 2, 'company_head' => 3, 'admin' => 4];
$required_order = isset($role_order[$row['role_required']]) ? $role_order[$row['role_required']] : 1;
$user_order = isset($role_order[$user_role]) ? $role_order[$user_role] : 1;
if ($user_order >= $required_order) {
$available_dashboards[] = $row;
}
}
if (empty($available_dashboards)) {
echo '<div style="padding: 40px; text-align: center; font-family: sans-serif;">
<h3>📊 Нет доступных дашбордов</h3>
<p>Для вашей компании ещё не настроены дашборды BI-аналитики.</p>
</div>';
exit;
}
$default_dashboard = $available_dashboards[0];
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BI Аналитика Тестирование</title>
<script src="https://unpkg.com/@superset-ui/embedded-sdk"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
background: #f0f2f5;
padding: 20px;
}
.bi-container {
max-width: 1600px;
margin: 0 auto;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
overflow: hidden;
}
.bi-header {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: white;
padding: 20px 24px;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.bi-header h1 {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
}
.bi-header p {
font-size: 14px;
opacity: 0.8;
}
.bi-header .badge {
display: inline-block;
background: #4CAF50;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
margin-top: 12px;
}
.bi-tabs {
background: #f8f9fa;
padding: 0 24px;
border-bottom: 1px solid #e9ecef;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.bi-tab-btn {
padding: 12px 24px;
border: none;
background: transparent;
color: #666;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
}
.bi-tab-btn:hover {
color: #4CAF50;
}
.bi-tab-btn.active {
color: #4CAF50;
border-bottom-color: #4CAF50;
background: white;
}
.bi-content {
padding: 20px 24px;
min-height: 600px;
}
#superset-dashboard-container {
width: 100%;
height: calc(100vh - 220px);
min-height: 550px;
position: relative;
background: #fff;
border-radius: 8px;
overflow: hidden;
}
#superset-dashboard-container iframe {
width: 100% !important;
height: 100% !important;
border: none !important;
}
.loading-overlay {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
height: 100%;
color: #666;
}
.error-message {
padding: 40px;
text-align: center;
color: #f44336;
}
.footer {
text-align: center;
padding: 16px;
font-size: 12px;
color: #999;
border-top: 1px solid #eee;
background: #fafafa;
}
.company-info {
display: inline-block;
background: rgba(255,255,255,0.15);
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
margin-left: 16px;
}
</style>
</head>
<body>
<div class="bi-container">
<div class="bi-header">
<h1>
📊 BI Аналитика
<span class="company-info">Компания ID: <?= $company_id ?></span>
<span class="badge">🧪 Тестовый режим</span>
</h1>
<p>Тестирование дашбордов Superset</p>
</div>
<?php if (count($available_dashboards) > 1): ?>
<div class="bi-tabs">
<?php foreach ($available_dashboards as $dash): ?>
<button class="bi-tab-btn <?= $dash['dashboard_uuid'] === $default_dashboard['dashboard_uuid'] ? 'active' : '' ?>"
data-uuid="<?= htmlspecialchars($dash['dashboard_uuid']) ?>"
onclick="switchDashboard(this)">
<?= htmlspecialchars($dash['dashboard_name']) ?>
</button>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="bi-content">
<div id="superset-dashboard-container">
<div class="loading-overlay">
<img src="/images/rocket-spinner.svg" width="32" style="margin-right: 8px;">
Загрузка дашборда...
</div>
</div>
</div>
<div class="footer">
🔍 Тестовый режим | Пользователь: <?= htmlspecialchars($_SESSION['id']) ?> | Дата: <?= date('d.m.Y H:i') ?>
</div>
</div>
<script>
let currentDashboardUuid = '<?= $default_dashboard['dashboard_uuid'] ?>';
function switchDashboard(btn) {
const newUuid = btn.getAttribute('data-uuid');
// Обновляем активный класс
document.querySelectorAll('.bi-tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
if (newUuid !== currentDashboardUuid) {
currentDashboardUuid = newUuid;
reloadDashboard();
}
}
function reloadDashboard() {
const container = document.getElementById('superset-dashboard-container');
if (!container) return;
// Показываем загрузку
container.innerHTML = '<div class="loading-overlay"><img src="/images/rocket-spinner.svg" width="32" style="margin-right:8px;"> Загрузка дашборда...</div>';
setTimeout(() => {
initSupersetEmbed();
}, 100);
}
function initSupersetEmbed() {
const container = document.getElementById('superset-dashboard-container');
if (!container) return;
try {
console.log("🚀 Загрузка дашборда:", currentDashboardUuid);
supersetEmbeddedSdk.embedDashboard({
id: currentDashboardUuid,
supersetDomain: 'https://bi.joywork.ru',
mountPoint: container,
fetchGuestToken: async () => {
const response = await fetch('/ajax/getSupersetGuestToken.php?dashboard_id=' + encodeURIComponent(currentDashboardUuid));
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (!data.token) {
throw new Error('Токен не получен');
}
return data.token;
},
dashboardUiConfig: {
hideTitle: false,
hideTab: false,
//hideChartControls: true,
showSQL: true,
filters: {
expanded: true,
},
urlParams: {
lang: 'ru',
filter_company_id: '<?= $company_id ?>'
}
},
}).catch(error => {
console.error("Ошибка встраивания:", error);
container.innerHTML = `<div class="error-message">
Ошибка загрузки дашборда<br>
<small>${error.message}</small>
</div>`;
});
} catch (error) {
console.error("Ошибка инициализации:", error);
container.innerHTML = `<div class="error-message">
Ошибка инициализации<br>
<small>${error.message}</small>
</div>`;
}
}
// Запускаем при загрузке
document.addEventListener('DOMContentLoaded', function() {
initSupersetEmbed();
});
</script>
</body>
</html>

View File

@ -0,0 +1,184 @@
<?php
class BiAnalytics
{
private $supersetDomain;
private $loginEndpoint;
private $guestTokenEndpoint;
private $adminUsername;
private $adminPassword;
public function __construct()
{
$this->supersetDomain = trim('http://10.91.76.110');
$this->loginEndpoint = $this->supersetDomain . '/api/v1/security/login';
$this->guestTokenEndpoint = $this->supersetDomain . '/api/v1/security/guest_token/';
$this->adminUsername = 'admin';
$this->adminPassword = '6a6a78543e215c5adce1b828c9a30ef5';
}
public static function getAvailableUserIds($userId, $role)
{
if ($role === 'company_head') {
return null;
}
if ($role === 'agent') {
return array($userId);
}
if ($role === 'manager') {
$subordinates = self::getAllSubordinateIds($userId);
$subordinates[] = $userId;
return array_unique($subordinates);
}
return array($userId);
}
private static function getAllSubordinateIds($managerId)
{
$sql = "SELECT id FROM users WHERE id_manager = " . intval($managerId) . " AND blocked = 0";
$result = mysql_query($sql);
if (!$result) {
return array();
}
$subIds = array();
while ($row = mysql_fetch_assoc($result)) {
$subId = (int)$row['id'];
$subIds[] = $subId;
$subSubIds = self::getAllSubordinateIds($subId);
$subIds = array_merge($subIds, $subSubIds);
}
return $subIds;
}
public static function buildRlsRules($companyId, $userId, $role)
{
$tables = array(
'cur.clients_base',
'bi.client_kpi_daily',
'logs.client_events',
'cur.requisitions_base',
'bi.requisition_kpi_daily',
'logs.requisition_events'
);
$availableUserIds = self::getAvailableUserIds($userId, $role);
$rules = array();
// Одно правило company_id на все таблицы (без dataset_name — Superset применит ко всем)
$rules[] = array(
'clause' => "company_id = {$companyId}"
);
// Если не company_head — добавляем фильтр по who_work
if ($availableUserIds !== null) {
$userIdsString = implode(',', $availableUserIds);
$rules[] = array(
'clause' => "(who_work IN ({$userIdsString}) OR who_work = 0)"
);
}
return $rules;
}
public function getGuestToken($dashboardUuid, $companyId, $userId, $userRole, $userInfo = array())
{
try {
$accessToken = $this->getAdminAccessToken();
if (!$accessToken) {
return array('error' => 'Failed to obtain admin access token');
}
$rlsRules = self::buildRlsRules($companyId, $userId, $userRole);
$guestTokenData = array(
'resources' => array(
array(
'type' => 'dashboard',
'id' => $dashboardUuid
)
),
'rls' => $rlsRules,
'ui_config' => array(
'show_sql' => in_array($userRole, array('manager', 'company_head', 'admin', 'agent')),
'url_params' => array(
'filter_company_id' => (string)$companyId
)
),
'user' => array(
'username' => 'joywork_user_' . $userId,
'first_name' => isset($userInfo['first_name']) ? $userInfo['first_name'] : 'User',
'last_name' => isset($userInfo['last_name']) ? $userInfo['last_name'] : (string)$userId,
'email' => isset($userInfo['email']) ? $userInfo['email'] : null,
),
'attributes' => array(
'company_id' => $companyId,
'available_user_ids' => self::getAvailableUserIds($userId, $userRole),
),
);
$response = $this->makeRequest($this->guestTokenEndpoint, $guestTokenData, $accessToken);
if (isset($response['token'])) {
return array('token' => $response['token']);
} else {
return array('error' => 'Guest token not returned', 'details' => $response);
}
} catch (Exception $e) {
return array('error' => $e->getMessage());
}
}
private function getAdminAccessToken()
{
$loginData = array(
'username' => $this->adminUsername,
'password' => $this->adminPassword,
'provider' => 'db',
'refresh' => true,
);
$response = $this->makeRequest($this->loginEndpoint, $loginData);
if (isset($response['access_token'])) {
return $response['access_token'];
}
return null;
}
private function makeRequest($url, $postData, $accessToken = null)
{
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($postData),
CURLOPT_HTTPHEADER => array(
'Content-Type: application/json',
'Accept: application/json',
),
CURLOPT_SSL_VERIFYPEER => false,
));
if ($accessToken) {
$headers = array(
'Content-Type: application/json',
'Accept: application/json',
'Authorization: Bearer ' . $accessToken,
);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
}
$responseBody = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("API request failed. HTTP code: $httpCode. Response: $responseBody");
}
$decoded = json_decode($responseBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception("Invalid JSON response: " . json_last_error_msg());
}
return $decoded;
}
}