diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58d8882 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# IDE +.idea/ + +# Claude Code +CLAUDE.md +tasks.md + +# Dependencies +/vendor/ + +# Logs +*.log diff --git a/App/Controllers/ModuleMonitorActiveCallsController.php b/App/Controllers/ModuleMonitorActiveCallsController.php index c39d7a4..b8a9ff7 100644 --- a/App/Controllers/ModuleMonitorActiveCallsController.php +++ b/App/Controllers/ModuleMonitorActiveCallsController.php @@ -9,7 +9,7 @@ use MikoPBX\AdminCabinet\Controllers\BaseController; use MikoPBX\AdminCabinet\Controllers\SessionController; use MikoPBX\Common\Models\Extensions; -use MikoPBX\Common\Models\Users; +use MikoPBX\Common\Models\PbxExtensionModules; use MikoPBX\Common\Providers\PBXConfModulesProvider; use MikoPBX\Common\Providers\SessionProvider; use MikoPBX\Modules\Config\CDRConfigInterface; @@ -17,12 +17,13 @@ use Modules\ModuleMonitorActiveCalls\App\Forms\ModuleMonitorActiveCallsForm; use Modules\ModuleMonitorActiveCalls\bin\WorkerAmiActions; use Modules\ModuleMonitorActiveCalls\Lib\CacheManager; +use Modules\ModuleMonitorActiveCalls\Lib\MonitorActiveCallsMain; use Modules\ModuleMonitorActiveCalls\Models\ModuleMonitorActiveCalls; use Modules\ModuleMonitorActiveCalls\Models\UsersSettings; use Modules\ModuleUsersUI\Lib\Constants; use Modules\ModuleUsersUI\Models\AccessGroups; use Modules\ModuleUsersUI\Models\UsersCredentials; -use DateTime; +use Modules\ModuleSoftphoneBackend\Lib\RestAPI\Controllers\ApiController as ModuleSoftphoneBackendApi; class ModuleMonitorActiveCallsController extends BaseController { @@ -56,6 +57,7 @@ public function indexAction(): void $headerCollectionCSS->addCss("css/cache/$this->moduleUniqueID/module-monitor-active-calls.css", true); $headerCollectionCSS->addCss('css/vendor/datatable/dataTables.semanticui.min.css', true); $headerCollectionCSS->addCss('css/vendor/semantic/comment.css', true); + $headerCollectionCSS->addCss('css/vendor/semantic/card.css', true); $headerCollectionCSS->addCss('css/vendor/semantic/list.css', true); $this->view->form = new ModuleMonitorActiveCallsForm(); @@ -76,24 +78,35 @@ public function indexAction(): void $user['username'] = "$user[callerid] <$user[number]>"; } $this->view->usersArray = $users; + $this->view->userRestrictions = implode(',', $userRestrictions); $this->view->userId = $userId; $this->view->userNumber = $userNumber; $this->view->cid = $cid; - $this->view->queueId = ''; + $this->view->queueIds = []; - $settings = UsersSettings::findFirst([ - 'userId=:userId: AND key=:key:', + $settings = UsersSettings::find([ + 'userId=:userId:', 'bind' => [ - 'userId' => $userId, - 'key' => 'queueId' + 'userId' => (string)$userId, ] ]); - if($settings){ - $this->view->queueId = $settings->value; + $minWaitVisible = 0; + foreach ($settings as $setting){ + if('queueIds' === $setting->key ){ + $decoded = json_decode($setting->value, true); + $this->view->queueIds = is_array($decoded) ? $decoded : []; + }elseif ('minWaitVisible' === $setting->key ){ + $minWaitVisible = intval($setting->value); + } } - + $minWaitVisibleVariants = []; + foreach ([0,10,20,30,40,50,60] as $value){ + $minWaitVisibleVariants[] = ['id' => $value, 'active' => $minWaitVisible === $value, 'name' => $value]; + } + $this->view->minWaitVisible = $minWaitVisibleVariants; + $this->view->minWaitVisibleValue = $minWaitVisible; } /** @@ -136,6 +149,20 @@ public function saveAction() :void $this->view->data = $data; } + public function backandEnableAction() :void + { + if (MonitorActiveCallsMain::backendExists()) { + // Модуль включен. + $api = new ModuleSoftphoneBackendApi(); + $api->initialize(); + $this->view->data = $api->createLoginResponse('1', 'admin'); + $this->view->success = true; + }else{ + $this->view->success = false; + $this->view->data = []; + } + } + public function saveUserAction():void { $data = $this->request->getPost(); @@ -147,9 +174,27 @@ public function saveUserAction():void } $settings->adminUserId = $data['adminUserId']??''; $this->view->success = $settings->save(); - }elseif (isset($data['queueId']) ){ + }elseif (isset($data['minWaitVisible']) ){ + [,,$userId,] = $this->getUserData(); + $key = 'minWaitVisible'; + $settings = UsersSettings::findFirst([ + 'userId=:userId: AND key=:key:', + 'bind' => [ + 'userId' => $userId, + 'key' => $key + ] + ]); + if(!$settings){ + $settings = new UsersSettings(); + $settings->userId = $userId; + $settings->key = $key; + } + $settings->value = $data['minWaitVisible']??''; + $this->view->success = $settings->save(); + }elseif (isset($data['queueIds']) ){ [,,$userId,] = $this->getUserData(); - $key = 'queueId'; + $userId = (string)$userId; + $key = 'queueIds'; $settings = UsersSettings::findFirst([ 'userId=:userId: AND key=:key:', 'bind' => [ @@ -162,7 +207,13 @@ public function saveUserAction():void $settings->userId = $userId; $settings->key = $key; } - $settings->value = $data['queueId']??''; + // Принимаем массив или JSON-строку + $queueIds = $data['queueIds']; + if (is_string($queueIds)) { + $decoded = json_decode($queueIds, true); + $queueIds = is_array($decoded) ? $decoded : []; + } + $settings->value = json_encode(is_array($queueIds) ? $queueIds : []); $this->view->success = $settings->save(); } $this->view->data = $data; diff --git a/App/Views/index.volt b/App/Views/index.volt index 5033eaa..698689b 100644 --- a/App/Views/index.volt +++ b/App/Views/index.volt @@ -6,7 +6,9 @@ - + + + {% if cid !== "" %}
@@ -47,96 +49,131 @@
+ +
+ {{ t._('module_monitorCalls_minWaitVisible') }}: + +
+

-
-
-
+ +
+ +
+ + +
+
+ +

- +
- - + <% queues[queueId].name %> [<% queues[queueId].number %>]

-
-
-

{{ t._('module_monitorCalls_waitingClients') }}

-
- +
-
<% call.src_num %>
+
+ + <% getClientHeader(call.src_num) %> +
{{ t._('module_monitorCalls_waitingTitleClient') }}: <% formatElapsedTime(call.queueData.EnterTime) %>
+
+
+
{{ t._('module_monitorCalls_noWaitingCalls') }}
+
+
-
-
-
- - - - - - - - - - - - - - - - - - -
{{ t._('module_monitorCalls_columnTitleAgent') }}{{ t._('module_monitorCalls_columnTitleSob') }}
-
-
- - <% number %> - + +
+
+
+
+
+ + <% agent.number %> + + <% agent.name %> +
+ +
+
+ <% getPeerPhoneLabel(agent.number) %>
-
- <% agent.name %> +
+ + <% getPeerNameLabel(agent.number) %>
-
<% getSrcNumForAgent(number) || '—' %> - -
- -
-
{{ t._('module_monitorCalls_legendTitleIdle') }}
-
{{ t._('module_monitorCalls_legendTitleInCall') }}
-
{{ t._('module_monitorCalls_legendTitleCalling') }}
-
{{ t._('module_monitorCalls_legendTitleSpy') }}
-
{{ t._('module_monitorCalls_legendTitleDisable') }}
+
+
+ + +
+
+ + {{ t._('module_monitorCalls_needSelectQueue') }} +
+
+ + +
+
+
{{ t._('module_monitorCalls_legendTitleIdle') }}
+
{{ t._('module_monitorCalls_legendTitleInCall') }}
+
{{ t._('module_monitorCalls_legendTitleCalling') }}
+
{{ t._('module_monitorCalls_legendTitleSpy') }}
+
{{ t._('module_monitorCalls_legendTitleDisable') }}
+
+
@@ -152,28 +189,29 @@ - <% 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); } });