@7365admin1/layer-common 3.2.8-staging.204 → 3.2.8-staging.206
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ClientMain.vue +45 -8
- package/components/HidAccessLogDashboard.vue +176 -246
- package/components/HidIntercomManagement.vue +51 -18
- package/components/HidQrCodeConfiguration.vue +2 -3
- package/components/HidReaderForm.vue +176 -10
- package/components/HidReaderManagement.vue +211 -96
- package/components/HidServiceSettingsPanel.vue +21 -6
- package/components/HidUserEnrollment.vue +371 -198
- package/components/Layout/NavigationDrawer.vue +19 -2
- package/composables/useHidAmico.ts +113 -28
- package/composables/useHidNavigation.ts +4 -2
- package/composables/useOptionalServices.ts +26 -16
- package/package.json +1 -1
- package/utils/client-nature.test.ts +77 -0
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
|
|
109
109
|
<HidReaderForm
|
|
110
110
|
v-model="formDialog"
|
|
111
|
+
:site="props.site"
|
|
111
112
|
:mode="selectedReader ? 'edit' : 'add'"
|
|
112
113
|
:reader="selectedReader"
|
|
113
114
|
:loading="saving"
|
|
@@ -125,6 +126,8 @@
|
|
|
125
126
|
<span>Location</span><strong>{{ selectedReader?.location || "N/A" }}</strong>
|
|
126
127
|
<span>HID Portal</span><strong>{{ selectedReader?.portalName || selectedReader?.portalId || "N/A" }}</strong>
|
|
127
128
|
<span>Monitor Path</span><strong>{{ selectedReader?.monitorPath || "N/A" }}</strong>
|
|
129
|
+
<span>Monitor Callback</span><strong>{{ formatMonitorStatus(selectedReader?.monitorStatus) }}</strong>
|
|
130
|
+
<span>Last Callback</span><strong>{{ formatCallback(selectedReader) }}</strong>
|
|
128
131
|
<span>Status</span><strong>{{ formatReaderStatus(resolveReaderStatus(selectedReader)) }}</strong>
|
|
129
132
|
<span>Username</span><strong>{{ selectedReader?.username || "N/A" }}</strong>
|
|
130
133
|
</div>
|
|
@@ -242,6 +245,80 @@
|
|
|
242
245
|
</template>
|
|
243
246
|
|
|
244
247
|
<script setup lang="ts">
|
|
248
|
+
type HidRecord = {
|
|
249
|
+
[key: string]: unknown;
|
|
250
|
+
_id?: string;
|
|
251
|
+
data?: HidRecord;
|
|
252
|
+
items?: HidRecord[];
|
|
253
|
+
identities?: HidRecord[];
|
|
254
|
+
users?: HidRecord[];
|
|
255
|
+
user_roles?: HidRecord[];
|
|
256
|
+
result?: HidRecord;
|
|
257
|
+
metadata?: HidRecord;
|
|
258
|
+
id?: string | number;
|
|
259
|
+
name?: string;
|
|
260
|
+
registration?: string;
|
|
261
|
+
card_value?: string | number;
|
|
262
|
+
cardNo?: string | number;
|
|
263
|
+
person?: string;
|
|
264
|
+
user?: string;
|
|
265
|
+
member?: string;
|
|
266
|
+
serviceProvider?: string;
|
|
267
|
+
visitor?: string;
|
|
268
|
+
type?: string;
|
|
269
|
+
status?: string;
|
|
270
|
+
location?: string;
|
|
271
|
+
deviceId?: string;
|
|
272
|
+
portalId?: number;
|
|
273
|
+
portalName?: string;
|
|
274
|
+
monitorPath?: string;
|
|
275
|
+
monitorStatus?: string;
|
|
276
|
+
operatingMode?: "standalone" | "online";
|
|
277
|
+
lastSyncAt?: string;
|
|
278
|
+
lastSyncStatus?: "idle" | "running" | "completed" | "failed";
|
|
279
|
+
lastSyncMessage?: string;
|
|
280
|
+
lastCallbackAt?: string;
|
|
281
|
+
lastCallbackType?: string;
|
|
282
|
+
readerModel?: "VL35LF" | "VL70LF" | "unknown" | "";
|
|
283
|
+
firmwareVersion?: string;
|
|
284
|
+
cardReadingMode?: "unconfigured" | "pacs" | "csn" | "csn_for_mifare";
|
|
285
|
+
csnByteOrder?: "msb" | "lsb";
|
|
286
|
+
pacsFormat?: "raw" | "wiegand";
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
type HidReaderFormPayload = {
|
|
290
|
+
name: string;
|
|
291
|
+
baseUrl: string;
|
|
292
|
+
deviceId: string;
|
|
293
|
+
portalId: number | null;
|
|
294
|
+
portalName: string;
|
|
295
|
+
location: string;
|
|
296
|
+
username: string;
|
|
297
|
+
password: string;
|
|
298
|
+
readerModel: "VL35LF" | "VL70LF" | "unknown" | "";
|
|
299
|
+
firmwareVersion: string;
|
|
300
|
+
cardReadingMode: "unconfigured" | "pacs" | "csn" | "csn_for_mifare";
|
|
301
|
+
csnByteOrder: "msb" | "lsb";
|
|
302
|
+
pacsFormat: "raw" | "wiegand";
|
|
303
|
+
operatingMode: "standalone" | "online";
|
|
304
|
+
monitorEnabled: boolean;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
type HidSyncUser = HidRecord & {
|
|
308
|
+
id: number;
|
|
309
|
+
name: string;
|
|
310
|
+
registration: string;
|
|
311
|
+
_identityType: string;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
type HidSyncPayload = {
|
|
315
|
+
objects: Array<{ object: "users"; values: HidSyncUser[] }>;
|
|
316
|
+
users: HidSyncUser[];
|
|
317
|
+
unmappedCount: number;
|
|
318
|
+
stats: { newEnrolled: number; updatedInformation: number; facialData: number };
|
|
319
|
+
defaultMessage: string;
|
|
320
|
+
};
|
|
321
|
+
|
|
245
322
|
const props = defineProps({
|
|
246
323
|
site: {
|
|
247
324
|
type: String,
|
|
@@ -255,12 +332,14 @@ const {
|
|
|
255
332
|
updateReader,
|
|
256
333
|
deleteReader,
|
|
257
334
|
testReader,
|
|
335
|
+
configureReaderIntegration,
|
|
336
|
+
setReaderMonitor,
|
|
337
|
+
setReaderOperatingMode,
|
|
258
338
|
getIdentities,
|
|
259
|
-
createIdentity,
|
|
260
339
|
runObjectOperation,
|
|
261
340
|
} = useHidAmico();
|
|
262
341
|
|
|
263
|
-
const readers = ref<
|
|
342
|
+
const readers = ref<HidRecord[]>([]);
|
|
264
343
|
const search = ref("");
|
|
265
344
|
const status = ref("");
|
|
266
345
|
const loading = ref(false);
|
|
@@ -270,7 +349,7 @@ const formDialog = ref(false);
|
|
|
270
349
|
const viewDialog = ref(false);
|
|
271
350
|
const confirmDialog = ref(false);
|
|
272
351
|
const resultDialog = ref(false);
|
|
273
|
-
const selectedReader = ref<
|
|
352
|
+
const selectedReader = ref<HidRecord | null>(null);
|
|
274
353
|
const pendingAction = ref<"test" | "sync" | "delete" | null>(null);
|
|
275
354
|
const actionStage = ref<"confirm" | "review" | "loading">("confirm");
|
|
276
355
|
const resultSuccess = ref(true);
|
|
@@ -278,7 +357,7 @@ const resultTitle = ref("");
|
|
|
278
357
|
const resultMessage = ref("");
|
|
279
358
|
const resultReaderName = ref("");
|
|
280
359
|
const syncMessage = ref("");
|
|
281
|
-
const syncPreviewPayload = ref<
|
|
360
|
+
const syncPreviewPayload = ref<HidSyncPayload | null>(null);
|
|
282
361
|
const syncPreviewStats = ref({
|
|
283
362
|
newEnrolled: 0,
|
|
284
363
|
updatedInformation: 0,
|
|
@@ -398,31 +477,31 @@ function openAdd() {
|
|
|
398
477
|
formDialog.value = true;
|
|
399
478
|
}
|
|
400
479
|
|
|
401
|
-
function openEdit(reader:
|
|
480
|
+
function openEdit(reader: HidRecord) {
|
|
402
481
|
selectedReader.value = reader;
|
|
403
482
|
formDialog.value = true;
|
|
404
483
|
}
|
|
405
484
|
|
|
406
|
-
function openView(reader:
|
|
485
|
+
function openView(reader: HidRecord) {
|
|
407
486
|
selectedReader.value = reader;
|
|
408
487
|
viewDialog.value = true;
|
|
409
488
|
}
|
|
410
489
|
|
|
411
|
-
function openTestConfirm(reader?:
|
|
490
|
+
function openTestConfirm(reader?: HidRecord | null) {
|
|
412
491
|
if (reader) selectedReader.value = reader;
|
|
413
492
|
pendingAction.value = "test";
|
|
414
493
|
resetActionFlow();
|
|
415
494
|
confirmDialog.value = true;
|
|
416
495
|
}
|
|
417
496
|
|
|
418
|
-
function openSyncConfirm(reader?:
|
|
497
|
+
function openSyncConfirm(reader?: HidRecord | null) {
|
|
419
498
|
if (reader) selectedReader.value = reader;
|
|
420
499
|
pendingAction.value = "sync";
|
|
421
500
|
resetActionFlow();
|
|
422
501
|
confirmDialog.value = true;
|
|
423
502
|
}
|
|
424
503
|
|
|
425
|
-
function openDeleteConfirm(reader:
|
|
504
|
+
function openDeleteConfirm(reader: HidRecord) {
|
|
426
505
|
selectedReader.value = reader;
|
|
427
506
|
pendingAction.value = "delete";
|
|
428
507
|
resetActionFlow();
|
|
@@ -445,16 +524,46 @@ function closeConfirmDialog() {
|
|
|
445
524
|
resetActionFlow();
|
|
446
525
|
}
|
|
447
526
|
|
|
448
|
-
async function saveReader(payload:
|
|
527
|
+
async function saveReader(payload: HidReaderFormPayload) {
|
|
449
528
|
saving.value = true;
|
|
450
529
|
try {
|
|
530
|
+
let readerId = String(selectedReader.value?._id || "");
|
|
531
|
+
let monitorWarning = "";
|
|
451
532
|
if (selectedReader.value?._id) {
|
|
452
533
|
await updateReader(selectedReader.value._id, payload);
|
|
534
|
+
if (payload.monitorEnabled) {
|
|
535
|
+
await configureReaderIntegration(selectedReader.value._id);
|
|
536
|
+
await setReaderOperatingMode(selectedReader.value._id, payload.operatingMode || "standalone");
|
|
537
|
+
} else {
|
|
538
|
+
await setReaderMonitor(selectedReader.value._id, false);
|
|
539
|
+
}
|
|
453
540
|
} else {
|
|
454
|
-
await createReader({ ...payload, site: props.site });
|
|
541
|
+
const response = await createReader({ ...payload, site: props.site });
|
|
542
|
+
const createdReader = response?.data || response || {};
|
|
543
|
+
readerId = String(createdReader?._id || "");
|
|
544
|
+
monitorWarning = String(createdReader?.monitorWarning || "");
|
|
545
|
+
}
|
|
546
|
+
if (!selectedReader.value?._id && readerId && payload.monitorEnabled && !monitorWarning) {
|
|
547
|
+
await setReaderOperatingMode(readerId, payload.operatingMode || "standalone");
|
|
455
548
|
}
|
|
456
549
|
formDialog.value = false;
|
|
457
550
|
await loadReaders();
|
|
551
|
+
if (monitorWarning) {
|
|
552
|
+
showResult(
|
|
553
|
+
false,
|
|
554
|
+
"Reader Requires Configuration",
|
|
555
|
+
monitorWarning,
|
|
556
|
+
{ readerName: payload.name || "HID Reader" },
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
} catch (error: unknown) {
|
|
560
|
+
console.error("Unable to save HID reader:", error);
|
|
561
|
+
showResult(
|
|
562
|
+
false,
|
|
563
|
+
"Save HID Reader Failed",
|
|
564
|
+
getFriendlyHidError(error),
|
|
565
|
+
{ readerName: payload.name || selectedReader.value?.name || "HID Reader" },
|
|
566
|
+
);
|
|
458
567
|
} finally {
|
|
459
568
|
saving.value = false;
|
|
460
569
|
}
|
|
@@ -485,22 +594,16 @@ async function runConfirmedAction() {
|
|
|
485
594
|
|
|
486
595
|
actionStage.value = "loading";
|
|
487
596
|
const payload = syncPreviewPayload.value;
|
|
488
|
-
const
|
|
489
|
-
const importedCount = hidUsersToImport.length
|
|
490
|
-
? await importHidUsers(selectedReader.value._id, hidUsersToImport)
|
|
491
|
-
: 0;
|
|
597
|
+
const unmappedCount = Number(payload.unmappedCount || 0);
|
|
492
598
|
|
|
493
599
|
if (!payload.objects.length) {
|
|
494
|
-
if (
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
newEnrolled: importedCount,
|
|
498
|
-
};
|
|
499
|
-
const message = syncMessage.value || `Synced ${importedCount} HID user(s) from reader.`;
|
|
600
|
+
if (unmappedCount) {
|
|
601
|
+
const message = syncMessage.value
|
|
602
|
+
|| `Found ${unmappedCount} HID user(s) that must be linked from the HID Users screen.`;
|
|
500
603
|
await applyReaderSyncState(selectedReader.value._id, "completed", message);
|
|
501
|
-
showResult(true, "Sync
|
|
604
|
+
showResult(true, "Sync Review Required", message, {
|
|
502
605
|
readerName: selectedReader.value.name || "HID Reader",
|
|
503
|
-
stats,
|
|
606
|
+
stats: syncPreviewStats.value,
|
|
504
607
|
});
|
|
505
608
|
confirmDialog.value = false;
|
|
506
609
|
resetActionFlow();
|
|
@@ -517,8 +620,10 @@ async function runConfirmedAction() {
|
|
|
517
620
|
return;
|
|
518
621
|
}
|
|
519
622
|
const result = await syncHidUsers(selectedReader.value._id, payload.users);
|
|
520
|
-
const
|
|
521
|
-
|
|
623
|
+
const mappingMessage = unmappedCount
|
|
624
|
+
? `${unmappedCount} HID user(s) still need to be linked from the HID Users screen.`
|
|
625
|
+
: "";
|
|
626
|
+
const message = syncMessage.value || [result.message, mappingMessage].filter(Boolean).join(" ");
|
|
522
627
|
await applyReaderSyncState(selectedReader.value._id, "completed", message);
|
|
523
628
|
showResult(true, "Sync Successful", message, {
|
|
524
629
|
readerName: selectedReader.value.name || "HID Reader",
|
|
@@ -532,7 +637,7 @@ async function runConfirmedAction() {
|
|
|
532
637
|
confirmDialog.value = false;
|
|
533
638
|
resetActionFlow();
|
|
534
639
|
await loadReaders();
|
|
535
|
-
} catch (error:
|
|
640
|
+
} catch (error: unknown) {
|
|
536
641
|
console.error("HID reader action failed:", error);
|
|
537
642
|
const friendlyMessage = getFriendlyHidError(error);
|
|
538
643
|
if ((pendingAction.value === "sync" || pendingAction.value === "test") && selectedReader.value?._id) {
|
|
@@ -552,7 +657,7 @@ async function runConfirmedAction() {
|
|
|
552
657
|
|
|
553
658
|
async function applyReaderSyncState(readerId: string, status: "completed" | "failed", message: string) {
|
|
554
659
|
const lastSyncAt = new Date().toISOString();
|
|
555
|
-
const patch:
|
|
660
|
+
const patch: HidRecord = {
|
|
556
661
|
lastSyncAt,
|
|
557
662
|
lastSyncStatus: status,
|
|
558
663
|
lastSyncMessage: message,
|
|
@@ -602,35 +707,40 @@ async function buildSyncPayload(readerId: string) {
|
|
|
602
707
|
const hidUsers = normalizeHidUsers(hidResponse);
|
|
603
708
|
return {
|
|
604
709
|
objects: [],
|
|
605
|
-
importedCount: 0,
|
|
606
710
|
users: [],
|
|
607
|
-
|
|
711
|
+
unmappedCount: hidUsers.length,
|
|
608
712
|
stats: {
|
|
609
|
-
newEnrolled:
|
|
713
|
+
newEnrolled: 0,
|
|
610
714
|
updatedInformation: 0,
|
|
611
715
|
facialData: countFacialData(hidUsers),
|
|
612
716
|
},
|
|
613
|
-
defaultMessage: hidUsers.length
|
|
717
|
+
defaultMessage: hidUsers.length
|
|
718
|
+
? `Found ${hidUsers.length} HID user(s) that must be linked from the HID Users screen.`
|
|
719
|
+
: "",
|
|
614
720
|
};
|
|
615
721
|
}
|
|
616
722
|
|
|
723
|
+
const allLinkedIds = new Set(
|
|
724
|
+
identities
|
|
725
|
+
.map((identity) => Number(identity.hidUserId))
|
|
726
|
+
.filter((id: number) => Number.isInteger(id) && id > 0),
|
|
727
|
+
);
|
|
617
728
|
const users = identities
|
|
729
|
+
.filter((identity) => (
|
|
730
|
+
identity.type !== "visitor"
|
|
731
|
+
&& (identity.metadata as Record<string, unknown> | undefined)?.temporary !== true
|
|
732
|
+
))
|
|
618
733
|
.map(toHidUserObject)
|
|
619
|
-
.filter((user:
|
|
734
|
+
.filter((user): user is HidSyncUser => user !== null);
|
|
620
735
|
const existingHidUsers = await getExistingHidUsers(readerId);
|
|
621
736
|
const existingIds = new Set(existingHidUsers.map((user) => Number(user.hidUserId)));
|
|
622
|
-
const localIds = new Set(
|
|
623
|
-
users
|
|
624
|
-
.map((user: Record<string, any>) => Number(user.id))
|
|
625
|
-
.filter((id) => Number.isInteger(id) && id > 0),
|
|
626
|
-
);
|
|
627
737
|
const hidUsersToImport = existingHidUsers.filter((user) => {
|
|
628
738
|
const hidUserId = Number(user.hidUserId);
|
|
629
|
-
return Number.isInteger(hidUserId) && hidUserId > 0 && !
|
|
739
|
+
return Number.isInteger(hidUserId) && hidUserId > 0 && !allLinkedIds.has(hidUserId);
|
|
630
740
|
});
|
|
631
|
-
const newEnrolled = users.filter((user
|
|
632
|
-
const updatedInformation = users.filter((user
|
|
633
|
-
const totalUsersToSync = users.length
|
|
741
|
+
const newEnrolled = users.filter((user) => !existingIds.has(Number(user.id))).length;
|
|
742
|
+
const updatedInformation = users.filter((user) => existingIds.has(Number(user.id))).length;
|
|
743
|
+
const totalUsersToSync = users.length;
|
|
634
744
|
|
|
635
745
|
return {
|
|
636
746
|
objects: users.length
|
|
@@ -642,22 +752,26 @@ async function buildSyncPayload(readerId: string) {
|
|
|
642
752
|
]
|
|
643
753
|
: [],
|
|
644
754
|
users,
|
|
645
|
-
hidUsersToImport,
|
|
646
|
-
importedCount: hidUsersToImport.length,
|
|
755
|
+
unmappedCount: hidUsersToImport.length,
|
|
647
756
|
stats: {
|
|
648
|
-
newEnrolled
|
|
757
|
+
newEnrolled,
|
|
649
758
|
updatedInformation,
|
|
650
759
|
facialData: countFacialData([...users, ...hidUsersToImport]),
|
|
651
760
|
},
|
|
652
|
-
defaultMessage:
|
|
761
|
+
defaultMessage: [
|
|
762
|
+
totalUsersToSync ? `Ready to sync ${totalUsersToSync} linked HID user(s).` : "",
|
|
763
|
+
hidUsersToImport.length
|
|
764
|
+
? `${hidUsersToImport.length} HID user(s) must be linked from the HID Users screen.`
|
|
765
|
+
: "",
|
|
766
|
+
].filter(Boolean).join(" "),
|
|
653
767
|
};
|
|
654
768
|
}
|
|
655
769
|
|
|
656
|
-
async function syncHidUsers(readerId: string, users:
|
|
770
|
+
async function syncHidUsers(readerId: string, users: HidSyncUser[]) {
|
|
657
771
|
const existingIds = await getExistingHidUserIds(readerId);
|
|
658
772
|
const usersToCreate = users.filter((user) => !existingIds.has(Number(user.id)));
|
|
659
773
|
const usersToModify = users.filter((user) => existingIds.has(Number(user.id)));
|
|
660
|
-
const results:
|
|
774
|
+
const results: HidRecord[] = [];
|
|
661
775
|
|
|
662
776
|
if (usersToModify.length) {
|
|
663
777
|
const modifyResults = [];
|
|
@@ -711,7 +825,7 @@ async function getExistingHidUsers(readerId: string) {
|
|
|
711
825
|
return normalizeHidUsers(hidResponse);
|
|
712
826
|
}
|
|
713
827
|
|
|
714
|
-
function normalizeHidUsers(response:
|
|
828
|
+
function normalizeHidUsers(response: HidRecord) {
|
|
715
829
|
const users =
|
|
716
830
|
response?.data?.users ||
|
|
717
831
|
response?.data?.data?.users ||
|
|
@@ -731,49 +845,29 @@ function normalizeHidUsers(response: Record<string, any>) {
|
|
|
731
845
|
: [];
|
|
732
846
|
}
|
|
733
847
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
site: props.site,
|
|
741
|
-
hidUserId: user.hidUserId,
|
|
742
|
-
registration: user.registration || "",
|
|
743
|
-
cardNo: user.cardNo || "",
|
|
744
|
-
type: "resident",
|
|
745
|
-
status: "active",
|
|
746
|
-
metadata: {
|
|
747
|
-
name: user.metadata?.name || user.name || `HID User ${user.hidUserId}`,
|
|
748
|
-
hidImported: true,
|
|
749
|
-
importedAt: new Date().toISOString(),
|
|
750
|
-
},
|
|
751
|
-
});
|
|
752
|
-
importedCount += 1;
|
|
753
|
-
} catch {
|
|
754
|
-
// Existing users may already be mapped; keep syncing the remaining HID users.
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
return importedCount;
|
|
758
|
-
}
|
|
848
|
+
function toHidUserObject(identity: HidRecord): HidSyncUser | null {
|
|
849
|
+
const id = toHidNumericId(identity.hidUserId);
|
|
850
|
+
const name = String(
|
|
851
|
+
identity.metadata?.name ?? identity.name ?? `User ${identity.hidUserId ?? ""}`,
|
|
852
|
+
).trim();
|
|
853
|
+
if (!id || !name) return null;
|
|
759
854
|
|
|
760
|
-
function toHidUserObject(identity: Record<string, any>) {
|
|
761
855
|
return {
|
|
762
|
-
id
|
|
763
|
-
name
|
|
856
|
+
id,
|
|
857
|
+
name,
|
|
764
858
|
registration: identity.registration || "",
|
|
765
859
|
_identityType: identity.type || "resident",
|
|
766
860
|
};
|
|
767
861
|
}
|
|
768
862
|
|
|
769
|
-
function toHidUserCreateValues(user:
|
|
863
|
+
function toHidUserCreateValues(user: HidSyncUser) {
|
|
770
864
|
return {
|
|
771
865
|
id: user.id,
|
|
772
866
|
...toHidUserValues(user),
|
|
773
867
|
};
|
|
774
868
|
}
|
|
775
869
|
|
|
776
|
-
function toHidUserValues(user:
|
|
870
|
+
function toHidUserValues(user: HidSyncUser) {
|
|
777
871
|
return {
|
|
778
872
|
name: user.name,
|
|
779
873
|
registration: user.registration || "",
|
|
@@ -873,11 +967,11 @@ function showResult(
|
|
|
873
967
|
resultDialog.value = true;
|
|
874
968
|
}
|
|
875
969
|
|
|
876
|
-
function countFacialData(users:
|
|
970
|
+
function countFacialData(users: HidRecord[]) {
|
|
877
971
|
return users.filter((user) => user.face || user.photo || user.image || user.imageUrl || user.faceImage).length;
|
|
878
972
|
}
|
|
879
973
|
|
|
880
|
-
function getFriendlyHidError(error:
|
|
974
|
+
function getFriendlyHidError(error: unknown) {
|
|
881
975
|
const message = getHidErrorMessage(error);
|
|
882
976
|
const lowerMessage = message.toLowerCase();
|
|
883
977
|
|
|
@@ -939,22 +1033,30 @@ function getFriendlyHidError(error: any) {
|
|
|
939
1033
|
return message || "The HID device rejected the request. Please check the reader connection and try again.";
|
|
940
1034
|
}
|
|
941
1035
|
|
|
942
|
-
function
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1036
|
+
function asHidRecord(value: unknown): HidRecord {
|
|
1037
|
+
return typeof value === "object" && value !== null ? value as HidRecord : {};
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function getHidErrorMessage(error: unknown) {
|
|
1041
|
+
const record = asHidRecord(error);
|
|
1042
|
+
const response = asHidRecord(record.response);
|
|
1043
|
+
const cause = asHidRecord(record.cause);
|
|
1044
|
+
const causeResponse = asHidRecord(cause.response);
|
|
1045
|
+
const data = asHidRecord(
|
|
1046
|
+
record.data
|
|
1047
|
+
?? response._data
|
|
1048
|
+
?? response.data
|
|
1049
|
+
?? cause.data
|
|
1050
|
+
?? causeResponse._data,
|
|
1051
|
+
);
|
|
950
1052
|
|
|
951
1053
|
return String(
|
|
952
|
-
data
|
|
953
|
-
data
|
|
954
|
-
data
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
"Request failed.",
|
|
1054
|
+
data.message
|
|
1055
|
+
?? data.error
|
|
1056
|
+
?? data.statusMessage
|
|
1057
|
+
?? record.statusMessage
|
|
1058
|
+
?? record.message
|
|
1059
|
+
?? "Request failed.",
|
|
958
1060
|
);
|
|
959
1061
|
}
|
|
960
1062
|
|
|
@@ -964,13 +1066,13 @@ function formatDate(value?: string) {
|
|
|
964
1066
|
return Number.isNaN(date.getTime()) ? "N/A" : date.toLocaleString();
|
|
965
1067
|
}
|
|
966
1068
|
|
|
967
|
-
function resolveReaderStatus(reader?:
|
|
1069
|
+
function resolveReaderStatus(reader?: HidRecord | null) {
|
|
968
1070
|
if (!reader) return "";
|
|
969
1071
|
if (reader.lastSyncStatus === "failed" && reader.status === "active") return "offline";
|
|
970
1072
|
return reader.status || "offline";
|
|
971
1073
|
}
|
|
972
1074
|
|
|
973
|
-
function normalizeReaderStatus(reader:
|
|
1075
|
+
function normalizeReaderStatus(reader: HidRecord) {
|
|
974
1076
|
return {
|
|
975
1077
|
...reader,
|
|
976
1078
|
status: resolveReaderStatus(reader),
|
|
@@ -982,6 +1084,19 @@ function formatReaderStatus(value?: string) {
|
|
|
982
1084
|
if (value === "offline") return "Offline";
|
|
983
1085
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
984
1086
|
}
|
|
1087
|
+
|
|
1088
|
+
function formatMonitorStatus(value?: string) {
|
|
1089
|
+
if (value === "configured") return "Enabled";
|
|
1090
|
+
if (value === "disabled") return "Disabled";
|
|
1091
|
+
if (value === "failed") return "Failed";
|
|
1092
|
+
return "Not configured";
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function formatCallback(reader?: HidRecord | null) {
|
|
1096
|
+
if (!reader?.lastCallbackAt) return "No callback received";
|
|
1097
|
+
const type = typeof reader.lastCallbackType === "string" ? reader.lastCallbackType : "event";
|
|
1098
|
+
return `${type} - ${formatDate(String(reader.lastCallbackAt))}`;
|
|
1099
|
+
}
|
|
985
1100
|
</script>
|
|
986
1101
|
|
|
987
1102
|
|
|
@@ -60,10 +60,18 @@
|
|
|
60
60
|
</template>
|
|
61
61
|
|
|
62
62
|
<script setup lang="ts">
|
|
63
|
+
type HidServiceMetadata = Record<string, unknown> & {
|
|
64
|
+
services?: unknown[];
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type HidServiceSite = Record<string, unknown> & {
|
|
68
|
+
metadata?: HidServiceMetadata;
|
|
69
|
+
};
|
|
70
|
+
|
|
63
71
|
const props = defineProps<{
|
|
64
72
|
site: string;
|
|
65
73
|
org: string;
|
|
66
|
-
siteData?:
|
|
74
|
+
siteData?: HidServiceSite | null;
|
|
67
75
|
}>();
|
|
68
76
|
|
|
69
77
|
const emit = defineEmits<{
|
|
@@ -112,13 +120,14 @@ function buildHidServices(enabled: boolean) {
|
|
|
112
120
|
status: enabled ? "active" : "inactive",
|
|
113
121
|
};
|
|
114
122
|
|
|
115
|
-
const index = currentServices.findIndex((item
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
const index = currentServices.findIndex((item) => {
|
|
124
|
+
const record = toServiceRecord(item);
|
|
125
|
+
return hidServiceKeys.includes(normalizeServiceKey(record.key ?? record.code ?? record.name));
|
|
126
|
+
});
|
|
118
127
|
|
|
119
128
|
if (index >= 0) {
|
|
120
129
|
currentServices[index] = {
|
|
121
|
-
...currentServices[index],
|
|
130
|
+
...toServiceRecord(currentServices[index]),
|
|
122
131
|
...service,
|
|
123
132
|
};
|
|
124
133
|
} else {
|
|
@@ -146,7 +155,7 @@ async function saveHidService(value: boolean) {
|
|
|
146
155
|
emit("refresh");
|
|
147
156
|
await refreshNuxtData(`layout-selected-site-details-${useRuntimeConfig().public.APP}`);
|
|
148
157
|
showMessage(`HID Face Reader ${value ? "enabled" : "disabled"} successfully`, "success");
|
|
149
|
-
} catch (error:
|
|
158
|
+
} catch (error: unknown) {
|
|
150
159
|
hidEnabled.value = previousValue;
|
|
151
160
|
showMessage(errorConverter(error), "error");
|
|
152
161
|
} finally {
|
|
@@ -154,6 +163,12 @@ async function saveHidService(value: boolean) {
|
|
|
154
163
|
}
|
|
155
164
|
}
|
|
156
165
|
|
|
166
|
+
function toServiceRecord(value: unknown): Record<string, unknown> {
|
|
167
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
168
|
+
? value as Record<string, unknown>
|
|
169
|
+
: {};
|
|
170
|
+
}
|
|
171
|
+
|
|
157
172
|
function goToHidReaders() {
|
|
158
173
|
navigateTo({
|
|
159
174
|
name: "org-site-access-mgmt-hid-readers",
|