w-web-perm 1.0.48 → 1.0.49
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/.github/workflows/ci-test.yml +2 -2
- package/_gh-pages-deploy/.nojekyll +0 -0
- package/_gh-pages-deploy/WWebPerm.html +938 -0
- package/_gh-pages-deploy/WWebPerm.mjs.html +1228 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/_gh-pages-deploy/index.html +84 -0
- package/_gh-pages-deploy/scripts/collapse.js +39 -0
- package/_gh-pages-deploy/scripts/commonNav.js +28 -0
- package/_gh-pages-deploy/scripts/linenumber.js +25 -0
- package/_gh-pages-deploy/scripts/nav.js +12 -0
- package/_gh-pages-deploy/scripts/polyfill.js +4 -0
- package/_gh-pages-deploy/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/_gh-pages-deploy/scripts/prettify/lang-css.js +2 -0
- package/_gh-pages-deploy/scripts/prettify/prettify.js +28 -0
- package/_gh-pages-deploy/scripts/search.js +99 -0
- package/_gh-pages-deploy/styles/jsdoc.css +776 -0
- package/_gh-pages-deploy/styles/prettify.css +80 -0
- package/dist/css/app.d8b16216.css +1 -0
- package/dist/css/{chunk-vendors.f149e124.css → chunk-vendors.2d3ef2ba.css} +1 -1
- package/dist/index.tmp +2 -2
- package/dist/js/app-legacy.fb48310a.js +1 -0
- package/dist/js/app.fb48310a.js +1 -0
- package/dist/js/chunk-vendors-legacy.8fdd4435.js +21 -0
- package/dist/js/chunk-vendors.8fdd4435.js +21 -0
- package/dist/w-web-perm.umd.js +2 -2
- package/dist/w-web-perm.umd.js.map +1 -1
- package/docs/WWebPerm.html +2 -2
- package/docs/WWebPerm.mjs.html +138 -199
- package/docs/index.html +1 -1
- package/package.json +15 -11
- package/public/index.html +2 -2
- package/server/WWebPerm.mjs +137 -198
- package/server/procCore.mjs +169 -0
- package/server/procLang.mjs +197 -0
- package/server/procStaInfor.mjs +118 -0
- package/server/provideTabs.mjs +1 -1
- package/server/srLog.mjs +30 -0
- package/server/staLogs/staEvent.callWorker.mjs +46 -0
- package/server/staLogs/staEvent.mjs +116 -0
- package/server/staLogs/staEvent.shellWorker.mjs +19 -0
- package/server/staLogs/staEventTable.mjs +100 -0
- package/server/staLogs/test_staEvent.mjs +22 -0
- package/settings.json +40 -0
- package/src/components/Common/CheckYes.vue +18 -1
- package/src/components/Layout.vue +1 -1
- package/src/components/LayoutContent.vue +13 -1
- package/src/components/LayoutContentGrups.vue +32 -23
- package/src/components/LayoutContentPemis.vue +32 -23
- package/src/components/LayoutContentStaInfor.vue +536 -0
- package/src/components/LayoutContentTargets.vue +32 -23
- package/src/components/LayoutContentUsers.vue +32 -23
- package/src/components/LayoutState.vue +5 -0
- package/src/components/VeCgrups.vue +12 -3
- package/src/components/VeCpemis.vue +12 -3
- package/src/components/VeCrules.vue +12 -3
- package/src/components/VeGrupBlngUsers.vue +18 -6
- package/src/components/VePemiBlngGrups.vue +18 -6
- package/src/getPerm.mjs +7 -17
- package/src/getPermUserInfor.mjs +8 -18
- package/src/main.js +1 -0
- package/src/plugins/mShare.mjs +8 -13
- package/src/plugins/mUI.mjs +11 -0
- package/srv.mjs +76 -101
- package/tableTags.json +1 -1
- package/db/wperm/grups/data.mdb +0 -0
- package/db/wperm/grups/lock.mdb +0 -0
- package/db/wperm/pemis/data.mdb +0 -0
- package/db/wperm/pemis/lock.mdb +0 -0
- package/db/wperm/targets/data.mdb +0 -0
- package/db/wperm/targets/lock.mdb +0 -0
- package/db/wperm/users/data.mdb +0 -0
- package/db/wperm/users/lock.mdb +0 -0
- package/dist/css/app.30e81bfe.css +0 -1
- package/dist/js/app-legacy.3324392e.js +0 -1
- package/dist/js/app.3324392e.js +0 -1
- package/dist/js/chunk-vendors-legacy.e8703728.js +0 -21
- package/dist/js/chunk-vendors.e8703728.js +0 -21
- package/test/getUserRules.test.mjs +0 -899
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import get from 'lodash-es/get.js'
|
|
2
|
+
import each from 'lodash-es/each.js'
|
|
3
|
+
import map from 'lodash-es/map.js'
|
|
4
|
+
import size from 'lodash-es/size.js'
|
|
5
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
6
|
+
import haskey from 'wsemi/src/haskey.mjs'
|
|
7
|
+
import arrHas from 'wsemi/src/arrHas.mjs'
|
|
8
|
+
import ltdtDiffByKey from 'wsemi/src/ltdtDiffByKey.mjs'
|
|
9
|
+
import ltdtmapping from 'wsemi/src/ltdtmapping.mjs'
|
|
10
|
+
import ds from '../src/schema/index.mjs'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
function proc(woItems, procOrm, { srLog, kmx }) {
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
//updateTabItems
|
|
17
|
+
let updateTabItems = async (keyTable, userId, rows, keyDetect) => {
|
|
18
|
+
// console.log('updateTabItems', keyTable, userId, rows.length, keyDetect)
|
|
19
|
+
|
|
20
|
+
//序列化同表之並行整表批次寫入: 防 lost update (同 keyTable 序列化, 不同表並行)
|
|
21
|
+
return await kmx('updateTabItems:' + keyTable, async () => {
|
|
22
|
+
|
|
23
|
+
//ltdtmapping
|
|
24
|
+
rows = ltdtmapping(rows, ds[keyTable].keys)
|
|
25
|
+
// console.log('ltdtmapping rows', rows)
|
|
26
|
+
|
|
27
|
+
//重給order
|
|
28
|
+
rows = map(rows, (r, k) => {
|
|
29
|
+
r.order = k + 1
|
|
30
|
+
return r
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
//ckKey, 回傳 err key (saveRowFieldInvalid / saveRowFieldDuplicate) 或 null; 動態細節 (row index/field/value) 改記 srLog, 不進使用者訊息
|
|
34
|
+
let ckKey = (rows, key) => {
|
|
35
|
+
let err = null
|
|
36
|
+
|
|
37
|
+
//check
|
|
38
|
+
let kp = {}
|
|
39
|
+
each(rows, (v, k) => {
|
|
40
|
+
|
|
41
|
+
//value
|
|
42
|
+
let value = get(v, key, '')
|
|
43
|
+
|
|
44
|
+
//check
|
|
45
|
+
if (!isestr(value)) {
|
|
46
|
+
srLog.error({ event: 'updateTabItems-ckKey-error', keyTable, key, msg: 'saveRowFieldInvalid', row: k })
|
|
47
|
+
err = `saveRowFieldInvalid`
|
|
48
|
+
return false //跳出
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//check
|
|
52
|
+
if (haskey(kp, value)) {
|
|
53
|
+
srLog.error({ event: 'updateTabItems-ckKey-error', keyTable, key, msg: 'saveRowFieldDuplicate', row: k, value })
|
|
54
|
+
err = `saveRowFieldDuplicate`
|
|
55
|
+
return false //跳出
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//kp
|
|
59
|
+
kp[value] = true
|
|
60
|
+
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return err
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//偵測未給予或重複
|
|
67
|
+
let err = null
|
|
68
|
+
if (true) {
|
|
69
|
+
if (arrHas(keyTable, ['targets'])) {
|
|
70
|
+
err = ckKey(rows, 'id')
|
|
71
|
+
if (err !== null) {
|
|
72
|
+
return Promise.reject(err)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (arrHas(keyTable, ['pemis', 'grups'])) { //users可重複name故不列入
|
|
76
|
+
err = ckKey(rows, 'name')
|
|
77
|
+
if (err !== null) {
|
|
78
|
+
return Promise.reject(err)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (arrHas(keyTable, ['users'])) {
|
|
82
|
+
err = ckKey(rows, 'email')
|
|
83
|
+
if (err !== null) {
|
|
84
|
+
return Promise.reject(err)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
//ltdtDiffByKey
|
|
90
|
+
let ltdtOld = await woItems[keyTable].select()
|
|
91
|
+
let ltdtNew = rows
|
|
92
|
+
let r = ltdtDiffByKey(ltdtOld, ltdtNew, keyDetect)
|
|
93
|
+
// console.log('ltdtDiffByKey r', r)
|
|
94
|
+
|
|
95
|
+
//del
|
|
96
|
+
if (size(r.del) > 0) {
|
|
97
|
+
await procOrm(userId, keyTable, 'del', r.del) //須使用procOrm才有辦法自動給予相關欄位
|
|
98
|
+
// .catch((err) => {
|
|
99
|
+
// console.log('woItems[keyTable].del err', err)
|
|
100
|
+
// })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//add
|
|
104
|
+
if (size(r.add) > 0) {
|
|
105
|
+
await procOrm(userId, keyTable, 'insert', r.add) //須使用procOrm才有辦法自動給予相關欄位
|
|
106
|
+
// .catch((err) => {
|
|
107
|
+
// console.log('woItems[keyTable].insert err', err)
|
|
108
|
+
// })
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//diff
|
|
112
|
+
if (size(r.diff) > 0) {
|
|
113
|
+
await procOrm(userId, keyTable, 'save', r.diff) //須使用procOrm才有辦法自動給予相關欄位
|
|
114
|
+
// .catch((err) => {
|
|
115
|
+
// console.log('woItems[keyTable].save err', err)
|
|
116
|
+
// })
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return ltdtNew
|
|
120
|
+
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
//updateTargets
|
|
126
|
+
let updateTargets = async (userId, rows) => {
|
|
127
|
+
await updateTabItems('targets', userId, rows, 'id')
|
|
128
|
+
srLog.info({ event: 'updateTargets-success', userId })
|
|
129
|
+
return 'saveTabItemsSuccess' //成功 msg key
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
//updatePemis
|
|
134
|
+
let updatePemis = async (userId, rows) => {
|
|
135
|
+
await updateTabItems('pemis', userId, rows, 'name')
|
|
136
|
+
srLog.info({ event: 'updatePemis-success', userId })
|
|
137
|
+
return 'saveTabItemsSuccess' //成功 msg key
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
//updateGrups
|
|
142
|
+
let updateGrups = async (userId, rows) => {
|
|
143
|
+
await updateTabItems('grups', userId, rows, 'name')
|
|
144
|
+
srLog.info({ event: 'updateGrups-success', userId })
|
|
145
|
+
return 'saveTabItemsSuccess' //成功 msg key
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
//updateUsers
|
|
150
|
+
let updateUsers = async (userId, rows) => {
|
|
151
|
+
await updateTabItems('users', userId, rows, 'id')
|
|
152
|
+
srLog.info({ event: 'updateUsers-success', userId })
|
|
153
|
+
return 'saveTabItemsSuccess' //成功 msg key
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
let p = {
|
|
158
|
+
updateTargets,
|
|
159
|
+
updatePemis,
|
|
160
|
+
updateGrups,
|
|
161
|
+
updateUsers,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
return p
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
export default proc
|
package/server/procLang.mjs
CHANGED
|
@@ -130,6 +130,97 @@ let kpLang = {
|
|
|
130
130
|
eng: `An unexpected error occurred, please contact the administrator`,
|
|
131
131
|
cht: `發生非預期錯誤,請洽管理員`,
|
|
132
132
|
},
|
|
133
|
+
|
|
134
|
+
//後端 reject 之 err key (eng + cht), 前端 .catch 依 lang 用 $tErr 反查顯示
|
|
135
|
+
cannotConnectServer: {
|
|
136
|
+
eng: `Cannot connect to server`,
|
|
137
|
+
cht: `無法連線至伺服器`,
|
|
138
|
+
},
|
|
139
|
+
saveRowFieldInvalid: {
|
|
140
|
+
eng: `Invalid field value in saved data`,
|
|
141
|
+
cht: `儲存資料的欄位值無效`,
|
|
142
|
+
},
|
|
143
|
+
saveRowFieldDuplicate: {
|
|
144
|
+
eng: `Duplicate field value in saved data`,
|
|
145
|
+
cht: `儲存資料的欄位值重複`,
|
|
146
|
+
},
|
|
147
|
+
saveTabItemsSuccess: {
|
|
148
|
+
eng: `Saved successfully`,
|
|
149
|
+
cht: `儲存成功`,
|
|
150
|
+
},
|
|
151
|
+
cannotGetUserId: {
|
|
152
|
+
eng: `Cannot get the user id`,
|
|
153
|
+
cht: `無法取得使用者 Id`,
|
|
154
|
+
},
|
|
155
|
+
cannotGetUserEmail: {
|
|
156
|
+
eng: `Cannot get the email of user`,
|
|
157
|
+
cht: `無法取得使用者 Email`,
|
|
158
|
+
},
|
|
159
|
+
cannotGetUserName: {
|
|
160
|
+
eng: `Cannot get the name of user`,
|
|
161
|
+
cht: `無法取得使用者名稱`,
|
|
162
|
+
},
|
|
163
|
+
cannotGetUserRole: {
|
|
164
|
+
eng: `Cannot get the role of user`,
|
|
165
|
+
cht: `無法取得使用者角色`,
|
|
166
|
+
},
|
|
167
|
+
cannotFindUserFromToken: {
|
|
168
|
+
eng: `Cannot identify the user from token`,
|
|
169
|
+
cht: `無法由 token 識別使用者`,
|
|
170
|
+
},
|
|
171
|
+
cannotGetUserProp: {
|
|
172
|
+
eng: `Cannot get the property of user`,
|
|
173
|
+
cht: `無法取得使用者對應屬性`,
|
|
174
|
+
},
|
|
175
|
+
cannotGetUserFromPerm: {
|
|
176
|
+
eng: `Cannot find the user in permission system`,
|
|
177
|
+
cht: `無法於權限系統取得使用者`,
|
|
178
|
+
},
|
|
179
|
+
userNoPermission: {
|
|
180
|
+
eng: `User does not have permission`,
|
|
181
|
+
cht: `使用者權限不足`,
|
|
182
|
+
},
|
|
183
|
+
invalidPayload: {
|
|
184
|
+
eng: `Invalid payload in request`,
|
|
185
|
+
cht: `請求內容無效`,
|
|
186
|
+
},
|
|
187
|
+
invalidFromInPayload: {
|
|
188
|
+
eng: `Invalid from in payload`,
|
|
189
|
+
cht: `請求來源欄位無效`,
|
|
190
|
+
},
|
|
191
|
+
invalidRowsInPayload: {
|
|
192
|
+
eng: `Invalid rows in payload`,
|
|
193
|
+
cht: `請求資料列無效`,
|
|
194
|
+
},
|
|
195
|
+
invalidFrom: {
|
|
196
|
+
eng: `Invalid from`,
|
|
197
|
+
cht: `來源欄位無效`,
|
|
198
|
+
},
|
|
199
|
+
invalidRows: {
|
|
200
|
+
eng: `Invalid rows`,
|
|
201
|
+
cht: `資料列無效`,
|
|
202
|
+
},
|
|
203
|
+
cannotGetUserRules: {
|
|
204
|
+
eng: `Cannot get rules of user`,
|
|
205
|
+
cht: `無法取得使用者權限規則`,
|
|
206
|
+
},
|
|
207
|
+
tokenNoPermission: {
|
|
208
|
+
eng: `Token does not have permission`,
|
|
209
|
+
cht: `權限不足`,
|
|
210
|
+
},
|
|
211
|
+
userIdNoPermrules: {
|
|
212
|
+
eng: `The user id does not have permission rules`,
|
|
213
|
+
cht: `此使用者沒有權限規則`,
|
|
214
|
+
},
|
|
215
|
+
invalidKeyTable: {
|
|
216
|
+
eng: `Invalid key table`,
|
|
217
|
+
cht: `資料表名稱無效`,
|
|
218
|
+
},
|
|
219
|
+
cannotFindUserId: {
|
|
220
|
+
eng: `Cannot find the user id`,
|
|
221
|
+
cht: `無法找到使用者 Id`,
|
|
222
|
+
},
|
|
223
|
+
|
|
133
224
|
unknow: {
|
|
134
225
|
eng: `Unknow`,
|
|
135
226
|
cht: `未知`,
|
|
@@ -751,6 +842,112 @@ let kpLang = {
|
|
|
751
842
|
cht: '更新時間',
|
|
752
843
|
},
|
|
753
844
|
|
|
845
|
+
invalidUrl: {
|
|
846
|
+
eng: `Invalid url`,
|
|
847
|
+
cht: `網址無效`,
|
|
848
|
+
},
|
|
849
|
+
invalidTokenTar: {
|
|
850
|
+
eng: `Invalid target token`,
|
|
851
|
+
cht: `目標 token 無效`,
|
|
852
|
+
},
|
|
853
|
+
invalidTokenSelf: {
|
|
854
|
+
eng: `Invalid self token`,
|
|
855
|
+
cht: `自身 token 無效`,
|
|
856
|
+
},
|
|
857
|
+
invalidUserIdTar: {
|
|
858
|
+
eng: `Invalid target user id`,
|
|
859
|
+
cht: `目標使用者 Id 無效`,
|
|
860
|
+
},
|
|
861
|
+
noTokenInUrl: {
|
|
862
|
+
eng: `Missing token in url`,
|
|
863
|
+
cht: `網址缺少 token`,
|
|
864
|
+
},
|
|
865
|
+
noTokenUserIdInUrl: {
|
|
866
|
+
eng: `Missing token or userId in url`,
|
|
867
|
+
cht: `網址缺少 token 或 userId`,
|
|
868
|
+
},
|
|
869
|
+
cannotGetUserByUrl: {
|
|
870
|
+
eng: `Cannot get user by url`,
|
|
871
|
+
cht: `無法由網址取得使用者`,
|
|
872
|
+
},
|
|
873
|
+
cannotGetUserDataByUrl: {
|
|
874
|
+
eng: `Cannot get user data by url`,
|
|
875
|
+
cht: `無法由網址取得使用者資料`,
|
|
876
|
+
},
|
|
877
|
+
noUserDataByUrl: {
|
|
878
|
+
eng: `No user data by url`,
|
|
879
|
+
cht: `該網址無使用者資料`,
|
|
880
|
+
},
|
|
881
|
+
noUserDataAfterConvert: {
|
|
882
|
+
eng: `No user data after permission conversion`,
|
|
883
|
+
cht: `權限轉換後無使用者資料`,
|
|
884
|
+
},
|
|
885
|
+
|
|
886
|
+
mmStaInfor: {
|
|
887
|
+
eng: `Statistics`,
|
|
888
|
+
cht: `統計資訊`,
|
|
889
|
+
},
|
|
890
|
+
staEventTitle: {
|
|
891
|
+
eng: `Event Frequency`,
|
|
892
|
+
cht: `事件發生頻率`,
|
|
893
|
+
},
|
|
894
|
+
staTimeGroup: {
|
|
895
|
+
eng: `Time grouping`,
|
|
896
|
+
cht: `時間分組`,
|
|
897
|
+
},
|
|
898
|
+
staTimeHr: {
|
|
899
|
+
eng: `Hourly`,
|
|
900
|
+
cht: `每小時`,
|
|
901
|
+
},
|
|
902
|
+
staTimeDay: {
|
|
903
|
+
eng: `Daily`,
|
|
904
|
+
cht: `每日`,
|
|
905
|
+
},
|
|
906
|
+
staSelectEvents: {
|
|
907
|
+
eng: `Events`,
|
|
908
|
+
cht: `事件`,
|
|
909
|
+
},
|
|
910
|
+
staSelectAll: {
|
|
911
|
+
eng: `Select all`,
|
|
912
|
+
cht: `全選`,
|
|
913
|
+
},
|
|
914
|
+
staClear: {
|
|
915
|
+
eng: `Clear`,
|
|
916
|
+
cht: `清除`,
|
|
917
|
+
},
|
|
918
|
+
staTotal: {
|
|
919
|
+
eng: `Total`,
|
|
920
|
+
cht: `全部加總`,
|
|
921
|
+
},
|
|
922
|
+
staNoData: {
|
|
923
|
+
eng: `No event data`,
|
|
924
|
+
cht: `尚無事件資料`,
|
|
925
|
+
},
|
|
926
|
+
staTableTitle: {
|
|
927
|
+
eng: `Event Statistics`,
|
|
928
|
+
cht: `事件統計表`,
|
|
929
|
+
},
|
|
930
|
+
staColEvent: {
|
|
931
|
+
eng: `Event`,
|
|
932
|
+
cht: `事件`,
|
|
933
|
+
},
|
|
934
|
+
staColLast1Day: {
|
|
935
|
+
eng: `Last 1 Day`,
|
|
936
|
+
cht: `最近1日`,
|
|
937
|
+
},
|
|
938
|
+
staColLast8Hour: {
|
|
939
|
+
eng: `Last 8 Hours`,
|
|
940
|
+
cht: `最近8小時`,
|
|
941
|
+
},
|
|
942
|
+
staColLast4Hour: {
|
|
943
|
+
eng: `Last 4 Hours`,
|
|
944
|
+
cht: `最近4小時`,
|
|
945
|
+
},
|
|
946
|
+
staColLast1Hour: {
|
|
947
|
+
eng: `Last 1 Hour`,
|
|
948
|
+
cht: `最近1小時`,
|
|
949
|
+
},
|
|
950
|
+
|
|
754
951
|
}
|
|
755
952
|
|
|
756
953
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import ot from 'dayjs'
|
|
2
|
+
import get from 'lodash-es/get.js'
|
|
3
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
4
|
+
import cache from 'wsemi/src/cache.mjs'
|
|
5
|
+
import staEvent from './staLogs/staEvent.callWorker.mjs'
|
|
6
|
+
import staEventTable from './staLogs/staEventTable.mjs'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
//mock 確定性資料集(供 e2e 統計圖穩定用):固定起點時間 + 固定 sin 計數,不依 now / log → 每次完全相同。
|
|
10
|
+
//觸發:opt.mock=true(由 settings.json staEventMock 經 srv.mjs → WWebPerm 傳入)。非 mock 時走真實 staEvent。
|
|
11
|
+
function genMockStaEvent(timeInterval = 'hr') {
|
|
12
|
+
let fmt = timeInterval === 'day' ? 'YYYY-MM-DD' : 'YYYY-MM-DDTHH'
|
|
13
|
+
let unit = timeInterval === 'hr' ? 'hour' : 'day'
|
|
14
|
+
let nBuckets = timeInterval === 'hr' ? 48 : 7
|
|
15
|
+
let events = ['verifyConn', 'updateTargets-success', 'checkUser-error', 'api/getPerm-success', 'getWebInfor-success']
|
|
16
|
+
let base = ot('2025-01-01T00:00:00') //固定起點,不依 now
|
|
17
|
+
let rs = []
|
|
18
|
+
for (let i = 0; i < nBuckets; i++) {
|
|
19
|
+
let t = base.add(i, unit)
|
|
20
|
+
let data = { count: 0 }
|
|
21
|
+
events.forEach((ev, k) => {
|
|
22
|
+
let c = Math.round(20 + 15 * Math.sin((i + k * 3) / 4)) //固定確定性計數
|
|
23
|
+
data[ev] = c
|
|
24
|
+
data.count += c
|
|
25
|
+
})
|
|
26
|
+
rs.push({ time: t.format(fmt), data })
|
|
27
|
+
}
|
|
28
|
+
return rs
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
//mock 確定性資料集(供 e2e 統計表穩定用):固定窗計數,不依 now / log → 每次完全相同。
|
|
33
|
+
//5 個 event 與 genMockStaEvent 同名單;各事件滿足 last1Day>last8Hour>last4Hour>last1Hour,且各事件 last1Day 互不相同(排序明確、上多下少)。
|
|
34
|
+
function genMockStaEventTable() {
|
|
35
|
+
let rs = [
|
|
36
|
+
{ event: 'verifyConn', last1Day: 240, last8Hour: 90, last4Hour: 50, last1Hour: 15 },
|
|
37
|
+
{ event: 'updateTargets-success', last1Day: 180, last8Hour: 70, last4Hour: 38, last1Hour: 11 },
|
|
38
|
+
{ event: 'checkUser-error', last1Day: 120, last8Hour: 45, last4Hour: 24, last1Hour: 7 },
|
|
39
|
+
{ event: 'api/getPerm-success', last1Day: 90, last8Hour: 33, last4Hour: 18, last1Hour: 5 },
|
|
40
|
+
{ event: 'getWebInfor-success', last1Day: 60, last8Hour: 22, last4Hour: 12, last1Hour: 3 },
|
|
41
|
+
]
|
|
42
|
+
return rs
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
function proc(opt = {}) {
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
//fdLog
|
|
50
|
+
let fdLog = get(opt, 'fdLog', '')
|
|
51
|
+
if (!isestr(fdLog)) {
|
|
52
|
+
fdLog = './_logs'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
//mock(e2e 統計圖穩定用)
|
|
57
|
+
let mock = get(opt, 'mock', false)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
//getStaEvent
|
|
61
|
+
let _getStaEvent = async (timeLength = 7, timeInterval = 'hr') => {
|
|
62
|
+
|
|
63
|
+
//mock 模式回固定確定性資料集
|
|
64
|
+
if (mock) {
|
|
65
|
+
return genMockStaEvent(timeInterval)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//staEvent
|
|
69
|
+
let rs = await staEvent(timeLength, timeInterval, { fdLog })
|
|
70
|
+
|
|
71
|
+
return rs
|
|
72
|
+
}
|
|
73
|
+
let ocGetStaEvent = cache()
|
|
74
|
+
let getStaEvent = async (userId, timeLength = 7, timeInterval = 'hr') => {
|
|
75
|
+
|
|
76
|
+
//cacheKey: 含 timeLength + timeInterval 避免不同分組互蓋快取
|
|
77
|
+
let cacheKey = `${timeLength}:${timeInterval}`
|
|
78
|
+
|
|
79
|
+
let r = await ocGetStaEvent.getProxy(cacheKey, { fun: _getStaEvent, inputs: [timeLength, timeInterval], timeExpired: 30 * 1000 }) //快取30秒
|
|
80
|
+
return r
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
//getStaEventTable
|
|
85
|
+
let _getStaEventTable = async () => {
|
|
86
|
+
|
|
87
|
+
//mock 模式回固定確定性資料集
|
|
88
|
+
if (mock) {
|
|
89
|
+
return genMockStaEventTable()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
//staEventTable
|
|
93
|
+
let rs = await staEventTable({ fdLog })
|
|
94
|
+
|
|
95
|
+
return rs
|
|
96
|
+
}
|
|
97
|
+
let ocGetStaEventTable = cache()
|
|
98
|
+
let getStaEventTable = async (userId) => {
|
|
99
|
+
|
|
100
|
+
let r = await ocGetStaEventTable.getProxy('staEventTable', { fun: _getStaEventTable, inputs: [], timeExpired: 30 * 1000 }) //快取30秒
|
|
101
|
+
return r
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
//pl
|
|
106
|
+
let pl = {
|
|
107
|
+
|
|
108
|
+
getStaEvent,
|
|
109
|
+
getStaEventTable,
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
return pl
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
export default proc
|
package/server/provideTabs.mjs
CHANGED
|
@@ -74,7 +74,7 @@ async function provideTabs(url, keyTable, from, rows) {
|
|
|
74
74
|
|
|
75
75
|
//ks
|
|
76
76
|
if (!haskey(kpKs, `ks_${keyTable}`)) {
|
|
77
|
-
return Promise.reject(
|
|
77
|
+
return Promise.reject('invalidKeyTable')
|
|
78
78
|
}
|
|
79
79
|
let ks = kpKs[`ks_${keyTable}`]
|
|
80
80
|
// console.log('ks', ks)
|
package/server/srLog.mjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import get from 'lodash-es/get.js'
|
|
2
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
3
|
+
import WSyslog from 'w-syslog/src/WSyslog.mjs'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
let init = (opt = {}) => {
|
|
7
|
+
|
|
8
|
+
let fdLog = get(opt, 'logFd', '')
|
|
9
|
+
if (!isestr(fdLog)) {
|
|
10
|
+
fdLog = './_logs'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let interval = get(opt, 'logInterval', '')
|
|
14
|
+
if (!isestr(interval)) {
|
|
15
|
+
interval = 'hr'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let srLog = WSyslog({
|
|
19
|
+
fdLog,
|
|
20
|
+
interval,
|
|
21
|
+
})
|
|
22
|
+
// srLog.info({ event: 'runner', msg: 'start' })
|
|
23
|
+
// srLog.warn({ event: 'monitor-memory', msg: 'usage-high', ratio: 85.4 })
|
|
24
|
+
// srLog.error({ event: 'crash', msg: 'db connection', code: 500 })
|
|
25
|
+
|
|
26
|
+
return srLog
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export default init
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { Worker } from 'worker_threads'
|
|
4
|
+
import genPm from 'wsemi/src/genPm.mjs'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
let __filename = fileURLToPath(import.meta.url)
|
|
8
|
+
let __dirname = path.dirname(__filename)
|
|
9
|
+
|
|
10
|
+
//worker 版 staEvent:於獨立 worker thread 計算(避免讀大量 log 阻塞主執行緒)。介面同 staEvent.mjs。
|
|
11
|
+
async function staEvent(timeLength = 7, timeInterval = 'hr', opt = {}) {
|
|
12
|
+
let pm = genPm()
|
|
13
|
+
|
|
14
|
+
//fpWk
|
|
15
|
+
let fpWk = path.resolve(__dirname, 'staEvent.shellWorker.mjs')
|
|
16
|
+
|
|
17
|
+
//wk
|
|
18
|
+
let wk = new Worker(fpWk)
|
|
19
|
+
|
|
20
|
+
wk.on('message', (msg) => {
|
|
21
|
+
|
|
22
|
+
if (msg.mode === 'done') {
|
|
23
|
+
pm.resolve(msg.payload)
|
|
24
|
+
}
|
|
25
|
+
else if (msg.mode === 'error') {
|
|
26
|
+
pm.reject(msg.payload)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
wk.terminate()
|
|
30
|
+
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
wk.on('error', (err) => {
|
|
34
|
+
pm.reject(err)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
wk.postMessage({
|
|
38
|
+
timeLength,
|
|
39
|
+
timeInterval,
|
|
40
|
+
opt,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
return pm
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default staEvent
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import ot from 'dayjs'
|
|
2
|
+
import get from 'lodash-es/get.js'
|
|
3
|
+
import size from 'lodash-es/size.js'
|
|
4
|
+
import groupBy from 'lodash-es/groupBy.js'
|
|
5
|
+
import each from 'lodash-es/each.js'
|
|
6
|
+
import mapValues from 'lodash-es/mapValues.js'
|
|
7
|
+
import merge from 'lodash-es/merge.js'
|
|
8
|
+
import genPm from 'wsemi/src/genPm.mjs'
|
|
9
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
10
|
+
import fsTreeFolder from 'wsemi/src/fsTreeFolder.mjs'
|
|
11
|
+
import fsBuildReadStreamText from 'wsemi/src/fsBuildReadStreamText.mjs'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
//統計各 event 事件於各時間桶之發生頻率(鏡像 SSO staLogs/staToken.mjs;差異:不限特定 event,改依 event 名分組)。
|
|
15
|
+
//回傳: [{ time, data: { count, <event1>: n, <event2>: m, ... } }, ...](count 為該時間桶全部事件數)
|
|
16
|
+
async function staEvent(timeLength = 7, timeInterval = 'hr', opt = {}) {
|
|
17
|
+
|
|
18
|
+
//fdLog
|
|
19
|
+
let fdLog = get(opt, 'fdLog')
|
|
20
|
+
if (!isestr(fdLog)) {
|
|
21
|
+
fdLog = './_logs'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//fmt
|
|
25
|
+
let fmt = timeInterval === 'day' ? 'YYYY-MM-DD' : 'YYYY-MM-DDTHH'
|
|
26
|
+
|
|
27
|
+
//unit
|
|
28
|
+
let unit = timeInterval === 'hr' ? 'hour' : 'day'
|
|
29
|
+
|
|
30
|
+
//kpTime, 產生完整的時間區間
|
|
31
|
+
let now = ot()
|
|
32
|
+
let tStart = now.subtract(timeLength, 'day')
|
|
33
|
+
let tCurr = tStart.startOf(unit)
|
|
34
|
+
let tEnd = now.startOf(unit)
|
|
35
|
+
let kpTime = {}
|
|
36
|
+
while (!tCurr.isAfter(tEnd)) {
|
|
37
|
+
kpTime[tCurr.format(fmt)] = { data: { count: 0 } }
|
|
38
|
+
tCurr = tCurr.add(1, unit)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//vpfs
|
|
42
|
+
let vpfs = fsTreeFolder(fdLog)
|
|
43
|
+
|
|
44
|
+
//logs
|
|
45
|
+
let logs = []
|
|
46
|
+
for (let vpf of vpfs) {
|
|
47
|
+
|
|
48
|
+
//fp
|
|
49
|
+
let fp = vpf.path
|
|
50
|
+
|
|
51
|
+
//ev
|
|
52
|
+
let ev = fsBuildReadStreamText(fp)
|
|
53
|
+
|
|
54
|
+
//line
|
|
55
|
+
ev.on('line', (line) => {
|
|
56
|
+
let v = null
|
|
57
|
+
try {
|
|
58
|
+
v = JSON.parse(line)
|
|
59
|
+
let t = ot(v.time)
|
|
60
|
+
let timeFmt = t.format(fmt)
|
|
61
|
+
let b1 = t.isAfter(tStart)
|
|
62
|
+
let b2 = isestr(v.event) //有 event 欄位即計入(不限特定 event)
|
|
63
|
+
let b = b1 && b2
|
|
64
|
+
if (b) {
|
|
65
|
+
logs.push({
|
|
66
|
+
timeFmt,
|
|
67
|
+
...v,
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (err) {}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
//await close
|
|
75
|
+
let pm = genPm()
|
|
76
|
+
ev.on('close', () => {
|
|
77
|
+
pm.resolve()
|
|
78
|
+
})
|
|
79
|
+
await pm
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//gs
|
|
84
|
+
let gs = groupBy(logs, 'timeFmt')
|
|
85
|
+
|
|
86
|
+
//gsLog, 各時間桶內依 event 名分組計數
|
|
87
|
+
let gsLog = mapValues(gs, vs => {
|
|
88
|
+
let count = size(vs)
|
|
89
|
+
let geve = groupBy(vs, 'event')
|
|
90
|
+
let kp = {}
|
|
91
|
+
each(geve, (vvs, event) => {
|
|
92
|
+
kp[event] = size(vvs)
|
|
93
|
+
})
|
|
94
|
+
return {
|
|
95
|
+
data: {
|
|
96
|
+
count,
|
|
97
|
+
...kp,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
//merge
|
|
103
|
+
let mr = merge({}, kpTime, gsLog)
|
|
104
|
+
|
|
105
|
+
//rs
|
|
106
|
+
let rs = Object.keys(mr)
|
|
107
|
+
.sort()
|
|
108
|
+
.map(time => ({
|
|
109
|
+
time,
|
|
110
|
+
...mr[time]
|
|
111
|
+
}))
|
|
112
|
+
|
|
113
|
+
return rs
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default staEvent
|