@7365admin1/layer-common 3.2.1-staging.72 → 3.2.2
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/CHANGELOG.md +39 -0
- package/components/EntryPassInformation.vue +4 -4
- package/components/FeedbackMain.vue +7 -7
- package/components/HidAccessLogDashboard.vue +44 -119
- package/components/HidIdentityMapping.vue +975 -0
- package/components/HidIntercomManagement.vue +201 -808
- package/components/HidQrCodeConfiguration.vue +95 -907
- package/components/HidServiceSettingsPanel.vue +1 -20
- package/components/HidUserEnrollment.vue +81 -392
- package/components/Layout/NavigationDrawer.vue +2 -43
- package/components/NavigationItem.vue +0 -9
- package/components/Nfc/NFCPatrolRouteMain.vue +7 -52
- package/components/OnlineFormConfigurationForm.vue +224 -620
- package/components/OnlineFormFill.vue +8 -36
- package/components/OnlineFormsConfiguration.vue +2 -6
- package/components/QrTemplate/CreditCardLandscape.vue +12 -19
- package/components/QrTemplate/PrintDialog.vue +196 -209
- package/components/SiteSettings.vue +2 -4
- package/components/VisitorForm.vue +78 -201
- package/components/VisitorManagement.vue +2 -1
- package/composables/useComment.ts +3 -3
- package/composables/useHidAmico.ts +0 -127
- package/composables/useHidNavigation.ts +7 -0
- package/composables/useOnlineFormPages.ts +0 -2
- package/composables/useSiteSettings.ts +0 -50
- package/composables/useWebUsb.ts +37 -127
- package/middleware/member.ts +4 -0
- package/package.json +1 -2
- package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +23 -0
- package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
- package/plugins/secure-member.client.ts +56 -21
- package/plugins/vuetify.ts +9 -37
- package/types/site.d.ts +0 -71
- package/.changeset/camera-wall-gated-endpoint.md +0 -47
- package/.changeset/camera-wall-plain-english.md +0 -60
- package/.changeset/camera-wall-selection-and-guidance.md +0 -65
- package/.changeset/dark-primary-contrast.md +0 -39
- package/components/CameraWall.vue +0 -1034
- package/components/CameraWallTile.vue +0 -818
- package/components/HidCameraCaptureDialog.vue +0 -199
- package/composables/useSipWebPhone.ts +0 -311
- package/utils/camera-wall.test.ts +0 -571
- package/utils/camera-wall.ts +0 -926
- package/utils/theme.test.ts +0 -201
- package/utils/theme.ts +0 -136
|
@@ -12,17 +12,7 @@
|
|
|
12
12
|
Add Contacts
|
|
13
13
|
</v-btn>
|
|
14
14
|
|
|
15
|
-
<v-
|
|
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>
|
|
15
|
+
<div v-else class="top-placeholder" />
|
|
26
16
|
|
|
27
17
|
<div class="toolbar-spacer" />
|
|
28
18
|
|
|
@@ -115,7 +105,6 @@
|
|
|
115
105
|
<v-list-item title="Call Settings" @click="openCallDialog(row)" />
|
|
116
106
|
<v-list-item title="Dial Code" @click="openDialCodeDialog(row)" />
|
|
117
107
|
<v-list-item title="Make a call" @click="openMakeCallDialog(row)" />
|
|
118
|
-
<v-list-item title="Web call" @click="openWebPhoneDialog(row)" />
|
|
119
108
|
</v-list>
|
|
120
109
|
</v-menu>
|
|
121
110
|
</td>
|
|
@@ -234,13 +223,6 @@
|
|
|
234
223
|
<v-radio label="UDP" value="UDP" />
|
|
235
224
|
<v-radio label="TCP" value="TCP" />
|
|
236
225
|
</v-radio-group>
|
|
237
|
-
<div class="sip-switch-row sip-section-switch">
|
|
238
|
-
<div>
|
|
239
|
-
<span>Enable video</span>
|
|
240
|
-
<small>Allow this reader to send its camera feed during SIP intercom calls.</small>
|
|
241
|
-
</div>
|
|
242
|
-
<v-switch v-model="sipForm.videoEnabled" :disabled="!sipForm.enabled" color="success" density="compact" hide-details />
|
|
243
|
-
</div>
|
|
244
226
|
</v-card-text>
|
|
245
227
|
<v-card-actions class="dialog-actions pa-0">
|
|
246
228
|
<v-btn class="text-none action-cancel" variant="flat" height="44" @click="sipDialog = false">Cancel</v-btn>
|
|
@@ -249,6 +231,36 @@
|
|
|
249
231
|
</v-card>
|
|
250
232
|
</v-dialog>
|
|
251
233
|
|
|
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
|
+
|
|
252
264
|
<v-dialog v-model="callDialog" max-width="360" persistent>
|
|
253
265
|
<v-card class="form-dialog call-dialog" rounded="lg">
|
|
254
266
|
<v-card-title class="dialog-title">Call Settings</v-card-title>
|
|
@@ -338,10 +350,10 @@
|
|
|
338
350
|
<v-card-title class="dialog-title">Make a Call</v-card-title>
|
|
339
351
|
<v-card-text class="dialog-body make-call-body">
|
|
340
352
|
<div class="make-call-field">
|
|
341
|
-
<div class="call-label">
|
|
353
|
+
<div class="call-label">Address to dial</div>
|
|
342
354
|
<v-text-field
|
|
343
355
|
v-model="makeCallForm.target"
|
|
344
|
-
placeholder="
|
|
356
|
+
placeholder="Enter extension, address, or contact"
|
|
345
357
|
variant="outlined"
|
|
346
358
|
density="compact"
|
|
347
359
|
hide-details="auto"
|
|
@@ -376,181 +388,8 @@
|
|
|
376
388
|
</div>
|
|
377
389
|
</v-card-text>
|
|
378
390
|
<v-card-actions class="dialog-actions pa-0">
|
|
379
|
-
<v-btn class="text-none action-
|
|
380
|
-
|
|
381
|
-
</v-card>
|
|
382
|
-
</v-dialog>
|
|
383
|
-
|
|
384
|
-
<v-dialog v-model="webPhoneDialog" max-width="820" persistent eager>
|
|
385
|
-
<v-card class="web-phone-dialog" rounded="lg">
|
|
386
|
-
<v-card-title class="dialog-title web-phone-title">
|
|
387
|
-
<span>Web Call</span>
|
|
388
|
-
<v-chip size="small" variant="flat" :class="webPhoneStatusClass">
|
|
389
|
-
{{ webPhoneStatusLabel }}
|
|
390
|
-
</v-chip>
|
|
391
|
-
</v-card-title>
|
|
392
|
-
|
|
393
|
-
<v-card-text class="web-phone-body">
|
|
394
|
-
<div class="web-phone-settings">
|
|
395
|
-
<div class="web-phone-section-title">SIP Account</div>
|
|
396
|
-
<div v-if="sipAccountLoading" class="sip-account-state">
|
|
397
|
-
<v-progress-circular indeterminate color="primary" size="30" width="3" />
|
|
398
|
-
<span>Preparing your private SIP account...</span>
|
|
399
|
-
</div>
|
|
400
|
-
<div v-else-if="sipAccountError" class="sip-account-state is-error">
|
|
401
|
-
<v-icon icon="mdi-alert-circle-outline" size="28" />
|
|
402
|
-
<span>{{ sipAccountError }}</span>
|
|
403
|
-
<v-btn
|
|
404
|
-
class="text-none sip-account-retry"
|
|
405
|
-
variant="outlined"
|
|
406
|
-
prepend-icon="mdi-refresh"
|
|
407
|
-
@click="loadSipAccount"
|
|
408
|
-
>
|
|
409
|
-
Retry
|
|
410
|
-
</v-btn>
|
|
411
|
-
</div>
|
|
412
|
-
<div v-else-if="sipAccount" class="sip-account-summary">
|
|
413
|
-
<div>
|
|
414
|
-
<span class="sip-account-label">Assigned extension</span>
|
|
415
|
-
<strong class="sip-account-extension">{{ sipAccount.extension }}</strong>
|
|
416
|
-
</div>
|
|
417
|
-
<span class="sip-account-address">{{ sipAccount.sipAddress }}</span>
|
|
418
|
-
</div>
|
|
419
|
-
<div class="call-toggle-row web-video-toggle">
|
|
420
|
-
<div>
|
|
421
|
-
<span>Receive HID video</span>
|
|
422
|
-
<small>Show the HID camera feed without sharing this device's camera.</small>
|
|
423
|
-
</div>
|
|
424
|
-
<v-switch
|
|
425
|
-
v-model="webPhoneForm.video"
|
|
426
|
-
color="success"
|
|
427
|
-
density="compact"
|
|
428
|
-
hide-details
|
|
429
|
-
:disabled="webPhone.isRegistered.value"
|
|
430
|
-
/>
|
|
431
|
-
</div>
|
|
432
|
-
<v-btn
|
|
433
|
-
v-if="!webPhone.isRegistered.value"
|
|
434
|
-
block
|
|
435
|
-
class="text-none web-phone-connect"
|
|
436
|
-
variant="flat"
|
|
437
|
-
prepend-icon="mdi-lan-connect"
|
|
438
|
-
:loading="webPhoneConnecting"
|
|
439
|
-
:disabled="!sipAccount || sipAccountLoading"
|
|
440
|
-
@click="connectWebPhone"
|
|
441
|
-
>
|
|
442
|
-
Connect Web Phone
|
|
443
|
-
</v-btn>
|
|
444
|
-
<v-btn
|
|
445
|
-
v-else
|
|
446
|
-
block
|
|
447
|
-
class="text-none web-phone-disconnect"
|
|
448
|
-
variant="outlined"
|
|
449
|
-
prepend-icon="mdi-lan-disconnect"
|
|
450
|
-
:loading="webPhoneConnecting"
|
|
451
|
-
:disabled="webPhone.hasCall.value"
|
|
452
|
-
@click="disconnectWebPhone"
|
|
453
|
-
>
|
|
454
|
-
Disconnect
|
|
455
|
-
</v-btn>
|
|
456
|
-
</div>
|
|
457
|
-
|
|
458
|
-
<div class="web-phone-console">
|
|
459
|
-
<div
|
|
460
|
-
class="web-phone-media"
|
|
461
|
-
:class="{
|
|
462
|
-
'has-video': webPhoneForm.video && webPhone.callState.value === 'active',
|
|
463
|
-
'has-remote-video': webPhone.remoteVideoAvailable.value,
|
|
464
|
-
}"
|
|
465
|
-
>
|
|
466
|
-
<video ref="remoteVideoEl" autoplay playsinline class="remote-video" />
|
|
467
|
-
<audio ref="remoteAudioEl" autoplay />
|
|
468
|
-
<div
|
|
469
|
-
v-if="!webPhoneForm.video || webPhone.callState.value !== 'active' || !webPhone.remoteVideoAvailable.value"
|
|
470
|
-
class="audio-call-state"
|
|
471
|
-
>
|
|
472
|
-
<v-icon size="54">{{ webPhoneCallIcon }}</v-icon>
|
|
473
|
-
<strong>{{ webPhoneCallLabel }}</strong>
|
|
474
|
-
<span v-if="webPhone.currentTarget.value">{{ webPhone.currentTarget.value }}</span>
|
|
475
|
-
<span v-if="webPhoneForm.video && webPhone.callState.value === 'active' && !webPhone.remoteVideoAvailable.value">
|
|
476
|
-
Waiting for video from the HID device
|
|
477
|
-
</span>
|
|
478
|
-
</div>
|
|
479
|
-
</div>
|
|
480
|
-
|
|
481
|
-
<div v-if="webPhone.callState.value === 'incoming'" class="incoming-call-actions">
|
|
482
|
-
<v-btn
|
|
483
|
-
class="text-none answer-call"
|
|
484
|
-
variant="flat"
|
|
485
|
-
prepend-icon="mdi-phone"
|
|
486
|
-
:loading="webPhoneActionLoading"
|
|
487
|
-
@click="answerWebCall"
|
|
488
|
-
>
|
|
489
|
-
Answer
|
|
490
|
-
</v-btn>
|
|
491
|
-
<v-btn
|
|
492
|
-
class="text-none reject-call"
|
|
493
|
-
variant="flat"
|
|
494
|
-
prepend-icon="mdi-phone-hangup"
|
|
495
|
-
:disabled="webPhoneActionLoading"
|
|
496
|
-
@click="declineWebCall"
|
|
497
|
-
>
|
|
498
|
-
Decline
|
|
499
|
-
</v-btn>
|
|
500
|
-
</div>
|
|
501
|
-
|
|
502
|
-
<template v-else>
|
|
503
|
-
<div class="call-label">Extension or SIP Address</div>
|
|
504
|
-
<div class="web-phone-dial-row">
|
|
505
|
-
<v-text-field
|
|
506
|
-
v-model="webPhoneForm.destination"
|
|
507
|
-
placeholder="1001 or device@example.com"
|
|
508
|
-
variant="outlined"
|
|
509
|
-
density="compact"
|
|
510
|
-
hide-details="auto"
|
|
511
|
-
class="call-input mb-0"
|
|
512
|
-
:disabled="!webPhone.isRegistered.value || webPhone.hasCall.value"
|
|
513
|
-
@keyup.enter="startWebCall"
|
|
514
|
-
/>
|
|
515
|
-
<v-btn
|
|
516
|
-
icon="mdi-phone"
|
|
517
|
-
class="start-web-call"
|
|
518
|
-
variant="flat"
|
|
519
|
-
:loading="webPhoneActionLoading && webPhone.callState.value === 'calling'"
|
|
520
|
-
:disabled="!webPhone.isRegistered.value || webPhone.hasCall.value || !webPhoneForm.destination.trim()"
|
|
521
|
-
@click="startWebCall"
|
|
522
|
-
/>
|
|
523
|
-
</div>
|
|
524
|
-
|
|
525
|
-
<div v-if="webPhone.hasCall.value" class="active-call-controls">
|
|
526
|
-
<v-btn
|
|
527
|
-
:icon="webPhone.muted.value ? 'mdi-microphone-off' : 'mdi-microphone'"
|
|
528
|
-
variant="outlined"
|
|
529
|
-
:disabled="webPhone.callState.value !== 'active'"
|
|
530
|
-
:title="webPhone.muted.value ? 'Unmute' : 'Mute'"
|
|
531
|
-
@click="toggleWebMute"
|
|
532
|
-
/>
|
|
533
|
-
<v-btn
|
|
534
|
-
icon="mdi-phone-hangup"
|
|
535
|
-
class="hangup-web-call"
|
|
536
|
-
variant="flat"
|
|
537
|
-
:loading="webPhoneActionLoading && webPhone.callState.value === 'ending'"
|
|
538
|
-
title="End call"
|
|
539
|
-
@click="hangupWebCall"
|
|
540
|
-
/>
|
|
541
|
-
</div>
|
|
542
|
-
</template>
|
|
543
|
-
|
|
544
|
-
<v-alert v-if="webPhone.errorMessage.value" type="error" variant="tonal" density="compact" class="web-phone-error">
|
|
545
|
-
{{ webPhone.errorMessage.value }}
|
|
546
|
-
</v-alert>
|
|
547
|
-
</div>
|
|
548
|
-
</v-card-text>
|
|
549
|
-
|
|
550
|
-
<v-card-actions class="dialog-actions pa-0">
|
|
551
|
-
<v-btn class="text-none action-submit full-action" variant="flat" height="44" @click="webPhoneDialog = false">
|
|
552
|
-
Close
|
|
553
|
-
</v-btn>
|
|
391
|
+
<v-btn class="text-none action-cancel" variant="flat" height="44" @click="makeCallDialog = false">Cancel</v-btn>
|
|
392
|
+
<v-btn class="text-none action-submit" variant="flat" height="44" @click="makeCallDialog = false">Close</v-btn>
|
|
554
393
|
</v-card-actions>
|
|
555
394
|
</v-card>
|
|
556
395
|
</v-dialog>
|
|
@@ -609,10 +448,6 @@
|
|
|
609
448
|
|
|
610
449
|
<script setup lang="ts">
|
|
611
450
|
const props = defineProps({
|
|
612
|
-
org: {
|
|
613
|
-
type: String,
|
|
614
|
-
required: true,
|
|
615
|
-
},
|
|
616
451
|
site: {
|
|
617
452
|
type: String,
|
|
618
453
|
required: true,
|
|
@@ -631,19 +466,6 @@ type ContactRow = {
|
|
|
631
466
|
dialCode: string;
|
|
632
467
|
};
|
|
633
468
|
|
|
634
|
-
type SipAccount = {
|
|
635
|
-
orgId: string;
|
|
636
|
-
site: string;
|
|
637
|
-
userId: string;
|
|
638
|
-
extension: string;
|
|
639
|
-
username: string;
|
|
640
|
-
password: string;
|
|
641
|
-
sipAddress: string;
|
|
642
|
-
websocketUrl: string;
|
|
643
|
-
status: "active";
|
|
644
|
-
provisionedAt: string;
|
|
645
|
-
};
|
|
646
|
-
|
|
647
469
|
type DetailItem = {
|
|
648
470
|
label: string;
|
|
649
471
|
value?: string | number | boolean;
|
|
@@ -652,7 +474,7 @@ type DetailItem = {
|
|
|
652
474
|
};
|
|
653
475
|
|
|
654
476
|
type IntercomRow = {
|
|
655
|
-
reader: Record<string,
|
|
477
|
+
reader: Record<string, any>;
|
|
656
478
|
name: string;
|
|
657
479
|
location: string;
|
|
658
480
|
serverAddress: string;
|
|
@@ -668,7 +490,6 @@ type IntercomRow = {
|
|
|
668
490
|
password: string;
|
|
669
491
|
operatingMode: string;
|
|
670
492
|
protocol: string;
|
|
671
|
-
videoEnabled: boolean;
|
|
672
493
|
dialMode: string;
|
|
673
494
|
dialModeLabel: string;
|
|
674
495
|
dialCode: string;
|
|
@@ -681,7 +502,7 @@ type IntercomRow = {
|
|
|
681
502
|
externalDialButton: boolean;
|
|
682
503
|
unknownContact: boolean;
|
|
683
504
|
callAfter: string;
|
|
684
|
-
pjsip: Record<string,
|
|
505
|
+
pjsip: Record<string, any>;
|
|
685
506
|
};
|
|
686
507
|
|
|
687
508
|
const {
|
|
@@ -692,7 +513,6 @@ const {
|
|
|
692
513
|
getIntercomStatus,
|
|
693
514
|
makeIntercomCall,
|
|
694
515
|
finalizeIntercomCall,
|
|
695
|
-
ensureSipAccount,
|
|
696
516
|
} = useHidAmico();
|
|
697
517
|
|
|
698
518
|
const activeTab = ref<IntercomTab>("intercom");
|
|
@@ -711,26 +531,21 @@ const limit = 20;
|
|
|
711
531
|
const selectedRow = ref<IntercomRow | null>(null);
|
|
712
532
|
const selectedContact = ref<ContactRow | null>(null);
|
|
713
533
|
const showSipPassword = ref(false);
|
|
714
|
-
const webPhoneConnecting = ref(false);
|
|
715
|
-
const webPhoneActionLoading = ref(false);
|
|
716
|
-
const sipAccount = ref<SipAccount | null>(null);
|
|
717
|
-
const sipAccountLoading = ref(false);
|
|
718
|
-
const sipAccountError = ref("");
|
|
719
|
-
const remoteVideoEl = ref<HTMLVideoElement | null>(null);
|
|
720
|
-
const remoteAudioEl = ref<HTMLAudioElement | null>(null);
|
|
721
|
-
const webPhone = useSipWebPhone();
|
|
722
534
|
|
|
723
535
|
const viewDialog = ref(false);
|
|
724
536
|
const sipDialog = ref(false);
|
|
537
|
+
const rebootConfirmDialog = ref(false);
|
|
538
|
+
const rebootingDialog = ref(false);
|
|
725
539
|
const callDialog = ref(false);
|
|
726
540
|
const dialCodeDialog = ref(false);
|
|
727
541
|
const makeCallDialog = ref(false);
|
|
728
|
-
const webPhoneDialog = ref(false);
|
|
729
542
|
const contactDialog = ref(false);
|
|
730
543
|
const contactViewDialog = ref(false);
|
|
731
544
|
const deleteContactDialog = ref(false);
|
|
732
545
|
|
|
733
546
|
const message = reactive<{ type: MessageType; text: string }>({ type: "info", text: "" });
|
|
547
|
+
const rebootCountdown = ref(59);
|
|
548
|
+
let rebootTimer: ReturnType<typeof setInterval> | undefined;
|
|
734
549
|
|
|
735
550
|
const statusOptions = ["Status", "Connected", "Not connected", "Connecting", "Disabled", "Failed"];
|
|
736
551
|
|
|
@@ -767,14 +582,13 @@ const sipForm = reactive({
|
|
|
767
582
|
serverIp: "",
|
|
768
583
|
serverPort: "5060",
|
|
769
584
|
initialRtpPort: "10000",
|
|
770
|
-
finalRtpPort: "
|
|
585
|
+
finalRtpPort: "1000",
|
|
771
586
|
numericExtension: false,
|
|
772
587
|
extension: "",
|
|
773
588
|
login: "",
|
|
774
589
|
password: "",
|
|
775
590
|
operatingMode: "sip-client",
|
|
776
591
|
protocol: "UDP",
|
|
777
|
-
videoEnabled: false,
|
|
778
592
|
});
|
|
779
593
|
|
|
780
594
|
const callForm = reactive({
|
|
@@ -803,11 +617,6 @@ const makeCallForm = reactive({
|
|
|
803
617
|
statusType: "info" as MessageType,
|
|
804
618
|
});
|
|
805
619
|
|
|
806
|
-
const webPhoneForm = reactive({
|
|
807
|
-
destination: "",
|
|
808
|
-
video: false,
|
|
809
|
-
});
|
|
810
|
-
|
|
811
620
|
const contactForm = reactive({
|
|
812
621
|
id: undefined as number | undefined,
|
|
813
622
|
readerId: "",
|
|
@@ -885,7 +694,6 @@ const intercomDetailSections = computed(() => {
|
|
|
885
694
|
items: [
|
|
886
695
|
{ label: "Operating Mode", value: row?.operatingMode },
|
|
887
696
|
{ label: "Protocol", value: row?.protocol },
|
|
888
|
-
{ label: "Video", value: row?.videoEnabled ? "Enabled" : "Disabled" },
|
|
889
697
|
],
|
|
890
698
|
},
|
|
891
699
|
{
|
|
@@ -909,45 +717,10 @@ const contactDetailRows = computed<DetailItem[]>(() => [
|
|
|
909
717
|
{ label: "Server Address", value: selectedContact.value?.serverAddress, link: true },
|
|
910
718
|
{ label: "Location", value: selectedContact.value?.location },
|
|
911
719
|
]);
|
|
912
|
-
const webPhoneStatusLabel = computed(() => {
|
|
913
|
-
if (webPhone.callState.value === "incoming") return "Incoming call";
|
|
914
|
-
if (webPhone.callState.value === "calling") return "Calling";
|
|
915
|
-
if (webPhone.callState.value === "active") return "In call";
|
|
916
|
-
if (webPhone.callState.value === "ending") return "Ending call";
|
|
917
|
-
if (webPhone.connectionState.value === "connecting") return "Connecting";
|
|
918
|
-
if (webPhone.connectionState.value === "registered") return "Ready";
|
|
919
|
-
if (webPhone.connectionState.value === "error") return "Connection failed";
|
|
920
|
-
return "Disconnected";
|
|
921
|
-
});
|
|
922
|
-
const webPhoneStatusClass = computed(() => {
|
|
923
|
-
if (webPhone.callState.value === "incoming") return "web-phone-status incoming";
|
|
924
|
-
if (webPhone.callState.value === "active" || webPhone.connectionState.value === "registered") return "web-phone-status ready";
|
|
925
|
-
if (webPhone.connectionState.value === "error") return "web-phone-status failed";
|
|
926
|
-
return "web-phone-status idle";
|
|
927
|
-
});
|
|
928
|
-
const webPhoneCallLabel = computed(() => {
|
|
929
|
-
if (webPhone.callState.value === "incoming") return "Incoming call";
|
|
930
|
-
if (webPhone.callState.value === "calling") return "Calling...";
|
|
931
|
-
if (webPhone.callState.value === "active") return "Call connected";
|
|
932
|
-
if (webPhone.callState.value === "ending") return "Ending call...";
|
|
933
|
-
return webPhone.isRegistered.value ? "Ready to call" : "Connect a SIP account";
|
|
934
|
-
});
|
|
935
|
-
const webPhoneCallIcon = computed(() => {
|
|
936
|
-
if (webPhone.callState.value === "incoming") return "mdi-phone-incoming";
|
|
937
|
-
if (webPhone.callState.value === "calling") return "mdi-phone-outgoing";
|
|
938
|
-
if (webPhone.callState.value === "active") return "mdi-phone-in-talk";
|
|
939
|
-
return "mdi-phone-outline";
|
|
940
|
-
});
|
|
941
720
|
|
|
942
721
|
watch(
|
|
943
|
-
|
|
944
|
-
() =>
|
|
945
|
-
if (webPhone.isRegistered.value) void webPhone.disconnect();
|
|
946
|
-
sipAccount.value = null;
|
|
947
|
-
sipAccountError.value = "";
|
|
948
|
-
webPhoneForm.destination = "";
|
|
949
|
-
void reload();
|
|
950
|
-
},
|
|
722
|
+
() => props.site,
|
|
723
|
+
() => reload(),
|
|
951
724
|
{ immediate: true },
|
|
952
725
|
);
|
|
953
726
|
|
|
@@ -959,12 +732,9 @@ watch(pages, () => {
|
|
|
959
732
|
if (page.value > pages.value) page.value = pages.value;
|
|
960
733
|
});
|
|
961
734
|
|
|
962
|
-
|
|
963
|
-
()
|
|
964
|
-
|
|
965
|
-
if (state === "incoming") webPhoneDialog.value = true;
|
|
966
|
-
},
|
|
967
|
-
);
|
|
735
|
+
onUnmounted(() => {
|
|
736
|
+
clearRebootTimer();
|
|
737
|
+
});
|
|
968
738
|
|
|
969
739
|
async function reload() {
|
|
970
740
|
if (!props.site) return;
|
|
@@ -1028,27 +798,19 @@ function paginate<T>(items: T[]) {
|
|
|
1028
798
|
return items.slice(start, start + limit);
|
|
1029
799
|
}
|
|
1030
800
|
|
|
1031
|
-
function toIntercomRow(reader: Record<string,
|
|
801
|
+
function toIntercomRow(reader: Record<string, any>, pjsip: Record<string, any>, status: string): IntercomRow {
|
|
1032
802
|
const peerToPeer = toFlag(getPjsipValue(pjsip, ["peer_to_peer_enabled", "peerToPeerEnabled"])) === "1";
|
|
1033
803
|
const dialMode = toText(getPjsipValue(pjsip, ["dialing_display_mode", "dialingDisplayMode"]), "0");
|
|
1034
|
-
const initialRtpPort = toText(getPjsipValue(pjsip, ["server_outbound_port", "initialRtpPort", "initial_rtp_port"]), "N/A");
|
|
1035
|
-
const rtpPortRange = getPjsipValue(pjsip, ["server_outbound_port_range"]);
|
|
1036
|
-
const explicitFinalRtpPort = getPjsipValue(pjsip, ["finalRtpPort", "final_rtp_port"]);
|
|
1037
|
-
const initialRtpPortNumber = Number(initialRtpPort);
|
|
1038
|
-
const rtpPortRangeNumber = Number(rtpPortRange);
|
|
1039
|
-
const finalRtpPort = rtpPortRange !== undefined && Number.isFinite(initialRtpPortNumber) && Number.isFinite(rtpPortRangeNumber)
|
|
1040
|
-
? String(initialRtpPortNumber + rtpPortRangeNumber)
|
|
1041
|
-
: toText(explicitFinalRtpPort, "N/A");
|
|
1042
804
|
|
|
1043
805
|
return {
|
|
1044
806
|
reader,
|
|
1045
|
-
name:
|
|
1046
|
-
location:
|
|
807
|
+
name: reader.name || reader.deviceId || "HID Reader",
|
|
808
|
+
location: reader.location || "N/A",
|
|
1047
809
|
serverAddress: toText(getPjsipValue(pjsip, ["server_ip", "serverAddress", "server_address", "serverIp"]), "N/A"),
|
|
1048
810
|
serverPort: toText(getPjsipValue(pjsip, ["server_port", "serverPort", "port"]), "N/A"),
|
|
1049
|
-
initialRtpPort,
|
|
1050
|
-
finalRtpPort,
|
|
1051
|
-
monitorPath:
|
|
811
|
+
initialRtpPort: toText(getPjsipValue(pjsip, ["server_outbound_port", "initialRtpPort", "initial_rtp_port"]), "N/A"),
|
|
812
|
+
finalRtpPort: toText(getPjsipValue(pjsip, ["server_outbound_port_range", "finalRtpPort", "final_rtp_port"]), "N/A"),
|
|
813
|
+
monitorPath: reader.monitorPath || "N/A",
|
|
1052
814
|
status,
|
|
1053
815
|
sipEnabled: toFlag(getPjsipValue(pjsip, ["enabled"])) === "1",
|
|
1054
816
|
numericExtension: toFlag(getPjsipValue(pjsip, ["numeric_branch_enabled", "numericExtension", "numericExtensionEnabled"])) === "1",
|
|
@@ -1057,7 +819,6 @@ function toIntercomRow(reader: Record<string, unknown>, pjsip: Record<string, un
|
|
|
1057
819
|
password: toText(getPjsipValue(pjsip, ["password"]), ""),
|
|
1058
820
|
operatingMode: peerToPeer ? "Peer-to-Peer" : "SIP Client",
|
|
1059
821
|
protocol: toText(getPjsipValue(pjsip, ["protocol"]), "UDP"),
|
|
1060
|
-
videoEnabled: toFlag(getPjsipValue(pjsip, ["video_enabled", "videoEnabled"])) === "1",
|
|
1061
822
|
dialMode,
|
|
1062
823
|
dialModeLabel: getDialModeLabel(dialMode),
|
|
1063
824
|
dialCode: toText(getPjsipValue(pjsip, ["open_door_command", "dialCode"]), "N/A"),
|
|
@@ -1074,7 +835,7 @@ function toIntercomRow(reader: Record<string, unknown>, pjsip: Record<string, un
|
|
|
1074
835
|
};
|
|
1075
836
|
}
|
|
1076
837
|
|
|
1077
|
-
function normalizePjsip(...sources: Record<string,
|
|
838
|
+
function normalizePjsip(...sources: Record<string, any>[]) {
|
|
1078
839
|
for (const source of sources) {
|
|
1079
840
|
const candidate = findPjsipCandidate(source);
|
|
1080
841
|
if (candidate) return candidate;
|
|
@@ -1082,46 +843,45 @@ function normalizePjsip(...sources: Record<string, unknown>[]) {
|
|
|
1082
843
|
return {};
|
|
1083
844
|
}
|
|
1084
845
|
|
|
1085
|
-
function findPjsipCandidate(source: Record<string,
|
|
846
|
+
function findPjsipCandidate(source: Record<string, any> | undefined): Record<string, any> | undefined {
|
|
1086
847
|
if (!source || typeof source !== "object") return undefined;
|
|
1087
848
|
const candidates = [
|
|
1088
849
|
source.pjsip,
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
toRecord(toRecord(toRecord(source.data).data).configuration),
|
|
850
|
+
source.configuration?.pjsip,
|
|
851
|
+
source.result?.pjsip,
|
|
852
|
+
source.result?.configuration?.pjsip,
|
|
853
|
+
source.data?.pjsip,
|
|
854
|
+
source.data?.configuration?.pjsip,
|
|
855
|
+
source.data?.result?.pjsip,
|
|
856
|
+
source.data?.result?.configuration?.pjsip,
|
|
857
|
+
source.data?.data?.pjsip,
|
|
858
|
+
source.data?.data?.configuration?.pjsip,
|
|
859
|
+
source.configuration,
|
|
860
|
+
source.data?.configuration,
|
|
861
|
+
source.data?.data?.configuration,
|
|
1102
862
|
source,
|
|
1103
863
|
];
|
|
1104
864
|
|
|
1105
865
|
return candidates.find((candidate) => isPjsipCandidate(candidate));
|
|
1106
866
|
}
|
|
1107
867
|
|
|
1108
|
-
function isPjsipCandidate(value: unknown): value is Record<string,
|
|
868
|
+
function isPjsipCandidate(value: unknown): value is Record<string, any> {
|
|
1109
869
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1110
870
|
return pjsipKeys.some((key) => Object.prototype.hasOwnProperty.call(value, key)) ||
|
|
1111
871
|
["serverAddress", "serverIp", "serverPort", "extension", "peerToPeerEnabled"].some((key) => Object.prototype.hasOwnProperty.call(value, key));
|
|
1112
872
|
}
|
|
1113
873
|
|
|
1114
|
-
function getPjsipValue(source: Record<string,
|
|
874
|
+
function getPjsipValue(source: Record<string, any>, keys: string[]) {
|
|
1115
875
|
for (const key of keys) {
|
|
1116
876
|
if (source?.[key] !== undefined && source?.[key] !== null && source?.[key] !== "") return source[key];
|
|
1117
877
|
}
|
|
1118
878
|
return undefined;
|
|
1119
879
|
}
|
|
1120
880
|
|
|
1121
|
-
function normalizeStatus(response: Record<string,
|
|
881
|
+
function normalizeStatus(response: Record<string, any>, pjsip: Record<string, any>) {
|
|
1122
882
|
const status = Number(response?.status);
|
|
1123
|
-
if (toFlag(pjsip.enabled) === "0" || status === -1) return "Disabled";
|
|
1124
883
|
if (status === 200) return "Connected";
|
|
884
|
+
if (status === -1 || toFlag(pjsip.enabled) === "0") return "Disabled";
|
|
1125
885
|
if (status === 0 || status === 100) return "Connecting";
|
|
1126
886
|
if ([401, 403, 408, 503].includes(status)) return "Failed";
|
|
1127
887
|
return "Not connected";
|
|
@@ -1151,14 +911,13 @@ function openSipDialog(row: IntercomRow) {
|
|
|
1151
911
|
sipForm.serverIp = row.serverAddress === "N/A" ? "" : row.serverAddress;
|
|
1152
912
|
sipForm.serverPort = row.serverPort === "N/A" ? "5060" : row.serverPort;
|
|
1153
913
|
sipForm.initialRtpPort = row.initialRtpPort === "N/A" ? "10000" : row.initialRtpPort;
|
|
1154
|
-
sipForm.finalRtpPort = row.finalRtpPort === "N/A" ? "
|
|
914
|
+
sipForm.finalRtpPort = row.finalRtpPort === "N/A" ? "1000" : row.finalRtpPort;
|
|
1155
915
|
sipForm.numericExtension = row.numericExtension;
|
|
1156
916
|
sipForm.extension = row.extension === "N/A" ? "" : row.extension;
|
|
1157
917
|
sipForm.login = row.login === "N/A" ? "" : row.login;
|
|
1158
918
|
sipForm.password = row.password;
|
|
1159
919
|
sipForm.operatingMode = row.operatingMode === "Peer-to-Peer" ? "peer-to-peer" : "sip-client";
|
|
1160
920
|
sipForm.protocol = row.protocol || "UDP";
|
|
1161
|
-
sipForm.videoEnabled = row.videoEnabled;
|
|
1162
921
|
sipDialog.value = true;
|
|
1163
922
|
}
|
|
1164
923
|
|
|
@@ -1186,134 +945,12 @@ function openDialCodeDialog(row: IntercomRow) {
|
|
|
1186
945
|
|
|
1187
946
|
function openMakeCallDialog(row: IntercomRow) {
|
|
1188
947
|
selectedRow.value = row;
|
|
1189
|
-
makeCallForm.target =
|
|
948
|
+
makeCallForm.target = row.extension !== "N/A" ? row.extension : "";
|
|
1190
949
|
makeCallForm.status = "";
|
|
1191
950
|
makeCallForm.statusType = "info";
|
|
1192
951
|
makeCallDialog.value = true;
|
|
1193
952
|
}
|
|
1194
953
|
|
|
1195
|
-
function openWebPhoneDialog(row?: IntercomRow) {
|
|
1196
|
-
const activeRow = row || selectedRow.value || intercomRows.value[0];
|
|
1197
|
-
if (activeRow) {
|
|
1198
|
-
selectedRow.value = activeRow;
|
|
1199
|
-
if (!webPhone.isRegistered.value && activeRow.extension !== "N/A") {
|
|
1200
|
-
webPhoneForm.destination = activeRow.extension;
|
|
1201
|
-
webPhoneForm.video = activeRow.videoEnabled;
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
webPhoneDialog.value = true;
|
|
1205
|
-
if (!sipAccount.value && !sipAccountLoading.value) void loadSipAccount();
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
async function loadSipAccount() {
|
|
1209
|
-
if (!props.org || !props.site) return;
|
|
1210
|
-
|
|
1211
|
-
sipAccountLoading.value = true;
|
|
1212
|
-
sipAccountError.value = "";
|
|
1213
|
-
try {
|
|
1214
|
-
const response = await ensureSipAccount(props.site, props.org);
|
|
1215
|
-
sipAccount.value = normalizeSipAccount(response);
|
|
1216
|
-
} catch (error: unknown) {
|
|
1217
|
-
sipAccount.value = null;
|
|
1218
|
-
sipAccountError.value = getErrorMessage(error);
|
|
1219
|
-
} finally {
|
|
1220
|
-
sipAccountLoading.value = false;
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
async function connectWebPhone() {
|
|
1225
|
-
const account = sipAccount.value;
|
|
1226
|
-
if (!account) {
|
|
1227
|
-
await loadSipAccount();
|
|
1228
|
-
if (!sipAccount.value) return;
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
|
-
const activeAccount = sipAccount.value;
|
|
1232
|
-
if (!activeAccount) return;
|
|
1233
|
-
|
|
1234
|
-
webPhoneConnecting.value = true;
|
|
1235
|
-
try {
|
|
1236
|
-
await webPhone.connect({
|
|
1237
|
-
webSocketServer: activeAccount.websocketUrl,
|
|
1238
|
-
aor: activeAccount.sipAddress,
|
|
1239
|
-
authorizationUsername: activeAccount.username,
|
|
1240
|
-
authorizationPassword: activeAccount.password,
|
|
1241
|
-
displayName: "iService365 Web",
|
|
1242
|
-
video: webPhoneForm.video,
|
|
1243
|
-
}, {
|
|
1244
|
-
remoteVideo: remoteVideoEl.value,
|
|
1245
|
-
remoteAudio: remoteAudioEl.value,
|
|
1246
|
-
});
|
|
1247
|
-
showMessage("Web phone connected and ready to receive calls.", "success");
|
|
1248
|
-
} catch (error: unknown) {
|
|
1249
|
-
showMessage(getErrorMessage(error), "error");
|
|
1250
|
-
} finally {
|
|
1251
|
-
webPhoneConnecting.value = false;
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
async function disconnectWebPhone() {
|
|
1256
|
-
webPhoneConnecting.value = true;
|
|
1257
|
-
try {
|
|
1258
|
-
await webPhone.disconnect();
|
|
1259
|
-
showMessage("Web phone disconnected.", "info");
|
|
1260
|
-
} finally {
|
|
1261
|
-
webPhoneConnecting.value = false;
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
async function startWebCall() {
|
|
1266
|
-
webPhoneActionLoading.value = true;
|
|
1267
|
-
try {
|
|
1268
|
-
await webPhone.call(webPhoneForm.destination);
|
|
1269
|
-
} catch (error: unknown) {
|
|
1270
|
-
showMessage(getErrorMessage(error), "error");
|
|
1271
|
-
} finally {
|
|
1272
|
-
webPhoneActionLoading.value = false;
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
async function answerWebCall() {
|
|
1277
|
-
webPhoneActionLoading.value = true;
|
|
1278
|
-
try {
|
|
1279
|
-
await webPhone.answer();
|
|
1280
|
-
} catch (error: unknown) {
|
|
1281
|
-
showMessage(getErrorMessage(error), "error");
|
|
1282
|
-
} finally {
|
|
1283
|
-
webPhoneActionLoading.value = false;
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
async function declineWebCall() {
|
|
1288
|
-
webPhoneActionLoading.value = true;
|
|
1289
|
-
try {
|
|
1290
|
-
await webPhone.decline();
|
|
1291
|
-
} catch (error: unknown) {
|
|
1292
|
-
showMessage(getErrorMessage(error), "error");
|
|
1293
|
-
} finally {
|
|
1294
|
-
webPhoneActionLoading.value = false;
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
async function hangupWebCall() {
|
|
1299
|
-
webPhoneActionLoading.value = true;
|
|
1300
|
-
try {
|
|
1301
|
-
await webPhone.hangup();
|
|
1302
|
-
} catch (error: unknown) {
|
|
1303
|
-
showMessage(getErrorMessage(error), "error");
|
|
1304
|
-
} finally {
|
|
1305
|
-
webPhoneActionLoading.value = false;
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
function toggleWebMute() {
|
|
1310
|
-
try {
|
|
1311
|
-
webPhone.toggleMute();
|
|
1312
|
-
} catch (error: unknown) {
|
|
1313
|
-
showMessage(getErrorMessage(error), "error");
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
954
|
function openContactDialog(contact?: ContactRow) {
|
|
1318
955
|
selectedContact.value = contact || null;
|
|
1319
956
|
contactForm.id = contact?.id;
|
|
@@ -1337,35 +974,21 @@ function openDeleteContact(contact: ContactRow) {
|
|
|
1337
974
|
async function saveSipSettings() {
|
|
1338
975
|
if (!selectedRow.value) return;
|
|
1339
976
|
const isPeerToPeer = sipForm.operatingMode === "peer-to-peer";
|
|
1340
|
-
const initialRtpPort = Number(sipForm.initialRtpPort);
|
|
1341
|
-
const finalRtpPort = Number(sipForm.finalRtpPort);
|
|
1342
|
-
|
|
1343
|
-
if (
|
|
1344
|
-
!Number.isInteger(initialRtpPort) ||
|
|
1345
|
-
!Number.isInteger(finalRtpPort) ||
|
|
1346
|
-
initialRtpPort < 1 ||
|
|
1347
|
-
finalRtpPort > 65535 ||
|
|
1348
|
-
finalRtpPort <= initialRtpPort
|
|
1349
|
-
) {
|
|
1350
|
-
showMessage("Final RTP Port must be greater than Initial RTP Port and both ports must be between 1 and 65535.", "warning");
|
|
1351
|
-
return;
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
977
|
const saved = await savePjsip(selectedRow.value.reader._id, {
|
|
1355
978
|
enabled: sipForm.enabled ? "1" : "0",
|
|
1356
979
|
server_ip: sipForm.serverIp,
|
|
1357
980
|
server_port: sipForm.serverPort,
|
|
1358
|
-
server_outbound_port:
|
|
1359
|
-
server_outbound_port_range:
|
|
981
|
+
server_outbound_port: sipForm.initialRtpPort,
|
|
982
|
+
server_outbound_port_range: sipForm.finalRtpPort,
|
|
1360
983
|
numeric_branch_enabled: sipForm.numericExtension ? "1" : "0",
|
|
1361
984
|
branch: isPeerToPeer ? "" : sipForm.extension,
|
|
1362
985
|
login: isPeerToPeer ? "" : sipForm.login,
|
|
1363
986
|
password: isPeerToPeer ? "" : sipForm.password,
|
|
1364
987
|
peer_to_peer_enabled: isPeerToPeer ? "1" : "0",
|
|
1365
|
-
|
|
1366
|
-
});
|
|
988
|
+
}, { showSuccessMessage: false });
|
|
1367
989
|
if (saved) {
|
|
1368
990
|
sipDialog.value = false;
|
|
991
|
+
rebootConfirmDialog.value = true;
|
|
1369
992
|
}
|
|
1370
993
|
}
|
|
1371
994
|
|
|
@@ -1397,13 +1020,7 @@ async function runMakeCall() {
|
|
|
1397
1020
|
if (!selectedRow.value) return;
|
|
1398
1021
|
const target = makeCallForm.target.trim();
|
|
1399
1022
|
if (!target) {
|
|
1400
|
-
makeCallForm.status = "Please enter an
|
|
1401
|
-
makeCallForm.statusType = "warning";
|
|
1402
|
-
return;
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
if (!isIpAddress(target)) {
|
|
1406
|
-
makeCallForm.status = "Address to dial must be an IP address.";
|
|
1023
|
+
makeCallForm.status = "Please enter an address to dial.";
|
|
1407
1024
|
makeCallForm.statusType = "warning";
|
|
1408
1025
|
return;
|
|
1409
1026
|
}
|
|
@@ -1425,15 +1042,6 @@ async function runMakeCall() {
|
|
|
1425
1042
|
}
|
|
1426
1043
|
}
|
|
1427
1044
|
|
|
1428
|
-
function isIpAddress(value = "") {
|
|
1429
|
-
const parts = value.trim().split(".");
|
|
1430
|
-
return parts.length === 4 && parts.every((part) => {
|
|
1431
|
-
if (!/^\d{1,3}$/.test(part)) return false;
|
|
1432
|
-
const value = Number(part);
|
|
1433
|
-
return value >= 0 && value <= 255;
|
|
1434
|
-
});
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
1045
|
async function runHangupCall() {
|
|
1438
1046
|
if (!selectedRow.value) return;
|
|
1439
1047
|
hangingUp.value = true;
|
|
@@ -1470,6 +1078,30 @@ async function savePjsip(readerId: string, payload: Record<string, string>, opti
|
|
|
1470
1078
|
}
|
|
1471
1079
|
}
|
|
1472
1080
|
|
|
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
|
+
|
|
1473
1105
|
async function saveContact() {
|
|
1474
1106
|
if (!contactForm.readerId || !contactForm.name.trim() || !contactForm.serverAddress.trim()) {
|
|
1475
1107
|
showMessage("Please complete the required contact fields.", "warning");
|
|
@@ -1550,42 +1182,8 @@ function showMessage(text: string, type: MessageType = "info") {
|
|
|
1550
1182
|
message.type = type;
|
|
1551
1183
|
}
|
|
1552
1184
|
|
|
1553
|
-
function
|
|
1554
|
-
|
|
1555
|
-
const payload = toRecord(responseRecord.data || responseRecord);
|
|
1556
|
-
const requiredFields = ["extension", "username", "password", "sipAddress", "websocketUrl"] as const;
|
|
1557
|
-
|
|
1558
|
-
if (requiredFields.some((field) => !toText(payload[field]))) {
|
|
1559
|
-
throw new Error("The SIP account response is incomplete. Please try again.");
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
return {
|
|
1563
|
-
orgId: toText(payload.orgId, props.org),
|
|
1564
|
-
site: toText(payload.site, props.site),
|
|
1565
|
-
userId: toText(payload.userId),
|
|
1566
|
-
extension: toText(payload.extension),
|
|
1567
|
-
username: toText(payload.username),
|
|
1568
|
-
password: toText(payload.password),
|
|
1569
|
-
sipAddress: toText(payload.sipAddress),
|
|
1570
|
-
websocketUrl: toText(payload.websocketUrl),
|
|
1571
|
-
status: "active",
|
|
1572
|
-
provisionedAt: toText(payload.provisionedAt),
|
|
1573
|
-
};
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
function getErrorMessage(error: unknown) {
|
|
1577
|
-
const errorRecord = toRecord(error);
|
|
1578
|
-
const data = toRecord(errorRecord.data);
|
|
1579
|
-
const response = toRecord(errorRecord.response);
|
|
1580
|
-
const responseData = toRecord(response._data);
|
|
1581
|
-
return toText(data.message)
|
|
1582
|
-
|| toText(responseData.message)
|
|
1583
|
-
|| toText(errorRecord.message)
|
|
1584
|
-
|| "HID intercom request failed.";
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
function toRecord(value: unknown): Record<string, unknown> {
|
|
1588
|
-
return typeof value === "object" && value !== null ? value as Record<string, unknown> : {};
|
|
1185
|
+
function getErrorMessage(error: any) {
|
|
1186
|
+
return error?.data?.message || error?.response?._data?.message || error?.message || "HID intercom request failed.";
|
|
1589
1187
|
}
|
|
1590
1188
|
|
|
1591
1189
|
function toText(value: unknown, fallback = "") {
|
|
@@ -1661,23 +1259,6 @@ export default {
|
|
|
1661
1259
|
width: 160px;
|
|
1662
1260
|
}
|
|
1663
1261
|
|
|
1664
|
-
.web-call-button {
|
|
1665
|
-
min-width: 160px;
|
|
1666
|
-
background: #e5e7eb;
|
|
1667
|
-
color: #0b2c40;
|
|
1668
|
-
font-size: 12px;
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
.web-call-button.is-connected {
|
|
1672
|
-
background: #d9f2e1;
|
|
1673
|
-
color: #16743a;
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
.web-call-button.has-incoming-call {
|
|
1677
|
-
background: #1769aa;
|
|
1678
|
-
color: #fff;
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
1262
|
.add-button {
|
|
1682
1263
|
min-width: 160px;
|
|
1683
1264
|
background: #dedede;
|
|
@@ -1887,20 +1468,6 @@ export default {
|
|
|
1887
1468
|
margin-bottom: 12px;
|
|
1888
1469
|
}
|
|
1889
1470
|
|
|
1890
|
-
.sip-section-switch > div {
|
|
1891
|
-
display: flex;
|
|
1892
|
-
min-width: 0;
|
|
1893
|
-
padding: 8px 12px 8px 0;
|
|
1894
|
-
flex-direction: column;
|
|
1895
|
-
gap: 2px;
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
.sip-section-switch small {
|
|
1899
|
-
color: #667085;
|
|
1900
|
-
font-size: 10px;
|
|
1901
|
-
line-height: 1.4;
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
1471
|
.sip-switch-row :deep(.v-switch) {
|
|
1905
1472
|
flex: 0 0 auto;
|
|
1906
1473
|
}
|
|
@@ -2063,6 +1630,98 @@ export default {
|
|
|
2063
1630
|
overflow: hidden;
|
|
2064
1631
|
}
|
|
2065
1632
|
|
|
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
|
+
|
|
2066
1725
|
.call-dialog .dialog-body {
|
|
2067
1726
|
padding: 18px 24px 24px;
|
|
2068
1727
|
}
|
|
@@ -2272,263 +1931,6 @@ export default {
|
|
|
2272
1931
|
line-height: 1.45;
|
|
2273
1932
|
}
|
|
2274
1933
|
|
|
2275
|
-
.web-phone-dialog {
|
|
2276
|
-
width: 820px;
|
|
2277
|
-
max-width: 94vw;
|
|
2278
|
-
overflow: hidden;
|
|
2279
|
-
background: #fff;
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
.web-phone-title {
|
|
2283
|
-
display: flex;
|
|
2284
|
-
align-items: center;
|
|
2285
|
-
justify-content: space-between;
|
|
2286
|
-
gap: 16px;
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
.web-phone-status {
|
|
2290
|
-
min-width: 92px;
|
|
2291
|
-
justify-content: center;
|
|
2292
|
-
font-size: 10px;
|
|
2293
|
-
}
|
|
2294
|
-
|
|
2295
|
-
.web-phone-status.ready {
|
|
2296
|
-
background: #d9f2e1;
|
|
2297
|
-
color: #16743a;
|
|
2298
|
-
}
|
|
2299
|
-
|
|
2300
|
-
.web-phone-status.incoming {
|
|
2301
|
-
background: #dcecff;
|
|
2302
|
-
color: #125c9a;
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
.web-phone-status.failed {
|
|
2306
|
-
background: #fce2e2;
|
|
2307
|
-
color: #a92222;
|
|
2308
|
-
}
|
|
2309
|
-
|
|
2310
|
-
.web-phone-status.idle {
|
|
2311
|
-
background: #e5e7eb;
|
|
2312
|
-
color: #5d6673;
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
|
-
.web-phone-body {
|
|
2316
|
-
display: grid;
|
|
2317
|
-
grid-template-columns: minmax(280px, 0.9fr) minmax(320px, 1.1fr);
|
|
2318
|
-
gap: 24px;
|
|
2319
|
-
min-height: 440px;
|
|
2320
|
-
padding: 22px 24px 24px;
|
|
2321
|
-
}
|
|
2322
|
-
|
|
2323
|
-
.web-phone-settings {
|
|
2324
|
-
padding-right: 24px;
|
|
2325
|
-
border-right: 1px solid #e5e7eb;
|
|
2326
|
-
}
|
|
2327
|
-
|
|
2328
|
-
.web-phone-section-title {
|
|
2329
|
-
margin-bottom: 14px;
|
|
2330
|
-
color: #0d1720;
|
|
2331
|
-
font-size: 13px;
|
|
2332
|
-
font-weight: 700;
|
|
2333
|
-
}
|
|
2334
|
-
|
|
2335
|
-
.sip-account-state {
|
|
2336
|
-
display: flex;
|
|
2337
|
-
min-height: 126px;
|
|
2338
|
-
align-items: center;
|
|
2339
|
-
justify-content: center;
|
|
2340
|
-
padding: 18px;
|
|
2341
|
-
border: 1px solid #dce2e8;
|
|
2342
|
-
border-radius: 6px;
|
|
2343
|
-
color: #5d6673;
|
|
2344
|
-
flex-direction: column;
|
|
2345
|
-
font-size: 12px;
|
|
2346
|
-
gap: 12px;
|
|
2347
|
-
text-align: center;
|
|
2348
|
-
}
|
|
2349
|
-
|
|
2350
|
-
.sip-account-state.is-error {
|
|
2351
|
-
border-color: #f0c7c7;
|
|
2352
|
-
background: #fff8f8;
|
|
2353
|
-
color: #a92222;
|
|
2354
|
-
}
|
|
2355
|
-
|
|
2356
|
-
.sip-account-retry {
|
|
2357
|
-
min-width: 104px;
|
|
2358
|
-
border-color: #aeb8c2;
|
|
2359
|
-
color: #344054;
|
|
2360
|
-
}
|
|
2361
|
-
|
|
2362
|
-
.sip-account-summary {
|
|
2363
|
-
display: grid;
|
|
2364
|
-
min-height: 96px;
|
|
2365
|
-
align-items: center;
|
|
2366
|
-
padding: 16px;
|
|
2367
|
-
border: 1px solid #dce2e8;
|
|
2368
|
-
border-radius: 6px;
|
|
2369
|
-
background: #f8fafc;
|
|
2370
|
-
grid-template-columns: minmax(0, 1fr) auto;
|
|
2371
|
-
gap: 18px;
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2374
|
-
.sip-account-summary > div {
|
|
2375
|
-
display: flex;
|
|
2376
|
-
min-width: 0;
|
|
2377
|
-
flex-direction: column;
|
|
2378
|
-
gap: 4px;
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
|
-
.sip-account-label {
|
|
2382
|
-
color: #667085;
|
|
2383
|
-
font-size: 11px;
|
|
2384
|
-
}
|
|
2385
|
-
|
|
2386
|
-
.sip-account-extension {
|
|
2387
|
-
color: #0d1720;
|
|
2388
|
-
font-size: 24px;
|
|
2389
|
-
line-height: 1.1;
|
|
2390
|
-
}
|
|
2391
|
-
|
|
2392
|
-
.sip-account-address {
|
|
2393
|
-
max-width: 170px;
|
|
2394
|
-
overflow: hidden;
|
|
2395
|
-
color: #536273;
|
|
2396
|
-
font-size: 11px;
|
|
2397
|
-
text-align: right;
|
|
2398
|
-
text-overflow: ellipsis;
|
|
2399
|
-
white-space: nowrap;
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
.web-video-toggle {
|
|
2403
|
-
margin-top: 12px;
|
|
2404
|
-
}
|
|
2405
|
-
|
|
2406
|
-
.web-phone-connect {
|
|
2407
|
-
min-height: 42px;
|
|
2408
|
-
margin-top: 14px;
|
|
2409
|
-
background: #082b40;
|
|
2410
|
-
color: #fff;
|
|
2411
|
-
}
|
|
2412
|
-
|
|
2413
|
-
.web-phone-disconnect {
|
|
2414
|
-
min-height: 42px;
|
|
2415
|
-
margin-top: 14px;
|
|
2416
|
-
border-color: #aeb8c2;
|
|
2417
|
-
color: #344054;
|
|
2418
|
-
}
|
|
2419
|
-
|
|
2420
|
-
.web-phone-console {
|
|
2421
|
-
display: flex;
|
|
2422
|
-
min-width: 0;
|
|
2423
|
-
flex-direction: column;
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
.web-phone-media {
|
|
2427
|
-
position: relative;
|
|
2428
|
-
display: grid;
|
|
2429
|
-
min-height: 260px;
|
|
2430
|
-
overflow: hidden;
|
|
2431
|
-
place-items: center;
|
|
2432
|
-
border: 1px solid #d7dde4;
|
|
2433
|
-
border-radius: 6px;
|
|
2434
|
-
background: #102a3b;
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2437
|
-
.remote-video {
|
|
2438
|
-
display: none;
|
|
2439
|
-
width: 100%;
|
|
2440
|
-
height: 100%;
|
|
2441
|
-
object-fit: cover;
|
|
2442
|
-
}
|
|
2443
|
-
|
|
2444
|
-
.web-phone-media.has-remote-video .remote-video {
|
|
2445
|
-
display: block;
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
.web-phone-media.has-remote-video .audio-call-state {
|
|
2449
|
-
display: none;
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
.audio-call-state {
|
|
2453
|
-
display: flex;
|
|
2454
|
-
align-items: center;
|
|
2455
|
-
justify-content: center;
|
|
2456
|
-
padding: 28px;
|
|
2457
|
-
color: #fff;
|
|
2458
|
-
flex-direction: column;
|
|
2459
|
-
gap: 10px;
|
|
2460
|
-
text-align: center;
|
|
2461
|
-
}
|
|
2462
|
-
|
|
2463
|
-
.audio-call-state strong {
|
|
2464
|
-
font-size: 16px;
|
|
2465
|
-
font-weight: 600;
|
|
2466
|
-
}
|
|
2467
|
-
|
|
2468
|
-
.audio-call-state span {
|
|
2469
|
-
max-width: 100%;
|
|
2470
|
-
overflow: hidden;
|
|
2471
|
-
color: #c9d7e1;
|
|
2472
|
-
font-size: 11px;
|
|
2473
|
-
text-overflow: ellipsis;
|
|
2474
|
-
white-space: nowrap;
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
|
-
.web-phone-dial-row {
|
|
2478
|
-
display: grid;
|
|
2479
|
-
grid-template-columns: minmax(0, 1fr) 42px;
|
|
2480
|
-
gap: 8px;
|
|
2481
|
-
align-items: start;
|
|
2482
|
-
}
|
|
2483
|
-
|
|
2484
|
-
.start-web-call,
|
|
2485
|
-
.hangup-web-call {
|
|
2486
|
-
width: 42px;
|
|
2487
|
-
height: 38px;
|
|
2488
|
-
border-radius: 4px;
|
|
2489
|
-
background: #2a9b50;
|
|
2490
|
-
color: #fff;
|
|
2491
|
-
}
|
|
2492
|
-
|
|
2493
|
-
.hangup-web-call,
|
|
2494
|
-
.reject-call {
|
|
2495
|
-
background: #c9293d;
|
|
2496
|
-
}
|
|
2497
|
-
|
|
2498
|
-
.incoming-call-actions {
|
|
2499
|
-
display: grid;
|
|
2500
|
-
grid-template-columns: 1fr 1fr;
|
|
2501
|
-
gap: 10px;
|
|
2502
|
-
margin-top: 14px;
|
|
2503
|
-
}
|
|
2504
|
-
|
|
2505
|
-
.answer-call,
|
|
2506
|
-
.reject-call {
|
|
2507
|
-
min-height: 42px;
|
|
2508
|
-
color: #fff;
|
|
2509
|
-
}
|
|
2510
|
-
|
|
2511
|
-
.answer-call {
|
|
2512
|
-
background: #2a9b50;
|
|
2513
|
-
}
|
|
2514
|
-
|
|
2515
|
-
.active-call-controls {
|
|
2516
|
-
display: flex;
|
|
2517
|
-
justify-content: center;
|
|
2518
|
-
gap: 12px;
|
|
2519
|
-
margin-top: 16px;
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
.active-call-controls .v-btn {
|
|
2523
|
-
width: 46px;
|
|
2524
|
-
height: 46px;
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
.web-phone-error {
|
|
2528
|
-
margin-top: 14px;
|
|
2529
|
-
font-size: 11px;
|
|
2530
|
-
}
|
|
2531
|
-
|
|
2532
1934
|
.call-two-col {
|
|
2533
1935
|
gap: 12px;
|
|
2534
1936
|
}
|
|
@@ -2662,22 +2064,13 @@ export default {
|
|
|
2662
2064
|
max-width: none;
|
|
2663
2065
|
}
|
|
2664
2066
|
|
|
2665
|
-
.
|
|
2666
|
-
|
|
2067
|
+
.rebooting-card {
|
|
2068
|
+
grid-template-columns: 1fr;
|
|
2069
|
+
padding: 28px;
|
|
2667
2070
|
}
|
|
2668
2071
|
|
|
2669
|
-
.
|
|
2670
|
-
|
|
2671
|
-
max-height: 78vh;
|
|
2672
|
-
overflow: auto;
|
|
2072
|
+
.wait-word {
|
|
2073
|
+
font-size: 80px;
|
|
2673
2074
|
}
|
|
2674
|
-
|
|
2675
|
-
.web-phone-settings {
|
|
2676
|
-
padding-right: 0;
|
|
2677
|
-
padding-bottom: 20px;
|
|
2678
|
-
border-right: 0;
|
|
2679
|
-
border-bottom: 1px solid #e5e7eb;
|
|
2680
|
-
}
|
|
2681
|
-
|
|
2682
2075
|
}
|
|
2683
2076
|
</style>
|