| <% formatTimestampToTime(call.start) %> |
- <% call.src_num %> |
+ <% getClientHeader(call.src_num) %> |
- <%call.dst_num%> |
+ <% getClientHeader(call.dst_num) %> |
- <% bridge.src_num %>
+ <% getClientHeader(bridge.src_num) %>
- <% bridge.dst_num %>
+ <% getClientHeader(bridge.dst_num) %>
|
- <% chanData.number %>
+ <% getClientHeader(chanData.number) %>
|
@@ -182,7 +220,7 @@
<% call.spy_num %>
|
- <%call.exten%> |
+ <% getClientHeader(call.exten) %> |
diff --git a/Lib/AsteriskManager.php b/Lib/AsteriskManager.php
index 82411f5..917eb95 100644
--- a/Lib/AsteriskManager.php
+++ b/Lib/AsteriskManager.php
@@ -205,7 +205,7 @@ public function pingAMIListener(string $pingTube = 'CdrConnector'): bool
* @param array $parameters The parameters for the request (optional).
* @return array The response from the socket.
*/
- public function sendRequestTimeout(string $action, array $parameters = []): array
+ public function sendRequestTimeout(string $action, array $parameters = [], int $waitDuration = 0): array
{
if (! is_resource($this->socket) && !$this->connectDefault()) {
return [];
@@ -229,6 +229,9 @@ public function sendRequestTimeout(string $action, array $parameters = []): arra
$response = [];
if ($result) {
+ if($waitDuration>0){
+ usleep($waitDuration);
+ }
$response = $this->waitResponse(true);
}
return $response;
@@ -599,7 +602,7 @@ public function waitUserEvent(bool $allow_timeout = false): array
* @example examples/sip_show_peer.php Get information about a sip peer
*
*/
- public function connect(string $server = null, string $username = null, string $secret = null, string $events = 'on'): bool
+ public function connect(?string $server = null, ?string $username = null, ?string $secret = null, string $events = 'on'): bool
{
$this->listenEvents = $events;
// use config if not specified
@@ -755,20 +758,19 @@ public function ChangeMonitor(string $channel, string $file): array
* Execute Command
*
* @param string $command
- * @param ?string $actionid message matching variable
+ * @param ?string $actionId message matching variable
*
* @return array
* @example examples/sip_show_peer.php Get information about a sip peer
* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Command
* @link http://www.voip-info.org/wiki-Asterisk+CLI
*/
- public function Command(string $command, string $actionid = null): array
+ public function Command(string $command, ?string $actionId = null): array
{
$parameters = ['Command' => $command];
- if ($actionid) {
- $parameters['ActionID'] = $actionid;
+ if ($actionId) {
+ $parameters['ActionID'] = $actionId;
}
-
return $this->sendRequest('Command', $parameters);
}
diff --git a/Lib/Logger.php b/Lib/Logger.php
index d47c4c5..8c16321 100644
--- a/Lib/Logger.php
+++ b/Lib/Logger.php
@@ -50,8 +50,9 @@ public function __construct(string $class, string $module_name)
$logPath = Directories::getDir(Directories::CORE_LOGS_DIR) . '/' . $this->module_name . '/';
if (!file_exists($logPath)) {
Util::mwMkdir($logPath);
- Util::addRegularWWWRights($logPath);
}
+ // Always ensure correct permissions on log directory
+ Util::addRegularWWWRights($logPath);
$this->logFile = $logPath . $class . '.log';
$this->initLogger();
}
diff --git a/Lib/MonitorActiveCallsConf.php b/Lib/MonitorActiveCallsConf.php
index d573236..9bcb841 100644
--- a/Lib/MonitorActiveCallsConf.php
+++ b/Lib/MonitorActiveCallsConf.php
@@ -9,17 +9,15 @@
namespace Modules\ModuleMonitorActiveCalls\Lib;
-use MikoPBX\Core\System\SystemMessages;
use MikoPBX\Core\System\Util;
use MikoPBX\Core\Workers\Cron\WorkerSafeScriptsCore;
use MikoPBX\Modules\Config\ConfigClass;
-use MikoPBX\PBXCoreREST\Lib\PBXApiResult;
-use Modules\ModuleMonitorActiveCalls\bin\ActiveCallsFromCdr;
use Modules\ModuleMonitorActiveCalls\bin\WorkerActiveCalls;
use Modules\ModuleMonitorActiveCalls\bin\WorkerAmiActions;
class MonitorActiveCallsConf extends ConfigClass
{
+ public const AMI_USER = 'monitor-active-calls';
/**
* Receive information about mikopbx main database changes
@@ -38,12 +36,6 @@ public function modelsEventChangeData($data): void
public function getModuleWorkers(): array
{
return [
- /*
- [
- 'type' => WorkerSafeScriptsCore::CHECK_BY_PID_NOT_ALERT,
- 'worker' => ActiveCallsFromCdr::class,
- ],
- //*/
[
'type' => WorkerSafeScriptsCore::CHECK_BY_BEANSTALK,
'worker' => WorkerAmiActions::class,
@@ -55,6 +47,28 @@ public function getModuleWorkers(): array
];
}
+ /**
+ * Генератор секции пиров для manager.conf
+ *
+ *
+ * @return string
+ */
+ public function generateManagerConf(): string
+ {
+ $arr_params = array_merge(WorkerActiveCalls::CALL_EVENTS, WorkerActiveCalls::QUEUE_EVENTS);
+ $conf = "[".self::AMI_USER."]" . PHP_EOL;
+ $conf .= "secret=".self::AMI_USER . PHP_EOL;
+ $conf .= 'deny=0.0.0.0/0.0.0.0' . PHP_EOL;
+ $conf .= 'permit=127.0.0.1/255.255.255.255' . PHP_EOL;
+ $conf .= 'read=system,agent,call,cdr,user' . PHP_EOL;
+ $conf .= 'write=system,agent,call,originate' . PHP_EOL;
+ $conf .= 'eventfilter=!UserEvent: CdrConnector' . PHP_EOL;
+ $conf .= 'eventfilter=Event: (' . implode('|', $arr_params) . ')' . PHP_EOL;
+ $conf .= PHP_EOL;
+
+ return $conf;
+ }
+
/**
* @param array $tasks
*/
diff --git a/Lib/MonitorActiveCallsMain.php b/Lib/MonitorActiveCallsMain.php
index db10e12..53dd7ca 100644
--- a/Lib/MonitorActiveCallsMain.php
+++ b/Lib/MonitorActiveCallsMain.php
@@ -3,8 +3,8 @@
namespace Modules\ModuleMonitorActiveCalls\Lib;
+use MikoPBX\Common\Models\PbxExtensionModules;
use MikoPBX\Core\System\Processes;
-use MikoPBX\Core\System\Util;
use MikoPBX\Core\Workers\Cron\WorkerSafeScriptsCore;
use MikoPBX\Modules\PbxExtensionBase;
use MikoPBX\Modules\PbxExtensionUtils;
@@ -76,4 +76,15 @@ public function startAllServices(bool $restart = false): void
}
}
}
+
+
+ /**
+ * Checks whether the ModuleSoftphoneBackend module exists and is enabled..
+ * @return bool
+ */
+ public static function backendExists(): bool
+ {
+ $result = PbxExtensionModules::findFirstByUniqid("ModuleSoftphoneBackend");
+ return $result !== null && intval($result->disabled ) === 0;
+ }
}
\ No newline at end of file
diff --git a/Messages/en.php b/Messages/en.php
index 8144609..b9e1e73 100644
--- a/Messages/en.php
+++ b/Messages/en.php
@@ -49,4 +49,6 @@
'module_monitorCalls_legendTitleSpy' => 'Eavesdropping',
'module_monitorCalls_needSelectQueue' => 'Select a queue',
'module_monitorCalls_legendTitleCalling' => 'Dialing',
+ 'module_monitorCalls_noWaitingCalls' => 'No waiting calls',
+ 'module_monitorCalls_saveQueuesFilter' => 'Apply',
];
diff --git a/Messages/ru.php b/Messages/ru.php
index 445a370..342b4fb 100644
--- a/Messages/ru.php
+++ b/Messages/ru.php
@@ -27,6 +27,7 @@
'module_monitorCalls_userNumberNotFound' => 'не найден',
'module_monitorCalls_userNumberHelpMessage' => 'На этот номер будет поступать обратный звонок при использовании функций супервизора',
'module_monitorCalls_needSelectQueue' => 'Выберите очередь',
+ 'module_monitorCalls_minWaitVisible' => 'Очередь, время ожидания больше',
'module_monitorCalls_waitingClients' => 'Ожидают',
'module_monitorCalls_waitingTitleClient' => 'ожидание',
'module_monitorCalls_columnTitleAgent' => 'Агент',
@@ -39,6 +40,6 @@
'module_monitorCalls_columnTitleWaitTime' => 'Ожидание',
'module_monitorCalls_columnTitleTalkTime' => 'Разговор',
'module_monitorCalls_columnTitleAction' => 'Действия',
-
-
+ 'module_monitorCalls_noWaitingCalls' => 'Нет ожидающих звонков',
+ 'module_monitorCalls_saveQueuesFilter' => 'Применить',
];
\ No newline at end of file
diff --git a/Messages/th.php b/Messages/th.php
index c03242c..c9b02fd 100644
--- a/Messages/th.php
+++ b/Messages/th.php
@@ -11,7 +11,7 @@
'mo_ModuleModuleMonitorActiveCalls' => '',
'BreadcrumbModuleMonitorActiveCalls' => '',
'SubHeaderModuleMonitorActiveCalls' => '',
- 'module_template_AddNewRecord' => '',
+ 'module_template_AddNewRecord' => 'เพิ่ม',
'module_monitor_active_callsTextFieldLabel' => '',
'module_monitor_active_callsTextAreaFieldLabel' => '',
'module_monitor_active_callsPasswordFieldLabel' => '',
diff --git a/bin/WorkerActiveCalls.php b/bin/WorkerActiveCalls.php
index 9852486..0fe2a42 100644
--- a/bin/WorkerActiveCalls.php
+++ b/bin/WorkerActiveCalls.php
@@ -24,21 +24,25 @@
use MikoPBX\Common\Models\PbxSettings;
use MikoPBX\Core\System\SystemMessages;
use Modules\ModuleMonitorActiveCalls\Lib\AsteriskManager as CustomAsteriskManager;
-use MikoPBX\Core\Asterisk\AsteriskManager;
use MikoPBX\Core\Workers\WorkerBase;
use MikoPBX\Core\System\Util;
use Modules\ModuleMonitorActiveCalls\Lib\CacheManager;
use Modules\ModuleMonitorActiveCalls\Lib\Logger;
+use Modules\ModuleMonitorActiveCalls\Lib\MonitorActiveCallsConf;
+use Modules\ModuleMonitorActiveCalls\Lib\MonitorActiveCallsMain;
+use Modules\ModuleSoftphoneBackend\Lib\RestAPI\Controllers\ApiController AS BackendApiController;
require_once 'Globals.php';
class WorkerActiveCalls extends WorkerBase
{
public Logger $logger;
+
+ private bool $backendExists = false;
private bool $init = true;
- private string $lastPrintHash= '';
- /** @var AsteriskManager $am */
- protected AsteriskManager $am;
+ private string $lastPrintHash = '';
+ private string $lastPrintUserHash = '';
+ private int $lastControlActiveCalls = 0;
protected CustomAsteriskManager $amCustom;
private array $activeChannels = [];
private array $states = [];
@@ -47,6 +51,7 @@ class WorkerActiveCalls extends WorkerBase
private array $callType = [];
private array $queuesData = [];
private array $spyerChannels = [];
+ private array $agentToQueues = []; // agent number => [queueIds]
public const ENDPOINT_TYPE_PEER = '1';
public const ENDPOINT_TYPE_PROVIDER = '2';
@@ -54,25 +59,26 @@ class WorkerActiveCalls extends WorkerBase
public const STATE_RINGING = 'Ringing';
public const STATE_ONHOLD = 'OnHold';
public const STATE_RING = 'Ring';
- public const STATE_UNAVAILIBLE = 'Unavailable';
- private const CALL_EVENTS = [
+ public const CALL_EVENTS = [
+ 'UserEvent',
+ 'ExtensionStatus',
'NewCallerid',
+ 'NewConnectedLine',
'BridgeEnter',
'BridgeLeave',
'ChanSpyStart',
'ChanSpyStop',
- 'ExtensionStatus',
'Hangup',
'Newstate',
'Newchannel',
];
public const QUEUE_AGENT_STATES = [
- '0' => self::STATE_UNAVAILIBLE, // AST_DEVICE_UNKNOWN
+ '0' => self::STATE_UNAVAILABLE, // AST_DEVICE_UNKNOWN
'1' => self::STATE_IDLE, //AST_DEVICE_NOT_INUSE
'2' => self::STATE_BUSY, //AST_DEVICE_INUSE
'3' => self::STATE_BUSY, // AST_DEVICE_UNAVAILABLE
- '4' => self::STATE_UNAVAILIBLE, // AST_DEVICE_INVALID
+ '4' => self::STATE_UNAVAILABLE, // AST_DEVICE_INVALID
'6' => self::STATE_RINGING, // AST_DEVICE_RINGING
'7' => self::STATE_ONHOLD, // AST_DEVICE_ONHOLD
];
@@ -85,13 +91,18 @@ class WorkerActiveCalls extends WorkerBase
public const CALL_TYPE_IN = 'incoming';
- private const QUEUE_EVENTS = [
+ public const QUEUE_EVENTS = [
'QueueCallerJoin',
'QueueMemberStatus',
'QueueCallerLeave'
];
- private $queueEntryes = [];
+ private const CACHE_TTL = 80000;
+ private const CONTROL_INTERVAL = 60;
+ private const MAX_BRIDGE_ITERATIONS = 200;
+ private const AMI_REQUEST_TIMEOUT = 200000;
+
+ private array $queueEntryes = [];
/**
* Replies to a ping request from the worker
@@ -114,9 +125,93 @@ public function replyOnPingRequest(array $parameters): bool
LOG_WARNING
);
}
+
+
return false;
}
+ /**
+ * Дополнительный контроль активных вызовов.
+ * @return void
+ */
+ private function channelAdditionalControl()
+ {
+ if(empty($this->activeChannels)){
+ return;
+ }
+ $this->logger->writeInfo('Start channelAdditionalControl...');
+ try{
+ $channelsData = WorkerAmiActions::invokeApi('getChannels', []);
+ if (!is_array($channelsData) || empty($channelsData)) {
+ // Пустой массив может быть признаком некорректной работы $channelsData
+ // Не обрабатывает такой вариант.
+ return;
+ }
+
+ // Cleanup by linkedid and also prune stale channels inside existing linkedid.
+ foreach (array_keys($this->activeChannels) as $linkedId) {
+ if (!isset($channelsData[$linkedId]) || !is_array($channelsData[$linkedId])) {
+ unset(
+ $this->activeChannels[$linkedId],
+ $this->callType[$linkedId],
+ $this->activeBridges[$linkedId],
+ $this->spyerChannels[$linkedId]
+ );
+ continue;
+ }
+
+ $actualChannels = array_flip($channelsData[$linkedId]);
+ foreach (array_keys($this->activeChannels[$linkedId]) as $channel) {
+ if (!isset($actualChannels[$channel])) {
+ unset($this->activeChannels[$linkedId][$channel]);
+ if(strpos($channel, '/') !== false) {
+ $endpoint = self::getEndpointName($channel);
+ unset($this->states[$endpoint]['channels'][$channel]);
+ }
+ }
+ }
+
+ if (empty($this->activeChannels[$linkedId])) {
+ unset(
+ $this->activeChannels[$linkedId],
+ $this->callType[$linkedId],
+ $this->activeBridges[$linkedId],
+ $this->spyerChannels[$linkedId]
+ );
+ }
+ }
+
+ // Cleanup queueEntryes for linkedIds that no longer exist
+ foreach ($this->queueEntryes as $queueId => $queueChannels) {
+ foreach ($queueChannels as $channel => $data) {
+ $queueLinkedId = $data['Linkedid'] ?? '';
+ if (!empty($queueLinkedId) && !isset($channelsData[$queueLinkedId])) {
+ unset($this->queueEntryes[$queueId][$channel]);
+ }
+ }
+ if (empty($this->queueEntryes[$queueId])) {
+ unset($this->queueEntryes[$queueId]);
+ }
+ }
+
+ // Cleanup orphaned spyerChannels
+ foreach (array_keys($this->spyerChannels) as $spyLinkedId) {
+ if (!isset($channelsData[$spyLinkedId])) {
+ unset($this->spyerChannels[$spyLinkedId]);
+ }
+ }
+
+ // Cleanup orphaned activeBridges
+ foreach (array_keys($this->activeBridges) as $bridgeLinkedId) {
+ if (!isset($channelsData[$bridgeLinkedId])) {
+ unset($this->activeBridges[$bridgeLinkedId]);
+ }
+ }
+ }catch (\Throwable $e){
+ SystemMessages::sysLogMsg( static::class, "Channel control: " . $e->getMessage(), LOG_WARNING);
+ }
+ }
+
/**
* Старт работы листнера.
*
@@ -124,13 +219,13 @@ public function replyOnPingRequest(array $parameters): bool
*/
public function start($argv):void
{
- $this->logger = new Logger('ActiveCalls', 'WorkerActiveCalls');
+ $this->logger = new Logger('ActiveCalls', 'ModuleMonitorActiveCalls');
$this->logger->writeInfo('Starting...');
- $this->initManagerAsterisk();
+ $this->backendExists = MonitorActiveCallsMain::backendExists();
+ $this->initManagerAsterisk();
$this->getExtensionsInfo();
$this->updateStates();
- $this->logger->writeInfo('Collect active lines...');
$this->collectActiveChannels();
$this->collectActiveBridges();
@@ -140,9 +235,16 @@ public function start($argv):void
$this->printActiveCalls();
$this->logger->writeInfo('Wait events...');
while (true) {
- $this->amCustom->waitUserEvent(true);
- if (!$this->amCustom->loggedIn()) {
- sleep(1);
+ try {
+ $this->amCustom->waitUserEvent(true);
+ if (!$this->amCustom->loggedIn()) {
+ sleep(1);
+ $this->logger->writeInfo('initManagerAsterisk...');
+ $this->initManagerAsterisk();
+ }
+ } catch (\Throwable $e) {
+ $this->logger->writeError("Error in main loop: " . $e->getMessage());
+ sleep(2);
$this->initManagerAsterisk();
}
}
@@ -175,6 +277,10 @@ private function printActiveCalls():void
if($this->init){
return;
}
+ if(time() - $this->lastControlActiveCalls > self::CONTROL_INTERVAL){
+ $this->lastControlActiveCalls = time();
+ $this->channelAdditionalControl();
+ }
$queuesData = $this->queuesData;
foreach ($queuesData as $qId => $queueTmpData){
@@ -227,7 +333,7 @@ private function printActiveCalls():void
'spyer' => false,
'spy_num' => '',
'spy_chan' => '',
- 'queueData' => $queueCalls[$linkedid]??[],
+ 'queueData' => $this->getQueueData($queueCalls, $linkedid),
'lastQueue' => $this->callType[$linkedid]['queue']??''
];
$dstChannel = $srcChan;
@@ -240,14 +346,8 @@ private function printActiveCalls():void
$call['dst_num'] = $callData[$dstChannel]['CallerIDNum'];
// Обновляем статус агента очереди
- foreach ($queuesData as $qId => $queueTmpData) {
- if(isset($queuesData[$qId]['agents'][$call['dst_num']])){
- $queuesData[$qId]['agents'][$call['dst_num']]['state'] = self::STATE_UP;
- }
- if(isset($queuesData[$qId]['agents'][$call['src_num']])){
- $queuesData[$qId]['agents'][$call['src_num']]['state'] = self::STATE_UP;
- }
- }
+ $this->updateAgentState($queuesData, $call['dst_num'], self::STATE_UP);
+ $this->updateAgentState($queuesData, $call['src_num'], self::STATE_UP);
}else{
$bridgeChannels = [];
// Поиск вызываемых каналов.
@@ -259,13 +359,13 @@ private function printActiveCalls():void
$tmpBridgeStart = time();
$tmpChFound = $this->findBridgeChannel($linkedid,$tmpDstChannel, $tmpBridgeStart);
if(!$tmpChFound){
- // Идет дозвони.
+ // Идет дозвон.
$call['calledChannels'][] = [
'channel' => $channel,
'number' => $channelData['CallerIDNum'],
];
}elseif(!isset($bridgeChannels[$channel])){
- // Вероятная переадресация с кнсультацией. Начальный канал в ожидании.
+ // Вероятная переадресация с консультацией. Начальный канал в ожидании.
$bridgeChannels[$channel] = true;
$bridgeChannels[$tmpDstChannel] = true;
@@ -279,14 +379,8 @@ private function printActiveCalls():void
'dst_num' => $tmpDstNum
];
// Обновляем статус агента очереди
- foreach ($queuesData as $qId => $queueTmpData) {
- if(isset($queuesData[$qId]['agents'][$tmpSrcNum])){
- $queuesData[$qId]['agents'][$tmpSrcNum]['state'] = self::STATE_UP;
- }
- if(isset($queuesData[$qId]['agents'][$tmpDstNum])){
- $queuesData[$qId]['agents'][$tmpDstNum]['state'] = self::STATE_UP;
- }
- }
+ $this->updateAgentState($queuesData, $tmpSrcNum, self::STATE_UP);
+ $this->updateAgentState($queuesData, $tmpDstNum, self::STATE_UP);
}
}
@@ -308,18 +402,142 @@ private function printActiveCalls():void
$queuesData[$call['lastQueue']]['calls'][] = $call;
}
}
- $dataPrint = json_encode(['queues' => $queuesData, 'calls' => $calls], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
+
+ // Move Unavailable agents to the end of the list (keep original order for the rest).
+ foreach ($queuesData as $qId => $queueTmpData) {
+ if (empty($queuesData[$qId]['agents']) || !is_array($queuesData[$qId]['agents'])) {
+ continue;
+ }
+ $availableAgents = [];
+ $unavailableAgents = [];
+ foreach ($queuesData[$qId]['agents'] as $agentNumber => $agentData) {
+ $state = $agentData['state'] ?? '';
+ if ($state === self::STATE_UNAVAILABLE) {
+ $unavailableAgents[$agentNumber] = $agentData;
+ } else {
+ $availableAgents[$agentNumber] = $agentData;
+ }
+ }
+ $queuesData[$qId]['agents'] = $availableAgents + $unavailableAgents;
+ }
+
+ $callData = ['queues' => $queuesData, 'calls' => $calls];
+ $dataPrint = json_encode($callData, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
$newPrintHash = md5($dataPrint);
if($newPrintHash <> $this->lastPrintHash){
$this->lastPrintHash = $newPrintHash;
- CacheManager::setCacheData('getActiveChannelsV2Action', ['queues' => $queuesData, 'calls' => $calls], 80000);
+ CacheManager::setCacheData('getActiveChannelsV2Action', $callData, self::CACHE_TTL);
+ if($this->backendExists) {
+ BackendApiController::publishActiveCalls($callData);
+ }
+ }
+ unset($callData);
+
+ $enrichedStates = $this->enrichStatesWithConnections();
+ $data = ['states' => $enrichedStates];
+ $dataPrint = json_encode($data, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
+ $newPrintHash = md5($dataPrint);
+ if($newPrintHash <> $this->lastPrintUserHash){
+ $this->lastPrintUserHash = $newPrintHash;
+ CacheManager::setCacheData('getUsersStates', $data, self::CACHE_TTL);
+ if($this->backendExists){
+ BackendApiController::publishUserStates($data);
+ }
+ }
+
+ }
+
+ /**
+ * Обогащает states информацией о соединённых каналах.
+ * Создаёт копию $this->states, не модифицируя оригинал.
+ *
+ * @return array Enriched states with connection info
+ */
+ private function enrichStatesWithConnections(): array
+ {
+ $states = $this->states;
+
+ // Строим карту channel -> [linkedId, connectedChannel, connectedNumber]
+ $channelConnections = [];
+ foreach ($this->activeBridges as $linkedId => $bridges) {
+ foreach ($bridges as $bridgeChannels) {
+ $channelList = array_keys($bridgeChannels);
+ if (count($channelList) !== 2) {
+ continue;
+ }
+ $ch1 = $channelList[0];
+ $ch2 = $channelList[1];
+ $num1 = $this->activeChannels[$linkedId][$ch1]['CallerIDNum'] ?? '';
+ $num2 = $this->activeChannels[$linkedId][$ch2]['CallerIDNum'] ?? '';
+
+ $channelConnections[$ch1] = ['channel' => $ch2, 'number' => $num2];
+ $channelConnections[$ch2] = ['channel' => $ch1, 'number' => $num1];
+ }
}
+
+ // Строим карту channel -> ConnectedLineNum из activeChannels
+ $channelToConnectedLine = [];
+ foreach ($this->activeChannels as $linkedId => $channels) {
+ foreach ($channels as $channel => $data) {
+ $connectedNum = $data['ConnectedLineNum'] ?? '';
+ // Фильтруем и подобные значения
+ if (!empty($connectedNum) && strpos($connectedNum, '<') === false) {
+ $channelToConnectedLine[$channel] = $connectedNum;
+ }
+ }
+ }
+
+ // Обновляем channels в копии states
+ foreach ($states as $endpoint => &$stateData) {
+ if (empty($stateData['channels'])) {
+ continue;
+ }
+ $enrichedChannels = [];
+ foreach (array_keys($stateData['channels']) as $channel) {
+ if (isset($channelConnections[$channel])) {
+ $enrichedChannels[$channel] = $channelConnections[$channel];
+ } else {
+ // Канал не в бридже (звонит/ожидает) - используем ConnectedLineNum
+ $connectedNum = $channelToConnectedLine[$channel] ?? '';
+ $enrichedChannels[$channel] = ['channel' => '', 'number' => $connectedNum];
+ }
+ }
+ $stateData['channels'] = $enrichedChannels;
+ }
+ unset($stateData);
+
+ return $states;
+ }
+
+ /**
+ * Получение данных о времени входа в очередь.
+ * Использует queueCalls если доступно, иначе берёт из callType.
+ * @param array $queueCalls
+ * @param string $linkedid
+ * @return array
+ */
+ private function getQueueData(array $queueCalls, string $linkedid): array
+ {
+ if (!empty($queueCalls[$linkedid])) {
+ return $queueCalls[$linkedid];
+ }
+ // Fallback: использовать сохранённые данные из callType
+ $queueId = $this->callType[$linkedid]['queue'] ?? '';
+ $enterTime = $this->callType[$linkedid]['queueEnterTime'] ?? 0;
+ if (!empty($queueId) && $enterTime > 0) {
+ return [
+ 'QueueID' => $queueId,
+ 'EnterTime' => $enterTime,
+ ];
+ }
+ return [];
}
/**
* Поиск связанного канала.
* @param $linkedId
- * @param $srcChan
+ * @param $dstChannel
+ * @param $tmpBridgeStart
* @return bool
*/
private function findBridgeChannel($linkedId, &$dstChannel, &$tmpBridgeStart):bool
@@ -327,12 +545,11 @@ private function findBridgeChannel($linkedId, &$dstChannel, &$tmpBridgeStart):bo
$srcChan = $dstChannel;
$chFound = true;
- $ch = 200;
+ $ch = self::MAX_BRIDGE_ITERATIONS;
// Поиск связанного канала.
while ( ($dstChannel === $srcChan || stripos($dstChannel, 'Local/') !== false) && $chFound ) {
$ch--;
if($ch < 0){
- print_r('ERROR, while');
break;
}
$chFound = false;
@@ -370,8 +587,38 @@ function ($matches) {
);
}
+ /**
+ * Builds agent to queues index for O(1) lookup.
+ */
+ private function buildAgentIndex(): void
+ {
+ $this->agentToQueues = [];
+ foreach ($this->queuesData as $qId => $data) {
+ foreach ($data['agents'] as $agent) {
+ $this->agentToQueues[$agent][] = $qId;
+ }
+ }
+ }
+
+ /**
+ * Updates agent state in all queues where agent is a member.
+ *
+ * @param array $queuesData Reference to queues data array
+ * @param string $number Agent number
+ * @param string $state New state value
+ */
+ private function updateAgentState(array &$queuesData, string $number, string $state): void
+ {
+ foreach ($this->agentToQueues[$number] ?? [] as $qId) {
+ if (isset($queuesData[$qId]['agents'][$number])) {
+ $queuesData[$qId]['agents'][$number]['state'] = $state;
+ }
+ }
+ }
+
private function collectQueuesInfo():void
{
+ $this->logger->writeInfo('Update queues data...');
$this->queuesData = [];
$queues = CallQueues::find(['columns' => 'name,extension as number,uniqid as id']);
foreach ($queues as $queue){
@@ -382,7 +629,7 @@ private function collectQueuesInfo():void
foreach ($queuesAgents as $queuesAgent) {
$this->queuesData[$queuesAgent->queue]['agents'][] = $queuesAgent->extension;
}
-
+ $this->buildAgentIndex();
if(!$this->init){
return;
}
@@ -391,7 +638,7 @@ private function collectQueuesInfo():void
$queueMember = $queueInfo['data']['QueueMember']??[];
foreach ($queueMember as $member){
if(isset($this->mobileStates[$member['Name']])){
- $this->mobileStates[$member['Name']]['state'] = self::QUEUE_AGENT_STATES[$member['Status']]??self::STATE_UNAVAILIBLE;
+ $this->mobileStates[$member['Name']]['state'] = self::QUEUE_AGENT_STATES[$member['Status']]??self::STATE_UNAVAILABLE;
}
}
@@ -403,10 +650,8 @@ private function collectQueuesInfo():void
'Uniqueid' => $queueCall['Uniqueid'],
'Linkedid' => $linkedId
];
-
$this->callType[$linkedId]['queue'] = $queueCall['Queue'];
}
-
}
/**
@@ -421,9 +666,13 @@ private function collectActiveChannels():void
if(stripos($channel, 'local') !== false) {
continue;
}
+ // Пропускаем каналы без слеша (например, OutgoingSpoolFailed)
+ if(strpos($channel, '/') === false) {
+ continue;
+ }
$endpoint = self::getEndpointName($channel);
$context = $this->amCustom->GetVar($channel, 'CONTEXT', '', false);
- if(str_starts_with($context, 'ivr-')){
+ if(strpos($context, 'ivr-') === 0){
$extension = str_replace('ivr-', '', $context);
$inApp = true;
}else{
@@ -434,6 +683,7 @@ private function collectActiveChannels():void
$chanData = [
'ChannelStateDesc' => $this->amCustom->GetVar($channel, 'CHANNEL(state)', '', false),
'CallerIDNum' => $this->amCustom->GetVar($channel, 'CALLERID(num)','', false),
+ 'ConnectedLineNum' => $this->amCustom->GetVar($channel, 'CONNECTEDLINE(num)','', false),
'Uniqueid' => $this->amCustom->GetVar($channel, 'CHANNEL(uniqueid)','', false),
'Endpoint' => $endpoint,
'Type' => (stripos($endpoint, 'SIP-') !== false)?self::ENDPOINT_TYPE_PROVIDER:self::ENDPOINT_TYPE_PEER,
@@ -506,7 +756,7 @@ private function updateCacheState():void
public function getPjSipPeers(): array
{
$peers = [];
- $result = $this->amCustom->sendRequestTimeout('PJSIPShowEndpoints');
+ $result = $this->amCustom->sendRequestTimeout('PJSIPShowEndpoints', [], self::AMI_REQUEST_TIMEOUT);
$state_array = [
'Not in use' => self::STATE_IDLE,
'Busy' => self::STATE_UP,
@@ -515,26 +765,28 @@ public function getPjSipPeers(): array
];
$endpoints = $result['data']['EndpointList']??[];
foreach ($endpoints as $index => $peer) {
- if ($peer['ObjectName'] === 'anonymous' || !is_numeric($peer['Auths'])) {
+ if ($peer['ObjectName'] === 'anonymous') {
unset($endpoints[$index]);
continue;
- }
- if($peer['ObjectName'] === "{$peer['Auths']}-WS"){
+ }elseif (!is_numeric($peer['ObjectName'])){
continue;
}
- $peers[$peer['Auths']] = [
- 'id' => $peer['Auths'],
+ $peers[$peer['ObjectName']] = [
+ 'id' => $peer['ObjectName'],
'state' => $state_array[$peer['DeviceState']] ?? $peer['DeviceState']
];
+ unset($endpoints[$index]);
}
foreach ($endpoints as $peer) {
- if($peer['ObjectName'] !== "{$peer['Auths']}-WS"){
- continue;
- }
- $wsState = $state_array[$peer['DeviceState']];
- if($wsState === self::STATE_IDLE){
- $peers[$peer['Auths']]['state'] = $state_array[$peer['DeviceState']];
+ $dataObjectName = explode('-',$peer['ObjectName']);
+ $id = $dataObjectName[0]??'';
+ $prefix = $dataObjectName[1]??'';
+ if( is_numeric($id) && $prefix === 'WS' ){
+ $wsState = $state_array[$peer['DeviceState']];
+ if($wsState === self::STATE_IDLE){
+ $peers[$id]['state'] = $state_array[$peer['DeviceState']];
+ }
}
}
return array_values($peers);
@@ -596,9 +848,9 @@ public function getExtensions()
private function initManagerAsterisk():void
{
$amiPort = PbxSettings::getValueByKey('AMIPort');
-
- $this->amCustom = new CustomAsteriskManager(); // Оригинальный AsteriskManager работает плохо с BridgeList и BridgeInfo
- $this->amCustom->connect("127.0.0.1:$amiPort");
+ $this->amCustom = new CustomAsteriskManager();
+ // Оригинальный AsteriskManager работает плохо с BridgeList и BridgeInfo
+ $this->amCustom->connect("127.0.0.1:$amiPort", MonitorActiveCallsConf::AMI_USER, MonitorActiveCallsConf::AMI_USER);
$pingTube = $this->makePingTubeName(self::class);
$params = ['Operation' => 'Add', 'Filter' => 'UserEvent: '.$pingTube];
@@ -629,9 +881,17 @@ private function initManagerAsterisk():void
*/
public function callEvents($parameters):void
{
- if('Hangup' === $parameters['Event']){
- $linkedId = $parameters['Linkedid'];
- $channel = $parameters['Channel'];
+ $event = $parameters['Event'] ?? '';
+ if (empty($event)) {
+ return;
+ }
+
+ if('Hangup' === $event){
+ $linkedId = $parameters['Linkedid'] ?? '';
+ $channel = $parameters['Channel'] ?? '';
+ if (empty($linkedId) || empty($channel) || strpos($channel, '/') === false) {
+ return;
+ }
$endpoint = self::getEndpointName($channel);
unset($this->activeChannels[$linkedId][$channel]);
unset($this->states[$endpoint]['channels'][$channel]);
@@ -639,22 +899,32 @@ public function callEvents($parameters):void
unset($this->activeChannels[$linkedId]);
unset($this->callType[$linkedId]);
}
- }elseif(in_array($parameters['Event'],['Newchannel','Newstate']) && stripos($parameters['Channel'], 'local') === false){
- $linkedId = $parameters['Linkedid'];
- $endpoint = self::getEndpointName($parameters['Channel']);
+ }elseif(in_array($event, ['Newchannel','Newstate']) && stripos($parameters['Channel'] ?? '', 'local') === false){
+ $linkedId = $parameters['Linkedid'] ?? '';
+ $channel = $parameters['Channel'] ?? '';
+ if (empty($linkedId) || empty($channel)) {
+ return;
+ }
+ // Пропускаем каналы без слеша (например, OutgoingSpoolFailed)
+ if(strpos($channel, '/') === false) {
+ return;
+ }
+ $endpoint = self::getEndpointName($channel);
- if(str_starts_with($parameters['Context'], 'ivr-')){
- $extension = str_replace('ivr-', '', $parameters['Context']);
+ $context = $parameters['Context'] ?? '';
+ if(strpos($context, 'ivr-') === 0){
+ $extension = str_replace('ivr-', '', $context);
$inApp = true;
}else{
- $extension = $parameters['Exten'];
- $inApp = $parameters['Context'] === 'applications';
+ $extension = $parameters['Exten'] ?? '';
+ $inApp = $context === 'applications';
}
$chanData = [
- 'ChannelStateDesc' => $parameters['ChannelStateDesc'],
- 'CallerIDNum' => $parameters['CallerIDNum'],
- 'Uniqueid' => $parameters['Uniqueid'],
+ 'ChannelStateDesc' => $parameters['ChannelStateDesc'] ?? '',
+ 'CallerIDNum' => $parameters['CallerIDNum'] ?? '',
+ 'ConnectedLineNum' => $parameters['ConnectedLineNum'] ?? '',
+ 'Uniqueid' => $parameters['Uniqueid'] ?? '',
'Endpoint' => $endpoint,
'Type' => (stripos($endpoint, 'SIP-') !== false)?self::ENDPOINT_TYPE_PROVIDER:self::ENDPOINT_TYPE_PEER,
'Exten' => $extension,
@@ -662,7 +932,7 @@ public function callEvents($parameters):void
];
if($chanData['Type'] === self::ENDPOINT_TYPE_PEER){
- $this->states[$endpoint]['channels'][$parameters['Channel']] = true;
+ $this->states[$endpoint]['channels'][$channel] = true;
if($this->states[$endpoint]['state'] <> self::STATE_UP){
$this->states[$endpoint]['state'] = $chanData['ChannelStateDesc'];
}
@@ -671,66 +941,96 @@ public function callEvents($parameters):void
$did = '';
if($chanData['Type'] === self::ENDPOINT_TYPE_PROVIDER){
$callType = self::CALL_TYPE_IN;
- $did = $parameters['Exten'];
- }elseif ($chanData['Type'] === self::ENDPOINT_TYPE_PEER && strlen($parameters['Exten']) < 5){
+ $did = $extension;
+ }elseif ($chanData['Type'] === self::ENDPOINT_TYPE_PEER && strlen($extension) < 5){
$callType = self::CALL_TYPE_INNER;
}else{
$callType = self::CALL_TYPE_OUT;
}
$this->callType[$linkedId] = [
'type' => $callType,
- 'src_chan' => $parameters['Channel'],
+ 'src_chan' => $channel,
'did' => $did,
'time' => str_replace('mikopbx-','',$chanData['Uniqueid'])
];
}
- if($this->callType[$linkedId]['src_chan'] <> $parameters['Channel'] && $chanData['ChannelStateDesc'] === self::STATE_UP){
+ if(($this->callType[$linkedId]['src_chan'] ?? '') !== $channel && $chanData['ChannelStateDesc'] === self::STATE_UP){
// Обновляем время ответа на вызов.
- $this->callType[$linkedId]['answer'] = $parameters['Timestamp'];
- }
- $this->activeChannels[$linkedId][$parameters['Channel']] = $chanData;
- }elseif ('NewCallerid' === $parameters['Event'] && str_starts_with($parameters['Channel'], 'PJSIP/') &&
- isset($this->activeChannels[$parameters['Linkedid']][$parameters['Channel']]) ){
- $this->activeChannels[$parameters['Linkedid']][$parameters['Channel']]['CallerIDNum'] = $parameters['CallerIDNum'];
- }elseif ('BridgeEnter' === $parameters['Event']){
- $linkedId = $parameters['Linkedid'];
- $this->activeBridges[$linkedId][$parameters['BridgeUniqueid']][$parameters['Channel']] = $parameters['Timestamp'];
- }elseif ('ChanSpyStart' === $parameters['Event']){
-
- if(stripos($parameters['SpyerChannel'], 'local') !==false){
- $linkedId = $parameters['SpyerLinkedid'];
+ $this->callType[$linkedId]['answer'] = $parameters['Timestamp'] ?? time();
+ }
+ $this->activeChannels[$linkedId][$channel] = $chanData;
+ }elseif ('NewCallerid' === $event){
+ $ncChannel = $parameters['Channel'] ?? '';
+ $ncLinkedId = $parameters['Linkedid'] ?? '';
+ if (!empty($ncChannel) && strpos($ncChannel, 'PJSIP/') === 0 &&
+ isset($this->activeChannels[$ncLinkedId][$ncChannel])) {
+ $this->activeChannels[$ncLinkedId][$ncChannel]['CallerIDNum'] = $parameters['CallerIDNum'] ?? '';
+ }
+ }elseif ('NewConnectedLine' === $event){
+ $nclChannel = $parameters['Channel'] ?? '';
+ $nclLinkedId = $parameters['Linkedid'] ?? '';
+ $connectedNum = $parameters['ConnectedLineNum'] ?? '';
+ if (!empty($nclChannel) && !empty($nclLinkedId) &&
+ isset($this->activeChannels[$nclLinkedId][$nclChannel]) &&
+ !empty($connectedNum) && strpos($connectedNum, '<') === false) {
+ $this->activeChannels[$nclLinkedId][$nclChannel]['ConnectedLineNum'] = $connectedNum;
+ }
+ }elseif ('BridgeEnter' === $event){
+ $linkedId = $parameters['Linkedid'] ?? '';
+ $bridgeUniqueid = $parameters['BridgeUniqueid'] ?? '';
+ $beChannel = $parameters['Channel'] ?? '';
+ if (!empty($linkedId) && !empty($bridgeUniqueid) && !empty($beChannel)) {
+ $this->activeBridges[$linkedId][$bridgeUniqueid][$beChannel] = $parameters['Timestamp'] ?? time();
+ }
+ }elseif ('ChanSpyStart' === $event){
+ $spyerChannel = $parameters['SpyerChannel'] ?? '';
+ $spyerLinkedId = $parameters['SpyerLinkedid'] ?? '';
+ $spyeeLinkedId = $parameters['SpyeeLinkedid'] ?? '';
+ if (empty($spyerLinkedId) || empty($spyeeLinkedId)) {
+ return;
+ }
+
+ if(stripos($spyerChannel, 'local') !== false){
+ $linkedId = $spyerLinkedId;
$tmpBridgeStart = time();
- $tmpDstChannel = $this->swapLocalSuffix($parameters['SpyerChannel']);
- $orgChan = $this->findBridgeChannel($linkedId,$tmpDstChannel, $tmpBridgeStart)?$tmpDstChannel:'';
+ $tmpDstChannel = $this->swapLocalSuffix($spyerChannel);
+ $orgChan = $this->findBridgeChannel($linkedId, $tmpDstChannel, $tmpBridgeStart) ? $tmpDstChannel : '';
}else{
- $orgChan = $parameters['SpyerChannel'];
+ $orgChan = $spyerChannel;
}
- $this->spyerChannels[$parameters['SpyerLinkedid']] = [
+ $this->spyerChannels[$spyerLinkedId] = [
'spyer' => true,
'src_chan' => $orgChan,
- 'src_num' => $parameters['SpyerCallerIDNum'],
- 'dst_chan' => $parameters['SpyeeChannel'],
- 'dst_num' => $parameters['SpyeeCallerIDNum'],
+ 'src_num' => $parameters['SpyerCallerIDNum'] ?? '',
+ 'dst_chan' => $parameters['SpyeeChannel'] ?? '',
+ 'dst_num' => $parameters['SpyeeCallerIDNum'] ?? '',
];
- $this->spyerChannels[$parameters['SpyeeLinkedid']] = [
+ $this->spyerChannels[$spyeeLinkedId] = [
'spyer' => false,
'src_chan' => $orgChan,
- 'src_num' => $parameters['SpyerCallerIDNum'],
- 'dst_chan' => $parameters['SpyeeChannel'],
- 'dst_num' => $parameters['SpyeeCallerIDNum'],
+ 'src_num' => $parameters['SpyerCallerIDNum'] ?? '',
+ 'dst_chan' => $parameters['SpyeeChannel'] ?? '',
+ 'dst_num' => $parameters['SpyeeCallerIDNum'] ?? '',
];
- }elseif ('ChanSpyStop' === $parameters['Event']){
+ }elseif ('ChanSpyStop' === $event){
+ $spyeeLinkedId = $parameters['SpyeeLinkedid'] ?? '';
+ $spyerLinkedId = $parameters['SpyerLinkedid'] ?? '';
unset(
- $this->spyerChannels[$parameters['SpyeeLinkedid']],
- $this->spyerChannels[$parameters['SpyerLinkedid']]
+ $this->spyerChannels[$spyeeLinkedId],
+ $this->spyerChannels[$spyerLinkedId]
);
- }elseif ('BridgeLeave' === $parameters['Event']){
- $linkedId = $parameters['Linkedid'];
- unset($this->activeBridges[$linkedId][$parameters['BridgeUniqueid']][$parameters['Channel']]);
- if(empty($this->activeBridges[$linkedId][$parameters['BridgeUniqueid']])){
- unset($this->activeBridges[$linkedId][$parameters['BridgeUniqueid']]);
+ }elseif ('BridgeLeave' === $event){
+ $linkedId = $parameters['Linkedid'] ?? '';
+ $bridgeUniqueid = $parameters['BridgeUniqueid'] ?? '';
+ $blChannel = $parameters['Channel'] ?? '';
+ if (empty($linkedId) || empty($bridgeUniqueid) || empty($blChannel)) {
+ return;
+ }
+ unset($this->activeBridges[$linkedId][$bridgeUniqueid][$blChannel]);
+ if(empty($this->activeBridges[$linkedId][$bridgeUniqueid])){
+ unset($this->activeBridges[$linkedId][$bridgeUniqueid]);
}
if(empty($this->activeBridges[$linkedId])){
unset($this->activeBridges[$linkedId]);
@@ -757,20 +1057,42 @@ public static function getEndpointName(string $channel):string
*/
public function queueEvents($parameters):void
{
- if('QueueCallerJoin' === $parameters['Event']){
- $this->queueEntryes[$parameters['Queue']][$parameters['Channel']] = [
- 'EnterTime' => time(),
- 'Uniqueid' => $parameters['Uniqueid'],
- 'Linkedid' => $parameters['Linkedid']
- ];
+ $event = $parameters['Event'] ?? '';
+ if (empty($event)) {
+ return;
+ }
- $this->callType[$parameters['Linkedid']]['queue'] = $parameters['Queue'];
- }elseif ('QueueMemberStatus' === $parameters['Event'] && isset($this->mobileStates[$parameters['MemberName']])){
- $this->mobileStates[$parameters['MemberName']]['state'] = self::QUEUE_AGENT_STATES[$parameters['Status']]??self::STATE_UNAVAILIBLE;
- }elseif ('QueueCallerLeave' === $parameters['Event'] ){
- unset($this->queueEntryes[$parameters['Queue']][$parameters['Channel']]);
- if(empty($this->queueEntryes[$parameters['Queue']])){
- unset($this->queueEntryes[$parameters['Queue']]);
+ if('QueueCallerJoin' === $event){
+ $queue = $parameters['Queue'] ?? '';
+ $channel = $parameters['Channel'] ?? '';
+ $linkedId = $parameters['Linkedid'] ?? '';
+ if (empty($queue) || empty($channel)) {
+ return;
+ }
+ $enterTime = time();
+ $this->queueEntryes[$queue][$channel] = [
+ 'EnterTime' => $enterTime,
+ 'Uniqueid' => $parameters['Uniqueid'] ?? '',
+ 'Linkedid' => $linkedId
+ ];
+ if (!empty($linkedId)) {
+ $this->callType[$linkedId]['queue'] = $queue;
+ $this->callType[$linkedId]['queueEnterTime'] = $enterTime;
+ }
+ }elseif ('QueueMemberStatus' === $event){
+ $memberName = $parameters['MemberName'] ?? '';
+ if (!empty($memberName) && isset($this->mobileStates[$memberName])){
+ $this->mobileStates[$memberName]['state'] = self::QUEUE_AGENT_STATES[$parameters['Status'] ?? ''] ?? self::STATE_UNAVAILABLE;
+ }
+ }elseif ('QueueCallerLeave' === $event){
+ $queue = $parameters['Queue'] ?? '';
+ $channel = $parameters['Channel'] ?? '';
+ if (empty($queue) || empty($channel)) {
+ return;
+ }
+ unset($this->queueEntryes[$queue][$channel]);
+ if(empty($this->queueEntryes[$queue])){
+ unset($this->queueEntryes[$queue]);
}
}else{
return;
@@ -787,15 +1109,25 @@ public function queueEvents($parameters):void
*/
public function stateEvents($parameters):void
{
- if ($parameters['Event'] === 'UserEvent' && $this->replyOnPingRequest($parameters)){
+ $event = $parameters['Event'] ?? '';
+ if (empty($event)) {
+ return;
+ }
+
+ if ($event === 'UserEvent' && $this->replyOnPingRequest($parameters)){
$this->logger->writeInfo($parameters,'update settings...');
$this->getExtensionsInfo();
$this->collectQueuesInfo();
+ $this->backendExists = MonitorActiveCallsMain::backendExists();
+ // Force periodic refresh/cleanup. Without this, stale calls may persist
+ // forever if we missed a Hangup event for any reason.
+ $this->printActiveCalls();
return;
}
- if($parameters['Event'] === 'ExtensionStatus'){
- if(isset($this->states[$parameters['Exten']])){
- $this->states[$parameters['Exten']]['state'] = $parameters['StatusText'];
+ if($event === 'ExtensionStatus'){
+ $exten = $parameters['Exten'] ?? '';
+ if(!empty($exten) && isset($this->states[$exten])){
+ $this->states[$exten]['state'] = $parameters['StatusText'] ?? '';
$this->logger->writeInfo($parameters,'stateEvents...');
$this->printActiveCalls();
$this->updateCacheState();
diff --git a/bin/WorkerAmiActions.php b/bin/WorkerAmiActions.php
index ec49d49..8eb87bc 100644
--- a/bin/WorkerAmiActions.php
+++ b/bin/WorkerAmiActions.php
@@ -20,19 +20,26 @@
namespace Modules\ModuleMonitorActiveCalls\bin;
require_once 'Globals.php';
+use MikoPBX\Common\Models\PbxSettings;
use MikoPBX\Core\Asterisk\AsteriskManager;
use MikoPBX\Core\System\BeanstalkClient;
use MikoPBX\Core\System\SystemMessages;
use MikoPBX\Core\System\Util;
use MikoPBX\Core\Workers\WorkerBase;
use MikoPBX\PBXCoreREST\Lib\PBXApiResult;
+use Modules\ModuleMonitorActiveCalls\Lib\AsteriskManager as CustomAsteriskManager;
+use Modules\ModuleMonitorActiveCalls\Lib\Logger;
use Modules\ModuleMonitorActiveCalls\Lib\MikoPBXVersion;
+use Modules\ModuleMonitorActiveCalls\Lib\MonitorActiveCallsConf;
class WorkerAmiActions extends WorkerBase
{
+ public Logger $logger;
+
public int $countReq = 0;
public float $counterStartTime = 0;
- protected AsteriskManager $am;
+ protected CustomAsteriskManager $amCustom;
+
/**
* Handles the received signal.
@@ -45,6 +52,25 @@ public function signalHandler(int $signal): void
{
parent::signalHandler($signal);
cli_set_process_title('SHUTDOWN_'.cli_get_process_title());
+ $this->needRestart = true;
+ $this->logger->writeInfo('signalHandler...'.$signal);
+ }
+
+
+ /**
+ * Подключение к AMI.
+ * @param string $events
+ * @return CustomAsteriskManager
+ */
+ public function getAstManager(string $events = 'on'):CustomAsteriskManager
+ {
+ $am = new CustomAsteriskManager();
+ $port = PbxSettings::getValueByKey('AMIPort');
+ $result = $am->connect("127.0.0.1:$port", MonitorActiveCallsConf::AMI_USER, MonitorActiveCallsConf::AMI_USER, $events);
+ if(!$result){
+ $this->logger->writeError('Fail connect AMI...');
+ }
+ return $am;
}
/**
@@ -54,13 +80,16 @@ public function signalHandler(int $signal): void
*/
public function start($argv):void
{
- $this->am = Util::getAstManager();
+ $this->logger = new Logger('AmiActions', 'ModuleMonitorActiveCalls');
+ $this->logger->writeInfo('Starting...');
+ $this->amCustom = $this->getAstManager();
$beanstalk = new BeanstalkClient(self::class);
$beanstalk->subscribe(self::class, [$this, 'onEvents']);
$beanstalk->subscribe($this->makePingTubeName(self::class), [$this, 'pingCallBack']);
while ($this->needRestart === false) {
$beanstalk->wait();
}
+ $this->amCustom->disconnect();
}
/**
@@ -75,6 +104,7 @@ public function onEvents($tube): void
}catch (\Throwable $e){
return;
}
+ $this->logger->writeInfo($data, 'Events...');
$res_data = '';
$funcName = $data['function']??'';
if(method_exists($this, $funcName)){
@@ -83,8 +113,10 @@ public function onEvents($tube): void
}else{
$res_data = $this->$funcName(...$data['args']??[]);
}
- $res_data = serialize($res_data);
- $res_data = $this->saveResultInTmpFile($res_data);
+ $this->logger->writeInfo($res_data, 'Result...');
+ $res_data = $this->saveResultInTmpFile(serialize($res_data));
+ }else{
+ $this->logger->writeError('method not exists...');
}
$tube->reply($res_data);
}
@@ -98,6 +130,9 @@ public function onEvents($tube): void
*/
public function restAPICallback(array $request): PBXApiResult
{
+ $this->amCustom->disconnect();
+ $this->amCustom = $this->getAstManager();
+
$res = new PBXApiResult();
$res->processor = __METHOD__;
$action = strtolower($request['action']);
@@ -115,31 +150,24 @@ public function restAPICallback(array $request): PBXApiResult
if('join' === $action) {
$res->success = true;
- $am = Util::getAstManager('off');
$variable = "pt1c_cid=SPY-{$request['data']['number']},ALLOW_MULTY_ANSWER=1";
$channel = "Local/{$request['data']['number']}@internal-originate";
- $am->Originate($channel, null, null, null, 'ChanSpy', $actionChannel.',qBS', null, $request['data']['number'], $variable);
-
+ $this->amCustom->Originate($channel, null, null, null, 'ChanSpy', $actionChannel.',qBS', null, $request['data']['number'], $variable);
SystemMessages::sysLogMsg('SPY-ACTIVE-CHAN', "$action: {$request['data']['number']} to $actionChannel. mode 'qBS'");
}elseif ('whisper' === $action){
$res->success = true;
- $am = Util::getAstManager('off');
$variable = "pt1c_cid=SPY-{$request['data']['number']},ALLOW_MULTY_ANSWER=1";
$channel = "Local/{$request['data']['number']}@internal-originate";
- $am->Originate($channel, null, null, null, 'ChanSpy', $actionChannel.',qwS', null, $request['data']['number'], $variable);
-
+ $this->amCustom->Originate($channel, null, null, null, 'ChanSpy', $actionChannel.',qwS', null, $request['data']['number'], $variable);
SystemMessages::sysLogMsg('SPY-ACTIVE-CHAN', "$action: {$request['data']['number']} to $actionChannel. mode 'qw'");
}elseif ('listen' === $action){
$res->success = true;
- $am = Util::getAstManager('off');
$variable = "pt1c_cid=SPY-{$request['data']['number']},ALLOW_MULTY_ANSWER=1";
$channel = "Local/{$request['data']['number']}@internal-originate";
- $am->Originate($channel, null, null, null, 'ChanSpy', $actionChannel.',qS', null, $request['data']['number'], $variable);
-
+ $this->amCustom->Originate($channel, null, null, null, 'ChanSpy', $actionChannel.',qS', null, $request['data']['number'], $variable);
SystemMessages::sysLogMsg('SPY-ACTIVE-CHAN', "$action: {$request['data']['number']} to $actionChannel. mode 'qoS'");
}elseif ('hangup' === $action){
- $am = Util::getAstManager('off');
- $am->Hangup($request['data']['ch1']??'');
+ $this->amCustom->Hangup($request['data']['ch1']??'');
}else{
$res->success = false;
$res->messages[] = 'API action not found in moduleRestAPICallback ModuleMonitorActiveCalls '.$action;
@@ -147,6 +175,13 @@ public function restAPICallback(array $request): PBXApiResult
return $res;
}
+ public function getChannels()
+ {
+ $this->amCustom->disconnect();
+ $this->amCustom = $this->getAstManager();
+ return $this->amCustom->getChannels();
+ }
+
/**
* Сериализует данные и сохраняет их во временный файл.
* @param $data
@@ -195,9 +230,10 @@ private function saveResultInTmpFile($data):string
* Метод следует вызывать при работе с API из прочих процессов.
* @param $function
* @param $args
+ * @param int $timeout
* @return mixed|PBXApiResult
*/
- public static function invokeApi($function, $args)
+ public static function invokeApi($function, $args, int $timeout = 20)
{
$req = [
'function' => $function,
@@ -205,7 +241,7 @@ public static function invokeApi($function, $args)
];
$client = new BeanstalkClient(self::class);
try {
- $result = $client->request(json_encode($req, JSON_THROW_ON_ERROR), 20);
+ $result = $client->request(json_encode($req, JSON_THROW_ON_ERROR), $timeout);
if(file_exists($result)){
$filename = $result;
$result = json_decode(file_get_contents($result), true, 512, JSON_THROW_ON_ERROR);
diff --git a/public/assets/css/module-monitor-active-calls.css b/public/assets/css/module-monitor-active-calls.css
index 2c068e5..768601f 100644
--- a/public/assets/css/module-monitor-active-calls.css
+++ b/public/assets/css/module-monitor-active-calls.css
@@ -12,4 +12,67 @@
.ui.table tbody tr.row-dialing > td { background-color: rgba(246, 229, 251, 0.65) !important; }
.ui.table tbody tr.row-offline > td { background-color:#f5f5f5 !important; color:rgba(0,0,0,.45); }
.ui.table tbody tr.row-pause > td { background-color: #fff3e0 !important; color: rgba(0, 0, 0, 0.6) !important;
-}
\ No newline at end of file
+}
+
+/* Agents tiles (cards) */
+.ui.cards.agent-cards {
+ margin-top: 0 !important;
+}
+.ui.cards.agent-cards > .ui.card.agent-card {
+ width: 240px;
+ border: 1px solid rgba(34, 36, 38, 0.15);
+ box-shadow: none;
+ margin: 0.5em !important;
+}
+.ui.cards.agent-cards > .ui.card.agent-card > .content {
+ padding: 0.6em 0.7em;
+}
+.ui.card.agent-card .agent-card-header {
+ display: flex;
+ align-items: center;
+ gap: 0.5em;
+ white-space: nowrap;
+}
+.ui.card.agent-card .content > .header.agent-card-header {
+ font-size: inherit !important;
+ font-weight: 600;
+ line-height: 1.2;
+}
+.ui.card.agent-card .content > .meta.agent-peer {
+ font-size: inherit !important;
+}
+.ui.card.agent-card .agent-name {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.ui.basic.label.agent-num-label {
+ border: none !important;
+ box-shadow: none !important;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ font-variant-numeric: tabular-nums;
+ padding: 0.15em 0.45em !important;
+}
+.ui.card.agent-card .agent-peer {
+ margin-top: 0.35em;
+ line-height: 1.2;
+}
+.ui.card.agent-card .peer-line {
+ display: flex;
+ align-items: center;
+ gap: 0.45em;
+ white-space: nowrap;
+}
+.ui.card.agent-card .agent-phone {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ font-variant-numeric: tabular-nums;
+}
+.ui.card.agent-card .peer-name {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* Status highlight for cards (same palette as table rows) */
+.ui.card.agent-card.row-available { background-color:#e8f5e9 !important; border-color: rgba(33, 186, 69, 0.35) !important; }
+.ui.card.agent-card.row-in-call { background-color:#e8f0fe !important; border-color: rgba(33, 133, 208, 0.35) !important; }
+.ui.card.agent-card.row-dialing { background-color: rgba(246, 229, 251, 0.65) !important; border-color: rgba(224, 57, 151, 0.25) !important; }
+.ui.card.agent-card.row-offline { background-color:#f5f5f5 !important; border-color: rgba(0,0,0,0.12) !important; color:rgba(0,0,0,.55); }
\ No newline at end of file
diff --git a/public/assets/js/module-monitor-active-calls-index.js b/public/assets/js/module-monitor-active-calls-index.js
index 979d9e9..4cd8e45 100644
--- a/public/assets/js/module-monitor-active-calls-index.js
+++ b/public/assets/js/module-monitor-active-calls-index.js
@@ -1,16 +1,26 @@
"use strict";
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
-function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
-function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
-function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
+function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
+function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
+function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
+function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
-function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
+function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
+function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
+function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
+function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
+function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
+function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
+function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
+function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
-function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) { n[e] = r[e]; } return n; }
+function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
+function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
+function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
/*
* Copyright (C) MIKO LLC - All Rights Reserved
* Unauthorized copying of this file, via any medium is strictly prohibited
@@ -26,12 +36,14 @@ var inputClassName = 'mikopbx-module-input';
/* global $, globalRootUrl, globalTranslate, Form, Config, Vue, Extensions */
var ModuleMonitorActiveCalls = {
isInit: true,
- queueNameSelector: '#app-queue div.scrolling.dropdown',
+ contactsCacheTtlMs: 120 * 60 * 1000,
+ queuesFilterSelector: '#queuesFilter',
$formObj: $('#' + idForm),
$checkBoxes: $('#' + idForm + ' .ui.checkbox'),
$dropDowns: $('#' + idForm + ' .ui.dropdown'),
activeChannelsUrl: globalRootUrl + idUrl + "/getActiveChannels",
activeChannelsUrlV2: globalRootUrl + idUrl + "/getActiveChannelsV2",
+ backendEnableUrl: globalRootUrl + idUrl + "/backandEnable",
executeCallUrl: globalRootUrl + idUrl + "/executeCall",
saveUserActionUrl: globalRootUrl + idUrl + "/saveUser",
$widget: undefined,
@@ -44,50 +56,426 @@ var ModuleMonitorActiveCalls = {
* On page load we init some Semantic UI library
*/
initialize: function initialize() {
+ this.initContactsCache();
+ this.requestBackendEnable();
$("#nowUser.dropdown.enable").dropdown({
onChange: function onChange(value, text, $choice) {
window[className].onChangeSetting('adminUserId', value);
}
});
+ $("#minWaitVisible.dropdown.enable").dropdown({
+ onChange: function onChange(value, text, $choice) {
+ $('#minWaitVisibleValue').val(value);
+ window[className].onChangeSetting('minWaitVisible', value);
+ }
+ });
var userNumber = $('#userNumber').val();
window[className].$widgetQueues = new Vue({
el: '#app-queue',
delimiters: ["<%", "%>"],
methods: {
updatedCallsFromResponse: function updatedCallsFromResponse(data) {
- var queueNameEl = $(window[className].queueNameSelector);
- this.queues = data.queues;
- var queueId = $('#queueId').val();
- if (queueId in data.queues) {
- this.id = data.queues[queueId].id;
- this.name = data.queues[queueId].name;
- this.number = data.queues[queueId].number;
- this.agents = data.queues[queueId].agents;
- this.calls = Array.isArray(data.queues[queueId].calls) ? data.queues[queueId].calls : [];
- this.allCalls = data.calls;
- } else {
- this.calls = [];
+ // Keep last payload to allow re-render on queue switch (WS mode).
+ this.lastActiveCallsPayload = data;
+ this.minWaitVisible = 1 * $('#minWaitVisibleValue').val();
+ this.queues = data.queues || {};
+ this.allCalls = data.calls || [];
+
+ // Initialize multi-select dropdown if not yet done
+ this.initQueuesFilter();
+
+ // Normalize Semantic UI Card typography after render
+ this.$nextTick(function () {
+ this.normalizeAgentCards();
+ });
+ },
+ initQueuesFilter: function initQueuesFilter() {
+ var self = this;
+ var $filter = $(window[className].queuesFilterSelector);
+ if ($filter.length === 0) return;
+
+ // Wait for Vue to render menu items
+ this.$nextTick(function () {
+ // Reinitialize dropdown to pick up new menu items
+ if ($filter.data('initialized')) {
+ // Dropdown already exists, just refresh menu
+ // Save current selection before refresh to prevent reset
+ var currentSelection = self.selectedQueueIds ? self.selectedQueueIds.slice() : [];
+ $filter.data('refreshing', true);
+ $filter.dropdown('refresh');
+ $filter.data('refreshing', false);
+
+ // Restore selection after refresh if it was cleared
+ if (currentSelection.length > 0 && (!self.selectedQueueIds || self.selectedQueueIds.length === 0)) {
+ self.selectedQueueIds = currentSelection;
+ $filter.dropdown('set exactly', currentSelection);
+ }
+
+ // After refresh, ensure default text is hidden if we have selections
+ if (self.selectedQueueIds && self.selectedQueueIds.length > 0) {
+ $filter.find('.default.text').hide();
+ } else {
+ $filter.find('.default.text').show();
+ }
+ } else {
+ // First time initialization
+ $filter.data('initialized', true);
+ $filter.dropdown({
+ fullTextSearch: true,
+ onChange: function onChange(value) {
+ // Skip onChange during programmatic refresh
+ if ($filter.data('refreshing')) {
+ return;
+ }
+ // value is comma-separated string of selected queue IDs
+ var selectedIds = value ? value.split(',').filter(function (v) {
+ return v !== '';
+ }) : [];
+ self.selectedQueueIds = selectedIds;
+ // Auto-save on change
+ window[className].onChangeSetting('queueIds', JSON.stringify(selectedIds));
+ }
+ });
+
+ // Set initial values from hidden input
+ var savedQueueIds = [];
+ try {
+ var raw = $('#queueIds').val();
+ savedQueueIds = JSON.parse(raw || '[]');
+ } catch (e) {
+ savedQueueIds = [];
+ }
+ if (Array.isArray(savedQueueIds) && savedQueueIds.length > 0) {
+ window[className].isInit = true;
+ $filter.dropdown('set exactly', savedQueueIds);
+ self.selectedQueueIds = savedQueueIds;
+ window[className].isInit = false;
+ // Hide default text when values are selected
+ $filter.find('.default.text').hide();
+ }
+ }
+ });
+ },
+ refreshFromLastPayload: function refreshFromLastPayload() {
+ if (this.lastActiveCallsPayload) {
+ this.updatedCallsFromResponse(this.lastActiveCallsPayload);
}
- if (queueNameEl.dropdown('is hidden')) {
- queueNameEl.dropdown({
- onChange: function onChange(value, text, $choice) {
- window[className].onChangeSetting('queueId', value);
+ },
+ getQueueCalls: function getQueueCalls(queueId) {
+ var queue = this.queues[queueId];
+ if (!queue) return [];
+ return Array.isArray(queue.calls) ? queue.calls : [];
+ },
+ getQueueAgentsList: function getQueueAgentsList(queueId) {
+ var queue = this.queues[queueId];
+ if (!queue || !queue.agents) return [];
+ return this.buildAgentsList(queue.agents);
+ },
+ hasWaitingCalls: function hasWaitingCalls(queueId) {
+ var calls = this.getQueueCalls(queueId);
+ var self = this;
+ for (var i = 0; i < calls.length; i++) {
+ var call = calls[i];
+ if (call.dst_chan === '' && call.queueData && call.queueData.EnterTime !== undefined) {
+ var elapsed = self.formatElapsedTime(call.queueData.EnterTime);
+ if (self.minWaitVisible <= elapsed) {
+ return true;
}
- });
- if (queueNameEl.dropdown('get value') === '') {
- window[className].isInit = true;
- queueNameEl.dropdown('set value', $('#queueId').val());
- window[className].isInit = false;
}
}
+ return false;
+ },
+ buildAgentsList: function buildAgentsList(agentsObj) {
+ var entries = Object.entries(agentsObj || {});
+ var available = [];
+ var unavailable = [];
+ for (var _i = 0, _entries = entries; _i < _entries.length; _i++) {
+ var _entries$_i = _slicedToArray(_entries[_i], 2),
+ number = _entries$_i[0],
+ agent = _entries$_i[1];
+ var state = (agent === null || agent === void 0 ? void 0 : agent.state) || '';
+ var item = _objectSpread({
+ number: number
+ }, agent);
+ if (state === 'Unavailable') {
+ unavailable.push(item);
+ } else {
+ available.push(item);
+ }
+ }
+ return available.concat(unavailable);
+ },
+ normalizePhone10: function normalizePhone10(phone) {
+ var digits = String(phone || '').replace(/\D+/g, '');
+ if (digits.length <= 10) return digits;
+ return digits.slice(-10);
+ },
+ updateContactFromWs: function updateContactFromWs(contact) {
+ var phone10 = this.normalizePhone10(contact === null || contact === void 0 ? void 0 : contact.number);
+ if (!phone10) return;
+ var displayName = String((contact === null || contact === void 0 ? void 0 : contact.client) || (contact === null || contact === void 0 ? void 0 : contact.contact) || '').trim();
+ if (!displayName) return;
+ // Vue2: ensure reactivity for new keys
+ if (this.$set) {
+ this.$set(this.contactsByPhone10, phone10, displayName);
+ } else {
+ this.contactsByPhone10[phone10] = displayName;
+ }
+ },
+ getClientNameByPhone: function getClientNameByPhone(phone) {
+ var phone10 = this.normalizePhone10(phone);
+ return this.contactsByPhone10[phone10] || '';
+ },
+ getClientHeader: function getClientHeader(phone) {
+ var client = this.getClientNameByPhone(phone);
+ if (!client) return phone;
+ return "".concat(client, " <").concat(phone, ">");
+ },
+ hasClientByPhone: function hasClientByPhone(phone) {
+ return !!this.getClientNameByPhone(phone);
},
formatElapsedTime: function formatElapsedTime(enterTime) {
+ // Make this method reactive to the UI ticker.
+ void this.nowTick;
return window[className].formatElapsedTime(enterTime);
},
+ normalizeAgentCards: function normalizeAgentCards() {
+ if (!this.$el) return;
+ var self = this;
+
+ // Cleanup artifacts from previous experiments (placeholders/spacers).
+ var artifacts = this.$el.querySelectorAll('.agent-peer-placeholder, .agent-peer-spacer');
+ artifacts.forEach(function (el) {
+ el.remove();
+ });
+
+ // Dense layout (masonry) that still fills left-to-right:
+ // flex-wrap can't place items into vertical gaps under tall cards.
+ this.ensureAgentCardsGridMasonry();
+
+ // Process all agent card containers (one per queue block)
+ var cardsContainers = this.$el.querySelectorAll('.ui.cards.agent-cards');
+ cardsContainers.forEach(function (cardsContainer) {
+ cardsContainer.style.alignItems = 'flex-start';
+ cardsContainer.style.alignContent = 'flex-start';
+ });
+ var cards = this.$el.querySelectorAll('.ui.cards.agent-cards > .ui.card.agent-card');
+ cards.forEach(function (card) {
+ card.style.alignSelf = 'flex-start';
+ });
+
+ // Semantic UI makes .header bigger than normal text; we need same font size.
+ var headers = this.$el.querySelectorAll('.ui.card.agent-card .header.agent-card-header');
+ headers.forEach(function (el) {
+ el.style.fontSize = '1em';
+ el.style.lineHeight = '1.2';
+ el.style.display = 'flex';
+ el.style.alignItems = 'center';
+ el.style.gap = '0.5em';
+ el.style.whiteSpace = 'nowrap';
+ });
+ var metas = this.$el.querySelectorAll('.ui.card.agent-card .meta.agent-peer');
+ metas.forEach(function (el) {
+ el.style.fontSize = '1em';
+ el.style.lineHeight = '1.2';
+ });
+
+ // Normalize label/name typography so they have same text height.
+ var numLabels = this.$el.querySelectorAll('.ui.card.agent-card .agent-num-label');
+ numLabels.forEach(function (el) {
+ el.style.fontSize = '1em';
+ el.style.lineHeight = '1.2';
+ el.style.display = 'inline-flex';
+ el.style.alignItems = 'center';
+ el.style.paddingTop = '0';
+ el.style.paddingBottom = '0';
+ // Allow label to shrink (otherwise long numbers force card wider than 180px)
+ el.style.flex = '0 1 auto';
+ el.style.minWidth = '0';
+ el.style.maxWidth = '14ch';
+ el.style.overflow = 'hidden';
+ el.style.textOverflow = 'ellipsis';
+ el.style.whiteSpace = 'nowrap';
+ });
+ var names = this.$el.querySelectorAll('.ui.card.agent-card .agent-name');
+ names.forEach(function (el) {
+ el.style.lineHeight = '1.2';
+ // Ellipsis for long names (e.g. "Салтыков-Щедрин")
+ el.style.minWidth = '0';
+ el.style.flex = '1 1 auto';
+ el.style.overflow = 'hidden';
+ el.style.textOverflow = 'ellipsis';
+ el.style.whiteSpace = 'nowrap';
+ });
+
+ // Grid masonry needs row-span calculation after layout.
+ requestAnimationFrame(function () {
+ requestAnimationFrame(function () {
+ self.layoutAgentCardsGridMasonry();
+ });
+ });
+ },
+ adjustAgentCardsGap: function adjustAgentCardsGap() {
+ if (!this.$el) return;
+ var container = this.$el.querySelector('.ui.cards.agent-cards');
+ if (!container) return;
+ var cards = Array.from(container.querySelectorAll('.ui.card.agent-card'));
+ if (!cards.length) return;
+ var tallCard = cards.find(function (c) {
+ return c.querySelector('.meta.agent-peer');
+ });
+ var shortCard = cards.find(function (c) {
+ return !c.querySelector('.meta.agent-peer');
+ });
+ if (!tallCard || !shortCard) return;
+ var ht = tallCard.getBoundingClientRect().height;
+ var hs = shortCard.getBoundingClientRect().height;
+ if (!ht || !hs) return;
+
+ // From 2*(hs+g) = ht+g => g = ht - 2*hs
+ var gap = ht - 2 * hs;
+ if (!Number.isFinite(gap)) return;
+
+ // Clamp to sane range; negative means "no extra gap needed".
+ gap = Math.max(0, Math.min(20, Math.round(gap)));
+ container.style.setProperty('--agent-card-gap', "".concat(gap, "px"));
+ },
+ adjustAgentCardsColumnCount: function adjustAgentCardsColumnCount() {
+ if (!this.$el) return;
+ var container = this.$el.querySelector('.ui.cards.agent-cards.agent-cards-masonry');
+ if (!container) return;
+ var w = container.clientWidth;
+ if (!w) return;
+
+ // Minimum acceptable card width in px (tune if needed)
+ var minCardWidth = 150;
+ var cs = window.getComputedStyle(container);
+ var gapRaw = cs.columnGap || cs.getPropertyValue('column-gap') || '16px';
+ var gapPx = parseFloat(gapRaw) || 16;
+ var count = Math.max(1, Math.min(12, Math.floor((w + gapPx) / (minCardWidth + gapPx))));
+ container.style.setProperty('--agent-card-col-count', String(count));
+ },
+ ensureAgentCardsGridMasonry: function ensureAgentCardsGridMasonry() {
+ var self = this;
+ var styleId = 'agent-cards-layout-style';
+ var styleEl = document.getElementById(styleId);
+ if (!styleEl) {
+ styleEl = document.createElement('style');
+ styleEl.id = styleId;
+ document.head.appendChild(styleEl);
+ }
+
+ // Grid masonry: fills left-to-right and can pack items into gaps.
+ // minmax(240px, 1fr) - карточки минимум 240px, растягиваются равномерно
+ styleEl.textContent = '\
+.ui.cards.agent-cards.agent-cards-grid {\
+ display: grid !important;\
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\
+ justify-content: start;\
+ gap: var(--agent-card-gap, 8px);\
+ grid-auto-rows: 1px;\
+ margin-bottom: 1em !important;\
+}\
+.ui.cards.agent-cards.agent-cards-grid > .ui.card.agent-card {\
+ width: 100% !important;\
+ min-width: 0;\
+ margin: 0 !important;\
+ overflow: hidden;\
+ align-self: start;\
+}';
+
+ // Process all agent card containers (one per queue block)
+ var cardsContainers = this.$el ? this.$el.querySelectorAll('.ui.cards.agent-cards') : [];
+ cardsContainers.forEach(function (cardsContainer) {
+ cardsContainer.classList.remove('agent-cards-masonry');
+ cardsContainer.classList.remove('agent-cards-flex');
+ cardsContainer.classList.add('agent-cards-grid');
+ });
+
+ // Bind once: relayout on resize.
+ if (!this._agentCardsResizeBound) {
+ this._agentCardsResizeBound = true;
+ window.addEventListener('resize', function () {
+ self.layoutAgentCardsGridMasonry();
+ });
+ }
+ },
+ layoutAgentCardsGridMasonry: function layoutAgentCardsGridMasonry() {
+ if (!this.$el) return;
+ var self = this;
+
+ // Process all grid containers (one per queue block)
+ var grids = this.$el.querySelectorAll('.ui.cards.agent-cards.agent-cards-grid');
+ grids.forEach(function (grid) {
+ self.layoutSingleGridMasonry(grid);
+ });
+ },
+ layoutSingleGridMasonry: function layoutSingleGridMasonry(grid) {
+ if (!grid) return;
+ var cs = window.getComputedStyle(grid);
+ var rowHeight = parseFloat(cs.getPropertyValue('grid-auto-rows')) || 1;
+ var rowGap = parseFloat(cs.getPropertyValue('row-gap')) || parseFloat(cs.getPropertyValue('gap')) || 8;
+ var items = Array.from(grid.querySelectorAll('.ui.card.agent-card'));
+ if (!items.length) return;
+
+ // Reset row spans and min-heights to measure natural heights.
+ items.forEach(function (item) {
+ item.style.gridRowEnd = '';
+ item.style.minHeight = '';
+ });
+ var tall = items.filter(function (c) {
+ return c.querySelector('.meta.agent-peer');
+ });
+ var short = items.filter(function (c) {
+ return !c.querySelector('.meta.agent-peer');
+ });
+
+ // If we don't have both types, just do normal masonry spans.
+ if (!tall.length || !short.length) {
+ items.forEach(function (item) {
+ var h = item.getBoundingClientRect().height;
+ var span = Math.max(1, Math.ceil((h + rowGap) / (rowHeight + rowGap)));
+ item.style.gridRowEnd = 'span ' + span;
+ });
+ return;
+ }
+ var shortHeights = short.map(function (c) {
+ return c.getBoundingClientRect().height;
+ });
+ var tallHeights = tall.map(function (c) {
+ return c.getBoundingClientRect().height;
+ });
+ var hs = Math.max.apply(Math, shortHeights);
+ var ht = Math.max.apply(Math, tallHeights);
+
+ // Want: 2*(hs + g) = (ht + g) => g = ht - 2*hs
+ var g = ht - 2 * hs;
+ if (!Number.isFinite(g)) g = rowGap;
+ g = Math.max(0, Math.min(24, Math.round(g)));
+
+ // Apply gap and enforce min-heights so the relation holds visually.
+ grid.style.setProperty('--agent-card-gap', g + 'px');
+ var shortH = Math.round(hs);
+ var tallH = Math.round(Math.max(ht, 2 * hs + g));
+ short.forEach(function (c) {
+ c.style.minHeight = shortH + 'px';
+ });
+ tall.forEach(function (c) {
+ c.style.minHeight = tallH + 'px';
+ });
+
+ // Now compute row spans from final rendered heights.
+ var effectiveGap = g;
+ items.forEach(function (item) {
+ var h = item.getBoundingClientRect().height;
+ var span = Math.max(1, Math.ceil((h + effectiveGap) / (rowHeight + effectiveGap)));
+ item.style.gridRowEnd = 'span ' + span;
+ });
+ },
getSrcNumForAgent: function getSrcNumForAgent(agentNumber) {
var result = '-';
var answeredFound = false;
- var _iterator = _createForOfIteratorHelper(this.calls),
+ var _iterator = _createForOfIteratorHelper(this.allCalls),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -166,25 +554,51 @@ var ModuleMonitorActiveCalls = {
}
}
return result;
+ },
+ hasPeerPhone: function hasPeerPhone(agentNumber) {
+ var phone = String(this.getSrcNumForAgent(agentNumber) || '').trim();
+ return phone !== '' && phone !== '-' && phone !== '—';
+ },
+ getPeerPhoneLabel: function getPeerPhoneLabel(agentNumber) {
+ var phone = String(this.getSrcNumForAgent(agentNumber) || '').trim();
+ return this.hasPeerPhone(agentNumber) ? phone : '—';
+ },
+ getPeerNameLabel: function getPeerNameLabel(agentNumber) {
+ // Use cached contacts (WS + IndexedDB) to show client name for peer phone.
+ var phone = this.getPeerPhoneLabel(agentNumber);
+ var client = this.getClientNameByPhone(phone);
+ return client || '—';
}
},
data: {
- "name": "",
- "number": "",
- "queues": [],
- "agents": {},
- "calls": []
+ "minWaitVisible": 30,
+ "nowTick": 0,
+ "queues": {},
+ "allCalls": [],
+ "selectedQueueIds": [],
+ "lastActiveCallsPayload": null,
+ "contactsByPhone10": {}
}
});
+ window[className].applyContactsCacheToQueueWidget();
window[className].$callsWidget = new Vue({
el: '#calls',
delimiters: ["<%", "%>"],
data: {
+ "minWaitVisible": 30,
+ "nowTick": 0,
userNumber: userNumber,
fullAccess: $('#fullAccess').val() === "1" || userNumber === '',
calls: []
},
methods: {
+ callIsVisible: function callIsVisible(call) {
+ void this.nowTick;
+ if (call.dst_chan === '' && call.queueData.EnterTime !== undefined) {
+ return this.minWaitVisible <= this.getWaitTime(call);
+ }
+ return true;
+ },
formatTimestampToTime: function formatTimestampToTime(timestamp) {
// Если timestamp строка — приводим к числу
var ts = typeof timestamp === 'string' ? parseFloat(timestamp) : timestamp;
@@ -198,6 +612,7 @@ var ModuleMonitorActiveCalls = {
return "".concat(hours, ":").concat(minutes, ":").concat(seconds);
},
getWaitTime: function getWaitTime(call) {
+ void this.nowTick;
var answer = Math.floor(Date.now() / 1000);
if (call.answer !== '') {
answer = call.answer;
@@ -205,12 +620,14 @@ var ModuleMonitorActiveCalls = {
return window[className].secondToTime(answer - call.start);
},
getCallTime: function getCallTime(call) {
+ void this.nowTick;
if (call.answer === '') {
return '-';
}
return window[className].formatElapsedTime(call.answer);
},
updatedCallsFromResponse: function updatedCallsFromResponse(data) {
+ this.minWaitVisible = 1 * $('#minWaitVisibleValue').val();
// Проходим по всем очередям
for (var queueId in data.queues) {
var queue = data.queues[queueId];
@@ -229,6 +646,13 @@ var ModuleMonitorActiveCalls = {
formatElapsedTime: function formatElapsedTime(enterTime) {
return window[className].formatElapsedTime(enterTime);
},
+ getClientHeader: function getClientHeader(phone) {
+ var q = window[className].$widgetQueues;
+ if (q && typeof q.getClientHeader === 'function') {
+ return q.getClientHeader(phone);
+ }
+ return phone;
+ },
hangupAction: function hangupAction(event) {
var target = $(event.target);
if (target.attr('data-ch1') === undefined) {
@@ -372,6 +796,7 @@ var ModuleMonitorActiveCalls = {
window[className].$dropDowns.dropdown();
window[className].initializeForm();
$('.menu .item').tab();
+ window[className].startUiTicker();
//////
// Удаляем отступы контейнера.
$('#main-content-container').removeClass('container');
@@ -379,8 +804,455 @@ var ModuleMonitorActiveCalls = {
$('.ui.clearing.hidden.divider').remove();
// Окончание форматирования базовой страницы
//////
+ this.startPollingActiveCalls();
+
+ // Allow settings to be saved after initialization
+ setTimeout(function () {
+ window[className].isInit = false;
+ }, 1000);
+ },
+ startUiTicker: function startUiTicker() {
+ if (this._uiTicker) return;
+ this._uiTicker = setInterval(function () {
+ var now = Date.now();
+ if (window[className].$widgetQueues) {
+ window[className].$widgetQueues.nowTick = now;
+ }
+ if (window[className].$callsWidget) {
+ window[className].$callsWidget.nowTick = now;
+ }
+ }, 1000);
+ },
+ startPollingActiveCalls: function startPollingActiveCalls() {
+ if (this._activeCallsPollTimer) return;
window[className].updateLines();
- setInterval(window[className].updateLines, 2000);
+ this._activeCallsPollTimer = setInterval(window[className].updateLines, 2000);
+ },
+ stopPollingActiveCalls: function stopPollingActiveCalls() {
+ if (!this._activeCallsPollTimer) return;
+ clearInterval(this._activeCallsPollTimer);
+ this._activeCallsPollTimer = null;
+ },
+ initContactsCache: function initContactsCache() {
+ var _this = this;
+ return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
+ var _t;
+ return _regenerator().w(function (_context) {
+ while (1) switch (_context.p = _context.n) {
+ case 0:
+ _context.p = 0;
+ _context.n = 1;
+ return _this.idbLoadAllContacts();
+ case 1:
+ _this._contactsCacheByPhone10 = _context.v;
+ _this.applyContactsCacheToQueueWidget();
+ _context.n = 3;
+ break;
+ case 2:
+ _context.p = 2;
+ _t = _context.v;
+ console.log('contacts cache init error', _t);
+ _this._contactsCacheByPhone10 = {};
+ case 3:
+ return _context.a(2);
+ }
+ }, _callee, null, [[0, 2]]);
+ }))();
+ },
+ applyContactsCacheToQueueWidget: function applyContactsCacheToQueueWidget() {
+ if (!this._contactsCacheByPhone10) return;
+ if (!window[className].$widgetQueues) return;
+ for (var _i2 = 0, _Object$entries = Object.entries(this._contactsCacheByPhone10); _i2 < _Object$entries.length; _i2++) {
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i2], 2),
+ phone10 = _Object$entries$_i[0],
+ client = _Object$entries$_i[1];
+ if (window[className].$widgetQueues.$set) {
+ window[className].$widgetQueues.$set(window[className].$widgetQueues.contactsByPhone10, phone10, client);
+ } else {
+ window[className].$widgetQueues.contactsByPhone10[phone10] = client;
+ }
+ }
+ },
+ idbOpenContactsDb: function idbOpenContactsDb() {
+ return new Promise(function (resolve, reject) {
+ try {
+ var req = indexedDB.open('ModuleMonitorActiveCalls', 1);
+ req.onupgradeneeded = function () {
+ var db = req.result;
+ if (!db.objectStoreNames.contains('contactsByPhone10')) {
+ db.createObjectStore('contactsByPhone10', {
+ keyPath: 'phone10'
+ });
+ }
+ };
+ req.onsuccess = function () {
+ return resolve(req.result);
+ };
+ req.onerror = function () {
+ return reject(req.error);
+ };
+ } catch (e) {
+ reject(e);
+ }
+ });
+ },
+ idbPutContact: function idbPutContact(phone10, client) {
+ var _this2 = this;
+ return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
+ var db;
+ return _regenerator().w(function (_context2) {
+ while (1) switch (_context2.n) {
+ case 0:
+ _context2.n = 1;
+ return _this2.idbOpenContactsDb();
+ case 1:
+ db = _context2.v;
+ return _context2.a(2, new Promise(function (resolve, reject) {
+ var tx = db.transaction('contactsByPhone10', 'readwrite');
+ var store = tx.objectStore('contactsByPhone10');
+ store.put({
+ phone10: phone10,
+ client: client,
+ updatedAt: Date.now()
+ });
+ tx.oncomplete = function () {
+ db.close();
+ resolve();
+ };
+ tx.onerror = function () {
+ var err = tx.error;
+ db.close();
+ reject(err);
+ };
+ }));
+ }
+ }, _callee2);
+ }))();
+ },
+ idbLoadAllContacts: function idbLoadAllContacts() {
+ var _this3 = this;
+ return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3() {
+ var db;
+ return _regenerator().w(function (_context3) {
+ while (1) switch (_context3.n) {
+ case 0:
+ _context3.n = 1;
+ return _this3.idbOpenContactsDb();
+ case 1:
+ db = _context3.v;
+ return _context3.a(2, new Promise(function (resolve, reject) {
+ var tx = db.transaction('contactsByPhone10', 'readwrite');
+ var store = tx.objectStore('contactsByPhone10');
+ var req = store.getAll();
+ req.onsuccess = function () {
+ var map = {};
+ var now = Date.now();
+ var ttlMs = Number(_this3.contactsCacheTtlMs) || 120 * 60 * 1000;
+ var _iterator2 = _createForOfIteratorHelper(req.result || []),
+ _step2;
+ try {
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
+ var row = _step2.value;
+ var phone10 = row === null || row === void 0 ? void 0 : row.phone10;
+ var client = row === null || row === void 0 ? void 0 : row.client;
+ var updatedAt = Number(row === null || row === void 0 ? void 0 : row.updatedAt) || 0;
+ var isFresh = phone10 && client && updatedAt > 0 && now - updatedAt <= ttlMs;
+ if (isFresh) {
+ map[phone10] = client;
+ } else if (phone10) {
+ // Cleanup expired/broken records
+ try {
+ store.delete(phone10);
+ } catch (e) {/* ignore */}
+ }
+ }
+ } catch (err) {
+ _iterator2.e(err);
+ } finally {
+ _iterator2.f();
+ }
+ tx.oncomplete = function () {
+ db.close();
+ resolve(map);
+ };
+ tx.onerror = function () {
+ var err = tx.error;
+ db.close();
+ reject(err);
+ };
+ };
+ req.onerror = function () {
+ var err = req.error;
+ db.close();
+ reject(err);
+ };
+ }));
+ }
+ }, _callee3);
+ }))();
+ },
+ requestBackendEnable: function requestBackendEnable() {
+ $.api({
+ url: window[className].backendEnableUrl,
+ on: 'now',
+ method: 'POST',
+ onSuccess: function onSuccess(response) {
+ var _response$data, _response$data2;
+ console.log('backandEnable response', response);
+ var accessToken = response === null || response === void 0 || (_response$data = response.data) === null || _response$data === void 0 ? void 0 : _response$data.access_token;
+ var refreshToken = response === null || response === void 0 || (_response$data2 = response.data) === null || _response$data2 === void 0 ? void 0 : _response$data2.refresh_token;
+ if (accessToken && refreshToken) {
+ window[className].setAuthTokens(accessToken, refreshToken);
+ window[className].connectContactsWs();
+ window[className].connectActiveCallsWs();
+ }
+ },
+ onFailure: function onFailure(response) {
+ console.log('backandEnable failure', response);
+ },
+ onError: function onError(errorMessage, element, xhr) {
+ console.log('backandEnable error', errorMessage, xhr);
+ }
+ });
+ },
+ setAuthTokens: function setAuthTokens(accessToken, refreshToken) {
+ this._authTokens = this._authTokens || {};
+ this._authTokens.access_token = accessToken;
+ this._authTokens.refresh_token = refreshToken;
+ this._authTokens.exp = this.getJwtExp(accessToken);
+ },
+ getJwtExp: function getJwtExp(token) {
+ try {
+ if (!token || typeof token !== 'string') return 0;
+ var parts = token.split('.');
+ if (parts.length < 2) return 0;
+ var payloadB64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
+ var padded = payloadB64 + '='.repeat((4 - payloadB64.length % 4) % 4);
+ var json = atob(padded);
+ var payload = JSON.parse(json);
+ return Number(payload === null || payload === void 0 ? void 0 : payload.exp) || 0;
+ } catch (e) {
+ return 0;
+ }
+ },
+ isAccessTokenExpired: function isAccessTokenExpired() {
+ var _this$_authTokens;
+ var skewSeconds = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
+ var exp = Number((_this$_authTokens = this._authTokens) === null || _this$_authTokens === void 0 ? void 0 : _this$_authTokens.exp) || 0;
+ if (!exp) return false; // unknown exp -> don't force refresh
+ var now = Math.floor(Date.now() / 1000);
+ return now + skewSeconds >= exp;
+ },
+ scheduleContactsWsTokenRefresh: function scheduleContactsWsTokenRefresh() {
+ var _this$_authTokens2,
+ _this4 = this;
+ // Proactively refresh token shortly before expiry by re-requesting backendEnable.
+ if (this._contactsWsTokenTimer) {
+ clearTimeout(this._contactsWsTokenTimer);
+ this._contactsWsTokenTimer = null;
+ }
+ var exp = Number((_this$_authTokens2 = this._authTokens) === null || _this$_authTokens2 === void 0 ? void 0 : _this$_authTokens2.exp) || 0;
+ if (!exp) return;
+ var now = Math.floor(Date.now() / 1000);
+ var refreshInSec = Math.max(1, exp - now - 15); // 15s before exp
+ this._contactsWsTokenTimer = setTimeout(function () {
+ // Re-get tokens and reconnect WS
+ _this4.requestBackendEnable();
+ }, refreshInSec * 1000);
+ },
+ scheduleContactsWsReconnect: function scheduleContactsWsReconnect(reason) {
+ var _this5 = this;
+ var forceReAuth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
+ if (this._contactsWsReconnectTimer) {
+ clearTimeout(this._contactsWsReconnectTimer);
+ this._contactsWsReconnectTimer = null;
+ }
+ this._contactsWsReconnectAttempt = (this._contactsWsReconnectAttempt || 0) + 1;
+ var delay = Math.min(30000, 1000 * Math.pow(2, Math.min(5, this._contactsWsReconnectAttempt - 1)));
+ this._contactsWsReconnectTimer = setTimeout(function () {
+ if (forceReAuth || _this5.isAccessTokenExpired(5)) {
+ _this5.requestBackendEnable();
+ } else {
+ _this5.connectContactsWs();
+ }
+ }, delay);
+ console.log('contacts ws reconnect scheduled', {
+ reason: reason,
+ delayMs: delay
+ });
+ },
+ connectContactsWs: function connectContactsWs() {
+ var _this6 = this;
+ try {
+ var _this$_authTokens3;
+ var accessToken = (_this$_authTokens3 = this._authTokens) === null || _this$_authTokens3 === void 0 ? void 0 : _this$_authTokens3.access_token;
+ if (!accessToken) return;
+
+ // Avoid reconnecting if already connected/connecting
+ if (this._contactsWs && (this._contactsWs.readyState === WebSocket.OPEN || this._contactsWs.readyState === WebSocket.CONNECTING)) {
+ return;
+ }
+ // Reset backoff on explicit connect attempt
+ this._contactsWsReconnectAttempt = 0;
+ var wsProto = window.location.protocol === 'https:' ? 'wss' : 'ws';
+ var wsHost = window.location.host; // host:port of current page
+ var tokenParam = encodeURIComponent(accessToken);
+ var wsUrl = "".concat(wsProto, "://").concat(wsHost, "/pbxcore/api/module-softphone-backend/v1/sub/contacts?authorization=").concat(tokenParam);
+ this._contactsWs = new WebSocket(wsUrl);
+ this._contactsWs.onopen = function () {
+ console.log('contacts ws connected');
+ _this6.scheduleContactsWsTokenRefresh();
+ };
+ this._contactsWs.onmessage = function (event) {
+ _this6.handleContactsWsMessage(event === null || event === void 0 ? void 0 : event.data);
+ };
+ this._contactsWs.onerror = function (event) {
+ console.log('contacts ws error', event);
+ };
+ this._contactsWs.onclose = function (event) {
+ var code = event === null || event === void 0 ? void 0 : event.code;
+ var reason = event === null || event === void 0 ? void 0 : event.reason;
+ console.log('contacts ws closed', {
+ code: code,
+ reason: reason
+ });
+ if (_this6._contactsWsTokenTimer) {
+ clearTimeout(_this6._contactsWsTokenTimer);
+ _this6._contactsWsTokenTimer = null;
+ }
+
+ // 1000 = normal close -> reconnect; auth closes vary by server implementation.
+ var authCloseCodes = new Set([1008, 4001, 4401, 4403]);
+ var forceReAuth = authCloseCodes.has(code) || _this6.isAccessTokenExpired(0);
+ _this6.scheduleContactsWsReconnect('close', forceReAuth);
+ };
+ } catch (e) {
+ console.log('contacts ws init error', e);
+ this.scheduleContactsWsReconnect('init_error', this.isAccessTokenExpired(0));
+ }
+ },
+ scheduleActiveCallsWsReconnect: function scheduleActiveCallsWsReconnect(reason) {
+ var _this7 = this;
+ var forceReAuth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
+ if (this._activeCallsWsReconnectTimer) {
+ clearTimeout(this._activeCallsWsReconnectTimer);
+ this._activeCallsWsReconnectTimer = null;
+ }
+ this._activeCallsWsReconnectAttempt = (this._activeCallsWsReconnectAttempt || 0) + 1;
+ var delay = Math.min(30000, 1000 * Math.pow(2, Math.min(5, this._activeCallsWsReconnectAttempt - 1)));
+ this._activeCallsWsReconnectTimer = setTimeout(function () {
+ if (forceReAuth || _this7.isAccessTokenExpired(5)) {
+ _this7.requestBackendEnable();
+ } else {
+ _this7.connectActiveCallsWs();
+ }
+ }, delay);
+ console.log('active-calls ws reconnect scheduled', {
+ reason: reason,
+ delayMs: delay
+ });
+ },
+ connectActiveCallsWs: function connectActiveCallsWs() {
+ var _this8 = this;
+ try {
+ var _this$_authTokens4;
+ var accessToken = (_this$_authTokens4 = this._authTokens) === null || _this$_authTokens4 === void 0 ? void 0 : _this$_authTokens4.access_token;
+ if (!accessToken) return;
+
+ // Avoid reconnecting if already connected/connecting
+ if (this._activeCallsWs && (this._activeCallsWs.readyState === WebSocket.OPEN || this._activeCallsWs.readyState === WebSocket.CONNECTING)) {
+ return;
+ }
+ // Reset backoff on explicit connect attempt
+ this._activeCallsWsReconnectAttempt = 0;
+
+ // Token exists -> use WS, disable polling fallback
+ this.stopPollingActiveCalls();
+ var wsProto = window.location.protocol === 'https:' ? 'wss' : 'ws';
+ var wsHost = window.location.host; // host:port of current page
+ var tokenParam = encodeURIComponent(accessToken);
+ var wsUrl = "".concat(wsProto, "://").concat(wsHost, "/pbxcore/api/module-softphone-backend/v1/sub/active-calls?authorization=").concat(tokenParam);
+ this._activeCallsWs = new WebSocket(wsUrl);
+ this._activeCallsWs.onopen = function () {
+ console.log('active-calls ws connected');
+ // Reuse the same token refresh timer (it triggers requestBackendEnable)
+ _this8.scheduleContactsWsTokenRefresh();
+ };
+ this._activeCallsWs.onmessage = function (event) {
+ _this8.handleActiveCallsWsMessage(event === null || event === void 0 ? void 0 : event.data);
+ };
+ this._activeCallsWs.onerror = function (event) {
+ console.log('active-calls ws error', event);
+ };
+ this._activeCallsWs.onclose = function (event) {
+ var code = event === null || event === void 0 ? void 0 : event.code;
+ var reason = event === null || event === void 0 ? void 0 : event.reason;
+ console.log('active-calls ws closed', {
+ code: code,
+ reason: reason
+ });
+
+ // Auth closes vary by server implementation.
+ var authCloseCodes = new Set([1008, 4001, 4401, 4403]);
+ var forceReAuth = authCloseCodes.has(code) || _this8.isAccessTokenExpired(0);
+ _this8.scheduleActiveCallsWsReconnect('close', forceReAuth);
+ };
+ } catch (e) {
+ console.log('active-calls ws init error', e);
+ this.scheduleActiveCallsWsReconnect('init_error', this.isAccessTokenExpired(0));
+ }
+ },
+ handleContactsWsMessage: function handleContactsWsMessage(data) {
+ try {
+ if (!data) return;
+ var parsed = typeof data === 'string' ? JSON.parse(data) : data;
+ var items = Array.isArray(parsed) ? parsed : [parsed];
+ var _iterator3 = _createForOfIteratorHelper(items),
+ _step3;
+ try {
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
+ var item = _step3.value;
+ var digits = String((item === null || item === void 0 ? void 0 : item.number) || '').replace(/\D+/g, '');
+ var phone10 = digits.length <= 10 ? digits : digits.slice(-10);
+ var displayName = String((item === null || item === void 0 ? void 0 : item.client) || (item === null || item === void 0 ? void 0 : item.contact) || '').trim();
+ if (phone10 && displayName) {
+ this._contactsCacheByPhone10 = this._contactsCacheByPhone10 || {};
+ this._contactsCacheByPhone10[phone10] = displayName;
+ this.idbPutContact(phone10, displayName).catch(function (e) {
+ return console.log('contacts cache save error', e);
+ });
+ }
+ if (window[className].$widgetQueues) {
+ window[className].$widgetQueues.updateContactFromWs(item);
+ }
+ // Calls table is a separate Vue instance and reads client name via $widgetQueues.
+ // Vue can't track cross-instance dependency, so force re-render on contact update.
+ if (window[className].$callsWidget && typeof window[className].$callsWidget.$forceUpdate === 'function') {
+ window[className].$callsWidget.$forceUpdate();
+ }
+ }
+ } catch (err) {
+ _iterator3.e(err);
+ } finally {
+ _iterator3.f();
+ }
+ } catch (e) {
+ console.log('contacts ws parse error', e);
+ }
+ },
+ handleActiveCallsWsMessage: function handleActiveCallsWsMessage(data) {
+ try {
+ var _parsed$data;
+ if (!data) return;
+ var parsed = typeof data === 'string' ? JSON.parse(data) : data;
+ var payload = parsed !== null && parsed !== void 0 && parsed.queues ? parsed : parsed !== null && parsed !== void 0 && (_parsed$data = parsed.data) !== null && _parsed$data !== void 0 && _parsed$data.queues ? parsed.data : null;
+ if (!payload) return;
+ if (!window[className].$widgetQueues || !window[className].$callsWidget) return;
+ window[className].$widgetQueues.updatedCallsFromResponse(payload);
+ window[className].$callsWidget.updatedCallsFromResponse(payload);
+ } catch (e) {
+ console.log('active-calls ws parse error', e);
+ }
},
formatElapsedTime: function formatElapsedTime(enterTime) {
if (!enterTime) return '—';
@@ -406,7 +1278,8 @@ var ModuleMonitorActiveCalls = {
if (window[className].isInit) {
return;
}
- var data = _defineProperty({}, settingName, value);
+ var data = {};
+ data[settingName] = value;
$.api({
url: window[className].saveUserActionUrl,
on: 'now',
@@ -416,8 +1289,13 @@ var ModuleMonitorActiveCalls = {
return response !== undefined && Object.keys(response).length > 0 && response.success === true;
},
onSuccess: function onSuccess(response) {
- if (settingName === 'queueId') {
- $('#queueId').val($(window[className].queueNameSelector).dropdown('get value'));
+ if (settingName === 'queueIds') {
+ // Update hidden input and Vue data
+ $('#queueIds').val(value);
+ // Re-render queue widget from last received payload (WS mode)
+ if (window[className].$widgetQueues && typeof window[className].$widgetQueues.refreshFromLastPayload === 'function') {
+ window[className].$widgetQueues.refreshFromLastPayload();
+ }
} else if (settingName === 'adminUserId') {
window.location.href = window.location.href;
}
diff --git a/public/assets/js/module-monitor-active-calls-index.js.map b/public/assets/js/module-monitor-active-calls-index.js.map
index 70e5767..1058f22 100644
--- a/public/assets/js/module-monitor-active-calls-index.js.map
+++ b/public/assets/js/module-monitor-active-calls-index.js.map
@@ -1 +1 @@
-{"version":3,"file":"module-monitor-active-calls-index.js","names":["idUrl","idForm","className","inputClassName","ModuleMonitorActiveCalls","isInit","queueNameSelector","$formObj","$","$checkBoxes","$dropDowns","activeChannelsUrl","globalRootUrl","activeChannelsUrlV2","executeCallUrl","saveUserActionUrl","$widget","undefined","validateRules","initialize","dropdown","onChange","value","text","$choice","window","onChangeSetting","userNumber","val","$widgetQueues","Vue","el","delimiters","methods","updatedCallsFromResponse","data","queueNameEl","queues","queueId","id","name","number","agents","calls","Array","isArray","allCalls","formatElapsedTime","enterTime","getSrcNumForAgent","agentNumber","result","answeredFound","_iterator","_createForOfIteratorHelper","_step","s","n","done","call","dst_num","src_num","calledChannels","match","find","ch","bridgeChannels","err","e","f","i","length","tmpCall","spyer","spy_num","exten","$callsWidget","fullAccess","formatTimestampToTime","timestamp","ts","parseFloat","ms","date","Date","hours","String","getHours","padStart","minutes","getMinutes","seconds","getSeconds","concat","getWaitTime","answer","Math","floor","now","secondToTime","start","getCallTime","queue","_data$calls","push","apply","_toConsumableArray","$nextTick","Extensions","updatePhonesRepresent","hangupAction","event","target","attr","parent","executeCallAction","action","ch1","ch2","joinAction","whisperAction","spChannel","listenAction","lines","checkbox","initializeForm","tab","removeClass","hide","remove","updateLines","setInterval","diffSeconds","round","toString","settingName","_defineProperty","api","url","on","method","successTest","response","Object","keys","success","onSuccess","location","href","onFailure","console","log","onError","errorMessage","element","xhr","cbBeforeSendForm","settings","form","cbAfterSendForm","Form","document","ready"],"sources":["src/module-monitor-active-calls-index.js"],"sourcesContent":["/*\n * Copyright (C) MIKO LLC - All Rights Reserved\n * Unauthorized copying of this file, via any medium is strictly prohibited\n * Proprietary and confidential\n * Written by Nikolay Beketov, 11 2018\n *\n */\nconst idUrl = 'module-monitor-active-calls';\nconst idForm = 'module-monitor-active-calls-form';\nconst className = 'ModuleMonitorActiveCalls';\nconst inputClassName = 'mikopbx-module-input';\n\n/* global $, globalRootUrl, globalTranslate, Form, Config, Vue, Extensions */\nconst ModuleMonitorActiveCalls = {\n\tisInit: true,\n\tqueueNameSelector: '#app-queue div.scrolling.dropdown',\n\t$formObj: $('#'+idForm),\n\t$checkBoxes: $('#'+idForm+' .ui.checkbox'),\n\t$dropDowns: $('#'+idForm+' .ui.dropdown'),\n\tactiveChannelsUrl: globalRootUrl + idUrl + \"/getActiveChannels\",\n\tactiveChannelsUrlV2: globalRootUrl + idUrl + \"/getActiveChannelsV2\",\n\texecuteCallUrl: globalRootUrl + idUrl + \"/executeCall\",\n\tsaveUserActionUrl: globalRootUrl + idUrl + \"/saveUser\",\n\t$widget: undefined,\n\n\t/**\n\t * Field validation rules\n\t * https://semantic-ui.com/behaviors/form.html\n\t */\n\tvalidateRules: {},\n\t/**\n\t * On page load we init some Semantic UI library\n\t */\n\tinitialize() {\n\t\t$(\"#nowUser.dropdown.enable\").dropdown({\n\t\t\tonChange: function onChange(value, text, $choice) {\n\t\t\t\twindow[className].onChangeSetting('adminUserId', value);\n\t\t\t}\n\t\t});\n\t\tlet userNumber = $('#userNumber').val();\n\n\t\twindow[className].$widgetQueues = new Vue({\n\t\t\tel: '#app-queue',\n\t\t\tdelimiters: [\"<%\",\"%>\"],\n\t\t\tmethods: {\n\t\t\t\tupdatedCallsFromResponse(data) {\n\t\t\t\t\tlet queueNameEl = $(window[className].queueNameSelector);\n\n\t\t\t\t\tthis.queues = data.queues;\n\t\t\t\t\tlet queueId = $('#queueId').val();\n\t\t\t\t\tif (queueId in data.queues) {\n\t\t\t\t\t\tthis.id = data.queues[queueId].id;\n\t\t\t\t\t\tthis.name = data.queues[queueId].name;\n\t\t\t\t\t\tthis.number = data.queues[queueId].number;\n\t\t\t\t\t\tthis.agents = data.queues[queueId].agents;\n\t\t\t\t\t\tthis.calls = Array.isArray(data.queues[queueId].calls) ? data.queues[queueId].calls : [];\n\t\t\t\t\t\tthis.allCalls = data.calls;\n\t\t\t\t\t}else{\n\t\t\t\t\t\tthis.calls = [];\n\t\t\t\t\t}\n\t\t\t\t\tif(queueNameEl.dropdown('is hidden')){\n\t\t\t\t\t\tqueueNameEl.dropdown({\n\t\t\t\t\t\t\tonChange: function onChange(value, text, $choice) {\n\t\t\t\t\t\t\t\twindow[className].onChangeSetting('queueId', value);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif(queueNameEl.dropdown('get value') === ''){\n\t\t\t\t\t\t\twindow[className].isInit = true;\n\t\t\t\t\t\t\tqueueNameEl.dropdown('set value', $('#queueId').val())\n\t\t\t\t\t\t\twindow[className].isInit = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tformatElapsedTime(enterTime) {\n\t\t\t\t\treturn window[className].formatElapsedTime(enterTime);\n\t\t\t\t},\n\t\t\t\tgetSrcNumForAgent(agentNumber) {\n\t\t\t\t\tlet result = '-';\n\t\t\t\t\tlet answeredFound = false;\n\t\t\t\t\tfor (const call of this.calls) {\n\t\t\t\t\t\tif(call.dst_num === agentNumber){\n\t\t\t\t\t\t\tansweredFound = true;\n\t\t\t\t\t\t\tresult = call.src_num;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (call.calledChannels && Array.isArray(call.calledChannels)) {\n\t\t\t\t\t\t\tconst match = call.calledChannels.find(ch => ch.number === agentNumber);\n\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\tresult = call.src_num;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (call.bridgeChannels && Array.isArray(call.bridgeChannels)) {\n\t\t\t\t\t\t\tconst match = call.bridgeChannels.find(ch => (ch.src_num === agentNumber || ch.dst_num === agentNumber));\n\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\tif(match.src_num === agentNumber){\n\t\t\t\t\t\t\t\t\tresult = match.dst_num;\n\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\tresult = match.src_num;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tansweredFound = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif(answeredFound === false){\n\t\t\t\t\t\tfor (let i = 0; i < this.allCalls.length; i++) {\n\t\t\t\t\t\t\tconst tmpCall = this.allCalls[i];\n\t\t\t\t\t\t\tif(tmpCall.src_num === agentNumber){\n\t\t\t\t\t\t\t\t// Исходящий\n\t\t\t\t\t\t\t\tif(tmpCall.dst_num === ''){\n\t\t\t\t\t\t\t\t\t// не ответа, дозвон.\n\t\t\t\t\t\t\t\t\tif (tmpCall.calledChannels && Array.isArray(tmpCall.calledChannels) && tmpCall.calledChannels.length) {\n\t\t\t\t\t\t\t\t\t\tconst match = tmpCall.calledChannels.find(ch => ch.number !== agentNumber);\n\t\t\t\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\t\t\t\tresult = match.number;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}else if(tmpCall.spyer){\n\t\t\t\t\t\t\t\t\t\t// шпионит за номером.\n\t\t\t\t\t\t\t\t\t\tresult = tmpCall.spy_num;\n\t\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\t\t// нет вызываемых каналов, возможно это вызов на приложение / ivr.\n\t\t\t\t\t\t\t\t\t\tresult = tmpCall.exten;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\tresult = tmpCall.dst_num;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}else if(tmpCall.dst_num === agentNumber){\n\t\t\t\t\t\t\t\t// Входящий на агента, отвечен.\n\t\t\t\t\t\t\t\tresult = tmpCall.src_num;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\tif (tmpCall.calledChannels && Array.isArray(tmpCall.calledChannels)) {\n\t\t\t\t\t\t\t\t\tconst match = tmpCall.calledChannels.find(ch => ch.number === agentNumber);\n\t\t\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\t\t\tresult = tmpCall.src_num;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdata: {\n\t\t\t\t\"name\": \"\",\n\t\t\t\t\"number\": \"\",\n\t\t\t\t\"queues\": [],\n\t\t\t\t\"agents\": {\n\t\t\t\t},\n\t\t\t\t\"calls\": [\n\t\t\t\t]\n\t\t\t},\n\t\t});\n\n\t\twindow[className].$callsWidget = new Vue({\n\t\t\tel: '#calls',\n\t\t\tdelimiters: [\"<%\",\"%>\"],\n\t\t\tdata: {\n\t\t\t\tuserNumber: userNumber,\n\t\t\t\tfullAccess: ($('#fullAccess').val() === \"1\" || userNumber === ''),\n\t\t\t\tcalls: [\n\t\t\t\t]\n\t\t\t},\n\t\t\tmethods: {\n\t\t\t\tformatTimestampToTime(timestamp) {\n\t\t\t\t\t// Если timestamp строка — приводим к числу\n\t\t\t\t\tconst ts = typeof timestamp === 'string' ? parseFloat(timestamp) : timestamp;\n\n\t\t\t\t\t// Если timestamp в секундах (меньше 1e10), умножаем на 1000\n\t\t\t\t\tconst ms = ts < 1e10 ? ts * 1000 : ts;\n\n\t\t\t\t\tconst date = new Date(ms);\n\n\t\t\t\t\tconst hours = String(date.getHours()).padStart(2, '0');\n\t\t\t\t\tconst minutes = String(date.getMinutes()).padStart(2, '0');\n\t\t\t\t\tconst seconds = String(date.getSeconds()).padStart(2, '0');\n\n\t\t\t\t\treturn `${hours}:${minutes}:${seconds}`;\n\t\t\t\t},\n\t\t\t\tgetWaitTime(call){\n\t\t\t\t\tlet answer = Math.floor(Date.now() / 1000);\n\t\t\t\t\tif(call.answer !== ''){\n\t\t\t\t\t\tanswer = call.answer\n\t\t\t\t\t}\n\t\t\t\t\treturn window[className].secondToTime(answer - call.start);\n\t\t\t\t},\n\t\t\t\tgetCallTime(call){\n\t\t\t\t\tif(call.answer === ''){\n\t\t\t\t\t\treturn '-';\n\t\t\t\t\t}\n\t\t\t\t\treturn window[className].formatElapsedTime(call.answer);\n\t\t\t\t},\n\t\t\t\tupdatedCallsFromResponse(data) {\n\t\t\t\t\t// Проходим по всем очередям\n\t\t\t\t\tfor (const queueId in data.queues) {\n\t\t\t\t\t\tconst queue = data.queues[queueId];\n\t\t\t\t\t\t// Проверяем, есть ли у очереди поле calls и является ли оно массивом\n\t\t\t\t\t\tif (Array.isArray(queue.calls)) {\n\t\t\t\t\t\t\t// Добавляем все вызовы из этой очереди в общий массив\n\t\t\t\t\t\t\tdata.calls.push(...queue.calls);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.calls = data.calls;\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tExtensions.updatePhonesRepresent('need-update');\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tformatElapsedTime(enterTime) {\n\t\t\t\t\treturn window[className].formatElapsedTime(enterTime);\n\t\t\t\t},\n\t\t\t\thangupAction(event) {\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'hangup', ch1: target.attr('data-ch1'), ch2: target.attr('data-ch2')});\n\t\t\t\t},\n\t\t\t\tjoinAction(event) {\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\tif(this.userNumber === ''){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'join', ch1: target.attr('data-ch1'), ch2: target.attr('data-ch2'), number: this.userNumber});\n\t\t\t\t},\n\t\t\t\twhisperAction(event){\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\tif(this.userNumber === ''){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet spChannel = target.attr('data-ch1');\n\t\t\t\t\tif('incoming' === target.attr('data-call-type')){\n\t\t\t\t\t\tspChannel = target.attr('data-ch2');\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'whisper', ch1: spChannel, ch2: '', number: this.userNumber});\n\t\t\t\t},\n\t\t\t\tlistenAction(event){\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\tif(this.userNumber === ''){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'listen', ch1: target.attr('data-ch1'), ch2: target.attr('data-ch2'), number: this.userNumber});\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\twindow[className].$widget = new Vue({\n\t\t\tel: '#app',\n\t\t\tdelimiters: [\"<%\",\"%>\"],\n\t\t\tdata: {\n\t\t\t\tuserNumber: userNumber,\n\t\t\t\tfullAccess: ($('#fullAccess').val() === \"1\" || userNumber === ''),\n\t\t\t\tcalls: [\n\t\t\t\t]\n\t\t\t},\n\t\t\tmethods: {\n\t\t\t\tupdatedCallsFromResponse(lines) {\n\t\t\t\t\tthis.calls = lines;\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tExtensions.updatePhonesRepresent('need-update');\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\thangupAction(event) {\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'hangup', ch1: target.attr('data-ch1'), ch2: target.attr('data-ch2')});\n\t\t\t\t},\n\t\t\t\tjoinAction(event) {\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\tif(this.userNumber === ''){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'join', ch1: target.attr('data-ch1'), ch2: target.attr('data-ch2'), number: this.userNumber});\n\t\t\t\t},\n\t\t\t\twhisperAction(event){\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\tif(this.userNumber === ''){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet spChannel = target.attr('data-ch1');\n\t\t\t\t\tif('incoming' === target.attr('data-call-type')){\n\t\t\t\t\t\tspChannel = target.attr('data-ch2');\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'whisper', ch1: spChannel, ch2: '', number: this.userNumber});\n\t\t\t\t},\n\t\t\t\tlistenAction(event){\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\tif(this.userNumber === ''){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'listen', ch1: target.attr('data-ch1'), ch2: target.attr('data-ch2'), number: this.userNumber});\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\twindow[className].$checkBoxes.checkbox();\n\t\twindow[className].$dropDowns.dropdown();\n\t\twindow[className].initializeForm();\n\t\t$('.menu .item').tab();\n\t\t//////\n\t\t// Удаляем отступы контейнера.\n\t\t$('#main-content-container').removeClass('container');\n\t\t$('#module-status-toggle-segment').hide();\n\t\t$('.ui.clearing.hidden.divider').remove();\n\t\t// Окончание форматирования базовой страницы\n\t\t//////\n\t\twindow[className].updateLines();\n\t\tsetInterval(window[className].updateLines, 2000);\n\t},\n\tformatElapsedTime(enterTime) {\n\t\tif (!enterTime) return '—';\n\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\tconst diffSeconds = now - enterTime;\n\n\t\treturn window[className].secondToTime(diffSeconds);\n\t},\n\tsecondToTime(diffSeconds){\n\t\tif (diffSeconds < 0) return '0';\n\t\t// Форматируем: чч:мм:сс или мм:сс, или просто секунды\n\t\tconst hours = Math.floor(diffSeconds / 3600);\n\t\tconst minutes = Math.floor((diffSeconds % 3600) / 60);\n\t\tconst seconds = Math.round(diffSeconds % 60);\n\t\tif (hours > 0) {\n\t\t\treturn `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;\n\t\t} else if (minutes > 0) {\n\t\t\treturn `${minutes}:${seconds.toString().padStart(2, '0')}`;\n\t\t} else {\n\t\t\treturn `${seconds}`;\n\t\t}\n\t},\n\tonChangeSetting(settingName, value) {\n\t\tif(window[className].isInit){\n\t\t\treturn;\n\t\t}\n\t\tlet data = {\n\t\t\t[settingName]: value\n\t\t};\n\t\t$.api({\n\t\t\turl: window[className].saveUserActionUrl,\n\t\t\ton: 'now',\n\t\t\tmethod: 'POST',\n\t\t\tdata: data,\n\t\t\tsuccessTest(response) {\n\t\t\t\treturn response !== undefined && Object.keys(response).length > 0 && response.success === true;\n\t\t\t},\n\t\t\tonSuccess(response) {\n\t\t\t\tif(settingName === 'queueId'){\n\t\t\t\t\t$('#queueId').val($(window[className].queueNameSelector).dropdown('get value'));\n\t\t\t\t}else if( settingName === 'adminUserId'){\n\t\t\t\t\twindow.location.href = window.location.href;\n\t\t\t\t}\n\t\t\t},\n\t\t\tonFailure(response) {\n\t\t\t\tconsole.log(response);\n\t\t\t},\n\t\t\tonError(errorMessage, element, xhr) {\n\t\t\t\tconsole.log(errorMessage,xhr);\n\t\t\t}\n\t\t});\n\t},\n\texecuteCallAction(data) {\n\t\t$.api({\n\t\t\turl: window[className].executeCallUrl,\n\t\t\ton: 'now',\n\t\t\tmethod: 'POST',\n\t\t\tdata: data,\n\t\t\tsuccessTest(response) {\n\t\t\t\treturn response !== undefined && Object.keys(response).length > 0 && response.success === true;\n\t\t\t},\n\t\t\tonSuccess(response) {\n\t\t\t\tconsole.log(response);\n\t\t\t},\n\t\t\tonFailure(response) {\n\t\t\t\tconsole.log(response);\n\t\t\t},\n\t\t\tonError(errorMessage, element, xhr) {\n\t\t\t\tconsole.log(errorMessage,xhr);\n\t\t\t}\n\t\t});\n\t},\n\tupdateLines() {\n\t\t$.api({\n\t\t\turl: window[className].activeChannelsUrlV2,\n\t\t\ton: 'now',\n\t\t\tmethod: 'POST',\n\t\t\tsuccessTest(response) {\n\t\t\t\treturn response !== undefined && Object.keys(response).length > 0 && response.success === true;\n\t\t\t},\n\t\t\tonSuccess(response) {\n\t\t\t\twindow[className].$widgetQueues.updatedCallsFromResponse(response);\n\t\t\t\twindow[className].$callsWidget.updatedCallsFromResponse(response);\n\t\t\t},\n\t\t\tonFailure(response) {\n\t\t\t\tconsole.log(response);\n\t\t\t},\n\t\t\tonError(errorMessage, element, xhr) {\n\t\t\t\tconsole.log(errorMessage,xhr);\n\t\t\t}\n\t\t});\n\t},\n\n\t/**\n\t * We can modify some data before form send\n\t * @param settings\n\t * @returns {*}\n\t */\n\tcbBeforeSendForm(settings) {\n\t\tconst result = settings;\n\t\tresult.data = window[className].$formObj.form('get values');\n\t\treturn result;\n\t},\n\t/**\n\t * Some actions after forms send\n\t */\n\tcbAfterSendForm() {\n\n\t},\n\t/**\n\t * Initialize form parameters\n\t */\n\tinitializeForm() {\n\t\tForm.$formObj = window[className].$formObj;\n\t\tForm.url = `${globalRootUrl}${idUrl}/save`;\n\t\tForm.validateRules = window[className].validateRules;\n\t\tForm.cbBeforeSendForm = window[className].cbBeforeSendForm;\n\t\tForm.cbAfterSendForm = window[className].cbAfterSendForm;\n\t\tForm.initialize();\n\t},\n};\n\n$(document).ready(() => {\n\twindow[className].initialize();\n});\n\n"],"mappings":"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","ignoreList":[]}
\ No newline at end of file
+{"version":3,"file":"module-monitor-active-calls-index.js","names":["e","t","r","Symbol","n","iterator","o","toStringTag","i","c","prototype","Generator","u","Object","create","_regeneratorDefine2","f","p","y","G","v","a","d","bind","length","l","TypeError","call","done","value","return","GeneratorFunction","GeneratorFunctionPrototype","getPrototypeOf","setPrototypeOf","__proto__","displayName","_regenerator","w","m","defineProperty","_regeneratorDefine","_invoke","enumerable","configurable","writable","asyncGeneratorStep","Promise","resolve","then","_asyncToGenerator","arguments","apply","_next","_throw","_toConsumableArray","_arrayWithoutHoles","_iterableToArray","_unsupportedIterableToArray","_nonIterableSpread","Array","from","isArray","_arrayLikeToArray","_createForOfIteratorHelper","_n","F","s","next","ownKeys","keys","getOwnPropertySymbols","filter","getOwnPropertyDescriptor","push","_objectSpread","forEach","_defineProperty","getOwnPropertyDescriptors","defineProperties","_toPropertyKey","_toPrimitive","_typeof","toPrimitive","String","Number","_slicedToArray","_arrayWithHoles","_iterableToArrayLimit","_nonIterableRest","toString","slice","constructor","name","test","idUrl","idForm","className","inputClassName","ModuleMonitorActiveCalls","isInit","contactsCacheTtlMs","queuesFilterSelector","$formObj","$","$checkBoxes","$dropDowns","activeChannelsUrl","globalRootUrl","activeChannelsUrlV2","backendEnableUrl","executeCallUrl","saveUserActionUrl","$widget","undefined","validateRules","initialize","initContactsCache","requestBackendEnable","dropdown","onChange","text","$choice","window","onChangeSetting","val","userNumber","$widgetQueues","Vue","el","delimiters","methods","updatedCallsFromResponse","data","lastActiveCallsPayload","minWaitVisible","queues","allCalls","calls","initQueuesFilter","$nextTick","normalizeAgentCards","self","$filter","currentSelection","selectedQueueIds","find","hide","show","fullTextSearch","selectedIds","split","JSON","stringify","savedQueueIds","raw","parse","refreshFromLastPayload","getQueueCalls","queueId","queue","getQueueAgentsList","agents","buildAgentsList","hasWaitingCalls","dst_chan","queueData","EnterTime","elapsed","formatElapsedTime","agentsObj","entries","available","unavailable","_i","_entries","_entries$_i","number","agent","state","item","concat","normalizePhone10","phone","digits","replace","updateContactFromWs","contact","phone10","client","trim","$set","contactsByPhone10","getClientNameByPhone","getClientHeader","hasClientByPhone","enterTime","nowTick","$el","artifacts","querySelectorAll","remove","ensureAgentCardsGridMasonry","cardsContainers","cardsContainer","style","alignItems","alignContent","cards","card","alignSelf","headers","fontSize","lineHeight","display","gap","whiteSpace","metas","numLabels","paddingTop","paddingBottom","flex","minWidth","maxWidth","overflow","textOverflow","names","requestAnimationFrame","layoutAgentCardsGridMasonry","adjustAgentCardsGap","container","querySelector","tallCard","shortCard","ht","getBoundingClientRect","height","hs","isFinite","Math","max","min","round","setProperty","adjustAgentCardsColumnCount","clientWidth","minCardWidth","cs","getComputedStyle","gapRaw","columnGap","getPropertyValue","gapPx","parseFloat","count","floor","styleId","styleEl","document","getElementById","createElement","id","head","appendChild","textContent","classList","add","_agentCardsResizeBound","addEventListener","grids","grid","layoutSingleGridMasonry","rowHeight","rowGap","items","gridRowEnd","minHeight","tall","short","h","span","ceil","shortHeights","map","tallHeights","g","shortH","tallH","effectiveGap","getSrcNumForAgent","agentNumber","result","answeredFound","_iterator","_step","dst_num","src_num","calledChannels","match","ch","bridgeChannels","err","tmpCall","spyer","spy_num","exten","hasPeerPhone","getPeerPhoneLabel","getPeerNameLabel","applyContactsCacheToQueueWidget","$callsWidget","fullAccess","callIsVisible","getWaitTime","formatTimestampToTime","timestamp","ts","ms","date","Date","hours","getHours","padStart","minutes","getMinutes","seconds","getSeconds","answer","now","secondToTime","start","getCallTime","_data$calls","Extensions","updatePhonesRepresent","q","hangupAction","event","target","attr","parent","executeCallAction","action","ch1","ch2","joinAction","whisperAction","spChannel","listenAction","lines","checkbox","initializeForm","tab","startUiTicker","removeClass","startPollingActiveCalls","setTimeout","_uiTicker","setInterval","_activeCallsPollTimer","updateLines","stopPollingActiveCalls","clearInterval","_this","_callee","_t","_context","idbLoadAllContacts","_contactsCacheByPhone10","console","log","_i2","_Object$entries","_Object$entries$_i","idbOpenContactsDb","reject","req","indexedDB","open","onupgradeneeded","db","objectStoreNames","contains","createObjectStore","keyPath","onsuccess","onerror","error","idbPutContact","_this2","_callee2","_context2","tx","transaction","store","objectStore","put","updatedAt","oncomplete","close","_this3","_callee3","_context3","getAll","ttlMs","_iterator2","_step2","row","isFresh","delete","api","url","on","method","onSuccess","response","_response$data","_response$data2","accessToken","access_token","refreshToken","refresh_token","setAuthTokens","connectContactsWs","connectActiveCallsWs","onFailure","onError","errorMessage","element","xhr","_authTokens","exp","getJwtExp","token","parts","payloadB64","padded","repeat","json","atob","payload","isAccessTokenExpired","_this$_authTokens","skewSeconds","scheduleContactsWsTokenRefresh","_this$_authTokens2","_this4","_contactsWsTokenTimer","clearTimeout","refreshInSec","scheduleContactsWsReconnect","reason","_this5","forceReAuth","_contactsWsReconnectTimer","_contactsWsReconnectAttempt","delay","pow","delayMs","_this6","_this$_authTokens3","_contactsWs","readyState","WebSocket","OPEN","CONNECTING","wsProto","location","protocol","wsHost","host","tokenParam","encodeURIComponent","wsUrl","onopen","onmessage","handleContactsWsMessage","onclose","code","authCloseCodes","Set","has","scheduleActiveCallsWsReconnect","_this7","_activeCallsWsReconnectTimer","_activeCallsWsReconnectAttempt","_this8","_this$_authTokens4","_activeCallsWs","handleActiveCallsWsMessage","parsed","_iterator3","_step3","catch","$forceUpdate","_parsed$data","diffSeconds","settingName","successTest","success","href","cbBeforeSendForm","settings","form","cbAfterSendForm","Form","ready"],"sources":["src/module-monitor-active-calls-index.js"],"sourcesContent":["/*\n * Copyright (C) MIKO LLC - All Rights Reserved\n * Unauthorized copying of this file, via any medium is strictly prohibited\n * Proprietary and confidential\n * Written by Nikolay Beketov, 11 2018\n *\n */\nconst idUrl = 'module-monitor-active-calls';\nconst idForm = 'module-monitor-active-calls-form';\nconst className = 'ModuleMonitorActiveCalls';\nconst inputClassName = 'mikopbx-module-input';\n\n/* global $, globalRootUrl, globalTranslate, Form, Config, Vue, Extensions */\nconst ModuleMonitorActiveCalls = {\n\tisInit: true,\n\tcontactsCacheTtlMs: 120 * 60 * 1000,\n\tqueuesFilterSelector: '#queuesFilter',\n\t$formObj: $('#'+idForm),\n\t$checkBoxes: $('#'+idForm+' .ui.checkbox'),\n\t$dropDowns: $('#'+idForm+' .ui.dropdown'),\n\tactiveChannelsUrl: globalRootUrl + idUrl + \"/getActiveChannels\",\n\tactiveChannelsUrlV2: globalRootUrl + idUrl + \"/getActiveChannelsV2\",\n\tbackendEnableUrl: globalRootUrl + idUrl + \"/backandEnable\",\n\texecuteCallUrl: globalRootUrl + idUrl + \"/executeCall\",\n\tsaveUserActionUrl: globalRootUrl + idUrl + \"/saveUser\",\n\t$widget: undefined,\n\n\t/**\n\t * Field validation rules\n\t * https://semantic-ui.com/behaviors/form.html\n\t */\n\tvalidateRules: {},\n\t/**\n\t * On page load we init some Semantic UI library\n\t */\n\tinitialize() {\n\t\tthis.initContactsCache();\n\t\tthis.requestBackendEnable();\n\n\t\t$(\"#nowUser.dropdown.enable\").dropdown({\n\t\t\tonChange: function onChange(value, text, $choice) {\n\t\t\t\twindow[className].onChangeSetting('adminUserId', value);\n\t\t\t}\n\t\t});\n\t\t$(\"#minWaitVisible.dropdown.enable\").dropdown({\n\t\t\tonChange: function onChange(value, text, $choice) {\n\t\t\t\t$('#minWaitVisibleValue').val(value);\n\t\t\t\twindow[className].onChangeSetting('minWaitVisible', value);\n\t\t\t}\n\t\t});\n\t\tlet userNumber = $('#userNumber').val();\n\n\t\twindow[className].$widgetQueues = new Vue({\n\t\t\tel: '#app-queue',\n\t\t\tdelimiters: [\"<%\",\"%>\"],\n\t\t\tmethods: {\n\t\t\t\tupdatedCallsFromResponse(data) {\n\t\t\t\t\t// Keep last payload to allow re-render on queue switch (WS mode).\n\t\t\t\t\tthis.lastActiveCallsPayload = data;\n\n\t\t\t\t\tthis.minWaitVisible = 1*$('#minWaitVisibleValue').val();\n\t\t\t\t\tthis.queues = data.queues || {};\n\t\t\t\t\tthis.allCalls = data.calls || [];\n\n\t\t\t\t\t// Initialize multi-select dropdown if not yet done\n\t\t\t\t\tthis.initQueuesFilter();\n\n\t\t\t\t\t// Normalize Semantic UI Card typography after render\n\t\t\t\t\tthis.$nextTick(function() {\n\t\t\t\t\t\tthis.normalizeAgentCards();\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tinitQueuesFilter() {\n\t\t\t\t\tvar self = this;\n\t\t\t\t\tvar $filter = $(window[className].queuesFilterSelector);\n\t\t\t\t\tif ($filter.length === 0) return;\n\n\t\t\t\t\t// Wait for Vue to render menu items\n\t\t\t\t\tthis.$nextTick(function() {\n\t\t\t\t\t\t// Reinitialize dropdown to pick up new menu items\n\t\t\t\t\t\tif ($filter.data('initialized')) {\n\t\t\t\t\t\t\t// Dropdown already exists, just refresh menu\n\t\t\t\t\t\t\t// Save current selection before refresh to prevent reset\n\t\t\t\t\t\t\tvar currentSelection = self.selectedQueueIds ? self.selectedQueueIds.slice() : [];\n\t\t\t\t\t\t\t$filter.data('refreshing', true);\n\t\t\t\t\t\t\t$filter.dropdown('refresh');\n\t\t\t\t\t\t\t$filter.data('refreshing', false);\n\n\t\t\t\t\t\t\t// Restore selection after refresh if it was cleared\n\t\t\t\t\t\t\tif (currentSelection.length > 0 && (!self.selectedQueueIds || self.selectedQueueIds.length === 0)) {\n\t\t\t\t\t\t\t\tself.selectedQueueIds = currentSelection;\n\t\t\t\t\t\t\t\t$filter.dropdown('set exactly', currentSelection);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// After refresh, ensure default text is hidden if we have selections\n\t\t\t\t\t\t\tif (self.selectedQueueIds && self.selectedQueueIds.length > 0) {\n\t\t\t\t\t\t\t\t$filter.find('.default.text').hide();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t$filter.find('.default.text').show();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// First time initialization\n\t\t\t\t\t\t\t$filter.data('initialized', true);\n\t\t\t\t\t\t\t$filter.dropdown({\n\t\t\t\t\t\t\t\tfullTextSearch: true,\n\t\t\t\t\t\t\t\tonChange: function(value) {\n\t\t\t\t\t\t\t\t\t// Skip onChange during programmatic refresh\n\t\t\t\t\t\t\t\t\tif ($filter.data('refreshing')) {\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t// value is comma-separated string of selected queue IDs\n\t\t\t\t\t\t\t\t\tvar selectedIds = value ? value.split(',').filter(function(v) { return v !== ''; }) : [];\n\t\t\t\t\t\t\t\t\tself.selectedQueueIds = selectedIds;\n\t\t\t\t\t\t\t\t\t// Auto-save on change\n\t\t\t\t\t\t\t\t\twindow[className].onChangeSetting('queueIds', JSON.stringify(selectedIds));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t// Set initial values from hidden input\n\t\t\t\t\t\t\tvar savedQueueIds = [];\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tvar raw = $('#queueIds').val();\n\t\t\t\t\t\t\t\tsavedQueueIds = JSON.parse(raw || '[]');\n\t\t\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\t\t\tsavedQueueIds = [];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (Array.isArray(savedQueueIds) && savedQueueIds.length > 0) {\n\t\t\t\t\t\t\t\twindow[className].isInit = true;\n\t\t\t\t\t\t\t\t$filter.dropdown('set exactly', savedQueueIds);\n\t\t\t\t\t\t\t\tself.selectedQueueIds = savedQueueIds;\n\t\t\t\t\t\t\t\twindow[className].isInit = false;\n\t\t\t\t\t\t\t\t// Hide default text when values are selected\n\t\t\t\t\t\t\t\t$filter.find('.default.text').hide();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\trefreshFromLastPayload() {\n\t\t\t\t\tif (this.lastActiveCallsPayload) {\n\t\t\t\t\t\tthis.updatedCallsFromResponse(this.lastActiveCallsPayload);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tgetQueueCalls(queueId) {\n\t\t\t\t\tvar queue = this.queues[queueId];\n\t\t\t\t\tif (!queue) return [];\n\t\t\t\t\treturn Array.isArray(queue.calls) ? queue.calls : [];\n\t\t\t\t},\n\t\t\t\tgetQueueAgentsList(queueId) {\n\t\t\t\t\tvar queue = this.queues[queueId];\n\t\t\t\t\tif (!queue || !queue.agents) return [];\n\t\t\t\t\treturn this.buildAgentsList(queue.agents);\n\t\t\t\t},\n\t\t\t\thasWaitingCalls(queueId) {\n\t\t\t\t\tvar calls = this.getQueueCalls(queueId);\n\t\t\t\t\tvar self = this;\n\t\t\t\t\tfor (var i = 0; i < calls.length; i++) {\n\t\t\t\t\t\tvar call = calls[i];\n\t\t\t\t\t\tif (call.dst_chan === '' && call.queueData && call.queueData.EnterTime !== undefined) {\n\t\t\t\t\t\t\tvar elapsed = self.formatElapsedTime(call.queueData.EnterTime);\n\t\t\t\t\t\t\tif (self.minWaitVisible <= elapsed) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn false;\n\t\t\t\t},\n\t\t\t\tbuildAgentsList(agentsObj) {\n\t\t\t\t\tconst entries = Object.entries(agentsObj || {});\n\t\t\t\t\tconst available = [];\n\t\t\t\t\tconst unavailable = [];\n\t\t\t\t\tfor (const [number, agent] of entries) {\n\t\t\t\t\t\tconst state = agent?.state || '';\n\t\t\t\t\t\tconst item = { number, ...agent };\n\t\t\t\t\t\tif (state === 'Unavailable') {\n\t\t\t\t\t\t\tunavailable.push(item);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tavailable.push(item);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn available.concat(unavailable);\n\t\t\t\t},\n\t\t\t\tnormalizePhone10(phone) {\n\t\t\t\t\tconst digits = String(phone || '').replace(/\\D+/g, '');\n\t\t\t\t\tif (digits.length <= 10) return digits;\n\t\t\t\t\treturn digits.slice(-10);\n\t\t\t\t},\n\t\t\t\tupdateContactFromWs(contact) {\n\t\t\t\t\tconst phone10 = this.normalizePhone10(contact?.number);\n\t\t\t\t\tif (!phone10) return;\n\t\t\t\t\tconst displayName = String(contact?.client || contact?.contact || '').trim();\n\t\t\t\t\tif (!displayName) return;\n\t\t\t\t\t// Vue2: ensure reactivity for new keys\n\t\t\t\t\tif (this.$set) {\n\t\t\t\t\t\tthis.$set(this.contactsByPhone10, phone10, displayName);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.contactsByPhone10[phone10] = displayName;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tgetClientNameByPhone(phone) {\n\t\t\t\t\tconst phone10 = this.normalizePhone10(phone);\n\t\t\t\t\treturn this.contactsByPhone10[phone10] || '';\n\t\t\t\t},\n\t\t\t\tgetClientHeader(phone) {\n\t\t\t\t\tconst client = this.getClientNameByPhone(phone);\n\t\t\t\t\tif (!client) return phone;\n\t\t\t\t\treturn `${client} <${phone}>`;\n\t\t\t\t},\n\t\t\t\thasClientByPhone(phone) {\n\t\t\t\t\treturn !!this.getClientNameByPhone(phone);\n\t\t\t\t},\n\t\t\t\tformatElapsedTime(enterTime) {\n\t\t\t\t\t// Make this method reactive to the UI ticker.\n\t\t\t\t\tvoid this.nowTick;\n\t\t\t\t\treturn window[className].formatElapsedTime(enterTime);\n\t\t\t\t},\n\t\t\t\tnormalizeAgentCards() {\n\t\t\t\t\tif (!this.$el) return;\n\t\t\t\t\tvar self = this;\n\n\t\t\t\t\t// Cleanup artifacts from previous experiments (placeholders/spacers).\n\t\t\t\t\tvar artifacts = this.$el.querySelectorAll('.agent-peer-placeholder, .agent-peer-spacer');\n\t\t\t\t\tartifacts.forEach(function(el) { el.remove(); });\n\n\t\t\t\t\t// Dense layout (masonry) that still fills left-to-right:\n\t\t\t\t\t// flex-wrap can't place items into vertical gaps under tall cards.\n\t\t\t\t\tthis.ensureAgentCardsGridMasonry();\n\n\t\t\t\t\t// Process all agent card containers (one per queue block)\n\t\t\t\t\tvar cardsContainers = this.$el.querySelectorAll('.ui.cards.agent-cards');\n\t\t\t\t\tcardsContainers.forEach(function(cardsContainer) {\n\t\t\t\t\t\tcardsContainer.style.alignItems = 'flex-start';\n\t\t\t\t\t\tcardsContainer.style.alignContent = 'flex-start';\n\t\t\t\t\t});\n\n\t\t\t\t\tvar cards = this.$el.querySelectorAll('.ui.cards.agent-cards > .ui.card.agent-card');\n\t\t\t\t\tcards.forEach(function(card) {\n\t\t\t\t\t\tcard.style.alignSelf = 'flex-start';\n\t\t\t\t\t});\n\n\t\t\t\t\t// Semantic UI makes .header bigger than normal text; we need same font size.\n\t\t\t\t\tvar headers = this.$el.querySelectorAll('.ui.card.agent-card .header.agent-card-header');\n\t\t\t\t\theaders.forEach(function(el) {\n\t\t\t\t\t\tel.style.fontSize = '1em';\n\t\t\t\t\t\tel.style.lineHeight = '1.2';\n\t\t\t\t\t\tel.style.display = 'flex';\n\t\t\t\t\t\tel.style.alignItems = 'center';\n\t\t\t\t\t\tel.style.gap = '0.5em';\n\t\t\t\t\t\tel.style.whiteSpace = 'nowrap';\n\t\t\t\t\t});\n\n\t\t\t\t\tvar metas = this.$el.querySelectorAll('.ui.card.agent-card .meta.agent-peer');\n\t\t\t\t\tmetas.forEach(function(el) {\n\t\t\t\t\t\tel.style.fontSize = '1em';\n\t\t\t\t\t\tel.style.lineHeight = '1.2';\n\t\t\t\t\t});\n\n\t\t\t\t\t// Normalize label/name typography so they have same text height.\n\t\t\t\t\tvar numLabels = this.$el.querySelectorAll('.ui.card.agent-card .agent-num-label');\n\t\t\t\t\tnumLabels.forEach(function(el) {\n\t\t\t\t\t\tel.style.fontSize = '1em';\n\t\t\t\t\t\tel.style.lineHeight = '1.2';\n\t\t\t\t\t\tel.style.display = 'inline-flex';\n\t\t\t\t\t\tel.style.alignItems = 'center';\n\t\t\t\t\t\tel.style.paddingTop = '0';\n\t\t\t\t\t\tel.style.paddingBottom = '0';\n\t\t\t\t\t\t// Allow label to shrink (otherwise long numbers force card wider than 180px)\n\t\t\t\t\t\tel.style.flex = '0 1 auto';\n\t\t\t\t\t\tel.style.minWidth = '0';\n\t\t\t\t\t\tel.style.maxWidth = '14ch';\n\t\t\t\t\t\tel.style.overflow = 'hidden';\n\t\t\t\t\t\tel.style.textOverflow = 'ellipsis';\n\t\t\t\t\t\tel.style.whiteSpace = 'nowrap';\n\t\t\t\t\t});\n\t\t\t\t\tvar names = this.$el.querySelectorAll('.ui.card.agent-card .agent-name');\n\t\t\t\t\tnames.forEach(function(el) {\n\t\t\t\t\t\tel.style.lineHeight = '1.2';\n\t\t\t\t\t\t// Ellipsis for long names (e.g. \"Салтыков-Щедрин\")\n\t\t\t\t\t\tel.style.minWidth = '0';\n\t\t\t\t\t\tel.style.flex = '1 1 auto';\n\t\t\t\t\t\tel.style.overflow = 'hidden';\n\t\t\t\t\t\tel.style.textOverflow = 'ellipsis';\n\t\t\t\t\t\tel.style.whiteSpace = 'nowrap';\n\t\t\t\t\t});\n\n\t\t\t\t\t// Grid masonry needs row-span calculation after layout.\n\t\t\t\t\trequestAnimationFrame(function() {\n\t\t\t\t\t\trequestAnimationFrame(function() {\n\t\t\t\t\t\t\tself.layoutAgentCardsGridMasonry();\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tadjustAgentCardsGap() {\n\t\t\t\t\tif (!this.$el) return;\n\t\t\t\t\tconst container = this.$el.querySelector('.ui.cards.agent-cards');\n\t\t\t\t\tif (!container) return;\n\n\t\t\t\t\tconst cards = Array.from(container.querySelectorAll('.ui.card.agent-card'));\n\t\t\t\t\tif (!cards.length) return;\n\n\t\t\t\t\tconst tallCard = cards.find((c) => c.querySelector('.meta.agent-peer'));\n\t\t\t\t\tconst shortCard = cards.find((c) => !c.querySelector('.meta.agent-peer'));\n\t\t\t\t\tif (!tallCard || !shortCard) return;\n\n\t\t\t\t\tconst ht = tallCard.getBoundingClientRect().height;\n\t\t\t\t\tconst hs = shortCard.getBoundingClientRect().height;\n\t\t\t\t\tif (!ht || !hs) return;\n\n\t\t\t\t\t// From 2*(hs+g) = ht+g => g = ht - 2*hs\n\t\t\t\t\tlet gap = ht - 2 * hs;\n\t\t\t\t\tif (!Number.isFinite(gap)) return;\n\n\t\t\t\t\t// Clamp to sane range; negative means \"no extra gap needed\".\n\t\t\t\t\tgap = Math.max(0, Math.min(20, Math.round(gap)));\n\n\t\t\t\t\tcontainer.style.setProperty('--agent-card-gap', `${gap}px`);\n\t\t\t\t},\n\t\t\t\tadjustAgentCardsColumnCount() {\n\t\t\t\t\tif (!this.$el) return;\n\t\t\t\t\tconst container = this.$el.querySelector('.ui.cards.agent-cards.agent-cards-masonry');\n\t\t\t\t\tif (!container) return;\n\n\t\t\t\t\tconst w = container.clientWidth;\n\t\t\t\t\tif (!w) return;\n\n\t\t\t\t\t// Minimum acceptable card width in px (tune if needed)\n\t\t\t\t\tconst minCardWidth = 150;\n\n\t\t\t\t\tconst cs = window.getComputedStyle(container);\n\t\t\t\t\tconst gapRaw = cs.columnGap || cs.getPropertyValue('column-gap') || '16px';\n\t\t\t\t\tconst gapPx = parseFloat(gapRaw) || 16;\n\n\t\t\t\t\tconst count = Math.max(1, Math.min(12, Math.floor((w + gapPx) / (minCardWidth + gapPx))));\n\t\t\t\t\tcontainer.style.setProperty('--agent-card-col-count', String(count));\n\t\t\t\t},\n\t\t\t\tensureAgentCardsGridMasonry() {\n\t\t\t\t\tvar self = this;\n\t\t\t\t\tvar styleId = 'agent-cards-layout-style';\n\t\t\t\t\tvar styleEl = document.getElementById(styleId);\n\t\t\t\t\tif (!styleEl) {\n\t\t\t\t\t\tstyleEl = document.createElement('style');\n\t\t\t\t\t\tstyleEl.id = styleId;\n\t\t\t\t\t\tdocument.head.appendChild(styleEl);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Grid masonry: fills left-to-right and can pack items into gaps.\n\t\t\t\t\t// minmax(240px, 1fr) - карточки минимум 240px, растягиваются равномерно\n\t\t\t\t\tstyleEl.textContent = '\\\n.ui.cards.agent-cards.agent-cards-grid {\\\n display: grid !important;\\\n grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\\\n justify-content: start;\\\n gap: var(--agent-card-gap, 8px);\\\n grid-auto-rows: 1px;\\\n margin-bottom: 1em !important;\\\n}\\\n.ui.cards.agent-cards.agent-cards-grid > .ui.card.agent-card {\\\n width: 100% !important;\\\n min-width: 0;\\\n margin: 0 !important;\\\n overflow: hidden;\\\n align-self: start;\\\n}';\n\n\t\t\t\t\t// Process all agent card containers (one per queue block)\n\t\t\t\t\tvar cardsContainers = this.$el ? this.$el.querySelectorAll('.ui.cards.agent-cards') : [];\n\t\t\t\t\tcardsContainers.forEach(function(cardsContainer) {\n\t\t\t\t\t\tcardsContainer.classList.remove('agent-cards-masonry');\n\t\t\t\t\t\tcardsContainer.classList.remove('agent-cards-flex');\n\t\t\t\t\t\tcardsContainer.classList.add('agent-cards-grid');\n\t\t\t\t\t});\n\n\t\t\t\t\t// Bind once: relayout on resize.\n\t\t\t\t\tif (!this._agentCardsResizeBound) {\n\t\t\t\t\t\tthis._agentCardsResizeBound = true;\n\t\t\t\t\t\twindow.addEventListener('resize', function() {\n\t\t\t\t\t\t\tself.layoutAgentCardsGridMasonry();\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tlayoutAgentCardsGridMasonry() {\n\t\t\t\t\tif (!this.$el) return;\n\t\t\t\t\tvar self = this;\n\n\t\t\t\t\t// Process all grid containers (one per queue block)\n\t\t\t\t\tvar grids = this.$el.querySelectorAll('.ui.cards.agent-cards.agent-cards-grid');\n\t\t\t\t\tgrids.forEach(function(grid) {\n\t\t\t\t\t\tself.layoutSingleGridMasonry(grid);\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tlayoutSingleGridMasonry(grid) {\n\t\t\t\t\tif (!grid) return;\n\n\t\t\t\t\tvar cs = window.getComputedStyle(grid);\n\t\t\t\t\tvar rowHeight = parseFloat(cs.getPropertyValue('grid-auto-rows')) || 1;\n\t\t\t\t\tvar rowGap = parseFloat(cs.getPropertyValue('row-gap')) || parseFloat(cs.getPropertyValue('gap')) || 8;\n\n\t\t\t\t\tvar items = Array.from(grid.querySelectorAll('.ui.card.agent-card'));\n\t\t\t\t\tif (!items.length) return;\n\n\t\t\t\t\t// Reset row spans and min-heights to measure natural heights.\n\t\t\t\t\titems.forEach(function(item) {\n\t\t\t\t\t\titem.style.gridRowEnd = '';\n\t\t\t\t\t\titem.style.minHeight = '';\n\t\t\t\t\t});\n\n\t\t\t\t\tvar tall = items.filter(function(c) { return c.querySelector('.meta.agent-peer'); });\n\t\t\t\t\tvar short = items.filter(function(c) { return !c.querySelector('.meta.agent-peer'); });\n\n\t\t\t\t\t// If we don't have both types, just do normal masonry spans.\n\t\t\t\t\tif (!tall.length || !short.length) {\n\t\t\t\t\t\titems.forEach(function(item) {\n\t\t\t\t\t\t\tvar h = item.getBoundingClientRect().height;\n\t\t\t\t\t\t\tvar span = Math.max(1, Math.ceil((h + rowGap) / (rowHeight + rowGap)));\n\t\t\t\t\t\t\titem.style.gridRowEnd = 'span ' + span;\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar shortHeights = short.map(function(c) { return c.getBoundingClientRect().height; });\n\t\t\t\t\tvar tallHeights = tall.map(function(c) { return c.getBoundingClientRect().height; });\n\t\t\t\t\tvar hs = Math.max.apply(Math, shortHeights);\n\t\t\t\t\tvar ht = Math.max.apply(Math, tallHeights);\n\n\t\t\t\t\t// Want: 2*(hs + g) = (ht + g) => g = ht - 2*hs\n\t\t\t\t\tvar g = ht - 2 * hs;\n\t\t\t\t\tif (!Number.isFinite(g)) g = rowGap;\n\t\t\t\t\tg = Math.max(0, Math.min(24, Math.round(g)));\n\n\t\t\t\t\t// Apply gap and enforce min-heights so the relation holds visually.\n\t\t\t\t\tgrid.style.setProperty('--agent-card-gap', g + 'px');\n\n\t\t\t\t\tvar shortH = Math.round(hs);\n\t\t\t\t\tvar tallH = Math.round(Math.max(ht, 2 * hs + g));\n\t\t\t\t\tshort.forEach(function(c) { c.style.minHeight = shortH + 'px'; });\n\t\t\t\t\ttall.forEach(function(c) { c.style.minHeight = tallH + 'px'; });\n\n\t\t\t\t\t// Now compute row spans from final rendered heights.\n\t\t\t\t\tvar effectiveGap = g;\n\t\t\t\t\titems.forEach(function(item) {\n\t\t\t\t\t\tvar h = item.getBoundingClientRect().height;\n\t\t\t\t\t\tvar span = Math.max(1, Math.ceil((h + effectiveGap) / (rowHeight + effectiveGap)));\n\t\t\t\t\t\titem.style.gridRowEnd = 'span ' + span;\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tgetSrcNumForAgent(agentNumber) {\n\t\t\t\t\tlet result = '-';\n\t\t\t\t\tlet answeredFound = false;\n\t\t\t\t\tfor (const call of this.allCalls) {\n\t\t\t\t\t\tif(call.dst_num === agentNumber){\n\t\t\t\t\t\t\tansweredFound = true;\n\t\t\t\t\t\t\tresult = call.src_num;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (call.calledChannels && Array.isArray(call.calledChannels)) {\n\t\t\t\t\t\t\tconst match = call.calledChannels.find(ch => ch.number === agentNumber);\n\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\tresult = call.src_num;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (call.bridgeChannels && Array.isArray(call.bridgeChannels)) {\n\t\t\t\t\t\t\tconst match = call.bridgeChannels.find(ch => (ch.src_num === agentNumber || ch.dst_num === agentNumber));\n\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\tif(match.src_num === agentNumber){\n\t\t\t\t\t\t\t\t\tresult = match.dst_num;\n\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\tresult = match.src_num;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tansweredFound = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif(answeredFound === false){\n\t\t\t\t\t\tfor (let i = 0; i < this.allCalls.length; i++) {\n\t\t\t\t\t\t\tconst tmpCall = this.allCalls[i];\n\t\t\t\t\t\t\tif(tmpCall.src_num === agentNumber){\n\t\t\t\t\t\t\t\t// Исходящий\n\t\t\t\t\t\t\t\tif(tmpCall.dst_num === ''){\n\t\t\t\t\t\t\t\t\t// не ответа, дозвон.\n\t\t\t\t\t\t\t\t\tif (tmpCall.calledChannels && Array.isArray(tmpCall.calledChannels) && tmpCall.calledChannels.length) {\n\t\t\t\t\t\t\t\t\t\tconst match = tmpCall.calledChannels.find(ch => ch.number !== agentNumber);\n\t\t\t\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\t\t\t\tresult = match.number;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}else if(tmpCall.spyer){\n\t\t\t\t\t\t\t\t\t\t// шпионит за номером.\n\t\t\t\t\t\t\t\t\t\tresult = tmpCall.spy_num;\n\t\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\t\t// нет вызываемых каналов, возможно это вызов на приложение / ivr.\n\t\t\t\t\t\t\t\t\t\tresult = tmpCall.exten;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\t\tresult = tmpCall.dst_num;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}else if(tmpCall.dst_num === agentNumber){\n\t\t\t\t\t\t\t\t// Входящий на агента, отвечен.\n\t\t\t\t\t\t\t\tresult = tmpCall.src_num;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\t\tif (tmpCall.calledChannels && Array.isArray(tmpCall.calledChannels)) {\n\t\t\t\t\t\t\t\t\tconst match = tmpCall.calledChannels.find(ch => ch.number === agentNumber);\n\t\t\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\t\t\tresult = tmpCall.src_num;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn result;\n\t\t\t\t},\n\t\t\t\thasPeerPhone(agentNumber) {\n\t\t\t\t\tconst phone = String(this.getSrcNumForAgent(agentNumber) || '').trim();\n\t\t\t\t\treturn phone !== '' && phone !== '-' && phone !== '—';\n\t\t\t\t},\n\t\t\t\tgetPeerPhoneLabel(agentNumber) {\n\t\t\t\t\tconst phone = String(this.getSrcNumForAgent(agentNumber) || '').trim();\n\t\t\t\t\treturn this.hasPeerPhone(agentNumber) ? phone : '—';\n\t\t\t\t},\n\t\t\t\tgetPeerNameLabel(agentNumber) {\n\t\t\t\t\t// Use cached contacts (WS + IndexedDB) to show client name for peer phone.\n\t\t\t\t\tconst phone = this.getPeerPhoneLabel(agentNumber);\n\t\t\t\t\tconst client = this.getClientNameByPhone(phone);\n\t\t\t\t\treturn client || '—';\n\t\t\t\t}\n\t\t\t},\n\t\t\tdata: {\n\t\t\t\t\"minWaitVisible\": 30,\n\t\t\t\t\"nowTick\": 0,\n\t\t\t\t\"queues\": {},\n\t\t\t\t\"allCalls\": [],\n\t\t\t\t\"selectedQueueIds\": [],\n\t\t\t\t\"lastActiveCallsPayload\": null,\n\t\t\t\t\"contactsByPhone10\": {}\n\t\t\t},\n\t\t});\n\t\twindow[className].applyContactsCacheToQueueWidget();\n\n\t\twindow[className].$callsWidget = new Vue({\n\t\t\tel: '#calls',\n\t\t\tdelimiters: [\"<%\",\"%>\"],\n\t\t\tdata: {\n\t\t\t\t\"minWaitVisible\": 30,\n\t\t\t\t\"nowTick\": 0,\n\t\t\t\tuserNumber: userNumber,\n\t\t\t\tfullAccess: ($('#fullAccess').val() === \"1\" || userNumber === ''),\n\t\t\t\tcalls: [\n\t\t\t\t]\n\t\t\t},\n\t\t\tmethods: {\n\t\t\t\tcallIsVisible(call){\n\t\t\t\t\tvoid this.nowTick;\n\t\t\t\t\tif(call.dst_chan==='' && call.queueData.EnterTime !== undefined ){\n\t\t\t\t\t\treturn this.minWaitVisible <= this.getWaitTime(call);\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t},\n\t\t\t\tformatTimestampToTime(timestamp) {\n\t\t\t\t\t// Если timestamp строка — приводим к числу\n\t\t\t\t\tconst ts = typeof timestamp === 'string' ? parseFloat(timestamp) : timestamp;\n\n\t\t\t\t\t// Если timestamp в секундах (меньше 1e10), умножаем на 1000\n\t\t\t\t\tconst ms = ts < 1e10 ? ts * 1000 : ts;\n\n\t\t\t\t\tconst date = new Date(ms);\n\n\t\t\t\t\tconst hours = String(date.getHours()).padStart(2, '0');\n\t\t\t\t\tconst minutes = String(date.getMinutes()).padStart(2, '0');\n\t\t\t\t\tconst seconds = String(date.getSeconds()).padStart(2, '0');\n\n\t\t\t\t\treturn `${hours}:${minutes}:${seconds}`;\n\t\t\t\t},\n\t\t\t\tgetWaitTime(call){\n\t\t\t\t\tvoid this.nowTick;\n\t\t\t\t\tlet answer = Math.floor(Date.now() / 1000);\n\t\t\t\t\tif(call.answer !== ''){\n\t\t\t\t\t\tanswer = call.answer\n\t\t\t\t\t}\n\t\t\t\t\treturn window[className].secondToTime(answer - call.start);\n\t\t\t\t},\n\t\t\t\tgetCallTime(call){\n\t\t\t\t\tvoid this.nowTick;\n\t\t\t\t\tif(call.answer === ''){\n\t\t\t\t\t\treturn '-';\n\t\t\t\t\t}\n\t\t\t\t\treturn window[className].formatElapsedTime(call.answer);\n\t\t\t\t},\n\t\t\t\tupdatedCallsFromResponse(data) {\n\t\t\t\t\tthis.minWaitVisible = 1*$('#minWaitVisibleValue').val();\n\t\t\t\t\t// Проходим по всем очередям\n\t\t\t\t\tfor (const queueId in data.queues) {\n\t\t\t\t\t\tconst queue = data.queues[queueId];\n\t\t\t\t\t\t// Проверяем, есть ли у очереди поле calls и является ли оно массивом\n\t\t\t\t\t\tif (Array.isArray(queue.calls)) {\n\t\t\t\t\t\t\t// Добавляем все вызовы из этой очереди в общий массив\n\t\t\t\t\t\t\tdata.calls.push(...queue.calls);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.calls = data.calls;\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tExtensions.updatePhonesRepresent('need-update');\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tformatElapsedTime(enterTime) {\n\t\t\t\t\treturn window[className].formatElapsedTime(enterTime);\n\t\t\t\t},\n\t\t\t\tgetClientHeader(phone) {\n\t\t\t\t\tconst q = window[className].$widgetQueues;\n\t\t\t\t\tif (q && typeof q.getClientHeader === 'function') {\n\t\t\t\t\t\treturn q.getClientHeader(phone);\n\t\t\t\t\t}\n\t\t\t\t\treturn phone;\n\t\t\t\t},\n\t\t\t\thangupAction(event) {\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'hangup', ch1: target.attr('data-ch1'), ch2: target.attr('data-ch2')});\n\t\t\t\t},\n\t\t\t\tjoinAction(event) {\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\tif(this.userNumber === ''){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'join', ch1: target.attr('data-ch1'), ch2: target.attr('data-ch2'), number: this.userNumber});\n\t\t\t\t},\n\t\t\t\twhisperAction(event){\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\tif(this.userNumber === ''){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet spChannel = target.attr('data-ch1');\n\t\t\t\t\tif('incoming' === target.attr('data-call-type')){\n\t\t\t\t\t\tspChannel = target.attr('data-ch2');\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'whisper', ch1: spChannel, ch2: '', number: this.userNumber});\n\t\t\t\t},\n\t\t\t\tlistenAction(event){\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\tif(this.userNumber === ''){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'listen', ch1: target.attr('data-ch1'), ch2: target.attr('data-ch2'), number: this.userNumber});\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\twindow[className].$widget = new Vue({\n\t\t\tel: '#app',\n\t\t\tdelimiters: [\"<%\",\"%>\"],\n\t\t\tdata: {\n\t\t\t\tuserNumber: userNumber,\n\t\t\t\tfullAccess: ($('#fullAccess').val() === \"1\" || userNumber === ''),\n\t\t\t\tcalls: [\n\t\t\t\t]\n\t\t\t},\n\t\t\tmethods: {\n\t\t\t\tupdatedCallsFromResponse(lines) {\n\t\t\t\t\tthis.calls = lines;\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tExtensions.updatePhonesRepresent('need-update');\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\thangupAction(event) {\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'hangup', ch1: target.attr('data-ch1'), ch2: target.attr('data-ch2')});\n\t\t\t\t},\n\t\t\t\tjoinAction(event) {\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\tif(this.userNumber === ''){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'join', ch1: target.attr('data-ch1'), ch2: target.attr('data-ch2'), number: this.userNumber});\n\t\t\t\t},\n\t\t\t\twhisperAction(event){\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\tif(this.userNumber === ''){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet spChannel = target.attr('data-ch1');\n\t\t\t\t\tif('incoming' === target.attr('data-call-type')){\n\t\t\t\t\t\tspChannel = target.attr('data-ch2');\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'whisper', ch1: spChannel, ch2: '', number: this.userNumber});\n\t\t\t\t},\n\t\t\t\tlistenAction(event){\n\t\t\t\t\tlet target = $(event.target);\n\t\t\t\t\tif(target.attr('data-ch1') === undefined){\n\t\t\t\t\t\ttarget = $(event.target).parent();\n\t\t\t\t\t}\n\t\t\t\t\tif(this.userNumber === ''){\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\twindow[className].executeCallAction({action: 'listen', ch1: target.attr('data-ch1'), ch2: target.attr('data-ch2'), number: this.userNumber});\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\twindow[className].$checkBoxes.checkbox();\n\t\twindow[className].$dropDowns.dropdown();\n\t\twindow[className].initializeForm();\n\t\t$('.menu .item').tab();\n\t\twindow[className].startUiTicker();\n\t\t//////\n\t\t// Удаляем отступы контейнера.\n\t\t$('#main-content-container').removeClass('container');\n\t\t$('#module-status-toggle-segment').hide();\n\t\t$('.ui.clearing.hidden.divider').remove();\n\t\t// Окончание форматирования базовой страницы\n\t\t//////\n\t\tthis.startPollingActiveCalls();\n\n\t\t// Allow settings to be saved after initialization\n\t\tsetTimeout(function() {\n\t\t\twindow[className].isInit = false;\n\t\t}, 1000);\n\t},\n\tstartUiTicker() {\n\t\tif (this._uiTicker) return;\n\t\tthis._uiTicker = setInterval(() => {\n\t\t\tconst now = Date.now();\n\t\t\tif (window[className].$widgetQueues) {\n\t\t\t\twindow[className].$widgetQueues.nowTick = now;\n\t\t\t}\n\t\t\tif (window[className].$callsWidget) {\n\t\t\t\twindow[className].$callsWidget.nowTick = now;\n\t\t\t}\n\t\t}, 1000);\n\t},\n\tstartPollingActiveCalls() {\n\t\tif (this._activeCallsPollTimer) return;\n\t\twindow[className].updateLines();\n\t\tthis._activeCallsPollTimer = setInterval(window[className].updateLines, 2000);\n\t},\n\tstopPollingActiveCalls() {\n\t\tif (!this._activeCallsPollTimer) return;\n\t\tclearInterval(this._activeCallsPollTimer);\n\t\tthis._activeCallsPollTimer = null;\n\t},\n\tasync initContactsCache() {\n\t\ttry {\n\t\t\tthis._contactsCacheByPhone10 = await this.idbLoadAllContacts();\n\t\t\tthis.applyContactsCacheToQueueWidget();\n\t\t} catch (e) {\n\t\t\tconsole.log('contacts cache init error', e);\n\t\t\tthis._contactsCacheByPhone10 = {};\n\t\t}\n\t},\n\tapplyContactsCacheToQueueWidget() {\n\t\tif (!this._contactsCacheByPhone10) return;\n\t\tif (!window[className].$widgetQueues) return;\n\t\tfor (const [phone10, client] of Object.entries(this._contactsCacheByPhone10)) {\n\t\t\tif (window[className].$widgetQueues.$set) {\n\t\t\t\twindow[className].$widgetQueues.$set(window[className].$widgetQueues.contactsByPhone10, phone10, client);\n\t\t\t} else {\n\t\t\t\twindow[className].$widgetQueues.contactsByPhone10[phone10] = client;\n\t\t\t}\n\t\t}\n\t},\n\tidbOpenContactsDb() {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\ttry {\n\t\t\t\tconst req = indexedDB.open('ModuleMonitorActiveCalls', 1);\n\t\t\t\treq.onupgradeneeded = () => {\n\t\t\t\t\tconst db = req.result;\n\t\t\t\t\tif (!db.objectStoreNames.contains('contactsByPhone10')) {\n\t\t\t\t\t\tdb.createObjectStore('contactsByPhone10', { keyPath: 'phone10' });\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\treq.onsuccess = () => resolve(req.result);\n\t\t\t\treq.onerror = () => reject(req.error);\n\t\t\t} catch (e) {\n\t\t\t\treject(e);\n\t\t\t}\n\t\t});\n\t},\n\tasync idbPutContact(phone10, client) {\n\t\tconst db = await this.idbOpenContactsDb();\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst tx = db.transaction('contactsByPhone10', 'readwrite');\n\t\t\tconst store = tx.objectStore('contactsByPhone10');\n\t\t\tstore.put({ phone10, client, updatedAt: Date.now() });\n\t\t\ttx.oncomplete = () => { db.close(); resolve(); };\n\t\t\ttx.onerror = () => { const err = tx.error; db.close(); reject(err); };\n\t\t});\n\t},\n\tasync idbLoadAllContacts() {\n\t\tconst db = await this.idbOpenContactsDb();\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst tx = db.transaction('contactsByPhone10', 'readwrite');\n\t\t\tconst store = tx.objectStore('contactsByPhone10');\n\t\t\tconst req = store.getAll();\n\t\t\treq.onsuccess = () => {\n\t\t\t\tconst map = {};\n\t\t\t\tconst now = Date.now();\n\t\t\t\tconst ttlMs = Number(this.contactsCacheTtlMs) || (120 * 60 * 1000);\n\t\t\t\tfor (const row of req.result || []) {\n\t\t\t\t\tconst phone10 = row?.phone10;\n\t\t\t\t\tconst client = row?.client;\n\t\t\t\t\tconst updatedAt = Number(row?.updatedAt) || 0;\n\t\t\t\t\tconst isFresh = phone10 && client && updatedAt > 0 && (now - updatedAt) <= ttlMs;\n\t\t\t\t\tif (isFresh) {\n\t\t\t\t\t\tmap[phone10] = client;\n\t\t\t\t\t} else if (phone10) {\n\t\t\t\t\t\t// Cleanup expired/broken records\n\t\t\t\t\t\ttry { store.delete(phone10); } catch (e) { /* ignore */ }\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttx.oncomplete = () => { db.close(); resolve(map); };\n\t\t\t\ttx.onerror = () => { const err = tx.error; db.close(); reject(err); };\n\t\t\t};\n\t\t\treq.onerror = () => { const err = req.error; db.close(); reject(err); };\n\t\t});\n\t},\n\trequestBackendEnable() {\n\t\t$.api({\n\t\t\turl: window[className].backendEnableUrl,\n\t\t\ton: 'now',\n\t\t\tmethod: 'POST',\n\t\t\tonSuccess(response) {\n\t\t\t\tconsole.log('backandEnable response', response);\n\t\t\t\tconst accessToken = response?.data?.access_token;\n\t\t\t\tconst refreshToken = response?.data?.refresh_token;\n\t\t\t\tif (accessToken && refreshToken) {\n\t\t\t\t\twindow[className].setAuthTokens(accessToken, refreshToken);\n\t\t\t\t\twindow[className].connectContactsWs();\n\t\t\t\t\twindow[className].connectActiveCallsWs();\n\t\t\t\t}\n\t\t\t},\n\t\t\tonFailure(response) {\n\t\t\t\tconsole.log('backandEnable failure', response);\n\t\t\t},\n\t\t\tonError(errorMessage, element, xhr) {\n\t\t\t\tconsole.log('backandEnable error', errorMessage, xhr);\n\t\t\t}\n\t\t});\n\t},\n\tsetAuthTokens(accessToken, refreshToken) {\n\t\tthis._authTokens = this._authTokens || {};\n\t\tthis._authTokens.access_token = accessToken;\n\t\tthis._authTokens.refresh_token = refreshToken;\n\t\tthis._authTokens.exp = this.getJwtExp(accessToken);\n\t},\n\tgetJwtExp(token) {\n\t\ttry {\n\t\t\tif (!token || typeof token !== 'string') return 0;\n\t\t\tconst parts = token.split('.');\n\t\t\tif (parts.length < 2) return 0;\n\t\t\tconst payloadB64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n\t\t\tconst padded = payloadB64 + '='.repeat((4 - (payloadB64.length % 4)) % 4);\n\t\t\tconst json = atob(padded);\n\t\t\tconst payload = JSON.parse(json);\n\t\t\treturn Number(payload?.exp) || 0;\n\t\t} catch (e) {\n\t\t\treturn 0;\n\t\t}\n\t},\n\tisAccessTokenExpired(skewSeconds = 0) {\n\t\tconst exp = Number(this._authTokens?.exp) || 0;\n\t\tif (!exp) return false; // unknown exp -> don't force refresh\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\treturn now + skewSeconds >= exp;\n\t},\n\tscheduleContactsWsTokenRefresh() {\n\t\t// Proactively refresh token shortly before expiry by re-requesting backendEnable.\n\t\tif (this._contactsWsTokenTimer) {\n\t\t\tclearTimeout(this._contactsWsTokenTimer);\n\t\t\tthis._contactsWsTokenTimer = null;\n\t\t}\n\t\tconst exp = Number(this._authTokens?.exp) || 0;\n\t\tif (!exp) return;\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\tconst refreshInSec = Math.max(1, exp - now - 15); // 15s before exp\n\t\tthis._contactsWsTokenTimer = setTimeout(() => {\n\t\t\t// Re-get tokens and reconnect WS\n\t\t\tthis.requestBackendEnable();\n\t\t}, refreshInSec * 1000);\n\t},\n\tscheduleContactsWsReconnect(reason, forceReAuth = false) {\n\t\tif (this._contactsWsReconnectTimer) {\n\t\t\tclearTimeout(this._contactsWsReconnectTimer);\n\t\t\tthis._contactsWsReconnectTimer = null;\n\t\t}\n\t\tthis._contactsWsReconnectAttempt = (this._contactsWsReconnectAttempt || 0) + 1;\n\t\tconst delay = Math.min(30000, 1000 * Math.pow(2, Math.min(5, this._contactsWsReconnectAttempt - 1)));\n\t\tthis._contactsWsReconnectTimer = setTimeout(() => {\n\t\t\tif (forceReAuth || this.isAccessTokenExpired(5)) {\n\t\t\t\tthis.requestBackendEnable();\n\t\t\t} else {\n\t\t\t\tthis.connectContactsWs();\n\t\t\t}\n\t\t}, delay);\n\t\tconsole.log('contacts ws reconnect scheduled', { reason, delayMs: delay });\n\t},\n\tconnectContactsWs() {\n\t\ttry {\n\t\t\tconst accessToken = this._authTokens?.access_token;\n\t\t\tif (!accessToken) return;\n\n\t\t\t// Avoid reconnecting if already connected/connecting\n\t\t\tif (this._contactsWs && (this._contactsWs.readyState === WebSocket.OPEN || this._contactsWs.readyState === WebSocket.CONNECTING)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Reset backoff on explicit connect attempt\n\t\t\tthis._contactsWsReconnectAttempt = 0;\n\n\t\t\tconst wsProto = window.location.protocol === 'https:' ? 'wss' : 'ws';\n\t\t\tconst wsHost = window.location.host; // host:port of current page\n\t\t\tconst tokenParam = encodeURIComponent(accessToken);\n\t\t\tconst wsUrl = `${wsProto}://${wsHost}/pbxcore/api/module-softphone-backend/v1/sub/contacts?authorization=${tokenParam}`;\n\n\t\t\tthis._contactsWs = new WebSocket(wsUrl);\n\t\t\tthis._contactsWs.onopen = () => {\n\t\t\t\tconsole.log('contacts ws connected');\n\t\t\t\tthis.scheduleContactsWsTokenRefresh();\n\t\t\t};\n\t\t\tthis._contactsWs.onmessage = (event) => {\n\t\t\t\tthis.handleContactsWsMessage(event?.data);\n\t\t\t};\n\t\t\tthis._contactsWs.onerror = (event) => {\n\t\t\t\tconsole.log('contacts ws error', event);\n\t\t\t};\n\t\t\tthis._contactsWs.onclose = (event) => {\n\t\t\t\tconst code = event?.code;\n\t\t\t\tconst reason = event?.reason;\n\t\t\t\tconsole.log('contacts ws closed', { code, reason });\n\n\t\t\t\tif (this._contactsWsTokenTimer) {\n\t\t\t\t\tclearTimeout(this._contactsWsTokenTimer);\n\t\t\t\t\tthis._contactsWsTokenTimer = null;\n\t\t\t\t}\n\n\t\t\t\t// 1000 = normal close -> reconnect; auth closes vary by server implementation.\n\t\t\t\tconst authCloseCodes = new Set([1008, 4001, 4401, 4403]);\n\t\t\t\tconst forceReAuth = authCloseCodes.has(code) || this.isAccessTokenExpired(0);\n\t\t\t\tthis.scheduleContactsWsReconnect('close', forceReAuth);\n\t\t\t};\n\t\t} catch (e) {\n\t\t\tconsole.log('contacts ws init error', e);\n\t\t\tthis.scheduleContactsWsReconnect('init_error', this.isAccessTokenExpired(0));\n\t\t}\n\t},\n\tscheduleActiveCallsWsReconnect(reason, forceReAuth = false) {\n\t\tif (this._activeCallsWsReconnectTimer) {\n\t\t\tclearTimeout(this._activeCallsWsReconnectTimer);\n\t\t\tthis._activeCallsWsReconnectTimer = null;\n\t\t}\n\t\tthis._activeCallsWsReconnectAttempt = (this._activeCallsWsReconnectAttempt || 0) + 1;\n\t\tconst delay = Math.min(30000, 1000 * Math.pow(2, Math.min(5, this._activeCallsWsReconnectAttempt - 1)));\n\t\tthis._activeCallsWsReconnectTimer = setTimeout(() => {\n\t\t\tif (forceReAuth || this.isAccessTokenExpired(5)) {\n\t\t\t\tthis.requestBackendEnable();\n\t\t\t} else {\n\t\t\t\tthis.connectActiveCallsWs();\n\t\t\t}\n\t\t}, delay);\n\t\tconsole.log('active-calls ws reconnect scheduled', { reason, delayMs: delay });\n\t},\n\tconnectActiveCallsWs() {\n\t\ttry {\n\t\t\tconst accessToken = this._authTokens?.access_token;\n\t\t\tif (!accessToken) return;\n\n\t\t\t// Avoid reconnecting if already connected/connecting\n\t\t\tif (this._activeCallsWs && (this._activeCallsWs.readyState === WebSocket.OPEN || this._activeCallsWs.readyState === WebSocket.CONNECTING)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Reset backoff on explicit connect attempt\n\t\t\tthis._activeCallsWsReconnectAttempt = 0;\n\n\t\t\t// Token exists -> use WS, disable polling fallback\n\t\t\tthis.stopPollingActiveCalls();\n\n\t\t\tconst wsProto = window.location.protocol === 'https:' ? 'wss' : 'ws';\n\t\t\tconst wsHost = window.location.host; // host:port of current page\n\t\t\tconst tokenParam = encodeURIComponent(accessToken);\n\t\t\tconst wsUrl = `${wsProto}://${wsHost}/pbxcore/api/module-softphone-backend/v1/sub/active-calls?authorization=${tokenParam}`;\n\n\t\t\tthis._activeCallsWs = new WebSocket(wsUrl);\n\t\t\tthis._activeCallsWs.onopen = () => {\n\t\t\t\tconsole.log('active-calls ws connected');\n\t\t\t\t// Reuse the same token refresh timer (it triggers requestBackendEnable)\n\t\t\t\tthis.scheduleContactsWsTokenRefresh();\n\t\t\t};\n\t\t\tthis._activeCallsWs.onmessage = (event) => {\n\t\t\t\tthis.handleActiveCallsWsMessage(event?.data);\n\t\t\t};\n\t\t\tthis._activeCallsWs.onerror = (event) => {\n\t\t\t\tconsole.log('active-calls ws error', event);\n\t\t\t};\n\t\t\tthis._activeCallsWs.onclose = (event) => {\n\t\t\t\tconst code = event?.code;\n\t\t\t\tconst reason = event?.reason;\n\t\t\t\tconsole.log('active-calls ws closed', { code, reason });\n\n\t\t\t\t// Auth closes vary by server implementation.\n\t\t\t\tconst authCloseCodes = new Set([1008, 4001, 4401, 4403]);\n\t\t\t\tconst forceReAuth = authCloseCodes.has(code) || this.isAccessTokenExpired(0);\n\t\t\t\tthis.scheduleActiveCallsWsReconnect('close', forceReAuth);\n\t\t\t};\n\t\t} catch (e) {\n\t\t\tconsole.log('active-calls ws init error', e);\n\t\t\tthis.scheduleActiveCallsWsReconnect('init_error', this.isAccessTokenExpired(0));\n\t\t}\n\t},\n\thandleContactsWsMessage(data) {\n\t\ttry {\n\t\t\tif (!data) return;\n\t\t\tconst parsed = typeof data === 'string' ? JSON.parse(data) : data;\n\t\t\tconst items = Array.isArray(parsed) ? parsed : [parsed];\n\t\t\tfor (const item of items) {\n\t\t\t\tconst digits = String(item?.number || '').replace(/\\D+/g, '');\n\t\t\t\tconst phone10 = digits.length <= 10 ? digits : digits.slice(-10);\n\t\t\t\tconst displayName = String(item?.client || item?.contact || '').trim();\n\t\t\t\tif (phone10 && displayName) {\n\t\t\t\t\tthis._contactsCacheByPhone10 = this._contactsCacheByPhone10 || {};\n\t\t\t\t\tthis._contactsCacheByPhone10[phone10] = displayName;\n\t\t\t\t\tthis.idbPutContact(phone10, displayName).catch((e) => console.log('contacts cache save error', e));\n\t\t\t\t}\n\t\t\t\tif (window[className].$widgetQueues) {\n\t\t\t\t\twindow[className].$widgetQueues.updateContactFromWs(item);\n\t\t\t\t}\n\t\t\t\t// Calls table is a separate Vue instance and reads client name via $widgetQueues.\n\t\t\t\t// Vue can't track cross-instance dependency, so force re-render on contact update.\n\t\t\t\tif (window[className].$callsWidget && typeof window[className].$callsWidget.$forceUpdate === 'function') {\n\t\t\t\t\twindow[className].$callsWidget.$forceUpdate();\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.log('contacts ws parse error', e);\n\t\t}\n\t},\n\thandleActiveCallsWsMessage(data) {\n\t\ttry {\n\t\t\tif (!data) return;\n\t\t\tconst parsed = typeof data === 'string' ? JSON.parse(data) : data;\n\t\t\tconst payload = parsed?.queues ? parsed : (parsed?.data?.queues ? parsed.data : null);\n\t\t\tif (!payload) return;\n\t\t\tif (!window[className].$widgetQueues || !window[className].$callsWidget) return;\n\n\t\t\twindow[className].$widgetQueues.updatedCallsFromResponse(payload);\n\t\t\twindow[className].$callsWidget.updatedCallsFromResponse(payload);\n\t\t} catch (e) {\n\t\t\tconsole.log('active-calls ws parse error', e);\n\t\t}\n\t},\n\tformatElapsedTime(enterTime) {\n\t\tif (!enterTime) return '—';\n\n\t\tconst now = Math.floor(Date.now() / 1000);\n\t\tconst diffSeconds = now - enterTime;\n\n\t\treturn window[className].secondToTime(diffSeconds);\n\t},\n\tsecondToTime(diffSeconds){\n\t\tif (diffSeconds < 0) return '0';\n\t\t// Форматируем: чч:мм:сс или мм:сс, или просто секунды\n\t\tconst hours = Math.floor(diffSeconds / 3600);\n\t\tconst minutes = Math.floor((diffSeconds % 3600) / 60);\n\t\tconst seconds = Math.round(diffSeconds % 60);\n\t\tif (hours > 0) {\n\t\t\treturn `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;\n\t\t} else if (minutes > 0) {\n\t\t\treturn `${minutes}:${seconds.toString().padStart(2, '0')}`;\n\t\t} else {\n\t\t\treturn `${seconds}`;\n\t\t}\n\t},\n\tonChangeSetting(settingName, value) {\n\t\tif(window[className].isInit){\n\t\t\treturn;\n\t\t}\n\t\tvar data = {};\n\t\tdata[settingName] = value;\n\t\t$.api({\n\t\t\turl: window[className].saveUserActionUrl,\n\t\t\ton: 'now',\n\t\t\tmethod: 'POST',\n\t\t\tdata: data,\n\t\t\tsuccessTest: function(response) {\n\t\t\t\treturn response !== undefined && Object.keys(response).length > 0 && response.success === true;\n\t\t\t},\n\t\t\tonSuccess: function(response) {\n\t\t\t\tif(settingName === 'queueIds'){\n\t\t\t\t\t// Update hidden input and Vue data\n\t\t\t\t\t$('#queueIds').val(value);\n\t\t\t\t\t// Re-render queue widget from last received payload (WS mode)\n\t\t\t\t\tif (window[className].$widgetQueues && typeof window[className].$widgetQueues.refreshFromLastPayload === 'function') {\n\t\t\t\t\t\twindow[className].$widgetQueues.refreshFromLastPayload();\n\t\t\t\t\t}\n\t\t\t\t}else if( settingName === 'adminUserId'){\n\t\t\t\t\twindow.location.href = window.location.href;\n\t\t\t\t}\n\t\t\t},\n\t\t\tonFailure: function(response) {\n\t\t\t\tconsole.log(response);\n\t\t\t},\n\t\t\tonError: function(errorMessage, element, xhr) {\n\t\t\t\tconsole.log(errorMessage,xhr);\n\t\t\t}\n\t\t});\n\t},\n\texecuteCallAction(data) {\n\t\t$.api({\n\t\t\turl: window[className].executeCallUrl,\n\t\t\ton: 'now',\n\t\t\tmethod: 'POST',\n\t\t\tdata: data,\n\t\t\tsuccessTest(response) {\n\t\t\t\treturn response !== undefined && Object.keys(response).length > 0 && response.success === true;\n\t\t\t},\n\t\t\tonSuccess(response) {\n\t\t\t\tconsole.log(response);\n\t\t\t},\n\t\t\tonFailure(response) {\n\t\t\t\tconsole.log(response);\n\t\t\t},\n\t\t\tonError(errorMessage, element, xhr) {\n\t\t\t\tconsole.log(errorMessage,xhr);\n\t\t\t}\n\t\t});\n\t},\n\tupdateLines() {\n\t\t$.api({\n\t\t\turl: window[className].activeChannelsUrlV2,\n\t\t\ton: 'now',\n\t\t\tmethod: 'POST',\n\t\t\tsuccessTest(response) {\n\t\t\t\treturn response !== undefined && Object.keys(response).length > 0 && response.success === true;\n\t\t\t},\n\t\t\tonSuccess(response) {\n\t\t\t\twindow[className].$widgetQueues.updatedCallsFromResponse(response);\n\t\t\t\twindow[className].$callsWidget.updatedCallsFromResponse(response);\n\t\t\t},\n\t\t\tonFailure(response) {\n\t\t\t\tconsole.log(response);\n\t\t\t},\n\t\t\tonError(errorMessage, element, xhr) {\n\t\t\t\tconsole.log(errorMessage,xhr);\n\t\t\t}\n\t\t});\n\t},\n\n\t/**\n\t * We can modify some data before form send\n\t * @param settings\n\t * @returns {*}\n\t */\n\tcbBeforeSendForm(settings) {\n\t\tconst result = settings;\n\t\tresult.data = window[className].$formObj.form('get values');\n\t\treturn result;\n\t},\n\t/**\n\t * Some actions after forms send\n\t */\n\tcbAfterSendForm() {\n\n\t},\n\t/**\n\t * Initialize form parameters\n\t */\n\tinitializeForm() {\n\t\tForm.$formObj = window[className].$formObj;\n\t\tForm.url = `${globalRootUrl}${idUrl}/save`;\n\t\tForm.validateRules = window[className].validateRules;\n\t\tForm.cbBeforeSendForm = window[className].cbBeforeSendForm;\n\t\tForm.cbAfterSendForm = window[className].cbAfterSendForm;\n\t\tForm.initialize();\n\t},\n};\n\n$(document).ready(() => {\n\twindow[className].initialize();\n});\n\n"],"mappings":"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","ignoreList":[]}
\ No newline at end of file
diff --git a/public/assets/js/src/module-monitor-active-calls-index.js b/public/assets/js/src/module-monitor-active-calls-index.js
index 0bc479e..68d6229 100644
--- a/public/assets/js/src/module-monitor-active-calls-index.js
+++ b/public/assets/js/src/module-monitor-active-calls-index.js
@@ -13,12 +13,14 @@ const inputClassName = 'mikopbx-module-input';
/* global $, globalRootUrl, globalTranslate, Form, Config, Vue, Extensions */
const ModuleMonitorActiveCalls = {
isInit: true,
- queueNameSelector: '#app-queue div.scrolling.dropdown',
+ contactsCacheTtlMs: 120 * 60 * 1000,
+ queuesFilterSelector: '#queuesFilter',
$formObj: $('#'+idForm),
$checkBoxes: $('#'+idForm+' .ui.checkbox'),
$dropDowns: $('#'+idForm+' .ui.dropdown'),
activeChannelsUrl: globalRootUrl + idUrl + "/getActiveChannels",
activeChannelsUrlV2: globalRootUrl + idUrl + "/getActiveChannelsV2",
+ backendEnableUrl: globalRootUrl + idUrl + "/backandEnable",
executeCallUrl: globalRootUrl + idUrl + "/executeCall",
saveUserActionUrl: globalRootUrl + idUrl + "/saveUser",
$widget: undefined,
@@ -32,11 +34,20 @@ const ModuleMonitorActiveCalls = {
* On page load we init some Semantic UI library
*/
initialize() {
+ this.initContactsCache();
+ this.requestBackendEnable();
+
$("#nowUser.dropdown.enable").dropdown({
onChange: function onChange(value, text, $choice) {
window[className].onChangeSetting('adminUserId', value);
}
});
+ $("#minWaitVisible.dropdown.enable").dropdown({
+ onChange: function onChange(value, text, $choice) {
+ $('#minWaitVisibleValue').val(value);
+ window[className].onChangeSetting('minWaitVisible', value);
+ }
+ });
let userNumber = $('#userNumber').val();
window[className].$widgetQueues = new Vue({
@@ -44,40 +55,397 @@ const ModuleMonitorActiveCalls = {
delimiters: ["<%","%>"],
methods: {
updatedCallsFromResponse(data) {
- let queueNameEl = $(window[className].queueNameSelector);
-
- this.queues = data.queues;
- let queueId = $('#queueId').val();
- if (queueId in data.queues) {
- this.id = data.queues[queueId].id;
- this.name = data.queues[queueId].name;
- this.number = data.queues[queueId].number;
- this.agents = data.queues[queueId].agents;
- this.calls = Array.isArray(data.queues[queueId].calls) ? data.queues[queueId].calls : [];
- this.allCalls = data.calls;
- }else{
- this.calls = [];
- }
- if(queueNameEl.dropdown('is hidden')){
- queueNameEl.dropdown({
- onChange: function onChange(value, text, $choice) {
- window[className].onChangeSetting('queueId', value);
+ // Keep last payload to allow re-render on queue switch (WS mode).
+ this.lastActiveCallsPayload = data;
+
+ this.minWaitVisible = 1*$('#minWaitVisibleValue').val();
+ this.queues = data.queues || {};
+ this.allCalls = data.calls || [];
+
+ // Initialize multi-select dropdown if not yet done
+ this.initQueuesFilter();
+
+ // Normalize Semantic UI Card typography after render
+ this.$nextTick(function() {
+ this.normalizeAgentCards();
+ });
+ },
+ initQueuesFilter() {
+ var self = this;
+ var $filter = $(window[className].queuesFilterSelector);
+ if ($filter.length === 0) return;
+
+ // Wait for Vue to render menu items
+ this.$nextTick(function() {
+ // Reinitialize dropdown to pick up new menu items
+ if ($filter.data('initialized')) {
+ // Dropdown already exists, just refresh menu
+ // Save current selection before refresh to prevent reset
+ var currentSelection = self.selectedQueueIds ? self.selectedQueueIds.slice() : [];
+ $filter.data('refreshing', true);
+ $filter.dropdown('refresh');
+ $filter.data('refreshing', false);
+
+ // Restore selection after refresh if it was cleared
+ if (currentSelection.length > 0 && (!self.selectedQueueIds || self.selectedQueueIds.length === 0)) {
+ self.selectedQueueIds = currentSelection;
+ $filter.dropdown('set exactly', currentSelection);
+ }
+
+ // After refresh, ensure default text is hidden if we have selections
+ if (self.selectedQueueIds && self.selectedQueueIds.length > 0) {
+ $filter.find('.default.text').hide();
+ } else {
+ $filter.find('.default.text').show();
+ }
+ } else {
+ // First time initialization
+ $filter.data('initialized', true);
+ $filter.dropdown({
+ fullTextSearch: true,
+ onChange: function(value) {
+ // Skip onChange during programmatic refresh
+ if ($filter.data('refreshing')) {
+ return;
+ }
+ // value is comma-separated string of selected queue IDs
+ var selectedIds = value ? value.split(',').filter(function(v) { return v !== ''; }) : [];
+ self.selectedQueueIds = selectedIds;
+ // Auto-save on change
+ window[className].onChangeSetting('queueIds', JSON.stringify(selectedIds));
+ }
+ });
+
+ // Set initial values from hidden input
+ var savedQueueIds = [];
+ try {
+ var raw = $('#queueIds').val();
+ savedQueueIds = JSON.parse(raw || '[]');
+ } catch (e) {
+ savedQueueIds = [];
+ }
+ if (Array.isArray(savedQueueIds) && savedQueueIds.length > 0) {
+ window[className].isInit = true;
+ $filter.dropdown('set exactly', savedQueueIds);
+ self.selectedQueueIds = savedQueueIds;
+ window[className].isInit = false;
+ // Hide default text when values are selected
+ $filter.find('.default.text').hide();
}
- });
- if(queueNameEl.dropdown('get value') === ''){
- window[className].isInit = true;
- queueNameEl.dropdown('set value', $('#queueId').val())
- window[className].isInit = false;
}
+ });
+ },
+ refreshFromLastPayload() {
+ if (this.lastActiveCallsPayload) {
+ this.updatedCallsFromResponse(this.lastActiveCallsPayload);
}
},
+ getQueueCalls(queueId) {
+ var queue = this.queues[queueId];
+ if (!queue) return [];
+ return Array.isArray(queue.calls) ? queue.calls : [];
+ },
+ getQueueAgentsList(queueId) {
+ var queue = this.queues[queueId];
+ if (!queue || !queue.agents) return [];
+ return this.buildAgentsList(queue.agents);
+ },
+ hasWaitingCalls(queueId) {
+ var calls = this.getQueueCalls(queueId);
+ var self = this;
+ for (var i = 0; i < calls.length; i++) {
+ var call = calls[i];
+ if (call.dst_chan === '' && call.queueData && call.queueData.EnterTime !== undefined) {
+ var elapsed = self.formatElapsedTime(call.queueData.EnterTime);
+ if (self.minWaitVisible <= elapsed) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ buildAgentsList(agentsObj) {
+ const entries = Object.entries(agentsObj || {});
+ const available = [];
+ const unavailable = [];
+ for (const [number, agent] of entries) {
+ const state = agent?.state || '';
+ const item = { number, ...agent };
+ if (state === 'Unavailable') {
+ unavailable.push(item);
+ } else {
+ available.push(item);
+ }
+ }
+ return available.concat(unavailable);
+ },
+ normalizePhone10(phone) {
+ const digits = String(phone || '').replace(/\D+/g, '');
+ if (digits.length <= 10) return digits;
+ return digits.slice(-10);
+ },
+ updateContactFromWs(contact) {
+ const phone10 = this.normalizePhone10(contact?.number);
+ if (!phone10) return;
+ const displayName = String(contact?.client || contact?.contact || '').trim();
+ if (!displayName) return;
+ // Vue2: ensure reactivity for new keys
+ if (this.$set) {
+ this.$set(this.contactsByPhone10, phone10, displayName);
+ } else {
+ this.contactsByPhone10[phone10] = displayName;
+ }
+ },
+ getClientNameByPhone(phone) {
+ const phone10 = this.normalizePhone10(phone);
+ return this.contactsByPhone10[phone10] || '';
+ },
+ getClientHeader(phone) {
+ const client = this.getClientNameByPhone(phone);
+ if (!client) return phone;
+ return `${client} <${phone}>`;
+ },
+ hasClientByPhone(phone) {
+ return !!this.getClientNameByPhone(phone);
+ },
formatElapsedTime(enterTime) {
+ // Make this method reactive to the UI ticker.
+ void this.nowTick;
return window[className].formatElapsedTime(enterTime);
},
+ normalizeAgentCards() {
+ if (!this.$el) return;
+ var self = this;
+
+ // Cleanup artifacts from previous experiments (placeholders/spacers).
+ var artifacts = this.$el.querySelectorAll('.agent-peer-placeholder, .agent-peer-spacer');
+ artifacts.forEach(function(el) { el.remove(); });
+
+ // Dense layout (masonry) that still fills left-to-right:
+ // flex-wrap can't place items into vertical gaps under tall cards.
+ this.ensureAgentCardsGridMasonry();
+
+ // Process all agent card containers (one per queue block)
+ var cardsContainers = this.$el.querySelectorAll('.ui.cards.agent-cards');
+ cardsContainers.forEach(function(cardsContainer) {
+ cardsContainer.style.alignItems = 'flex-start';
+ cardsContainer.style.alignContent = 'flex-start';
+ });
+
+ var cards = this.$el.querySelectorAll('.ui.cards.agent-cards > .ui.card.agent-card');
+ cards.forEach(function(card) {
+ card.style.alignSelf = 'flex-start';
+ });
+
+ // Semantic UI makes .header bigger than normal text; we need same font size.
+ var headers = this.$el.querySelectorAll('.ui.card.agent-card .header.agent-card-header');
+ headers.forEach(function(el) {
+ el.style.fontSize = '1em';
+ el.style.lineHeight = '1.2';
+ el.style.display = 'flex';
+ el.style.alignItems = 'center';
+ el.style.gap = '0.5em';
+ el.style.whiteSpace = 'nowrap';
+ });
+
+ var metas = this.$el.querySelectorAll('.ui.card.agent-card .meta.agent-peer');
+ metas.forEach(function(el) {
+ el.style.fontSize = '1em';
+ el.style.lineHeight = '1.2';
+ });
+
+ // Normalize label/name typography so they have same text height.
+ var numLabels = this.$el.querySelectorAll('.ui.card.agent-card .agent-num-label');
+ numLabels.forEach(function(el) {
+ el.style.fontSize = '1em';
+ el.style.lineHeight = '1.2';
+ el.style.display = 'inline-flex';
+ el.style.alignItems = 'center';
+ el.style.paddingTop = '0';
+ el.style.paddingBottom = '0';
+ // Allow label to shrink (otherwise long numbers force card wider than 180px)
+ el.style.flex = '0 1 auto';
+ el.style.minWidth = '0';
+ el.style.maxWidth = '14ch';
+ el.style.overflow = 'hidden';
+ el.style.textOverflow = 'ellipsis';
+ el.style.whiteSpace = 'nowrap';
+ });
+ var names = this.$el.querySelectorAll('.ui.card.agent-card .agent-name');
+ names.forEach(function(el) {
+ el.style.lineHeight = '1.2';
+ // Ellipsis for long names (e.g. "Салтыков-Щедрин")
+ el.style.minWidth = '0';
+ el.style.flex = '1 1 auto';
+ el.style.overflow = 'hidden';
+ el.style.textOverflow = 'ellipsis';
+ el.style.whiteSpace = 'nowrap';
+ });
+
+ // Grid masonry needs row-span calculation after layout.
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ self.layoutAgentCardsGridMasonry();
+ });
+ });
+ },
+ adjustAgentCardsGap() {
+ if (!this.$el) return;
+ const container = this.$el.querySelector('.ui.cards.agent-cards');
+ if (!container) return;
+
+ const cards = Array.from(container.querySelectorAll('.ui.card.agent-card'));
+ if (!cards.length) return;
+
+ const tallCard = cards.find((c) => c.querySelector('.meta.agent-peer'));
+ const shortCard = cards.find((c) => !c.querySelector('.meta.agent-peer'));
+ if (!tallCard || !shortCard) return;
+
+ const ht = tallCard.getBoundingClientRect().height;
+ const hs = shortCard.getBoundingClientRect().height;
+ if (!ht || !hs) return;
+
+ // From 2*(hs+g) = ht+g => g = ht - 2*hs
+ let gap = ht - 2 * hs;
+ if (!Number.isFinite(gap)) return;
+
+ // Clamp to sane range; negative means "no extra gap needed".
+ gap = Math.max(0, Math.min(20, Math.round(gap)));
+
+ container.style.setProperty('--agent-card-gap', `${gap}px`);
+ },
+ adjustAgentCardsColumnCount() {
+ if (!this.$el) return;
+ const container = this.$el.querySelector('.ui.cards.agent-cards.agent-cards-masonry');
+ if (!container) return;
+
+ const w = container.clientWidth;
+ if (!w) return;
+
+ // Minimum acceptable card width in px (tune if needed)
+ const minCardWidth = 150;
+
+ const cs = window.getComputedStyle(container);
+ const gapRaw = cs.columnGap || cs.getPropertyValue('column-gap') || '16px';
+ const gapPx = parseFloat(gapRaw) || 16;
+
+ const count = Math.max(1, Math.min(12, Math.floor((w + gapPx) / (minCardWidth + gapPx))));
+ container.style.setProperty('--agent-card-col-count', String(count));
+ },
+ ensureAgentCardsGridMasonry() {
+ var self = this;
+ var styleId = 'agent-cards-layout-style';
+ var styleEl = document.getElementById(styleId);
+ if (!styleEl) {
+ styleEl = document.createElement('style');
+ styleEl.id = styleId;
+ document.head.appendChild(styleEl);
+ }
+
+ // Grid masonry: fills left-to-right and can pack items into gaps.
+ // minmax(240px, 1fr) - карточки минимум 240px, растягиваются равномерно
+ styleEl.textContent = '\
+.ui.cards.agent-cards.agent-cards-grid {\
+ display: grid !important;\
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));\
+ justify-content: start;\
+ gap: var(--agent-card-gap, 8px);\
+ grid-auto-rows: 1px;\
+ margin-bottom: 1em !important;\
+}\
+.ui.cards.agent-cards.agent-cards-grid > .ui.card.agent-card {\
+ width: 100% !important;\
+ min-width: 0;\
+ margin: 0 !important;\
+ overflow: hidden;\
+ align-self: start;\
+}';
+
+ // Process all agent card containers (one per queue block)
+ var cardsContainers = this.$el ? this.$el.querySelectorAll('.ui.cards.agent-cards') : [];
+ cardsContainers.forEach(function(cardsContainer) {
+ cardsContainer.classList.remove('agent-cards-masonry');
+ cardsContainer.classList.remove('agent-cards-flex');
+ cardsContainer.classList.add('agent-cards-grid');
+ });
+
+ // Bind once: relayout on resize.
+ if (!this._agentCardsResizeBound) {
+ this._agentCardsResizeBound = true;
+ window.addEventListener('resize', function() {
+ self.layoutAgentCardsGridMasonry();
+ });
+ }
+ },
+ layoutAgentCardsGridMasonry() {
+ if (!this.$el) return;
+ var self = this;
+
+ // Process all grid containers (one per queue block)
+ var grids = this.$el.querySelectorAll('.ui.cards.agent-cards.agent-cards-grid');
+ grids.forEach(function(grid) {
+ self.layoutSingleGridMasonry(grid);
+ });
+ },
+ layoutSingleGridMasonry(grid) {
+ if (!grid) return;
+
+ var cs = window.getComputedStyle(grid);
+ var rowHeight = parseFloat(cs.getPropertyValue('grid-auto-rows')) || 1;
+ var rowGap = parseFloat(cs.getPropertyValue('row-gap')) || parseFloat(cs.getPropertyValue('gap')) || 8;
+
+ var items = Array.from(grid.querySelectorAll('.ui.card.agent-card'));
+ if (!items.length) return;
+
+ // Reset row spans and min-heights to measure natural heights.
+ items.forEach(function(item) {
+ item.style.gridRowEnd = '';
+ item.style.minHeight = '';
+ });
+
+ var tall = items.filter(function(c) { return c.querySelector('.meta.agent-peer'); });
+ var short = items.filter(function(c) { return !c.querySelector('.meta.agent-peer'); });
+
+ // If we don't have both types, just do normal masonry spans.
+ if (!tall.length || !short.length) {
+ items.forEach(function(item) {
+ var h = item.getBoundingClientRect().height;
+ var span = Math.max(1, Math.ceil((h + rowGap) / (rowHeight + rowGap)));
+ item.style.gridRowEnd = 'span ' + span;
+ });
+ return;
+ }
+
+ var shortHeights = short.map(function(c) { return c.getBoundingClientRect().height; });
+ var tallHeights = tall.map(function(c) { return c.getBoundingClientRect().height; });
+ var hs = Math.max.apply(Math, shortHeights);
+ var ht = Math.max.apply(Math, tallHeights);
+
+ // Want: 2*(hs + g) = (ht + g) => g = ht - 2*hs
+ var g = ht - 2 * hs;
+ if (!Number.isFinite(g)) g = rowGap;
+ g = Math.max(0, Math.min(24, Math.round(g)));
+
+ // Apply gap and enforce min-heights so the relation holds visually.
+ grid.style.setProperty('--agent-card-gap', g + 'px');
+
+ var shortH = Math.round(hs);
+ var tallH = Math.round(Math.max(ht, 2 * hs + g));
+ short.forEach(function(c) { c.style.minHeight = shortH + 'px'; });
+ tall.forEach(function(c) { c.style.minHeight = tallH + 'px'; });
+
+ // Now compute row spans from final rendered heights.
+ var effectiveGap = g;
+ items.forEach(function(item) {
+ var h = item.getBoundingClientRect().height;
+ var span = Math.max(1, Math.ceil((h + effectiveGap) / (rowHeight + effectiveGap)));
+ item.style.gridRowEnd = 'span ' + span;
+ });
+ },
getSrcNumForAgent(agentNumber) {
let result = '-';
let answeredFound = false;
- for (const call of this.calls) {
+ for (const call of this.allCalls) {
if(call.dst_num === agentNumber){
answeredFound = true;
result = call.src_num;
@@ -139,29 +507,53 @@ const ModuleMonitorActiveCalls = {
}
}
return result;
+ },
+ hasPeerPhone(agentNumber) {
+ const phone = String(this.getSrcNumForAgent(agentNumber) || '').trim();
+ return phone !== '' && phone !== '-' && phone !== '—';
+ },
+ getPeerPhoneLabel(agentNumber) {
+ const phone = String(this.getSrcNumForAgent(agentNumber) || '').trim();
+ return this.hasPeerPhone(agentNumber) ? phone : '—';
+ },
+ getPeerNameLabel(agentNumber) {
+ // Use cached contacts (WS + IndexedDB) to show client name for peer phone.
+ const phone = this.getPeerPhoneLabel(agentNumber);
+ const client = this.getClientNameByPhone(phone);
+ return client || '—';
}
},
data: {
- "name": "",
- "number": "",
- "queues": [],
- "agents": {
- },
- "calls": [
- ]
+ "minWaitVisible": 30,
+ "nowTick": 0,
+ "queues": {},
+ "allCalls": [],
+ "selectedQueueIds": [],
+ "lastActiveCallsPayload": null,
+ "contactsByPhone10": {}
},
});
+ window[className].applyContactsCacheToQueueWidget();
window[className].$callsWidget = new Vue({
el: '#calls',
delimiters: ["<%","%>"],
data: {
+ "minWaitVisible": 30,
+ "nowTick": 0,
userNumber: userNumber,
fullAccess: ($('#fullAccess').val() === "1" || userNumber === ''),
calls: [
]
},
methods: {
+ callIsVisible(call){
+ void this.nowTick;
+ if(call.dst_chan==='' && call.queueData.EnterTime !== undefined ){
+ return this.minWaitVisible <= this.getWaitTime(call);
+ }
+ return true;
+ },
formatTimestampToTime(timestamp) {
// Если timestamp строка — приводим к числу
const ts = typeof timestamp === 'string' ? parseFloat(timestamp) : timestamp;
@@ -178,6 +570,7 @@ const ModuleMonitorActiveCalls = {
return `${hours}:${minutes}:${seconds}`;
},
getWaitTime(call){
+ void this.nowTick;
let answer = Math.floor(Date.now() / 1000);
if(call.answer !== ''){
answer = call.answer
@@ -185,12 +578,14 @@ const ModuleMonitorActiveCalls = {
return window[className].secondToTime(answer - call.start);
},
getCallTime(call){
+ void this.nowTick;
if(call.answer === ''){
return '-';
}
return window[className].formatElapsedTime(call.answer);
},
updatedCallsFromResponse(data) {
+ this.minWaitVisible = 1*$('#minWaitVisibleValue').val();
// Проходим по всем очередям
for (const queueId in data.queues) {
const queue = data.queues[queueId];
@@ -208,6 +603,13 @@ const ModuleMonitorActiveCalls = {
formatElapsedTime(enterTime) {
return window[className].formatElapsedTime(enterTime);
},
+ getClientHeader(phone) {
+ const q = window[className].$widgetQueues;
+ if (q && typeof q.getClientHeader === 'function') {
+ return q.getClientHeader(phone);
+ }
+ return phone;
+ },
hangupAction(event) {
let target = $(event.target);
if(target.attr('data-ch1') === undefined){
@@ -315,6 +717,7 @@ const ModuleMonitorActiveCalls = {
window[className].$dropDowns.dropdown();
window[className].initializeForm();
$('.menu .item').tab();
+ window[className].startUiTicker();
//////
// Удаляем отступы контейнера.
$('#main-content-container').removeClass('container');
@@ -322,8 +725,341 @@ const ModuleMonitorActiveCalls = {
$('.ui.clearing.hidden.divider').remove();
// Окончание форматирования базовой страницы
//////
+ this.startPollingActiveCalls();
+
+ // Allow settings to be saved after initialization
+ setTimeout(function() {
+ window[className].isInit = false;
+ }, 1000);
+ },
+ startUiTicker() {
+ if (this._uiTicker) return;
+ this._uiTicker = setInterval(() => {
+ const now = Date.now();
+ if (window[className].$widgetQueues) {
+ window[className].$widgetQueues.nowTick = now;
+ }
+ if (window[className].$callsWidget) {
+ window[className].$callsWidget.nowTick = now;
+ }
+ }, 1000);
+ },
+ startPollingActiveCalls() {
+ if (this._activeCallsPollTimer) return;
window[className].updateLines();
- setInterval(window[className].updateLines, 2000);
+ this._activeCallsPollTimer = setInterval(window[className].updateLines, 2000);
+ },
+ stopPollingActiveCalls() {
+ if (!this._activeCallsPollTimer) return;
+ clearInterval(this._activeCallsPollTimer);
+ this._activeCallsPollTimer = null;
+ },
+ async initContactsCache() {
+ try {
+ this._contactsCacheByPhone10 = await this.idbLoadAllContacts();
+ this.applyContactsCacheToQueueWidget();
+ } catch (e) {
+ console.log('contacts cache init error', e);
+ this._contactsCacheByPhone10 = {};
+ }
+ },
+ applyContactsCacheToQueueWidget() {
+ if (!this._contactsCacheByPhone10) return;
+ if (!window[className].$widgetQueues) return;
+ for (const [phone10, client] of Object.entries(this._contactsCacheByPhone10)) {
+ if (window[className].$widgetQueues.$set) {
+ window[className].$widgetQueues.$set(window[className].$widgetQueues.contactsByPhone10, phone10, client);
+ } else {
+ window[className].$widgetQueues.contactsByPhone10[phone10] = client;
+ }
+ }
+ },
+ idbOpenContactsDb() {
+ return new Promise((resolve, reject) => {
+ try {
+ const req = indexedDB.open('ModuleMonitorActiveCalls', 1);
+ req.onupgradeneeded = () => {
+ const db = req.result;
+ if (!db.objectStoreNames.contains('contactsByPhone10')) {
+ db.createObjectStore('contactsByPhone10', { keyPath: 'phone10' });
+ }
+ };
+ req.onsuccess = () => resolve(req.result);
+ req.onerror = () => reject(req.error);
+ } catch (e) {
+ reject(e);
+ }
+ });
+ },
+ async idbPutContact(phone10, client) {
+ const db = await this.idbOpenContactsDb();
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction('contactsByPhone10', 'readwrite');
+ const store = tx.objectStore('contactsByPhone10');
+ store.put({ phone10, client, updatedAt: Date.now() });
+ tx.oncomplete = () => { db.close(); resolve(); };
+ tx.onerror = () => { const err = tx.error; db.close(); reject(err); };
+ });
+ },
+ async idbLoadAllContacts() {
+ const db = await this.idbOpenContactsDb();
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction('contactsByPhone10', 'readwrite');
+ const store = tx.objectStore('contactsByPhone10');
+ const req = store.getAll();
+ req.onsuccess = () => {
+ const map = {};
+ const now = Date.now();
+ const ttlMs = Number(this.contactsCacheTtlMs) || (120 * 60 * 1000);
+ for (const row of req.result || []) {
+ const phone10 = row?.phone10;
+ const client = row?.client;
+ const updatedAt = Number(row?.updatedAt) || 0;
+ const isFresh = phone10 && client && updatedAt > 0 && (now - updatedAt) <= ttlMs;
+ if (isFresh) {
+ map[phone10] = client;
+ } else if (phone10) {
+ // Cleanup expired/broken records
+ try { store.delete(phone10); } catch (e) { /* ignore */ }
+ }
+ }
+ tx.oncomplete = () => { db.close(); resolve(map); };
+ tx.onerror = () => { const err = tx.error; db.close(); reject(err); };
+ };
+ req.onerror = () => { const err = req.error; db.close(); reject(err); };
+ });
+ },
+ requestBackendEnable() {
+ $.api({
+ url: window[className].backendEnableUrl,
+ on: 'now',
+ method: 'POST',
+ onSuccess(response) {
+ console.log('backandEnable response', response);
+ const accessToken = response?.data?.access_token;
+ const refreshToken = response?.data?.refresh_token;
+ if (accessToken && refreshToken) {
+ window[className].setAuthTokens(accessToken, refreshToken);
+ window[className].connectContactsWs();
+ window[className].connectActiveCallsWs();
+ }
+ },
+ onFailure(response) {
+ console.log('backandEnable failure', response);
+ },
+ onError(errorMessage, element, xhr) {
+ console.log('backandEnable error', errorMessage, xhr);
+ }
+ });
+ },
+ setAuthTokens(accessToken, refreshToken) {
+ this._authTokens = this._authTokens || {};
+ this._authTokens.access_token = accessToken;
+ this._authTokens.refresh_token = refreshToken;
+ this._authTokens.exp = this.getJwtExp(accessToken);
+ },
+ getJwtExp(token) {
+ try {
+ if (!token || typeof token !== 'string') return 0;
+ const parts = token.split('.');
+ if (parts.length < 2) return 0;
+ const payloadB64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
+ const padded = payloadB64 + '='.repeat((4 - (payloadB64.length % 4)) % 4);
+ const json = atob(padded);
+ const payload = JSON.parse(json);
+ return Number(payload?.exp) || 0;
+ } catch (e) {
+ return 0;
+ }
+ },
+ isAccessTokenExpired(skewSeconds = 0) {
+ const exp = Number(this._authTokens?.exp) || 0;
+ if (!exp) return false; // unknown exp -> don't force refresh
+ const now = Math.floor(Date.now() / 1000);
+ return now + skewSeconds >= exp;
+ },
+ scheduleContactsWsTokenRefresh() {
+ // Proactively refresh token shortly before expiry by re-requesting backendEnable.
+ if (this._contactsWsTokenTimer) {
+ clearTimeout(this._contactsWsTokenTimer);
+ this._contactsWsTokenTimer = null;
+ }
+ const exp = Number(this._authTokens?.exp) || 0;
+ if (!exp) return;
+ const now = Math.floor(Date.now() / 1000);
+ const refreshInSec = Math.max(1, exp - now - 15); // 15s before exp
+ this._contactsWsTokenTimer = setTimeout(() => {
+ // Re-get tokens and reconnect WS
+ this.requestBackendEnable();
+ }, refreshInSec * 1000);
+ },
+ scheduleContactsWsReconnect(reason, forceReAuth = false) {
+ if (this._contactsWsReconnectTimer) {
+ clearTimeout(this._contactsWsReconnectTimer);
+ this._contactsWsReconnectTimer = null;
+ }
+ this._contactsWsReconnectAttempt = (this._contactsWsReconnectAttempt || 0) + 1;
+ const delay = Math.min(30000, 1000 * Math.pow(2, Math.min(5, this._contactsWsReconnectAttempt - 1)));
+ this._contactsWsReconnectTimer = setTimeout(() => {
+ if (forceReAuth || this.isAccessTokenExpired(5)) {
+ this.requestBackendEnable();
+ } else {
+ this.connectContactsWs();
+ }
+ }, delay);
+ console.log('contacts ws reconnect scheduled', { reason, delayMs: delay });
+ },
+ connectContactsWs() {
+ try {
+ const accessToken = this._authTokens?.access_token;
+ if (!accessToken) return;
+
+ // Avoid reconnecting if already connected/connecting
+ if (this._contactsWs && (this._contactsWs.readyState === WebSocket.OPEN || this._contactsWs.readyState === WebSocket.CONNECTING)) {
+ return;
+ }
+ // Reset backoff on explicit connect attempt
+ this._contactsWsReconnectAttempt = 0;
+
+ const wsProto = window.location.protocol === 'https:' ? 'wss' : 'ws';
+ const wsHost = window.location.host; // host:port of current page
+ const tokenParam = encodeURIComponent(accessToken);
+ const wsUrl = `${wsProto}://${wsHost}/pbxcore/api/module-softphone-backend/v1/sub/contacts?authorization=${tokenParam}`;
+
+ this._contactsWs = new WebSocket(wsUrl);
+ this._contactsWs.onopen = () => {
+ console.log('contacts ws connected');
+ this.scheduleContactsWsTokenRefresh();
+ };
+ this._contactsWs.onmessage = (event) => {
+ this.handleContactsWsMessage(event?.data);
+ };
+ this._contactsWs.onerror = (event) => {
+ console.log('contacts ws error', event);
+ };
+ this._contactsWs.onclose = (event) => {
+ const code = event?.code;
+ const reason = event?.reason;
+ console.log('contacts ws closed', { code, reason });
+
+ if (this._contactsWsTokenTimer) {
+ clearTimeout(this._contactsWsTokenTimer);
+ this._contactsWsTokenTimer = null;
+ }
+
+ // 1000 = normal close -> reconnect; auth closes vary by server implementation.
+ const authCloseCodes = new Set([1008, 4001, 4401, 4403]);
+ const forceReAuth = authCloseCodes.has(code) || this.isAccessTokenExpired(0);
+ this.scheduleContactsWsReconnect('close', forceReAuth);
+ };
+ } catch (e) {
+ console.log('contacts ws init error', e);
+ this.scheduleContactsWsReconnect('init_error', this.isAccessTokenExpired(0));
+ }
+ },
+ scheduleActiveCallsWsReconnect(reason, forceReAuth = false) {
+ if (this._activeCallsWsReconnectTimer) {
+ clearTimeout(this._activeCallsWsReconnectTimer);
+ this._activeCallsWsReconnectTimer = null;
+ }
+ this._activeCallsWsReconnectAttempt = (this._activeCallsWsReconnectAttempt || 0) + 1;
+ const delay = Math.min(30000, 1000 * Math.pow(2, Math.min(5, this._activeCallsWsReconnectAttempt - 1)));
+ this._activeCallsWsReconnectTimer = setTimeout(() => {
+ if (forceReAuth || this.isAccessTokenExpired(5)) {
+ this.requestBackendEnable();
+ } else {
+ this.connectActiveCallsWs();
+ }
+ }, delay);
+ console.log('active-calls ws reconnect scheduled', { reason, delayMs: delay });
+ },
+ connectActiveCallsWs() {
+ try {
+ const accessToken = this._authTokens?.access_token;
+ if (!accessToken) return;
+
+ // Avoid reconnecting if already connected/connecting
+ if (this._activeCallsWs && (this._activeCallsWs.readyState === WebSocket.OPEN || this._activeCallsWs.readyState === WebSocket.CONNECTING)) {
+ return;
+ }
+ // Reset backoff on explicit connect attempt
+ this._activeCallsWsReconnectAttempt = 0;
+
+ // Token exists -> use WS, disable polling fallback
+ this.stopPollingActiveCalls();
+
+ const wsProto = window.location.protocol === 'https:' ? 'wss' : 'ws';
+ const wsHost = window.location.host; // host:port of current page
+ const tokenParam = encodeURIComponent(accessToken);
+ const wsUrl = `${wsProto}://${wsHost}/pbxcore/api/module-softphone-backend/v1/sub/active-calls?authorization=${tokenParam}`;
+
+ this._activeCallsWs = new WebSocket(wsUrl);
+ this._activeCallsWs.onopen = () => {
+ console.log('active-calls ws connected');
+ // Reuse the same token refresh timer (it triggers requestBackendEnable)
+ this.scheduleContactsWsTokenRefresh();
+ };
+ this._activeCallsWs.onmessage = (event) => {
+ this.handleActiveCallsWsMessage(event?.data);
+ };
+ this._activeCallsWs.onerror = (event) => {
+ console.log('active-calls ws error', event);
+ };
+ this._activeCallsWs.onclose = (event) => {
+ const code = event?.code;
+ const reason = event?.reason;
+ console.log('active-calls ws closed', { code, reason });
+
+ // Auth closes vary by server implementation.
+ const authCloseCodes = new Set([1008, 4001, 4401, 4403]);
+ const forceReAuth = authCloseCodes.has(code) || this.isAccessTokenExpired(0);
+ this.scheduleActiveCallsWsReconnect('close', forceReAuth);
+ };
+ } catch (e) {
+ console.log('active-calls ws init error', e);
+ this.scheduleActiveCallsWsReconnect('init_error', this.isAccessTokenExpired(0));
+ }
+ },
+ handleContactsWsMessage(data) {
+ try {
+ if (!data) return;
+ const parsed = typeof data === 'string' ? JSON.parse(data) : data;
+ const items = Array.isArray(parsed) ? parsed : [parsed];
+ for (const item of items) {
+ const digits = String(item?.number || '').replace(/\D+/g, '');
+ const phone10 = digits.length <= 10 ? digits : digits.slice(-10);
+ const displayName = String(item?.client || item?.contact || '').trim();
+ if (phone10 && displayName) {
+ this._contactsCacheByPhone10 = this._contactsCacheByPhone10 || {};
+ this._contactsCacheByPhone10[phone10] = displayName;
+ this.idbPutContact(phone10, displayName).catch((e) => console.log('contacts cache save error', e));
+ }
+ if (window[className].$widgetQueues) {
+ window[className].$widgetQueues.updateContactFromWs(item);
+ }
+ // Calls table is a separate Vue instance and reads client name via $widgetQueues.
+ // Vue can't track cross-instance dependency, so force re-render on contact update.
+ if (window[className].$callsWidget && typeof window[className].$callsWidget.$forceUpdate === 'function') {
+ window[className].$callsWidget.$forceUpdate();
+ }
+ }
+ } catch (e) {
+ console.log('contacts ws parse error', e);
+ }
+ },
+ handleActiveCallsWsMessage(data) {
+ try {
+ if (!data) return;
+ const parsed = typeof data === 'string' ? JSON.parse(data) : data;
+ const payload = parsed?.queues ? parsed : (parsed?.data?.queues ? parsed.data : null);
+ if (!payload) return;
+ if (!window[className].$widgetQueues || !window[className].$callsWidget) return;
+
+ window[className].$widgetQueues.updatedCallsFromResponse(payload);
+ window[className].$callsWidget.updatedCallsFromResponse(payload);
+ } catch (e) {
+ console.log('active-calls ws parse error', e);
+ }
},
formatElapsedTime(enterTime) {
if (!enterTime) return '—';
@@ -351,28 +1087,32 @@ const ModuleMonitorActiveCalls = {
if(window[className].isInit){
return;
}
- let data = {
- [settingName]: value
- };
+ var data = {};
+ data[settingName] = value;
$.api({
url: window[className].saveUserActionUrl,
on: 'now',
method: 'POST',
data: data,
- successTest(response) {
+ successTest: function(response) {
return response !== undefined && Object.keys(response).length > 0 && response.success === true;
},
- onSuccess(response) {
- if(settingName === 'queueId'){
- $('#queueId').val($(window[className].queueNameSelector).dropdown('get value'));
+ onSuccess: function(response) {
+ if(settingName === 'queueIds'){
+ // Update hidden input and Vue data
+ $('#queueIds').val(value);
+ // Re-render queue widget from last received payload (WS mode)
+ if (window[className].$widgetQueues && typeof window[className].$widgetQueues.refreshFromLastPayload === 'function') {
+ window[className].$widgetQueues.refreshFromLastPayload();
+ }
}else if( settingName === 'adminUserId'){
window.location.href = window.location.href;
}
},
- onFailure(response) {
+ onFailure: function(response) {
console.log(response);
},
- onError(errorMessage, element, xhr) {
+ onError: function(errorMessage, element, xhr) {
console.log(errorMessage,xhr);
}
});
|