@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.
@@ -17,16 +17,18 @@
17
17
  class="mb-3"
18
18
  />
19
19
 
20
- <div class="field-label">Base URL / IP address <span>*</span></div>
21
- <v-text-field
22
- v-model="draft.baseUrl"
23
- aria-label="Base URL / IP address"
24
- placeholder="http://192.168.0.129"
25
- variant="outlined"
26
- density="compact"
27
- hide-details="auto"
28
- class="mb-3"
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
- emit("submit", {
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>Base URL</th>
47
- <th>Device ID</th>
48
- <th>Status</th>
49
- <th>Last sync at</th>
50
- <th>Last sync message</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>{{ reader.name || "N/A" }}</td>
57
- <td>{{ reader.location || "N/A" }}</td>
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.deviceId || "N/A" }}</td>
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>{{ reader.lastSyncMessage || "N/A" }}</td>
77
- <td class="text-right">
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 v-bind="menuProps" icon="mdi-dots-vertical" variant="text" density="comfortable" />
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 to ${selectedReader.value?.baseUrl || "this HID Reader"}.`;
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, reachable from the server, and the Base URL/IP address is correct.";
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-card :deep(table) {
964
+ .reader-table :deep(table) {
976
965
  table-layout: fixed;
977
966
  width: 100%;
978
- min-width: 1120px;
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-card :deep(th),
982
- .table-card :deep(td) {
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
- .table-card :deep(th:nth-child(1)),
987
- .table-card :deep(td:nth-child(1)) {
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
- .table-card :deep(th:nth-child(2)),
992
- .table-card :deep(td:nth-child(2)) {
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
- .table-card :deep(th:nth-child(3)),
997
- .table-card :deep(td:nth-child(3)) {
998
- width: 18%;
1025
+ .sync-at-column,
1026
+ .sync-at-cell {
1027
+ width: 15%;
999
1028
  }
1000
1029
 
1001
- .table-card :deep(th:nth-child(4)),
1002
- .table-card :deep(td:nth-child(4)) {
1003
- width: 12%;
1030
+ .message-column,
1031
+ .message-cell {
1032
+ width: 20%;
1033
+ min-width: 0;
1004
1034
  }
1005
1035
 
1006
- .table-card :deep(th:nth-child(5)),
1007
- .table-card :deep(td:nth-child(5)) {
1008
- width: 10%;
1036
+ .action-column,
1037
+ .action-cell {
1038
+ width: 5%;
1039
+ text-align: right;
1009
1040
  }
1010
1041
 
1011
- .table-card :deep(th:nth-child(6)),
1012
- .table-card :deep(td:nth-child(6)) {
1013
- width: 14%;
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
- .table-card :deep(th:nth-child(7)),
1017
- .table-card :deep(td:nth-child(7)) {
1018
- width: 14%;
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
- .table-card :deep(th:nth-child(8)),
1024
- .table-card :deep(td:nth-child(8)) {
1025
- width: 6%;
1067
+ .action-menu-button {
1068
+ color: #344054;
1026
1069
  }
1027
1070
 
1028
1071
  .table-refresh {
1029
1072
  display: flex;
1030
- padding: 8px 10px;
1031
- border-bottom: 1px solid #eeeeee;
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;