@7365admin1/layer-common 3.0.18 → 3.0.19
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 +6 -0
- package/components/BuildingManagement/buildings.vue +7 -4
- package/components/BuildingManagement/units.vue +187 -9
- package/components/HidAccessLogDashboard.vue +184 -21
- package/components/HidReaderForm.vue +20 -12
- package/components/HidReaderManagement.vue +113 -80
- package/components/MemberInformation.vue +240 -163
- package/components/PassInformation.vue +6 -4
- package/components/VisitorForm.vue +41 -18
- package/composables/useBuilding.ts +14 -0
- package/composables/useVisitor.ts +19 -15
- package/package.json +1 -1
- package/pages/[org]/[site]/access-mgmt/access-logs/index.vue +3 -1
- package/types/visitor.d.ts +1 -1
|
@@ -17,16 +17,18 @@
|
|
|
17
17
|
class="mb-3"
|
|
18
18
|
/>
|
|
19
19
|
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
v-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
<template v-if="mode === 'add'">
|
|
21
|
+
<div class="field-label">Base URL / IP address <span>*</span></div>
|
|
22
|
+
<v-text-field
|
|
23
|
+
v-model="draft.baseUrl"
|
|
24
|
+
aria-label="Base URL / IP address"
|
|
25
|
+
placeholder="http://192.168.x.x."
|
|
26
|
+
variant="outlined"
|
|
27
|
+
density="compact"
|
|
28
|
+
hide-details="auto"
|
|
29
|
+
class="mb-3"
|
|
30
|
+
/>
|
|
31
|
+
</template>
|
|
30
32
|
|
|
31
33
|
<div class="field-label">Device ID <span>*</span></div>
|
|
32
34
|
<v-text-field
|
|
@@ -166,12 +168,18 @@ function close() {
|
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
function submit() {
|
|
169
|
-
|
|
171
|
+
const payload: Record<string, any> = {
|
|
170
172
|
...draft,
|
|
171
173
|
monitorPath: monitorEnabled.value ? "api/notifications" : "",
|
|
172
174
|
enabled: monitorEnabled.value,
|
|
173
175
|
status: monitorEnabled.value ? "active" : "inactive",
|
|
174
|
-
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
if (props.mode === "edit") {
|
|
179
|
+
delete payload.baseUrl;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
emit("submit", payload);
|
|
175
183
|
}
|
|
176
184
|
</script>
|
|
177
185
|
|
|
@@ -38,31 +38,26 @@
|
|
|
38
38
|
<v-btn icon="mdi-refresh" variant="text" density="comfortable" :loading="loading" @click="loadReaders" />
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
|
-
<v-table>
|
|
41
|
+
<v-table class="reader-table">
|
|
42
42
|
<thead>
|
|
43
43
|
<tr>
|
|
44
|
-
<th>Name</th>
|
|
45
|
-
<th>Location</th>
|
|
46
|
-
<th>
|
|
47
|
-
<th>
|
|
48
|
-
<th>
|
|
49
|
-
<th>Last sync
|
|
50
|
-
<th
|
|
51
|
-
<th class="text-right"></th>
|
|
44
|
+
<th class="name-column">Name</th>
|
|
45
|
+
<th class="location-column">Location</th>
|
|
46
|
+
<th class="device-column">Device ID</th>
|
|
47
|
+
<th class="status-column">Status</th>
|
|
48
|
+
<th class="sync-at-column">Last sync at</th>
|
|
49
|
+
<th class="message-column">Last sync message</th>
|
|
50
|
+
<th class="action-column"></th>
|
|
52
51
|
</tr>
|
|
53
52
|
</thead>
|
|
54
53
|
<tbody>
|
|
55
54
|
<tr v-for="reader in filteredReaders" :key="reader._id">
|
|
56
|
-
<td
|
|
57
|
-
|
|
58
|
-
<td>
|
|
59
|
-
<a v-if="reader.baseUrl" :href="reader.baseUrl" target="_blank" rel="noopener">
|
|
60
|
-
{{ reader.baseUrl }}
|
|
61
|
-
</a>
|
|
62
|
-
<span v-else>N/A</span>
|
|
55
|
+
<td class="name-cell">
|
|
56
|
+
<span class="cell-strong">{{ reader.name || "N/A" }}</span>
|
|
63
57
|
</td>
|
|
64
|
-
<td>{{ reader.
|
|
65
|
-
<td>
|
|
58
|
+
<td class="location-cell">{{ reader.location || "N/A" }}</td>
|
|
59
|
+
<td class="device-cell">{{ reader.deviceId || "N/A" }}</td>
|
|
60
|
+
<td class="status-cell">
|
|
66
61
|
<v-chip
|
|
67
62
|
size="small"
|
|
68
63
|
variant="flat"
|
|
@@ -72,12 +67,22 @@
|
|
|
72
67
|
{{ formatReaderStatus(resolveReaderStatus(reader)) }}
|
|
73
68
|
</v-chip>
|
|
74
69
|
</td>
|
|
75
|
-
<td>{{ formatDate(reader.lastSyncAt) }}</td>
|
|
76
|
-
<td
|
|
77
|
-
|
|
70
|
+
<td class="sync-at-cell">{{ formatDate(reader.lastSyncAt) }}</td>
|
|
71
|
+
<td class="message-cell">
|
|
72
|
+
<span class="message-text" :title="reader.lastSyncMessage || 'N/A'">
|
|
73
|
+
{{ reader.lastSyncMessage || "N/A" }}
|
|
74
|
+
</span>
|
|
75
|
+
</td>
|
|
76
|
+
<td class="action-cell">
|
|
78
77
|
<v-menu>
|
|
79
78
|
<template #activator="{ props: menuProps }">
|
|
80
|
-
<v-btn
|
|
79
|
+
<v-btn
|
|
80
|
+
v-bind="menuProps"
|
|
81
|
+
icon="mdi-dots-vertical"
|
|
82
|
+
variant="text"
|
|
83
|
+
density="comfortable"
|
|
84
|
+
class="action-menu-button"
|
|
85
|
+
/>
|
|
81
86
|
</template>
|
|
82
87
|
|
|
83
88
|
<v-list density="compact" min-width="180">
|
|
@@ -112,7 +117,6 @@
|
|
|
112
117
|
<v-card-text>
|
|
113
118
|
<div class="detail-grid">
|
|
114
119
|
<span>HID reader name</span><strong>{{ selectedReader?.name || "N/A" }}</strong>
|
|
115
|
-
<span>Base URL / IP address</span><strong>{{ selectedReader?.baseUrl || "N/A" }}</strong>
|
|
116
120
|
<span>Device ID</span><strong>{{ selectedReader?.deviceId || "N/A" }}</strong>
|
|
117
121
|
<span>Location</span><strong>{{ selectedReader?.location || "N/A" }}</strong>
|
|
118
122
|
<span>Monitor Path</span><strong>{{ selectedReader?.monitorPath || "N/A" }}</strong>
|
|
@@ -140,10 +144,6 @@
|
|
|
140
144
|
<v-icon icon="mdi-database-sync-outline" color="#1976d2" size="48" />
|
|
141
145
|
<h3>Test Device Connection</h3>
|
|
142
146
|
<p>You are about to test the connection for {{ selectedReader?.name || "this HID Reader" }}.</p>
|
|
143
|
-
<div class="reader-action-meta">
|
|
144
|
-
<span>IP Address:</span>
|
|
145
|
-
<strong>{{ readerIpAddress }}</strong>
|
|
146
|
-
</div>
|
|
147
147
|
</v-card-text>
|
|
148
148
|
|
|
149
149
|
<v-card-text v-else-if="pendingAction === 'sync' && actionStage === 'confirm'" class="reader-action-content">
|
|
@@ -312,7 +312,7 @@ const confirmMessage = computed(() => {
|
|
|
312
312
|
return `You're about to sync data with ${selectedReader.value?.name || "this HID Reader"}.`;
|
|
313
313
|
}
|
|
314
314
|
if (pendingAction.value === "test") {
|
|
315
|
-
return `You are about to test the connection
|
|
315
|
+
return `You are about to test the connection for ${selectedReader.value?.name || "this HID Reader"}.`;
|
|
316
316
|
}
|
|
317
317
|
return "Are you sure you want to permanently delete reader?";
|
|
318
318
|
});
|
|
@@ -325,8 +325,6 @@ const confirmActionLabel = computed(() => {
|
|
|
325
325
|
return "Confirm";
|
|
326
326
|
});
|
|
327
327
|
|
|
328
|
-
const readerIpAddress = computed(() => getReaderHost(selectedReader.value?.baseUrl));
|
|
329
|
-
|
|
330
328
|
watch(
|
|
331
329
|
() => props.site,
|
|
332
330
|
() => loadReaders(),
|
|
@@ -830,15 +828,6 @@ function countFacialData(users: Record<string, any>[]) {
|
|
|
830
828
|
return users.filter((user) => user.face || user.photo || user.image || user.imageUrl || user.faceImage).length;
|
|
831
829
|
}
|
|
832
830
|
|
|
833
|
-
function getReaderHost(value?: string) {
|
|
834
|
-
if (!value) return "N/A";
|
|
835
|
-
try {
|
|
836
|
-
return new URL(value).hostname || value;
|
|
837
|
-
} catch {
|
|
838
|
-
return value.replace(/^https?:\/\//, "").split("/")[0] || value;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
831
|
function getFriendlyHidError(error: any) {
|
|
843
832
|
const message = getHidErrorMessage(error);
|
|
844
833
|
const lowerMessage = message.toLowerCase();
|
|
@@ -863,7 +852,7 @@ function getFriendlyHidError(error: any) {
|
|
|
863
852
|
lowerMessage.includes("network error") ||
|
|
864
853
|
lowerMessage.includes("no route to host")
|
|
865
854
|
) {
|
|
866
|
-
return "Unable to connect to the HID device. Please check that the reader is online
|
|
855
|
+
return "Unable to connect to the HID device. Please check that the reader is online and reachable from the server.";
|
|
867
856
|
}
|
|
868
857
|
|
|
869
858
|
if (lowerMessage.includes("login failed") || lowerMessage.includes("session")) {
|
|
@@ -972,63 +961,119 @@ function formatReaderStatus(value?: string) {
|
|
|
972
961
|
overflow-y: hidden;
|
|
973
962
|
}
|
|
974
963
|
|
|
975
|
-
.table
|
|
964
|
+
.reader-table :deep(table) {
|
|
976
965
|
table-layout: fixed;
|
|
977
966
|
width: 100%;
|
|
978
|
-
min-width:
|
|
967
|
+
min-width: 920px;
|
|
968
|
+
border-collapse: separate;
|
|
969
|
+
border-spacing: 0;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
.reader-table :deep(th) {
|
|
973
|
+
height: 44px;
|
|
974
|
+
background: #f8fafc;
|
|
975
|
+
color: #344054;
|
|
976
|
+
font-size: 12px;
|
|
977
|
+
font-weight: 700;
|
|
978
|
+
letter-spacing: 0;
|
|
979
|
+
text-transform: none;
|
|
980
|
+
border-bottom: 1px solid #e7ebef;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
.reader-table :deep(td) {
|
|
984
|
+
height: 64px;
|
|
985
|
+
color: #1f2937;
|
|
986
|
+
font-size: 13px;
|
|
987
|
+
border-bottom: 1px solid #edf0f3;
|
|
988
|
+
vertical-align: middle;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
.reader-table :deep(tbody tr) {
|
|
992
|
+
transition: background-color 0.15s ease;
|
|
979
993
|
}
|
|
980
994
|
|
|
981
|
-
.table
|
|
982
|
-
|
|
995
|
+
.reader-table :deep(tbody tr:hover) {
|
|
996
|
+
background: #fbfcfe;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
.reader-table :deep(th),
|
|
1000
|
+
.reader-table :deep(td) {
|
|
1001
|
+
padding: 0 14px !important;
|
|
983
1002
|
white-space: nowrap;
|
|
984
1003
|
}
|
|
985
1004
|
|
|
986
|
-
.
|
|
987
|
-
.
|
|
1005
|
+
.name-column,
|
|
1006
|
+
.name-cell {
|
|
1007
|
+
width: 19%;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
.location-column,
|
|
1011
|
+
.location-cell {
|
|
988
1012
|
width: 14%;
|
|
989
1013
|
}
|
|
990
1014
|
|
|
991
|
-
.
|
|
992
|
-
.
|
|
1015
|
+
.device-column,
|
|
1016
|
+
.device-cell {
|
|
1017
|
+
width: 15%;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
.status-column,
|
|
1021
|
+
.status-cell {
|
|
993
1022
|
width: 12%;
|
|
994
1023
|
}
|
|
995
1024
|
|
|
996
|
-
.
|
|
997
|
-
.
|
|
998
|
-
width:
|
|
1025
|
+
.sync-at-column,
|
|
1026
|
+
.sync-at-cell {
|
|
1027
|
+
width: 15%;
|
|
999
1028
|
}
|
|
1000
1029
|
|
|
1001
|
-
.
|
|
1002
|
-
.
|
|
1003
|
-
width:
|
|
1030
|
+
.message-column,
|
|
1031
|
+
.message-cell {
|
|
1032
|
+
width: 20%;
|
|
1033
|
+
min-width: 0;
|
|
1004
1034
|
}
|
|
1005
1035
|
|
|
1006
|
-
.
|
|
1007
|
-
.
|
|
1008
|
-
width:
|
|
1036
|
+
.action-column,
|
|
1037
|
+
.action-cell {
|
|
1038
|
+
width: 5%;
|
|
1039
|
+
text-align: right;
|
|
1009
1040
|
}
|
|
1010
1041
|
|
|
1011
|
-
.
|
|
1012
|
-
|
|
1013
|
-
|
|
1042
|
+
.cell-strong {
|
|
1043
|
+
display: block;
|
|
1044
|
+
overflow: hidden;
|
|
1045
|
+
color: #111827;
|
|
1046
|
+
font-weight: 600;
|
|
1047
|
+
text-overflow: ellipsis;
|
|
1014
1048
|
}
|
|
1015
1049
|
|
|
1016
|
-
.
|
|
1017
|
-
.
|
|
1018
|
-
|
|
1050
|
+
.location-cell,
|
|
1051
|
+
.device-cell,
|
|
1052
|
+
.sync-at-cell {
|
|
1053
|
+
overflow: hidden;
|
|
1054
|
+
color: #475467;
|
|
1055
|
+
text-overflow: ellipsis;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
.message-text {
|
|
1059
|
+
display: block;
|
|
1060
|
+
max-width: 100%;
|
|
1019
1061
|
overflow: hidden;
|
|
1062
|
+
color: #475467;
|
|
1063
|
+
line-height: 1.35;
|
|
1020
1064
|
text-overflow: ellipsis;
|
|
1021
1065
|
}
|
|
1022
1066
|
|
|
1023
|
-
.
|
|
1024
|
-
|
|
1025
|
-
width: 6%;
|
|
1067
|
+
.action-menu-button {
|
|
1068
|
+
color: #344054;
|
|
1026
1069
|
}
|
|
1027
1070
|
|
|
1028
1071
|
.table-refresh {
|
|
1029
1072
|
display: flex;
|
|
1030
|
-
|
|
1031
|
-
|
|
1073
|
+
justify-content: flex-start;
|
|
1074
|
+
padding: 10px 14px;
|
|
1075
|
+
border-bottom: 1px solid #e7ebef;
|
|
1076
|
+
background: #ffffff;
|
|
1032
1077
|
}
|
|
1033
1078
|
|
|
1034
1079
|
.empty-state {
|
|
@@ -1116,18 +1161,6 @@ function formatReaderStatus(value?: string) {
|
|
|
1116
1161
|
line-height: 1.45;
|
|
1117
1162
|
}
|
|
1118
1163
|
|
|
1119
|
-
.reader-action-meta {
|
|
1120
|
-
display: inline-flex;
|
|
1121
|
-
gap: 8px;
|
|
1122
|
-
align-items: center;
|
|
1123
|
-
color: #344054;
|
|
1124
|
-
font-size: 13px;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
.reader-action-meta strong {
|
|
1128
|
-
font-weight: 700;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
1164
|
.reader-sync-review {
|
|
1132
1165
|
justify-items: stretch;
|
|
1133
1166
|
text-align: left;
|