Portions created by the Initial Developer are Copyright (C) 2008-2025 the Initial Developer. All Rights Reserved. Contributor(s): Mark J Crane Tim Fry */ // Includes require_once dirname(__DIR__, 2) . "/resources/require.php"; require_once "resources/check_auth.php"; // Check permissions if (!permission_exists('operator_panel_view')) { echo "access denied"; exit; } // Multi-lingual support $language = new text; $text = $language->get(); // Create token and register with the active operator panel service $token = (new token())->create($_SERVER['PHP_SELF']); subscriber::save_token($token, ['active.operator.panel']); // Get the status for the current user $sql = 'select user_status from v_users where user_uuid = :user_uuid'; $parameters = ['user_uuid' => $_SESSION['user_uuid'] ?? '']; $user_status = $database->select($sql, $parameters, 'column') ?? ''; // Gather user permissions for the JS side $perm = [ 'operator_panel_view' => permission_exists('operator_panel_view'), 'operator_panel_manage' => permission_exists('operator_panel_manage'), 'operator_panel_hangup' => permission_exists('operator_panel_hangup'), 'operator_panel_eavesdrop' => permission_exists('operator_panel_eavesdrop'), 'operator_panel_record' => permission_exists('operator_panel_record'), 'operator_panel_originate' => permission_exists('operator_panel_originate'), 'operator_panel_coach' => permission_exists('operator_panel_coach'), 'operator_panel_call_details' => permission_exists('operator_panel_call_details'), 'operator_panel_on_demand' => permission_exists('operator_panel_on_demand'), 'operator_panel_transfer_attended' => permission_exists('operator_panel_transfer_attended'), 'operator_panel_extensions' => permission_exists('operator_panel_extensions'), 'operator_panel_calls' => permission_exists('operator_panel_calls'), 'operator_panel_conferences' => permission_exists('operator_panel_conferences'), 'operator_panel_agents' => permission_exists('operator_panel_agents'), ]; // WebSocket settings from default_settings $ws_settings = [ 'reconnect_delay' => (int)$settings->get('operator_panel', 'reconnect_delay', 500), 'ping_interval' => (int)$settings->get('operator_panel', 'ping_interval', 5000), 'auth_timeout' => (int)$settings->get('operator_panel', 'auth_timeout', 5000), 'pong_timeout' => (int)$settings->get('operator_panel', 'pong_timeout', 1500), 'max_reconnect_delay' => (int)$settings->get('operator_panel', 'max_reconnect_delay', 5000), 'pong_timeout_max_retries' => (int)$settings->get('operator_panel', 'pong_timeout_max_retries', 2), 'refresh_interval' => (int)$settings->get('operator_panel', 'refresh_interval', 0), ]; // Theme colors for connection status indicator $status_colors = [ 'connected' => $settings->get('theme', 'operator_panel_status_connected', '#28a745'), 'warning' => $settings->get('theme', 'operator_panel_status_warning', '#ffc107'), 'disconnected' => $settings->get('theme', 'operator_panel_status_disconnected', '#dc3545'), 'connecting' => $settings->get('theme', 'operator_panel_status_connecting', '#6c757d'), ]; $status_icons = [ 'connected' => $settings->get('theme', 'operator_panel_status_icon_connected', 'fa-solid fa-plug-circle-check'), 'warning' => $settings->get('theme', 'operator_panel_status_icon_warning', 'fa-solid fa-plug-circle-exclamation'), 'disconnected' => $settings->get('theme', 'operator_panel_status_icon_disconnected', 'fa-solid fa-plug-circle-xmark'), 'connecting' => $settings->get('theme', 'operator_panel_status_icon_connecting', 'fa-solid fa-plug fa-fade'), ]; $conference_action_icons = [ 'mute' => $settings->get('theme', 'operator_panel_conference_icon_mute', 'fas fa-microphone'), 'unmute' => $settings->get('theme', 'operator_panel_conference_icon_unmute', 'fas fa-microphone-slash'), 'deaf' => $settings->get('theme', 'operator_panel_conference_icon_deaf', 'fas fa-headphones'), 'undeaf' => $settings->get('theme', 'operator_panel_conference_icon_undeaf', 'fas fa-deaf'), 'energy_up' => $settings->get('theme', 'operator_panel_conference_icon_energy_up', 'fas fa-plus'), 'energy_down' => $settings->get('theme', 'operator_panel_conference_icon_energy_down', 'fas fa-minus'), 'volume_down' => $settings->get('theme', 'operator_panel_conference_icon_volume_down', 'fas fa-volume-down'), 'volume_up' => $settings->get('theme', 'operator_panel_conference_icon_volume_up', 'fas fa-volume-up'), 'gain_down' => $settings->get('theme', 'operator_panel_conference_icon_gain_down', 'fas fa-sort-amount-down'), 'gain_up' => $settings->get('theme', 'operator_panel_conference_icon_gain_up', 'fas fa-sort-amount-up'), 'kick' => $settings->get('theme', 'operator_panel_conference_icon_kick', 'fas fa-ban'), ]; $status_show_icon = $settings->get('theme', 'operator_panel_status_show_icon', 'true') === 'true'; // Optional user status list for the presence dropdown $user_statuses = ['Available', 'Available (On Demand)', 'On Break', 'Do Not Disturb', 'Logged Out']; // Card label position for extension group cards: top, left, right, bottom, hidden $card_label_position = strtolower((string)$settings->get('operator_panel', 'card_label_position', 'left')); if (!in_array($card_label_position, ['top', 'left', 'right', 'bottom', 'hidden'], true)) { $card_label_position = 'left'; } // Optional polling reconciliation of registration state (can be disabled). $registrations_reconcile_enabled = $settings->get('operator_panel', 'registrations_reconcile_enabled', 'false') === 'true'; // Default auto-park destination for drag/drop parking. $park_destination = (string)$settings->get('operator_panel', 'park_destination', '*5900'); if (!preg_match('/^[0-9*#+]+$/', $park_destination)) { $park_destination = '*5900'; } // Get the logged-in user's own extension numbers (shown at top of Extensions panel) // and primary eavesdrop destination extension $user_own_extensions = []; if (!empty($_SESSION['user']['extensions'])) { // $_SESSION['user']['extensions'] is an array of extension number strings $user_own_extensions = array_values(array_filter($_SESSION['user']['extensions'])); } elseif (!empty($_SESSION['user']['extension'])) { foreach ($_SESSION['user']['extension'] as $ext_record) { if (!empty($ext_record['destination'])) { $user_own_extensions[] = $ext_record['destination']; } } } // Include the page header $document['title'] = $text['title-operator_panel'] ?? 'Operator Panel'; require_once "resources/header.php"; // Cache-busting hashes for JS assets $ws_client_hash = md5_file(__DIR__ . '/resources/javascript/websocket_client.js'); $lop_js_hash = md5_file(__DIR__ . '/resources/javascript/operator_panel.js'); // Cache-busting hash for CSS $operator_panel_css_file = $settings->get('theme', 'operator_panel_css_file', 'operator_panel.css'); $operator_panel_css_file = preg_replace('/[^a-z0-9_\-\.]/i', '', $operator_panel_css_file); $operator_panel_css_file = realpath(__DIR__ . "/resources/css/$operator_panel_css_file"); $operator_panel_css_hash = md5_file($operator_panel_css_file); echo "\n"; ?> \n"; echo "
" . $text['title-operator_panel'] . "\n"; // Connection status indicator (icon + text) echo "\t\t"; if ($status_show_icon) { echo ""; } echo "" . htmlspecialchars($text['status-connecting'] ?? 'Connecting') . ""; echo "\n"; echo "
\n"; // My status buttons (matching the original design) if ($perm['operator_panel_view']) { $status_btn_colors = [ 'Available' => '#28a745', 'Available (On Demand)'=> '#28a745', 'On Break' => '#b8860b', 'Do Not Disturb' => '#dc3545', 'Logged Out' => '#6c757d', ]; echo "
\n"; echo "
\n"; foreach ($user_statuses as $s) { $color = $status_btn_colors[$s] ?? '#6c757d'; $label = strtoupper(htmlspecialchars($s)); echo " \n"; } echo "
\n"; echo "
\n"; } echo "
\n"; echo "\n"; ?>