@7365admin1/layer-common 3.2.1-staging.71 → 3.2.1

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/components/EntryPassInformation.vue +4 -4
  3. package/components/HidAccessLogDashboard.vue +44 -119
  4. package/components/HidIdentityMapping.vue +975 -0
  5. package/components/HidIntercomManagement.vue +201 -808
  6. package/components/HidQrCodeConfiguration.vue +95 -907
  7. package/components/HidServiceSettingsPanel.vue +1 -20
  8. package/components/HidUserEnrollment.vue +81 -392
  9. package/components/Layout/NavigationDrawer.vue +2 -43
  10. package/components/NavigationItem.vue +0 -9
  11. package/components/Nfc/NFCPatrolRouteMain.vue +7 -52
  12. package/components/OnlineFormConfigurationForm.vue +224 -620
  13. package/components/OnlineFormFill.vue +8 -36
  14. package/components/OnlineFormsConfiguration.vue +2 -6
  15. package/components/QrTemplate/CreditCardLandscape.vue +12 -19
  16. package/components/QrTemplate/PrintDialog.vue +196 -209
  17. package/components/SiteSettings.vue +2 -4
  18. package/components/VisitorForm.vue +78 -201
  19. package/components/VisitorManagement.vue +1 -0
  20. package/components/VisitorSocketPopUp.vue +2 -56
  21. package/composables/useHidAmico.ts +0 -127
  22. package/composables/useHidNavigation.ts +7 -0
  23. package/composables/useOnlineFormPages.ts +0 -2
  24. package/composables/useSiteSettings.ts +0 -50
  25. package/composables/useVisitorSocket.ts +0 -26
  26. package/composables/useWebUsb.ts +37 -127
  27. package/middleware/member.ts +4 -0
  28. package/package.json +1 -3
  29. package/pages/[org]/[site]/access-mgmt/identity-mapping/index.vue +23 -0
  30. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  31. package/plugins/secure-member.client.ts +56 -21
  32. package/plugins/vuetify.ts +9 -37
  33. package/types/site.d.ts +0 -71
  34. package/.changeset/camera-wall-gated-endpoint.md +0 -47
  35. package/.changeset/camera-wall-plain-english.md +0 -60
  36. package/.changeset/camera-wall-selection-and-guidance.md +0 -65
  37. package/.changeset/dark-primary-contrast.md +0 -39
  38. package/components/CameraWall.vue +0 -1034
  39. package/components/CameraWallTile.vue +0 -818
  40. package/components/HidCameraCaptureDialog.vue +0 -199
  41. package/composables/useSipWebPhone.ts +0 -311
  42. package/test/visitor-socket.test.mjs +0 -36
  43. package/utils/camera-wall.test.ts +0 -571
  44. package/utils/camera-wall.ts +0 -926
  45. package/utils/theme.test.ts +0 -201
  46. package/utils/theme.ts +0 -136
@@ -12,17 +12,7 @@
12
12
  Add Contacts
13
13
  </v-btn>
14
14
 
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>
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">IP address to dial</div>
353
+ <div class="call-label">Address to dial</div>
342
354
  <v-text-field
343
355
  v-model="makeCallForm.target"
344
- placeholder="000.000.0.0"
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-submit full-action" variant="flat" height="44" @click="makeCallDialog = false">Close</v-btn>
380
- </v-card-actions>
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, unknown>;
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, unknown>;
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: "10100",
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
- [() => props.org, () => props.site],
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
- watch(
963
- () => webPhone.callState.value,
964
- (state) => {
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, unknown>, pjsip: Record<string, unknown>, status: string): IntercomRow {
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: toText(reader.name || reader.deviceId, "HID Reader"),
1046
- location: toText(reader.location, "N/A"),
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: toText(reader.monitorPath, "N/A"),
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, unknown>[]) {
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, unknown> | undefined): Record<string, unknown> | undefined {
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
- toRecord(source.pjsip),
1090
- toRecord(toRecord(source.configuration).pjsip),
1091
- toRecord(toRecord(source.result).pjsip),
1092
- toRecord(toRecord(toRecord(source.result).configuration).pjsip),
1093
- toRecord(toRecord(source.data).pjsip),
1094
- toRecord(toRecord(toRecord(source.data).configuration).pjsip),
1095
- toRecord(toRecord(toRecord(source.data).result).pjsip),
1096
- toRecord(toRecord(toRecord(toRecord(source.data).result).configuration).pjsip),
1097
- toRecord(toRecord(toRecord(source.data).data).pjsip),
1098
- toRecord(toRecord(toRecord(toRecord(source.data).data).configuration).pjsip),
1099
- toRecord(source.configuration),
1100
- toRecord(toRecord(source.data).configuration),
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, unknown> {
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, unknown>, keys: 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, unknown>, pjsip: Record<string, unknown>) {
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" ? "10100" : row.finalRtpPort;
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 = isIpAddress(row.serverAddress) ? row.serverAddress : "";
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: String(initialRtpPort),
1359
- server_outbound_port_range: String(finalRtpPort - initialRtpPort),
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
- video_enabled: sipForm.videoEnabled ? "1" : "0",
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 IP address to dial.";
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 normalizeSipAccount(response: unknown): SipAccount {
1554
- const responseRecord = toRecord(response);
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
- .web-call-button {
2666
- width: 100%;
2067
+ .rebooting-card {
2068
+ grid-template-columns: 1fr;
2069
+ padding: 28px;
2667
2070
  }
2668
2071
 
2669
- .web-phone-body {
2670
- grid-template-columns: minmax(0, 1fr);
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>