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.
Files changed (95) hide show
  1. package/.github/workflows/ci-test.yml +2 -2
  2. package/_gh-pages-deploy/.nojekyll +0 -0
  3. package/_gh-pages-deploy/WWebPerm.html +938 -0
  4. package/_gh-pages-deploy/WWebPerm.mjs.html +1228 -0
  5. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  6. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  7. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  8. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  9. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  10. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  11. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  12. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  13. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  14. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  15. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  16. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  17. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  18. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  19. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  20. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  21. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  22. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  23. package/_gh-pages-deploy/index.html +84 -0
  24. package/_gh-pages-deploy/scripts/collapse.js +39 -0
  25. package/_gh-pages-deploy/scripts/commonNav.js +28 -0
  26. package/_gh-pages-deploy/scripts/linenumber.js +25 -0
  27. package/_gh-pages-deploy/scripts/nav.js +12 -0
  28. package/_gh-pages-deploy/scripts/polyfill.js +4 -0
  29. package/_gh-pages-deploy/scripts/prettify/Apache-License-2.0.txt +202 -0
  30. package/_gh-pages-deploy/scripts/prettify/lang-css.js +2 -0
  31. package/_gh-pages-deploy/scripts/prettify/prettify.js +28 -0
  32. package/_gh-pages-deploy/scripts/search.js +99 -0
  33. package/_gh-pages-deploy/styles/jsdoc.css +776 -0
  34. package/_gh-pages-deploy/styles/prettify.css +80 -0
  35. package/dist/css/app.d8b16216.css +1 -0
  36. package/dist/css/{chunk-vendors.f149e124.css → chunk-vendors.2d3ef2ba.css} +1 -1
  37. package/dist/index.tmp +2 -2
  38. package/dist/js/app-legacy.fb48310a.js +1 -0
  39. package/dist/js/app.fb48310a.js +1 -0
  40. package/dist/js/chunk-vendors-legacy.8fdd4435.js +21 -0
  41. package/dist/js/chunk-vendors.8fdd4435.js +21 -0
  42. package/dist/w-web-perm.umd.js +2 -2
  43. package/dist/w-web-perm.umd.js.map +1 -1
  44. package/docs/WWebPerm.html +2 -2
  45. package/docs/WWebPerm.mjs.html +138 -199
  46. package/docs/index.html +1 -1
  47. package/package.json +15 -11
  48. package/public/index.html +2 -2
  49. package/server/WWebPerm.mjs +137 -198
  50. package/server/procCore.mjs +169 -0
  51. package/server/procLang.mjs +197 -0
  52. package/server/procStaInfor.mjs +118 -0
  53. package/server/provideTabs.mjs +1 -1
  54. package/server/srLog.mjs +30 -0
  55. package/server/staLogs/staEvent.callWorker.mjs +46 -0
  56. package/server/staLogs/staEvent.mjs +116 -0
  57. package/server/staLogs/staEvent.shellWorker.mjs +19 -0
  58. package/server/staLogs/staEventTable.mjs +100 -0
  59. package/server/staLogs/test_staEvent.mjs +22 -0
  60. package/settings.json +40 -0
  61. package/src/components/Common/CheckYes.vue +18 -1
  62. package/src/components/Layout.vue +1 -1
  63. package/src/components/LayoutContent.vue +13 -1
  64. package/src/components/LayoutContentGrups.vue +32 -23
  65. package/src/components/LayoutContentPemis.vue +32 -23
  66. package/src/components/LayoutContentStaInfor.vue +536 -0
  67. package/src/components/LayoutContentTargets.vue +32 -23
  68. package/src/components/LayoutContentUsers.vue +32 -23
  69. package/src/components/LayoutState.vue +5 -0
  70. package/src/components/VeCgrups.vue +12 -3
  71. package/src/components/VeCpemis.vue +12 -3
  72. package/src/components/VeCrules.vue +12 -3
  73. package/src/components/VeGrupBlngUsers.vue +18 -6
  74. package/src/components/VePemiBlngGrups.vue +18 -6
  75. package/src/getPerm.mjs +7 -17
  76. package/src/getPermUserInfor.mjs +8 -18
  77. package/src/main.js +1 -0
  78. package/src/plugins/mShare.mjs +8 -13
  79. package/src/plugins/mUI.mjs +11 -0
  80. package/srv.mjs +76 -101
  81. package/tableTags.json +1 -1
  82. package/db/wperm/grups/data.mdb +0 -0
  83. package/db/wperm/grups/lock.mdb +0 -0
  84. package/db/wperm/pemis/data.mdb +0 -0
  85. package/db/wperm/pemis/lock.mdb +0 -0
  86. package/db/wperm/targets/data.mdb +0 -0
  87. package/db/wperm/targets/lock.mdb +0 -0
  88. package/db/wperm/users/data.mdb +0 -0
  89. package/db/wperm/users/lock.mdb +0 -0
  90. package/dist/css/app.30e81bfe.css +0 -1
  91. package/dist/js/app-legacy.3324392e.js +0 -1
  92. package/dist/js/app.3324392e.js +0 -1
  93. package/dist/js/chunk-vendors-legacy.e8703728.js +0 -21
  94. package/dist/js/chunk-vendors.e8703728.js +0 -21
  95. 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
@@ -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
@@ -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(`invalid keyTable[${keyTable}]`)
77
+ return Promise.reject('invalidKeyTable')
78
78
  }
79
79
  let ks = kpKs[`ks_${keyTable}`]
80
80
  // console.log('ks', ks)
@@ -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