@7365admin1/layer-common 3.1.4-staging.49 → 3.1.4-staging.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/EntryPassInformation.vue +4 -4
- package/components/HidAccessLogDashboard.vue +53 -8
- package/components/HidIntercomManagement.vue +621 -174
- package/components/HidQrCodeConfiguration.vue +742 -94
- package/components/HidServiceSettingsPanel.vue +20 -1
- package/components/HidUserEnrollment.vue +2 -0
- package/components/VisitorForm.vue +201 -78
- package/components/VisitorManagement.vue +0 -1
- package/composables/useHidAmico.ts +66 -0
- package/composables/useHidNavigation.ts +0 -7
- package/composables/useSipWebPhone.ts +236 -0
- package/composables/useWebUsb.ts +127 -37
- package/package.json +2 -1
- package/plugins/secure-member.client.ts +21 -56
- package/types/site.d.ts +65 -0
- package/components/HidIdentityMapping.vue +0 -975
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +0 -23
|
@@ -12,7 +12,17 @@
|
|
|
12
12
|
Add Contacts
|
|
13
13
|
</v-btn>
|
|
14
14
|
|
|
15
|
-
<
|
|
15
|
+
<v-btn
|
|
16
|
+
v-else
|
|
17
|
+
rounded="xl"
|
|
18
|
+
class="text-none web-call-button"
|
|
19
|
+
:class="{ 'is-connected': webPhone.isRegistered.value, 'has-incoming-call': webPhone.callState.value === 'incoming' }"
|
|
20
|
+
variant="flat"
|
|
21
|
+
prepend-icon="mdi-phone-in-talk-outline"
|
|
22
|
+
@click="openWebPhoneDialog()"
|
|
23
|
+
>
|
|
24
|
+
{{ webPhone.callState.value === 'incoming' ? 'Incoming Call' : 'Web Call' }}
|
|
25
|
+
</v-btn>
|
|
16
26
|
|
|
17
27
|
<div class="toolbar-spacer" />
|
|
18
28
|
|
|
@@ -105,6 +115,7 @@
|
|
|
105
115
|
<v-list-item title="Call Settings" @click="openCallDialog(row)" />
|
|
106
116
|
<v-list-item title="Dial Code" @click="openDialCodeDialog(row)" />
|
|
107
117
|
<v-list-item title="Make a call" @click="openMakeCallDialog(row)" />
|
|
118
|
+
<v-list-item title="Web call" @click="openWebPhoneDialog(row)" />
|
|
108
119
|
</v-list>
|
|
109
120
|
</v-menu>
|
|
110
121
|
</td>
|
|
@@ -231,36 +242,6 @@
|
|
|
231
242
|
</v-card>
|
|
232
243
|
</v-dialog>
|
|
233
244
|
|
|
234
|
-
<v-dialog v-model="rebootConfirmDialog" max-width="700" persistent>
|
|
235
|
-
<v-card class="reboot-dialog" rounded="0">
|
|
236
|
-
<button class="reboot-close" type="button" aria-label="Close" @click="rebootConfirmDialog = false">
|
|
237
|
-
<v-icon icon="mdi-close" />
|
|
238
|
-
</button>
|
|
239
|
-
<v-card-title class="reboot-title">Reboot is needed</v-card-title>
|
|
240
|
-
<v-card-text class="reboot-message">
|
|
241
|
-
It is necessary to restart the device after applying the configurations. Do you want to proceed?
|
|
242
|
-
</v-card-text>
|
|
243
|
-
<v-card-actions class="reboot-actions">
|
|
244
|
-
<v-btn class="text-none reboot-cancel" variant="text" @click="rebootConfirmDialog = false">
|
|
245
|
-
Cancel
|
|
246
|
-
</v-btn>
|
|
247
|
-
<v-btn class="text-none reboot-ok" variant="flat" @click="startRebootWait">
|
|
248
|
-
OK
|
|
249
|
-
</v-btn>
|
|
250
|
-
</v-card-actions>
|
|
251
|
-
</v-card>
|
|
252
|
-
</v-dialog>
|
|
253
|
-
|
|
254
|
-
<v-dialog v-model="rebootingDialog" max-width="900" persistent>
|
|
255
|
-
<v-card class="rebooting-card" rounded="0">
|
|
256
|
-
<div class="wait-word">Wait!</div>
|
|
257
|
-
<div class="rebooting-copy">
|
|
258
|
-
<h3>Rebooting Equipment</h3>
|
|
259
|
-
<p>Wait {{ rebootCountdown }} seconds for the equipment to reboot.</p>
|
|
260
|
-
</div>
|
|
261
|
-
</v-card>
|
|
262
|
-
</v-dialog>
|
|
263
|
-
|
|
264
245
|
<v-dialog v-model="callDialog" max-width="360" persistent>
|
|
265
246
|
<v-card class="form-dialog call-dialog" rounded="lg">
|
|
266
247
|
<v-card-title class="dialog-title">Call Settings</v-card-title>
|
|
@@ -350,10 +331,10 @@
|
|
|
350
331
|
<v-card-title class="dialog-title">Make a Call</v-card-title>
|
|
351
332
|
<v-card-text class="dialog-body make-call-body">
|
|
352
333
|
<div class="make-call-field">
|
|
353
|
-
<div class="call-label">
|
|
334
|
+
<div class="call-label">IP address to dial</div>
|
|
354
335
|
<v-text-field
|
|
355
336
|
v-model="makeCallForm.target"
|
|
356
|
-
placeholder="
|
|
337
|
+
placeholder="000.000.0.0"
|
|
357
338
|
variant="outlined"
|
|
358
339
|
density="compact"
|
|
359
340
|
hide-details="auto"
|
|
@@ -388,8 +369,192 @@
|
|
|
388
369
|
</div>
|
|
389
370
|
</v-card-text>
|
|
390
371
|
<v-card-actions class="dialog-actions pa-0">
|
|
391
|
-
<v-btn class="text-none action-
|
|
392
|
-
|
|
372
|
+
<v-btn class="text-none action-submit full-action" variant="flat" height="44" @click="makeCallDialog = false">Close</v-btn>
|
|
373
|
+
</v-card-actions>
|
|
374
|
+
</v-card>
|
|
375
|
+
</v-dialog>
|
|
376
|
+
|
|
377
|
+
<v-dialog v-model="webPhoneDialog" max-width="820" persistent eager>
|
|
378
|
+
<v-card class="web-phone-dialog" rounded="lg">
|
|
379
|
+
<v-card-title class="dialog-title web-phone-title">
|
|
380
|
+
<span>Web Call</span>
|
|
381
|
+
<v-chip size="small" variant="flat" :class="webPhoneStatusClass">
|
|
382
|
+
{{ webPhoneStatusLabel }}
|
|
383
|
+
</v-chip>
|
|
384
|
+
</v-card-title>
|
|
385
|
+
|
|
386
|
+
<v-card-text class="web-phone-body">
|
|
387
|
+
<div class="web-phone-settings">
|
|
388
|
+
<div class="web-phone-section-title">SIP Account</div>
|
|
389
|
+
<div class="call-label">WebSocket Server <span>*</span></div>
|
|
390
|
+
<v-text-field
|
|
391
|
+
v-model="webPhoneForm.webSocketServer"
|
|
392
|
+
placeholder="wss://sip.example.com/ws"
|
|
393
|
+
variant="outlined"
|
|
394
|
+
density="compact"
|
|
395
|
+
hide-details="auto"
|
|
396
|
+
class="call-input"
|
|
397
|
+
:disabled="webPhone.isRegistered.value"
|
|
398
|
+
/>
|
|
399
|
+
<div class="call-label">SIP Address <span>*</span></div>
|
|
400
|
+
<v-text-field
|
|
401
|
+
v-model="webPhoneForm.aor"
|
|
402
|
+
placeholder="operator@example.com"
|
|
403
|
+
variant="outlined"
|
|
404
|
+
density="compact"
|
|
405
|
+
hide-details="auto"
|
|
406
|
+
class="call-input"
|
|
407
|
+
:disabled="webPhone.isRegistered.value"
|
|
408
|
+
/>
|
|
409
|
+
<div class="form-two-col">
|
|
410
|
+
<div>
|
|
411
|
+
<div class="call-label">Username <span>*</span></div>
|
|
412
|
+
<v-text-field
|
|
413
|
+
v-model="webPhoneForm.username"
|
|
414
|
+
placeholder="Username"
|
|
415
|
+
variant="outlined"
|
|
416
|
+
density="compact"
|
|
417
|
+
hide-details="auto"
|
|
418
|
+
class="call-input"
|
|
419
|
+
:disabled="webPhone.isRegistered.value"
|
|
420
|
+
/>
|
|
421
|
+
</div>
|
|
422
|
+
<div>
|
|
423
|
+
<div class="call-label">Password <span>*</span></div>
|
|
424
|
+
<v-text-field
|
|
425
|
+
v-model="webPhoneForm.password"
|
|
426
|
+
placeholder="Password"
|
|
427
|
+
:type="showWebPhonePassword ? 'text' : 'password'"
|
|
428
|
+
:append-inner-icon="showWebPhonePassword ? 'mdi-eye-off' : 'mdi-eye'"
|
|
429
|
+
variant="outlined"
|
|
430
|
+
density="compact"
|
|
431
|
+
hide-details="auto"
|
|
432
|
+
class="call-input"
|
|
433
|
+
:disabled="webPhone.isRegistered.value"
|
|
434
|
+
@click:append-inner="showWebPhonePassword = !showWebPhonePassword"
|
|
435
|
+
/>
|
|
436
|
+
</div>
|
|
437
|
+
</div>
|
|
438
|
+
<div class="call-toggle-row web-video-toggle">
|
|
439
|
+
<span>Enable video</span>
|
|
440
|
+
<v-switch
|
|
441
|
+
v-model="webPhoneForm.video"
|
|
442
|
+
color="success"
|
|
443
|
+
density="compact"
|
|
444
|
+
hide-details
|
|
445
|
+
:disabled="webPhone.isRegistered.value"
|
|
446
|
+
/>
|
|
447
|
+
</div>
|
|
448
|
+
<v-btn
|
|
449
|
+
v-if="!webPhone.isRegistered.value"
|
|
450
|
+
block
|
|
451
|
+
class="text-none web-phone-connect"
|
|
452
|
+
variant="flat"
|
|
453
|
+
prepend-icon="mdi-lan-connect"
|
|
454
|
+
:loading="webPhoneConnecting"
|
|
455
|
+
@click="connectWebPhone"
|
|
456
|
+
>
|
|
457
|
+
Connect Web Phone
|
|
458
|
+
</v-btn>
|
|
459
|
+
<v-btn
|
|
460
|
+
v-else
|
|
461
|
+
block
|
|
462
|
+
class="text-none web-phone-disconnect"
|
|
463
|
+
variant="outlined"
|
|
464
|
+
prepend-icon="mdi-lan-disconnect"
|
|
465
|
+
:loading="webPhoneConnecting"
|
|
466
|
+
:disabled="webPhone.hasCall.value"
|
|
467
|
+
@click="disconnectWebPhone"
|
|
468
|
+
>
|
|
469
|
+
Disconnect
|
|
470
|
+
</v-btn>
|
|
471
|
+
</div>
|
|
472
|
+
|
|
473
|
+
<div class="web-phone-console">
|
|
474
|
+
<div class="web-phone-media" :class="{ 'has-video': webPhoneForm.video && webPhone.callState.value === 'active' }">
|
|
475
|
+
<video ref="remoteVideoEl" autoplay playsinline class="remote-video" />
|
|
476
|
+
<video ref="localVideoEl" autoplay muted playsinline class="local-video" />
|
|
477
|
+
<audio ref="remoteAudioEl" autoplay />
|
|
478
|
+
<div v-if="!webPhoneForm.video || webPhone.callState.value !== 'active'" class="audio-call-state">
|
|
479
|
+
<v-icon size="54">{{ webPhoneCallIcon }}</v-icon>
|
|
480
|
+
<strong>{{ webPhoneCallLabel }}</strong>
|
|
481
|
+
<span v-if="webPhone.currentTarget.value">{{ webPhone.currentTarget.value }}</span>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
|
|
485
|
+
<div v-if="webPhone.callState.value === 'incoming'" class="incoming-call-actions">
|
|
486
|
+
<v-btn
|
|
487
|
+
class="text-none answer-call"
|
|
488
|
+
variant="flat"
|
|
489
|
+
prepend-icon="mdi-phone"
|
|
490
|
+
:loading="webPhoneActionLoading"
|
|
491
|
+
@click="answerWebCall"
|
|
492
|
+
>
|
|
493
|
+
Answer
|
|
494
|
+
</v-btn>
|
|
495
|
+
<v-btn
|
|
496
|
+
class="text-none reject-call"
|
|
497
|
+
variant="flat"
|
|
498
|
+
prepend-icon="mdi-phone-hangup"
|
|
499
|
+
:disabled="webPhoneActionLoading"
|
|
500
|
+
@click="declineWebCall"
|
|
501
|
+
>
|
|
502
|
+
Decline
|
|
503
|
+
</v-btn>
|
|
504
|
+
</div>
|
|
505
|
+
|
|
506
|
+
<template v-else>
|
|
507
|
+
<div class="call-label">Extension or SIP Address</div>
|
|
508
|
+
<div class="web-phone-dial-row">
|
|
509
|
+
<v-text-field
|
|
510
|
+
v-model="webPhoneForm.destination"
|
|
511
|
+
placeholder="1001 or device@example.com"
|
|
512
|
+
variant="outlined"
|
|
513
|
+
density="compact"
|
|
514
|
+
hide-details="auto"
|
|
515
|
+
class="call-input mb-0"
|
|
516
|
+
:disabled="!webPhone.isRegistered.value || webPhone.hasCall.value"
|
|
517
|
+
@keyup.enter="startWebCall"
|
|
518
|
+
/>
|
|
519
|
+
<v-btn
|
|
520
|
+
icon="mdi-phone"
|
|
521
|
+
class="start-web-call"
|
|
522
|
+
variant="flat"
|
|
523
|
+
:loading="webPhoneActionLoading && webPhone.callState.value === 'calling'"
|
|
524
|
+
:disabled="!webPhone.isRegistered.value || webPhone.hasCall.value || !webPhoneForm.destination.trim()"
|
|
525
|
+
@click="startWebCall"
|
|
526
|
+
/>
|
|
527
|
+
</div>
|
|
528
|
+
|
|
529
|
+
<div v-if="webPhone.hasCall.value" class="active-call-controls">
|
|
530
|
+
<v-btn
|
|
531
|
+
:icon="webPhone.muted.value ? 'mdi-microphone-off' : 'mdi-microphone'"
|
|
532
|
+
variant="outlined"
|
|
533
|
+
:disabled="webPhone.callState.value !== 'active'"
|
|
534
|
+
:title="webPhone.muted.value ? 'Unmute' : 'Mute'"
|
|
535
|
+
@click="webPhone.toggleMute"
|
|
536
|
+
/>
|
|
537
|
+
<v-btn
|
|
538
|
+
icon="mdi-phone-hangup"
|
|
539
|
+
class="hangup-web-call"
|
|
540
|
+
variant="flat"
|
|
541
|
+
:loading="webPhoneActionLoading && webPhone.callState.value === 'ending'"
|
|
542
|
+
title="End call"
|
|
543
|
+
@click="hangupWebCall"
|
|
544
|
+
/>
|
|
545
|
+
</div>
|
|
546
|
+
</template>
|
|
547
|
+
|
|
548
|
+
<v-alert v-if="webPhone.errorMessage.value" type="error" variant="tonal" density="compact" class="web-phone-error">
|
|
549
|
+
{{ webPhone.errorMessage.value }}
|
|
550
|
+
</v-alert>
|
|
551
|
+
</div>
|
|
552
|
+
</v-card-text>
|
|
553
|
+
|
|
554
|
+
<v-card-actions class="dialog-actions pa-0">
|
|
555
|
+
<v-btn class="text-none action-submit full-action" variant="flat" height="44" @click="webPhoneDialog = false">
|
|
556
|
+
Close
|
|
557
|
+
</v-btn>
|
|
393
558
|
</v-card-actions>
|
|
394
559
|
</v-card>
|
|
395
560
|
</v-dialog>
|
|
@@ -531,21 +696,25 @@ const limit = 20;
|
|
|
531
696
|
const selectedRow = ref<IntercomRow | null>(null);
|
|
532
697
|
const selectedContact = ref<ContactRow | null>(null);
|
|
533
698
|
const showSipPassword = ref(false);
|
|
699
|
+
const showWebPhonePassword = ref(false);
|
|
700
|
+
const webPhoneConnecting = ref(false);
|
|
701
|
+
const webPhoneActionLoading = ref(false);
|
|
702
|
+
const localVideoEl = ref<HTMLVideoElement | null>(null);
|
|
703
|
+
const remoteVideoEl = ref<HTMLVideoElement | null>(null);
|
|
704
|
+
const remoteAudioEl = ref<HTMLAudioElement | null>(null);
|
|
705
|
+
const webPhone = useSipWebPhone();
|
|
534
706
|
|
|
535
707
|
const viewDialog = ref(false);
|
|
536
708
|
const sipDialog = ref(false);
|
|
537
|
-
const rebootConfirmDialog = ref(false);
|
|
538
|
-
const rebootingDialog = ref(false);
|
|
539
709
|
const callDialog = ref(false);
|
|
540
710
|
const dialCodeDialog = ref(false);
|
|
541
711
|
const makeCallDialog = ref(false);
|
|
712
|
+
const webPhoneDialog = ref(false);
|
|
542
713
|
const contactDialog = ref(false);
|
|
543
714
|
const contactViewDialog = ref(false);
|
|
544
715
|
const deleteContactDialog = ref(false);
|
|
545
716
|
|
|
546
717
|
const message = reactive<{ type: MessageType; text: string }>({ type: "info", text: "" });
|
|
547
|
-
const rebootCountdown = ref(59);
|
|
548
|
-
let rebootTimer: ReturnType<typeof setInterval> | undefined;
|
|
549
718
|
|
|
550
719
|
const statusOptions = ["Status", "Connected", "Not connected", "Connecting", "Disabled", "Failed"];
|
|
551
720
|
|
|
@@ -582,7 +751,7 @@ const sipForm = reactive({
|
|
|
582
751
|
serverIp: "",
|
|
583
752
|
serverPort: "5060",
|
|
584
753
|
initialRtpPort: "10000",
|
|
585
|
-
finalRtpPort: "
|
|
754
|
+
finalRtpPort: "10100",
|
|
586
755
|
numericExtension: false,
|
|
587
756
|
extension: "",
|
|
588
757
|
login: "",
|
|
@@ -617,6 +786,15 @@ const makeCallForm = reactive({
|
|
|
617
786
|
statusType: "info" as MessageType,
|
|
618
787
|
});
|
|
619
788
|
|
|
789
|
+
const webPhoneForm = reactive({
|
|
790
|
+
webSocketServer: "",
|
|
791
|
+
aor: "",
|
|
792
|
+
username: "",
|
|
793
|
+
password: "",
|
|
794
|
+
destination: "",
|
|
795
|
+
video: false,
|
|
796
|
+
});
|
|
797
|
+
|
|
620
798
|
const contactForm = reactive({
|
|
621
799
|
id: undefined as number | undefined,
|
|
622
800
|
readerId: "",
|
|
@@ -717,6 +895,35 @@ const contactDetailRows = computed<DetailItem[]>(() => [
|
|
|
717
895
|
{ label: "Server Address", value: selectedContact.value?.serverAddress, link: true },
|
|
718
896
|
{ label: "Location", value: selectedContact.value?.location },
|
|
719
897
|
]);
|
|
898
|
+
const webPhoneStatusLabel = computed(() => {
|
|
899
|
+
if (webPhone.callState.value === "incoming") return "Incoming call";
|
|
900
|
+
if (webPhone.callState.value === "calling") return "Calling";
|
|
901
|
+
if (webPhone.callState.value === "active") return "In call";
|
|
902
|
+
if (webPhone.callState.value === "ending") return "Ending call";
|
|
903
|
+
if (webPhone.connectionState.value === "connecting") return "Connecting";
|
|
904
|
+
if (webPhone.connectionState.value === "registered") return "Ready";
|
|
905
|
+
if (webPhone.connectionState.value === "error") return "Connection failed";
|
|
906
|
+
return "Disconnected";
|
|
907
|
+
});
|
|
908
|
+
const webPhoneStatusClass = computed(() => {
|
|
909
|
+
if (webPhone.callState.value === "incoming") return "web-phone-status incoming";
|
|
910
|
+
if (webPhone.callState.value === "active" || webPhone.connectionState.value === "registered") return "web-phone-status ready";
|
|
911
|
+
if (webPhone.connectionState.value === "error") return "web-phone-status failed";
|
|
912
|
+
return "web-phone-status idle";
|
|
913
|
+
});
|
|
914
|
+
const webPhoneCallLabel = computed(() => {
|
|
915
|
+
if (webPhone.callState.value === "incoming") return "Incoming call";
|
|
916
|
+
if (webPhone.callState.value === "calling") return "Calling...";
|
|
917
|
+
if (webPhone.callState.value === "active") return "Call connected";
|
|
918
|
+
if (webPhone.callState.value === "ending") return "Ending call...";
|
|
919
|
+
return webPhone.isRegistered.value ? "Ready to call" : "Connect a SIP account";
|
|
920
|
+
});
|
|
921
|
+
const webPhoneCallIcon = computed(() => {
|
|
922
|
+
if (webPhone.callState.value === "incoming") return "mdi-phone-incoming";
|
|
923
|
+
if (webPhone.callState.value === "calling") return "mdi-phone-outgoing";
|
|
924
|
+
if (webPhone.callState.value === "active") return "mdi-phone-in-talk";
|
|
925
|
+
return "mdi-phone-outline";
|
|
926
|
+
});
|
|
720
927
|
|
|
721
928
|
watch(
|
|
722
929
|
() => props.site,
|
|
@@ -732,9 +939,12 @@ watch(pages, () => {
|
|
|
732
939
|
if (page.value > pages.value) page.value = pages.value;
|
|
733
940
|
});
|
|
734
941
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
942
|
+
watch(
|
|
943
|
+
() => webPhone.callState.value,
|
|
944
|
+
(state) => {
|
|
945
|
+
if (state === "incoming") webPhoneDialog.value = true;
|
|
946
|
+
},
|
|
947
|
+
);
|
|
738
948
|
|
|
739
949
|
async function reload() {
|
|
740
950
|
if (!props.site) return;
|
|
@@ -801,6 +1011,14 @@ function paginate<T>(items: T[]) {
|
|
|
801
1011
|
function toIntercomRow(reader: Record<string, any>, pjsip: Record<string, any>, status: string): IntercomRow {
|
|
802
1012
|
const peerToPeer = toFlag(getPjsipValue(pjsip, ["peer_to_peer_enabled", "peerToPeerEnabled"])) === "1";
|
|
803
1013
|
const dialMode = toText(getPjsipValue(pjsip, ["dialing_display_mode", "dialingDisplayMode"]), "0");
|
|
1014
|
+
const initialRtpPort = toText(getPjsipValue(pjsip, ["server_outbound_port", "initialRtpPort", "initial_rtp_port"]), "N/A");
|
|
1015
|
+
const rtpPortRange = getPjsipValue(pjsip, ["server_outbound_port_range"]);
|
|
1016
|
+
const explicitFinalRtpPort = getPjsipValue(pjsip, ["finalRtpPort", "final_rtp_port"]);
|
|
1017
|
+
const initialRtpPortNumber = Number(initialRtpPort);
|
|
1018
|
+
const rtpPortRangeNumber = Number(rtpPortRange);
|
|
1019
|
+
const finalRtpPort = rtpPortRange !== undefined && Number.isFinite(initialRtpPortNumber) && Number.isFinite(rtpPortRangeNumber)
|
|
1020
|
+
? String(initialRtpPortNumber + rtpPortRangeNumber)
|
|
1021
|
+
: toText(explicitFinalRtpPort, "N/A");
|
|
804
1022
|
|
|
805
1023
|
return {
|
|
806
1024
|
reader,
|
|
@@ -808,8 +1026,8 @@ function toIntercomRow(reader: Record<string, any>, pjsip: Record<string, any>,
|
|
|
808
1026
|
location: reader.location || "N/A",
|
|
809
1027
|
serverAddress: toText(getPjsipValue(pjsip, ["server_ip", "serverAddress", "server_address", "serverIp"]), "N/A"),
|
|
810
1028
|
serverPort: toText(getPjsipValue(pjsip, ["server_port", "serverPort", "port"]), "N/A"),
|
|
811
|
-
initialRtpPort
|
|
812
|
-
finalRtpPort
|
|
1029
|
+
initialRtpPort,
|
|
1030
|
+
finalRtpPort,
|
|
813
1031
|
monitorPath: reader.monitorPath || "N/A",
|
|
814
1032
|
status,
|
|
815
1033
|
sipEnabled: toFlag(getPjsipValue(pjsip, ["enabled"])) === "1",
|
|
@@ -880,8 +1098,8 @@ function getPjsipValue(source: Record<string, any>, keys: string[]) {
|
|
|
880
1098
|
|
|
881
1099
|
function normalizeStatus(response: Record<string, any>, pjsip: Record<string, any>) {
|
|
882
1100
|
const status = Number(response?.status);
|
|
1101
|
+
if (toFlag(pjsip.enabled) === "0" || status === -1) return "Disabled";
|
|
883
1102
|
if (status === 200) return "Connected";
|
|
884
|
-
if (status === -1 || toFlag(pjsip.enabled) === "0") return "Disabled";
|
|
885
1103
|
if (status === 0 || status === 100) return "Connecting";
|
|
886
1104
|
if ([401, 403, 408, 503].includes(status)) return "Failed";
|
|
887
1105
|
return "Not connected";
|
|
@@ -911,7 +1129,7 @@ function openSipDialog(row: IntercomRow) {
|
|
|
911
1129
|
sipForm.serverIp = row.serverAddress === "N/A" ? "" : row.serverAddress;
|
|
912
1130
|
sipForm.serverPort = row.serverPort === "N/A" ? "5060" : row.serverPort;
|
|
913
1131
|
sipForm.initialRtpPort = row.initialRtpPort === "N/A" ? "10000" : row.initialRtpPort;
|
|
914
|
-
sipForm.finalRtpPort = row.finalRtpPort === "N/A" ? "
|
|
1132
|
+
sipForm.finalRtpPort = row.finalRtpPort === "N/A" ? "10100" : row.finalRtpPort;
|
|
915
1133
|
sipForm.numericExtension = row.numericExtension;
|
|
916
1134
|
sipForm.extension = row.extension === "N/A" ? "" : row.extension;
|
|
917
1135
|
sipForm.login = row.login === "N/A" ? "" : row.login;
|
|
@@ -945,12 +1163,100 @@ function openDialCodeDialog(row: IntercomRow) {
|
|
|
945
1163
|
|
|
946
1164
|
function openMakeCallDialog(row: IntercomRow) {
|
|
947
1165
|
selectedRow.value = row;
|
|
948
|
-
makeCallForm.target = row.
|
|
1166
|
+
makeCallForm.target = isIpAddress(row.serverAddress) ? row.serverAddress : "";
|
|
949
1167
|
makeCallForm.status = "";
|
|
950
1168
|
makeCallForm.statusType = "info";
|
|
951
1169
|
makeCallDialog.value = true;
|
|
952
1170
|
}
|
|
953
1171
|
|
|
1172
|
+
function openWebPhoneDialog(row?: IntercomRow) {
|
|
1173
|
+
const activeRow = row || selectedRow.value || intercomRows.value[0];
|
|
1174
|
+
if (activeRow) {
|
|
1175
|
+
selectedRow.value = activeRow;
|
|
1176
|
+
if (!webPhone.isRegistered.value && activeRow.extension !== "N/A") {
|
|
1177
|
+
webPhoneForm.destination = activeRow.extension;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
webPhoneDialog.value = true;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
async function connectWebPhone() {
|
|
1184
|
+
webPhoneConnecting.value = true;
|
|
1185
|
+
try {
|
|
1186
|
+
await webPhone.connect({
|
|
1187
|
+
webSocketServer: webPhoneForm.webSocketServer,
|
|
1188
|
+
aor: webPhoneForm.aor,
|
|
1189
|
+
authorizationUsername: webPhoneForm.username,
|
|
1190
|
+
authorizationPassword: webPhoneForm.password,
|
|
1191
|
+
displayName: "iService365 Web",
|
|
1192
|
+
video: webPhoneForm.video,
|
|
1193
|
+
}, {
|
|
1194
|
+
localVideo: localVideoEl.value,
|
|
1195
|
+
remoteVideo: remoteVideoEl.value,
|
|
1196
|
+
remoteAudio: remoteAudioEl.value,
|
|
1197
|
+
});
|
|
1198
|
+
showMessage("Web phone connected and ready to receive calls.", "success");
|
|
1199
|
+
} catch (error: any) {
|
|
1200
|
+
showMessage(getErrorMessage(error), "error");
|
|
1201
|
+
} finally {
|
|
1202
|
+
webPhoneConnecting.value = false;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
async function disconnectWebPhone() {
|
|
1207
|
+
webPhoneConnecting.value = true;
|
|
1208
|
+
try {
|
|
1209
|
+
await webPhone.disconnect();
|
|
1210
|
+
showMessage("Web phone disconnected.", "info");
|
|
1211
|
+
} finally {
|
|
1212
|
+
webPhoneConnecting.value = false;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
async function startWebCall() {
|
|
1217
|
+
webPhoneActionLoading.value = true;
|
|
1218
|
+
try {
|
|
1219
|
+
await webPhone.call(webPhoneForm.destination);
|
|
1220
|
+
} catch (error: any) {
|
|
1221
|
+
showMessage(getErrorMessage(error), "error");
|
|
1222
|
+
} finally {
|
|
1223
|
+
webPhoneActionLoading.value = false;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
async function answerWebCall() {
|
|
1228
|
+
webPhoneActionLoading.value = true;
|
|
1229
|
+
try {
|
|
1230
|
+
await webPhone.answer();
|
|
1231
|
+
} catch (error: any) {
|
|
1232
|
+
showMessage(getErrorMessage(error), "error");
|
|
1233
|
+
} finally {
|
|
1234
|
+
webPhoneActionLoading.value = false;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
async function declineWebCall() {
|
|
1239
|
+
webPhoneActionLoading.value = true;
|
|
1240
|
+
try {
|
|
1241
|
+
await webPhone.decline();
|
|
1242
|
+
} catch (error: any) {
|
|
1243
|
+
showMessage(getErrorMessage(error), "error");
|
|
1244
|
+
} finally {
|
|
1245
|
+
webPhoneActionLoading.value = false;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
async function hangupWebCall() {
|
|
1250
|
+
webPhoneActionLoading.value = true;
|
|
1251
|
+
try {
|
|
1252
|
+
await webPhone.hangup();
|
|
1253
|
+
} catch (error: any) {
|
|
1254
|
+
showMessage(getErrorMessage(error), "error");
|
|
1255
|
+
} finally {
|
|
1256
|
+
webPhoneActionLoading.value = false;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
954
1260
|
function openContactDialog(contact?: ContactRow) {
|
|
955
1261
|
selectedContact.value = contact || null;
|
|
956
1262
|
contactForm.id = contact?.id;
|
|
@@ -974,21 +1280,34 @@ function openDeleteContact(contact: ContactRow) {
|
|
|
974
1280
|
async function saveSipSettings() {
|
|
975
1281
|
if (!selectedRow.value) return;
|
|
976
1282
|
const isPeerToPeer = sipForm.operatingMode === "peer-to-peer";
|
|
1283
|
+
const initialRtpPort = Number(sipForm.initialRtpPort);
|
|
1284
|
+
const finalRtpPort = Number(sipForm.finalRtpPort);
|
|
1285
|
+
|
|
1286
|
+
if (
|
|
1287
|
+
!Number.isInteger(initialRtpPort) ||
|
|
1288
|
+
!Number.isInteger(finalRtpPort) ||
|
|
1289
|
+
initialRtpPort < 1 ||
|
|
1290
|
+
finalRtpPort > 65535 ||
|
|
1291
|
+
finalRtpPort <= initialRtpPort
|
|
1292
|
+
) {
|
|
1293
|
+
showMessage("Final RTP Port must be greater than Initial RTP Port and both ports must be between 1 and 65535.", "warning");
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
977
1297
|
const saved = await savePjsip(selectedRow.value.reader._id, {
|
|
978
1298
|
enabled: sipForm.enabled ? "1" : "0",
|
|
979
1299
|
server_ip: sipForm.serverIp,
|
|
980
1300
|
server_port: sipForm.serverPort,
|
|
981
|
-
server_outbound_port:
|
|
982
|
-
server_outbound_port_range:
|
|
1301
|
+
server_outbound_port: String(initialRtpPort),
|
|
1302
|
+
server_outbound_port_range: String(finalRtpPort - initialRtpPort),
|
|
983
1303
|
numeric_branch_enabled: sipForm.numericExtension ? "1" : "0",
|
|
984
1304
|
branch: isPeerToPeer ? "" : sipForm.extension,
|
|
985
1305
|
login: isPeerToPeer ? "" : sipForm.login,
|
|
986
1306
|
password: isPeerToPeer ? "" : sipForm.password,
|
|
987
1307
|
peer_to_peer_enabled: isPeerToPeer ? "1" : "0",
|
|
988
|
-
}
|
|
1308
|
+
});
|
|
989
1309
|
if (saved) {
|
|
990
1310
|
sipDialog.value = false;
|
|
991
|
-
rebootConfirmDialog.value = true;
|
|
992
1311
|
}
|
|
993
1312
|
}
|
|
994
1313
|
|
|
@@ -1020,7 +1339,13 @@ async function runMakeCall() {
|
|
|
1020
1339
|
if (!selectedRow.value) return;
|
|
1021
1340
|
const target = makeCallForm.target.trim();
|
|
1022
1341
|
if (!target) {
|
|
1023
|
-
makeCallForm.status = "Please enter an address to dial.";
|
|
1342
|
+
makeCallForm.status = "Please enter an IP address to dial.";
|
|
1343
|
+
makeCallForm.statusType = "warning";
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
if (!isIpAddress(target)) {
|
|
1348
|
+
makeCallForm.status = "Address to dial must be an IP address.";
|
|
1024
1349
|
makeCallForm.statusType = "warning";
|
|
1025
1350
|
return;
|
|
1026
1351
|
}
|
|
@@ -1042,6 +1367,15 @@ async function runMakeCall() {
|
|
|
1042
1367
|
}
|
|
1043
1368
|
}
|
|
1044
1369
|
|
|
1370
|
+
function isIpAddress(value = "") {
|
|
1371
|
+
const parts = value.trim().split(".");
|
|
1372
|
+
return parts.length === 4 && parts.every((part) => {
|
|
1373
|
+
if (!/^\d{1,3}$/.test(part)) return false;
|
|
1374
|
+
const value = Number(part);
|
|
1375
|
+
return value >= 0 && value <= 255;
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1045
1379
|
async function runHangupCall() {
|
|
1046
1380
|
if (!selectedRow.value) return;
|
|
1047
1381
|
hangingUp.value = true;
|
|
@@ -1078,30 +1412,6 @@ async function savePjsip(readerId: string, payload: Record<string, string>, opti
|
|
|
1078
1412
|
}
|
|
1079
1413
|
}
|
|
1080
1414
|
|
|
1081
|
-
function startRebootWait() {
|
|
1082
|
-
rebootConfirmDialog.value = false;
|
|
1083
|
-
rebootCountdown.value = 59;
|
|
1084
|
-
rebootingDialog.value = true;
|
|
1085
|
-
clearRebootTimer();
|
|
1086
|
-
|
|
1087
|
-
rebootTimer = setInterval(() => {
|
|
1088
|
-
rebootCountdown.value -= 1;
|
|
1089
|
-
if (rebootCountdown.value <= 0) {
|
|
1090
|
-
clearRebootTimer();
|
|
1091
|
-
rebootingDialog.value = false;
|
|
1092
|
-
showMessage("Equipment reboot wait completed.", "success");
|
|
1093
|
-
reload();
|
|
1094
|
-
}
|
|
1095
|
-
}, 1000);
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
function clearRebootTimer() {
|
|
1099
|
-
if (rebootTimer) {
|
|
1100
|
-
clearInterval(rebootTimer);
|
|
1101
|
-
rebootTimer = undefined;
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
1415
|
async function saveContact() {
|
|
1106
1416
|
if (!contactForm.readerId || !contactForm.name.trim() || !contactForm.serverAddress.trim()) {
|
|
1107
1417
|
showMessage("Please complete the required contact fields.", "warning");
|
|
@@ -1259,6 +1569,23 @@ export default {
|
|
|
1259
1569
|
width: 160px;
|
|
1260
1570
|
}
|
|
1261
1571
|
|
|
1572
|
+
.web-call-button {
|
|
1573
|
+
min-width: 160px;
|
|
1574
|
+
background: #e5e7eb;
|
|
1575
|
+
color: #0b2c40;
|
|
1576
|
+
font-size: 12px;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
.web-call-button.is-connected {
|
|
1580
|
+
background: #d9f2e1;
|
|
1581
|
+
color: #16743a;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
.web-call-button.has-incoming-call {
|
|
1585
|
+
background: #1769aa;
|
|
1586
|
+
color: #fff;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1262
1589
|
.add-button {
|
|
1263
1590
|
min-width: 160px;
|
|
1264
1591
|
background: #dedede;
|
|
@@ -1630,98 +1957,6 @@ export default {
|
|
|
1630
1957
|
overflow: hidden;
|
|
1631
1958
|
}
|
|
1632
1959
|
|
|
1633
|
-
.reboot-dialog {
|
|
1634
|
-
position: relative;
|
|
1635
|
-
min-height: 220px;
|
|
1636
|
-
overflow: hidden;
|
|
1637
|
-
background: #fff;
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
.reboot-close {
|
|
1641
|
-
position: absolute;
|
|
1642
|
-
top: 14px;
|
|
1643
|
-
right: 14px;
|
|
1644
|
-
display: grid;
|
|
1645
|
-
width: 40px;
|
|
1646
|
-
height: 40px;
|
|
1647
|
-
padding: 0;
|
|
1648
|
-
border: 0;
|
|
1649
|
-
place-items: center;
|
|
1650
|
-
background: transparent;
|
|
1651
|
-
color: #c7c7c7;
|
|
1652
|
-
cursor: pointer;
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
.reboot-title {
|
|
1656
|
-
padding: 24px 20px 12px;
|
|
1657
|
-
color: #111;
|
|
1658
|
-
font-size: 28px;
|
|
1659
|
-
font-weight: 400;
|
|
1660
|
-
letter-spacing: 0;
|
|
1661
|
-
line-height: 1.2;
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
.reboot-message {
|
|
1665
|
-
padding: 24px 20px 28px;
|
|
1666
|
-
color: #222;
|
|
1667
|
-
font-size: 17px;
|
|
1668
|
-
line-height: 1.45;
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
.reboot-actions {
|
|
1672
|
-
display: flex;
|
|
1673
|
-
justify-content: flex-end;
|
|
1674
|
-
gap: 20px;
|
|
1675
|
-
padding: 0 20px 20px;
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
.reboot-cancel {
|
|
1679
|
-
color: #075fb7;
|
|
1680
|
-
font-size: 16px;
|
|
1681
|
-
letter-spacing: 0;
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
.reboot-ok {
|
|
1685
|
-
min-width: 64px;
|
|
1686
|
-
border-radius: 5px;
|
|
1687
|
-
background: #062f4d;
|
|
1688
|
-
color: #fff;
|
|
1689
|
-
font-size: 16px;
|
|
1690
|
-
letter-spacing: 0;
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
.rebooting-card {
|
|
1694
|
-
display: grid;
|
|
1695
|
-
min-height: 240px;
|
|
1696
|
-
grid-template-columns: minmax(0, 1.1fr) minmax(280px, 0.9fr);
|
|
1697
|
-
align-items: center;
|
|
1698
|
-
gap: 24px;
|
|
1699
|
-
padding: 20px 54px;
|
|
1700
|
-
background: #fff;
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
.wait-word {
|
|
1704
|
-
color: #347bd6;
|
|
1705
|
-
font-size: 120px;
|
|
1706
|
-
font-weight: 400;
|
|
1707
|
-
letter-spacing: 0;
|
|
1708
|
-
line-height: 0.95;
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
.rebooting-copy h3 {
|
|
1712
|
-
margin: 0 0 18px;
|
|
1713
|
-
color: #000;
|
|
1714
|
-
font-size: 30px;
|
|
1715
|
-
font-weight: 400;
|
|
1716
|
-
letter-spacing: 0;
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
.rebooting-copy p {
|
|
1720
|
-
margin: 0;
|
|
1721
|
-
color: #000;
|
|
1722
|
-
font-size: 16px;
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
1960
|
.call-dialog .dialog-body {
|
|
1726
1961
|
padding: 18px 24px 24px;
|
|
1727
1962
|
}
|
|
@@ -1931,6 +2166,209 @@ export default {
|
|
|
1931
2166
|
line-height: 1.45;
|
|
1932
2167
|
}
|
|
1933
2168
|
|
|
2169
|
+
.web-phone-dialog {
|
|
2170
|
+
width: 820px;
|
|
2171
|
+
max-width: 94vw;
|
|
2172
|
+
overflow: hidden;
|
|
2173
|
+
background: #fff;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
.web-phone-title {
|
|
2177
|
+
display: flex;
|
|
2178
|
+
align-items: center;
|
|
2179
|
+
justify-content: space-between;
|
|
2180
|
+
gap: 16px;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
.web-phone-status {
|
|
2184
|
+
min-width: 92px;
|
|
2185
|
+
justify-content: center;
|
|
2186
|
+
font-size: 10px;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
.web-phone-status.ready {
|
|
2190
|
+
background: #d9f2e1;
|
|
2191
|
+
color: #16743a;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
.web-phone-status.incoming {
|
|
2195
|
+
background: #dcecff;
|
|
2196
|
+
color: #125c9a;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
.web-phone-status.failed {
|
|
2200
|
+
background: #fce2e2;
|
|
2201
|
+
color: #a92222;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
.web-phone-status.idle {
|
|
2205
|
+
background: #e5e7eb;
|
|
2206
|
+
color: #5d6673;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
.web-phone-body {
|
|
2210
|
+
display: grid;
|
|
2211
|
+
grid-template-columns: minmax(280px, 0.9fr) minmax(320px, 1.1fr);
|
|
2212
|
+
gap: 24px;
|
|
2213
|
+
min-height: 440px;
|
|
2214
|
+
padding: 22px 24px 24px;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
.web-phone-settings {
|
|
2218
|
+
padding-right: 24px;
|
|
2219
|
+
border-right: 1px solid #e5e7eb;
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
.web-phone-section-title {
|
|
2223
|
+
margin-bottom: 14px;
|
|
2224
|
+
color: #0d1720;
|
|
2225
|
+
font-size: 13px;
|
|
2226
|
+
font-weight: 700;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
.web-video-toggle {
|
|
2230
|
+
margin-top: 12px;
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
.web-phone-connect {
|
|
2234
|
+
min-height: 42px;
|
|
2235
|
+
margin-top: 14px;
|
|
2236
|
+
background: #082b40;
|
|
2237
|
+
color: #fff;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
.web-phone-disconnect {
|
|
2241
|
+
min-height: 42px;
|
|
2242
|
+
margin-top: 14px;
|
|
2243
|
+
border-color: #aeb8c2;
|
|
2244
|
+
color: #344054;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
.web-phone-console {
|
|
2248
|
+
display: flex;
|
|
2249
|
+
min-width: 0;
|
|
2250
|
+
flex-direction: column;
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
.web-phone-media {
|
|
2254
|
+
position: relative;
|
|
2255
|
+
display: grid;
|
|
2256
|
+
min-height: 260px;
|
|
2257
|
+
overflow: hidden;
|
|
2258
|
+
place-items: center;
|
|
2259
|
+
border: 1px solid #d7dde4;
|
|
2260
|
+
border-radius: 6px;
|
|
2261
|
+
background: #102a3b;
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
.remote-video {
|
|
2265
|
+
display: none;
|
|
2266
|
+
width: 100%;
|
|
2267
|
+
height: 100%;
|
|
2268
|
+
object-fit: cover;
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
.web-phone-media.has-video .remote-video {
|
|
2272
|
+
display: block;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
.local-video {
|
|
2276
|
+
position: absolute;
|
|
2277
|
+
right: 12px;
|
|
2278
|
+
bottom: 12px;
|
|
2279
|
+
display: none;
|
|
2280
|
+
width: 108px;
|
|
2281
|
+
aspect-ratio: 3 / 4;
|
|
2282
|
+
border: 2px solid #fff;
|
|
2283
|
+
border-radius: 4px;
|
|
2284
|
+
background: #263b49;
|
|
2285
|
+
object-fit: cover;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
.web-phone-media.has-video .local-video {
|
|
2289
|
+
display: block;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
.audio-call-state {
|
|
2293
|
+
display: flex;
|
|
2294
|
+
align-items: center;
|
|
2295
|
+
justify-content: center;
|
|
2296
|
+
padding: 28px;
|
|
2297
|
+
color: #fff;
|
|
2298
|
+
flex-direction: column;
|
|
2299
|
+
gap: 10px;
|
|
2300
|
+
text-align: center;
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
.audio-call-state strong {
|
|
2304
|
+
font-size: 16px;
|
|
2305
|
+
font-weight: 600;
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
.audio-call-state span {
|
|
2309
|
+
max-width: 100%;
|
|
2310
|
+
overflow: hidden;
|
|
2311
|
+
color: #c9d7e1;
|
|
2312
|
+
font-size: 11px;
|
|
2313
|
+
text-overflow: ellipsis;
|
|
2314
|
+
white-space: nowrap;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
.web-phone-dial-row {
|
|
2318
|
+
display: grid;
|
|
2319
|
+
grid-template-columns: minmax(0, 1fr) 42px;
|
|
2320
|
+
gap: 8px;
|
|
2321
|
+
align-items: start;
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
.start-web-call,
|
|
2325
|
+
.hangup-web-call {
|
|
2326
|
+
width: 42px;
|
|
2327
|
+
height: 38px;
|
|
2328
|
+
border-radius: 4px;
|
|
2329
|
+
background: #2a9b50;
|
|
2330
|
+
color: #fff;
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
.hangup-web-call,
|
|
2334
|
+
.reject-call {
|
|
2335
|
+
background: #c9293d;
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
.incoming-call-actions {
|
|
2339
|
+
display: grid;
|
|
2340
|
+
grid-template-columns: 1fr 1fr;
|
|
2341
|
+
gap: 10px;
|
|
2342
|
+
margin-top: 14px;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
.answer-call,
|
|
2346
|
+
.reject-call {
|
|
2347
|
+
min-height: 42px;
|
|
2348
|
+
color: #fff;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
.answer-call {
|
|
2352
|
+
background: #2a9b50;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
.active-call-controls {
|
|
2356
|
+
display: flex;
|
|
2357
|
+
justify-content: center;
|
|
2358
|
+
gap: 12px;
|
|
2359
|
+
margin-top: 16px;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
.active-call-controls .v-btn {
|
|
2363
|
+
width: 46px;
|
|
2364
|
+
height: 46px;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
.web-phone-error {
|
|
2368
|
+
margin-top: 14px;
|
|
2369
|
+
font-size: 11px;
|
|
2370
|
+
}
|
|
2371
|
+
|
|
1934
2372
|
.call-two-col {
|
|
1935
2373
|
gap: 12px;
|
|
1936
2374
|
}
|
|
@@ -2064,13 +2502,22 @@ export default {
|
|
|
2064
2502
|
max-width: none;
|
|
2065
2503
|
}
|
|
2066
2504
|
|
|
2067
|
-
.
|
|
2068
|
-
|
|
2069
|
-
|
|
2505
|
+
.web-call-button {
|
|
2506
|
+
width: 100%;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
.web-phone-body {
|
|
2510
|
+
grid-template-columns: minmax(0, 1fr);
|
|
2511
|
+
max-height: 78vh;
|
|
2512
|
+
overflow: auto;
|
|
2070
2513
|
}
|
|
2071
2514
|
|
|
2072
|
-
.
|
|
2073
|
-
|
|
2515
|
+
.web-phone-settings {
|
|
2516
|
+
padding-right: 0;
|
|
2517
|
+
padding-bottom: 20px;
|
|
2518
|
+
border-right: 0;
|
|
2519
|
+
border-bottom: 1px solid #e5e7eb;
|
|
2074
2520
|
}
|
|
2521
|
+
|
|
2075
2522
|
}
|
|
2076
2523
|
</style>
|