w-web-perm 1.0.49 → 1.0.51

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 (120) hide show
  1. package/README.md +5 -5
  2. package/dist/css/app.8781355a.css +1 -0
  3. package/dist/css/{chunk-vendors.2d3ef2ba.css → chunk-vendors.58917286.css} +1 -1
  4. package/dist/index.tmp +1 -1
  5. package/dist/js/app-legacy.b2754bb1.js +1 -0
  6. package/dist/js/app.b2754bb1.js +1 -0
  7. package/dist/js/{chunk-vendors-legacy.8fdd4435.js → chunk-vendors-legacy.c14a1ed1.js} +6 -6
  8. package/dist/js/{chunk-vendors.8fdd4435.js → chunk-vendors.c14a1ed1.js} +6 -6
  9. package/dist/w-web-perm.umd.js +2 -2
  10. package/dist/w-web-perm.umd.js.map +1 -1
  11. package/docs/WWebPerm.html +3 -3
  12. package/docs/WWebPerm.mjs.html +33 -20
  13. package/docs/index.html +1 -1
  14. package/logs/2026-06-19T16.log +191 -0
  15. package/logs/2026-06-20T00.log +207 -0
  16. package/logs/2026-06-20T11.log +221 -0
  17. package/logs/2026-06-20T12.log +146 -0
  18. package/logs/2026-06-20T13.log +134 -0
  19. package/logs/2026-06-20T14.log +296 -0
  20. package/logs/2026-06-20T15.log +225 -0
  21. package/logs/2026-06-20T16.log +174 -0
  22. package/logs/2026-06-21T16.log +110 -0
  23. package/logs/2026-06-21T17.log +301 -0
  24. package/logs/2026-06-21T18.log +238 -0
  25. package/logs/2026-06-21T19.log +141 -0
  26. package/logs/2026-06-21T20.log +99 -0
  27. package/logs/2026-06-21T21.log +143 -0
  28. package/logs/2026-06-21T23.log +26 -0
  29. package/logs/2026-06-22T00.log +33 -0
  30. package/logs/2026-06-22T08.log +235 -0
  31. package/logs/2026-06-22T09.log +246 -0
  32. package/logs/2026-06-22T10.log +301 -0
  33. package/logs/2026-06-22T11.log +140 -0
  34. package/logs/2026-06-22T12.log +22 -0
  35. package/logs/2026-06-22T14.log +44 -0
  36. package/logs/2026-06-22T23.log +951 -0
  37. package/logs/2026-06-23T00.log +263 -0
  38. package/logs/2026-06-23T08.log +611 -0
  39. package/logs/2026-06-30T21.log +3609 -0
  40. package/logs/2026-06-30T22.log +3682 -0
  41. package/logs/2026-06-30T23.log +592 -0
  42. package/logs/2026-07-01T12.log +0 -0
  43. package/logs/2026-07-09T22.log +2220 -0
  44. package/logs/2026-07-09T23.log +1140 -0
  45. package/logs/2026-07-10T12.log +6 -0
  46. package/logs/2026-07-10T13.log +351 -0
  47. package/logs/2026-07-10T16.log +1272 -0
  48. package/logs/2026-07-10T19.log +6 -0
  49. package/logs/2026-07-10T20.log +1883 -0
  50. package/logs/2026-07-10T21.log +3505 -0
  51. package/logs/2026-07-10T22.log +3395 -0
  52. package/logs/2026-07-11T00.log +1338 -0
  53. package/logs/2026-07-11T01.log +2492 -0
  54. package/logs/2026-07-11T17.log +19 -0
  55. package/package.json +18 -15
  56. package/server/WWebPerm.mjs +32 -19
  57. package/server/procCore.mjs +22 -4
  58. package/server/procLang.mjs +84 -65
  59. package/server/procStaInfor.mjs +10 -0
  60. package/server/staLogs/filterVpfsByWindow.mjs +27 -0
  61. package/server/staLogs/staEvent.mjs +2 -0
  62. package/server/staLogs/staEventTable.mjs +2 -0
  63. package/settings.json +3 -0
  64. package/src/components/LayoutContent.vue +0 -2
  65. package/src/components/LayoutContentGrups.vue +0 -2
  66. package/src/components/LayoutContentPemis.vue +0 -2
  67. package/src/components/LayoutContentStaInfor.vue +0 -2
  68. package/src/components/LayoutContentTargets.vue +3 -5
  69. package/src/components/LayoutContentUsers.vue +0 -2
  70. package/src/components/VeCgrups.vue +7 -6
  71. package/src/components/VeCpemis.vue +7 -6
  72. package/src/components/VeCrules.vue +7 -6
  73. package/src/components/VeGrupBlngUsers.vue +21 -27
  74. package/src/components/VePemiBlngGrups.vue +21 -27
  75. package/src/main.js +3 -9
  76. package/src/perm.mjs +3 -4
  77. package/src/plugins/mUI.mjs +0 -48
  78. package/src/store/mutations.mjs +0 -10
  79. package/src/store/types.mjs +0 -2
  80. package/tableTags.json +1 -1
  81. package/z/344/277/256/346/255/243/350/246/217/345/212/203/346/233/270.md +450 -0
  82. package/_gh-pages-deploy/WWebPerm.html +0 -938
  83. package/_gh-pages-deploy/WWebPerm.mjs.html +0 -1228
  84. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  85. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  86. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  87. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  88. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  89. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  90. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  91. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  92. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  93. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -978
  94. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  95. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  96. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  97. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  98. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -1049
  99. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  100. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  101. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  102. package/_gh-pages-deploy/index.html +0 -84
  103. package/_gh-pages-deploy/scripts/collapse.js +0 -39
  104. package/_gh-pages-deploy/scripts/commonNav.js +0 -28
  105. package/_gh-pages-deploy/scripts/linenumber.js +0 -25
  106. package/_gh-pages-deploy/scripts/nav.js +0 -12
  107. package/_gh-pages-deploy/scripts/polyfill.js +0 -4
  108. package/_gh-pages-deploy/scripts/prettify/Apache-License-2.0.txt +0 -202
  109. package/_gh-pages-deploy/scripts/prettify/lang-css.js +0 -2
  110. package/_gh-pages-deploy/scripts/prettify/prettify.js +0 -28
  111. package/_gh-pages-deploy/scripts/search.js +0 -99
  112. package/_gh-pages-deploy/styles/jsdoc.css +0 -776
  113. package/_gh-pages-deploy/styles/prettify.css +0 -80
  114. package/dist/css/app.d8b16216.css +0 -1
  115. package/dist/js/app-legacy.fb48310a.js +0 -1
  116. package/dist/js/app.fb48310a.js +0 -1
  117. package/server/staLogs/test_staEvent.mjs +0 -22
  118. package/src/plugins/mDataSelectorSchema.mjs +0 -30
  119. package/tableTags-web-perm.json +0 -1
  120. /package/{_gh-pages-deploy/.nojekyll → logs/2026-06-19T23.log} +0 -0
@@ -0,0 +1,19 @@
1
+ {"level":30,"time":1783761673030,"pid":21968,"hostname":"semi-win-dev3","event":"api/getPerm-success","userId":"id-for-admin"}
2
+ {"level":50,"time":1783761673423,"pid":21968,"hostname":"semi-win-dev3","event":"getTokenUser-error","msg":"cannotFindUserFromToken"}
3
+ {"level":30,"time":1783761673428,"pid":21968,"hostname":"semi-win-dev3","event":"api/getPerm-success","userId":"id-for-admin"}
4
+ {"level":30,"time":1783761673433,"pid":21968,"hostname":"semi-win-dev3","event":"api/getPerm-success","userId":"id-for-admin"}
5
+ {"level":30,"time":1783761673440,"pid":21968,"hostname":"semi-win-dev3","event":"api/getPerm-success","userId":"id-for-admin"}
6
+ {"level":30,"time":1783761673444,"pid":21968,"hostname":"semi-win-dev3","event":"api/getPerm-success","userId":"id-for-admin"}
7
+ {"level":30,"time":1783761673450,"pid":21968,"hostname":"semi-win-dev3","event":"api/getPermUserInfor-success","userId":"id-for-peter"}
8
+ {"level":50,"time":1783761673453,"pid":21968,"hostname":"semi-win-dev3","event":"api/getPermUserInfor-error","msg":"tokenNoPermission"}
9
+ {"level":50,"time":1783761673455,"pid":21968,"hostname":"semi-win-dev3","event":"getAndVerifyAppUser-error","msg":"cannotFindUserFromToken"}
10
+ {"level":30,"time":1783761673458,"pid":21968,"hostname":"semi-win-dev3","event":"api/getPermUserInfor-success","userId":"id-not-exist-xyz"}
11
+ {"level":30,"time":1783761673461,"pid":21968,"hostname":"semi-win-dev3","event":"api/getPermUserInfor-success","userId":"id-for-peter"}
12
+ {"level":30,"time":1783761673463,"pid":21968,"hostname":"semi-win-dev3","event":"api/getPermUserInfor-success","userId":"id-for-peter"}
13
+ {"level":30,"time":1783761673466,"pid":21968,"hostname":"semi-win-dev3","event":"api/getPermUserInfor-success","userId":"id-for-peter"}
14
+ {"level":30,"time":1783762697759,"pid":18212,"hostname":"semi-win-dev3","event":"api/getPerm-success","userId":"id-for-admin"}
15
+ {"level":50,"time":1783762698186,"pid":18212,"hostname":"semi-win-dev3","event":"getTokenUser-error","msg":"cannotFindUserFromToken"}
16
+ {"level":30,"time":1783762698190,"pid":18212,"hostname":"semi-win-dev3","event":"api/getPerm-success","userId":"id-for-admin"}
17
+ {"level":30,"time":1783762698193,"pid":18212,"hostname":"semi-win-dev3","event":"api/getPerm-success","userId":"id-for-admin"}
18
+ {"level":30,"time":1783762698196,"pid":18212,"hostname":"semi-win-dev3","event":"api/getPerm-success","userId":"id-for-admin"}
19
+ {"level":30,"time":1783762698200,"pid":18212,"hostname":"semi-win-dev3","event":"api/getPerm-success","userId":"id-for-admin"}
package/package.json CHANGED
@@ -1,21 +1,21 @@
1
1
  {
2
2
  "name": "w-web-perm",
3
- "version": "1.0.49",
3
+ "version": "1.0.51",
4
4
  "main": "dist/w-web-perm.umd.js",
5
5
  "dependencies": {
6
6
  "@mdi/js": "^7.4.47",
7
- "axios": "^1.18.0",
7
+ "axios": "^1.18.1",
8
8
  "json5": "^2.2.3",
9
9
  "lodash-es": "^4.18.1",
10
10
  "showdown": "^2.1.0",
11
- "w-aggrid-vue": "^2.0.57",
12
- "w-component-vue": "^2.4.66",
13
- "w-data-collector": "^1.0.22",
14
- "w-serv-hapi": "^1.0.72",
15
- "w-serv-orm": "^1.0.38",
16
- "w-ui-loginout": "^1.0.10",
17
- "w-syslog": "^1.0.8",
18
- "wsemi": "^1.8.61"
11
+ "w-aggrid-vue": "^2.0.58",
12
+ "w-component-vue": "^2.4.74",
13
+ "w-data-collector": "^1.0.23",
14
+ "w-serv-hapi": "^1.0.74",
15
+ "w-serv-orm": "^1.0.40",
16
+ "w-ui-loginout": "^1.0.11",
17
+ "w-syslog": "^1.0.10",
18
+ "wsemi": "^1.8.66"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@vue/cli-plugin-babel": "^5.0.8",
@@ -29,18 +29,21 @@
29
29
  "eslint-plugin-promise": "^6.1.1",
30
30
  "eslint-plugin-vue": "^9.10.0",
31
31
  "pixelmatch": "^7.2.0",
32
- "playwright": "^1.61.0",
32
+ "playwright": "^1.61.1",
33
33
  "pngjs": "^7.0.0",
34
34
  "vue": "^2.6.14",
35
35
  "vuex": "^3.6.2",
36
- "w-orm-lmdb": "^1.0.6",
37
- "w-screenctl": "^1.0.3",
38
- "w-package-tools": "^1.1.9"
36
+ "w-orm-lmdb": "^1.0.8",
37
+ "w-screenctl": "^1.0.4",
38
+ "w-package-tools": "^1.1.11"
39
39
  },
40
40
  "scripts": {
41
41
  "serve": "vue-cli-service serve",
42
42
  "build": "vue-cli-service build",
43
- "test": "mocha --timeout 300000",
43
+ "test:unit": "mocha test/unit-staEvent.test.mjs --timeout 300000",
44
+ "test:apiunit": "mocha test/unit-*.test.mjs test/api-*.test.mjs test/getUserRules.test.mjs --timeout 300000",
45
+ "test:e2e": "node test/run-e2e-isolated.mjs",
46
+ "test": "npm run test:apiunit && npm run test:e2e",
44
47
  "lint": "vue-cli-service lint",
45
48
  "deploy": "gh-pages -d docs"
46
49
  },
@@ -1,10 +1,8 @@
1
1
  import path from 'path'
2
2
  import fs from 'fs'
3
3
  import get from 'lodash-es/get.js'
4
- import each from 'lodash-es/each.js'
5
4
  import map from 'lodash-es/map.js'
6
5
  import keys from 'lodash-es/keys.js'
7
- import size from 'lodash-es/size.js'
8
6
  import join from 'lodash-es/join.js'
9
7
  import iseobj from 'wsemi/src/iseobj.mjs'
10
8
  import isestr from 'wsemi/src/isestr.mjs'
@@ -42,7 +40,7 @@ import procStaInfor from './procStaInfor.mjs'
42
40
  * @param {Function} WOrm 輸入資料庫ORM函數
43
41
  * @param {String} url 輸入資料庫連線字串,例如w-orm-lmdb為'./db',或w-orm-mongodb為'mongodb://username:password@$127.0.0.1:27017'
44
42
  * @param {String} db 輸入資料庫名稱字串
45
- * @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n',且輸入'y'時會複寫權限系統該使用者之'isAdmin'欄位值
43
+ * @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n'。注意: token 回傳之'isAdmin'僅供識別,權限系統一律以自身users表該使用者之'isAdmin'欄位值為權威,不會被 token 宣稱之值複寫
46
44
  * @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
47
45
  * @param {Function} verifyAppUser 輸入驗證應用程序使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
48
46
  * @param {Object} [opt={}] 輸入設定物件,預設{}
@@ -285,6 +283,8 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
285
283
  }
286
284
  catch (err) {
287
285
  console.log(err)
286
+ srLog.error({ event: 'WServOrm-init-error', msg: (err && err.message) || 'WServOrmInitFailed' })
287
+ throw err //fail-fast: ORM 初始化失敗直接終止啟動, 不以 wp={} 繼續組裝出「port 起得來但 woItems/procOrm 為 undefined、CRUD 全 crash」的半死伺服器
288
288
  }
289
289
  let { woItems, procOrm } = wp
290
290
 
@@ -298,8 +298,12 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
298
298
  let pc = procCore(woItems, procOrm, { srLog, kmx })
299
299
 
300
300
 
301
- //procStaInfor: 統計資訊, fdLog 對齊 srLog 使用同一目錄; mock 由 settings.staEventMock 注入(e2e 統計圖穩定用)
302
- let psi = procStaInfor({ fdLog: './_logs', mock: get(opt, 'staEventMock', false) })
301
+ //procStaInfor: 統計資訊, fdLog 對齊 srLog 使用同一目錄(讀 settings.logFd, fallback 邏輯同 srLog.mjs); mock 由 settings.staEventMock 注入(e2e 統計圖穩定用)
302
+ let logFd = get(opt, 'logFd', '')
303
+ if (!isestr(logFd)) {
304
+ logFd = './_logs'
305
+ }
306
+ let psi = procStaInfor({ fdLog: logFd, mock: get(opt, 'staEventMock', false) })
303
307
 
304
308
 
305
309
  //getWebInfor
@@ -450,13 +454,10 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
450
454
  return Promise.reject(`cannotGetUserFromPerm`)
451
455
  }
452
456
 
453
- //複寫isAdmin
454
- let isAdminSrc = get(userSelf, 'isAdmin', '')
455
- let isAdminSelf = get(userFind, 'isAdmin', '')
456
- if (isAdminSrc !== isAdminSelf) {
457
- userFind.isAdmin = isAdminSelf
458
- }
459
- // console.log('userFind(isAdmin)', userFind)
457
+ //isAdmin 權威為 perm users 表之欄位值, 不以 token(getUserByToken)宣稱之 isAdmin 複寫。
458
+ //(2026-07-11 稽核 A-C1 裁決(b): 原此處有一段恆為 no-op 之「複寫isAdmin」死碼
459
+ // (userFind.isAdmin = isAdminSelf, 把自己指派給自己), 與 JSDoc 舊文「輸入'y'時會複寫」互斥;
460
+ // 依業主裁決採保守解: 刪除死碼、文件對齊實際行為, 不啟用 token→admin 提權路徑。)
460
461
 
461
462
  return userFind
462
463
  }
@@ -1058,7 +1059,7 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1058
1059
 
1059
1060
  //WServHapiServer
1060
1061
  instWServHapiServer = new WServHapiServer({
1061
- port: opt.serverPort,
1062
+ port: serverPort,
1062
1063
  pathStaticFiles,
1063
1064
  apiName: 'api',
1064
1065
  apis,
@@ -1133,15 +1134,27 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1133
1134
  updateUsers: pc.updateUsers,
1134
1135
 
1135
1136
  getStaEvent: async (userId, timeLength, timeInterval) => {
1136
- let rs = await psi.getStaEvent(userId, timeLength, timeInterval)
1137
- srLog.info({ event: 'getStaEvent-success', userId })
1138
- return rs
1137
+ try {
1138
+ let rs = await psi.getStaEvent(userId, timeLength, timeInterval)
1139
+ srLog.info({ event: 'getStaEvent-success', userId })
1140
+ return rs
1141
+ }
1142
+ catch (err) {
1143
+ srLog.error({ event: 'getStaEvent-error', userId, msg: (typeof err === 'string') ? err : ((err && err.message) || 'cannotGetStaEvent') })
1144
+ throw err
1145
+ }
1139
1146
  },
1140
1147
 
1141
1148
  getStaEventTable: async (userId) => {
1142
- let rs = await psi.getStaEventTable(userId)
1143
- srLog.info({ event: 'getStaEventTable-success', userId })
1144
- return rs
1149
+ try {
1150
+ let rs = await psi.getStaEventTable(userId)
1151
+ srLog.info({ event: 'getStaEventTable-success', userId })
1152
+ return rs
1153
+ }
1154
+ catch (err) {
1155
+ srLog.error({ event: 'getStaEventTable-error', userId, msg: (typeof err === 'string') ? err : ((err && err.message) || 'cannotGetStaEventTable') })
1156
+ throw err
1157
+ }
1145
1158
  },
1146
1159
 
1147
1160
  },
@@ -122,9 +122,18 @@ function proc(woItems, procOrm, { srLog, kmx }) {
122
122
  }
123
123
 
124
124
 
125
+ //errLog: 失敗統一記 err key (updateTabItems reject 為 key 字串如 saveRowFieldInvalid; procOrm 失敗為 Error)
126
+ let errLog = (event, userId, err) => {
127
+ srLog.error({ event, userId, msg: (typeof err === 'string') ? err : ((err && err.message) || event) })
128
+ }
129
+
130
+
125
131
  //updateTargets
126
132
  let updateTargets = async (userId, rows) => {
127
- await updateTabItems('targets', userId, rows, 'id')
133
+ try {
134
+ await updateTabItems('targets', userId, rows, 'id')
135
+ }
136
+ catch (err) { errLog('updateTargets-error', userId, err); throw err }
128
137
  srLog.info({ event: 'updateTargets-success', userId })
129
138
  return 'saveTabItemsSuccess' //成功 msg key
130
139
  }
@@ -132,7 +141,10 @@ function proc(woItems, procOrm, { srLog, kmx }) {
132
141
 
133
142
  //updatePemis
134
143
  let updatePemis = async (userId, rows) => {
135
- await updateTabItems('pemis', userId, rows, 'name')
144
+ try {
145
+ await updateTabItems('pemis', userId, rows, 'name')
146
+ }
147
+ catch (err) { errLog('updatePemis-error', userId, err); throw err }
136
148
  srLog.info({ event: 'updatePemis-success', userId })
137
149
  return 'saveTabItemsSuccess' //成功 msg key
138
150
  }
@@ -140,7 +152,10 @@ function proc(woItems, procOrm, { srLog, kmx }) {
140
152
 
141
153
  //updateGrups
142
154
  let updateGrups = async (userId, rows) => {
143
- await updateTabItems('grups', userId, rows, 'name')
155
+ try {
156
+ await updateTabItems('grups', userId, rows, 'name')
157
+ }
158
+ catch (err) { errLog('updateGrups-error', userId, err); throw err }
144
159
  srLog.info({ event: 'updateGrups-success', userId })
145
160
  return 'saveTabItemsSuccess' //成功 msg key
146
161
  }
@@ -148,7 +163,10 @@ function proc(woItems, procOrm, { srLog, kmx }) {
148
163
 
149
164
  //updateUsers
150
165
  let updateUsers = async (userId, rows) => {
151
- await updateTabItems('users', userId, rows, 'id')
166
+ try {
167
+ await updateTabItems('users', userId, rows, 'id')
168
+ }
169
+ catch (err) { errLog('updateUsers-error', userId, err); throw err }
152
170
  srLog.info({ event: 'updateUsers-success', userId })
153
171
  return 'saveTabItemsSuccess' //成功 msg key
154
172
  }
@@ -57,10 +57,11 @@ let kpLang = {
57
57
  cht: '等待數據中...',
58
58
  },
59
59
 
60
- isEditabled: {
61
- eng: 'Editable',
62
- cht: '是否編輯',
63
- },
60
+ //目前無引用,保留供日後使用(見 spec/設計要點與取捨.md ADR-014)
61
+ // isEditabled: {
62
+ // eng: 'Editable',
63
+ // cht: '是否編輯',
64
+ // },
64
65
  saveChanges: {
65
66
  eng: `Save changes`,
66
67
  cht: `儲存變更`,
@@ -85,10 +86,11 @@ let kpLang = {
85
86
  eng: `Delete`,
86
87
  cht: `刪除`,
87
88
  },
88
- cancel: {
89
- eng: `Cancel`,
90
- cht: `取消`,
91
- },
89
+ //目前無引用,保留供日後使用(見 spec/設計要點與取捨.md ADR-014)
90
+ // cancel: {
91
+ // eng: `Cancel`,
92
+ // cht: `取消`,
93
+ // },
92
94
  screenFull: {
93
95
  eng: `Max size`,
94
96
  cht: `最大化尺寸`,
@@ -221,10 +223,11 @@ let kpLang = {
221
223
  cht: `無法找到使用者 Id`,
222
224
  },
223
225
 
224
- unknow: {
225
- eng: `Unknow`,
226
- cht: `未知`,
227
- },
226
+ //目前無引用,保留供日後使用(見 spec/設計要點與取捨.md ADR-014)
227
+ // unknow: {
228
+ // eng: `Unknow`,
229
+ // cht: `未知`,
230
+ // },
228
231
 
229
232
  username: {
230
233
  eng: `Username`,
@@ -263,10 +266,11 @@ let kpLang = {
263
266
  eng: `Use groups`,
264
267
  cht: `所屬權限群組`,
265
268
  },
266
- belongPemis: {
267
- eng: `Use permissions`,
268
- cht: `所屬權限`,
269
- },
269
+ //目前無引用,保留供日後使用(見 spec/設計要點與取捨.md ADR-014)
270
+ // belongPemis: {
271
+ // eng: `Use permissions`,
272
+ // cht: `所屬權限`,
273
+ // },
270
274
  belongGrupsNames: {
271
275
  eng: `Use permission groups`,
272
276
  cht: `所屬權限群組名稱`,
@@ -289,6 +293,10 @@ let kpLang = {
289
293
  eng: `Name errors`,
290
294
  cht: `名稱出現錯誤待修復`,
291
295
  },
296
+ errInIds: {
297
+ eng: `ID errors`,
298
+ cht: `ID 出現錯誤待修復`,
299
+ },
292
300
  errInEmails: {
293
301
  eng: `Email errors`,
294
302
  cht: `Email出現錯誤待修復`,
@@ -383,10 +391,11 @@ let kpLang = {
383
391
  eng: `Enable`,
384
392
  cht: `是否使用`,
385
393
  },
386
- targetClearTargetsFail: {
387
- eng: `Failed to clear existing targets`,
388
- cht: `清除既有對象數據失敗`,
389
- },
394
+ //目前無引用,保留供日後使用(見 spec/設計要點與取捨.md ADR-014)
395
+ // targetClearTargetsFail: {
396
+ // eng: `Failed to clear existing targets`,
397
+ // cht: `清除既有對象數據失敗`,
398
+ // },
390
399
  targetSaveTargetsFail: {
391
400
  eng: `Failed to save targets`,
392
401
  cht: `儲存對象數據失敗`,
@@ -476,10 +485,11 @@ let kpLang = {
476
485
  eng: `Has {n} rule(s){nms}`,
477
486
  cht: `使用共 {n} 項對象{nms}`,
478
487
  },
479
- pemiClearPemisFail: {
480
- eng: `Failed to clear existing permissions`,
481
- cht: `清除既有權限數據失敗`,
482
- },
488
+ //目前無引用,保留供日後使用(見 spec/設計要點與取捨.md ADR-014)
489
+ // pemiClearPemisFail: {
490
+ // eng: `Failed to clear existing permissions`,
491
+ // cht: `清除既有權限數據失敗`,
492
+ // },
483
493
  pemiSavePemisFail: {
484
494
  eng: `Failed to save permissions`,
485
495
  cht: `儲存權限數據失敗`,
@@ -603,10 +613,11 @@ let kpLang = {
603
613
  eng: `Has {n} permission(s){nms}`,
604
614
  cht: `使用 {n} 項權限{nms}`,
605
615
  },
606
- grupClearGrupsFail: {
607
- eng: `Failed to clear existing permission groups`,
608
- cht: `清除既有權限群組數據失敗`,
609
- },
616
+ //目前無引用,保留供日後使用(見 spec/設計要點與取捨.md ADR-014)
617
+ // grupClearGrupsFail: {
618
+ // eng: `Failed to clear existing permission groups`,
619
+ // cht: `清除既有權限群組數據失敗`,
620
+ // },
610
621
  grupSaveGrupsEmpty: {
611
622
  eng: `No user`,
612
623
  cht: `未有變更權限群組資料`,
@@ -727,7 +738,7 @@ let kpLang = {
727
738
  cht: `編輯使用權限群組`,
728
739
  },
729
740
  userEditCgrupsForDisplay: {
730
- eng: `Edit list of user`,
741
+ eng: `Permission groups of user`, //展示版不帶 Edit 動詞(2026-07-10 訂正: 原誤與編輯版同值, 唯讀對話框標題顯示「Edit...」且 e2e 無法以標題區分模式)
731
742
  cht: `展示使用權限群組`,
732
743
  },
733
744
  userEditCgrupsNoUserId: {
@@ -784,10 +795,11 @@ let kpLang = {
784
795
  cht: `刪除勾選權限`,
785
796
  },
786
797
 
787
- allDefaults: {
788
- eng: `All defaults`,
789
- cht: `全部項目預設值`,
790
- },
798
+ //目前無引用,保留供日後使用(見 spec/設計要點與取捨.md ADR-014)
799
+ // allDefaults: {
800
+ // eng: `All defaults`,
801
+ // cht: `全部項目預設值`,
802
+ // },
791
803
  show: {
792
804
  eng: `Show`,
793
805
  cht: `顯示`,
@@ -796,34 +808,36 @@ let kpLang = {
796
808
  eng: `Active`,
797
809
  cht: `啟用`,
798
810
  },
799
- showChildren: {
800
- eng: `Show children`,
801
- cht: `所屬顯示`,
802
- },
803
- hideChildren: {
804
- eng: `Hide children`,
805
- cht: `所屬不顯示`,
806
- },
807
- activateChildren: {
808
- eng: `Activate children`,
809
- cht: `所屬啟用`,
810
- },
811
- deactivateChildren: {
812
- eng: `Deactivate children`,
813
- cht: `所屬不啟用`,
814
- },
815
- isUsernameEmpty: {
816
- eng: `Username is empty`,
817
- cht: `使用者名稱不得為空`,
818
- },
819
- failedToSaveUser: {
820
- eng: `Failed to save the user`,
821
- cht: `變更使用者失敗`,
822
- },
823
- successfulToSaveUser: {
824
- eng: `Save the user successfully`,
825
- cht: `變更使用者成功`,
826
- },
811
+ //目前無引用,保留供日後使用(見 spec/設計要點與取捨.md ADR-014)
812
+ // showChildren: {
813
+ // eng: `Show children`,
814
+ // cht: `所屬顯示`,
815
+ // },
816
+ // hideChildren: {
817
+ // eng: `Hide children`,
818
+ // cht: `所屬不顯示`,
819
+ // },
820
+ // activateChildren: {
821
+ // eng: `Activate children`,
822
+ // cht: `所屬啟用`,
823
+ // },
824
+ // deactivateChildren: {
825
+ // eng: `Deactivate children`,
826
+ // cht: `所屬不啟用`,
827
+ // },
828
+ //目前無引用,保留供日後使用(見 spec/設計要點與取捨.md ADR-014)
829
+ // isUsernameEmpty: {
830
+ // eng: `Username is empty`,
831
+ // cht: `使用者名稱不得為空`,
832
+ // },
833
+ // failedToSaveUser: {
834
+ // eng: `Failed to save the user`,
835
+ // cht: `變更使用者失敗`,
836
+ // },
837
+ // successfulToSaveUser: {
838
+ // eng: `Save the user successfully`,
839
+ // cht: `變更使用者成功`,
840
+ // },
827
841
 
828
842
  userId: {
829
843
  eng: `ID of the created user`,
@@ -891,10 +905,11 @@ let kpLang = {
891
905
  eng: `Event Frequency`,
892
906
  cht: `事件發生頻率`,
893
907
  },
894
- staTimeGroup: {
895
- eng: `Time grouping`,
896
- cht: `時間分組`,
897
- },
908
+ //目前無引用,保留供日後使用(見 spec/設計要點與取捨.md ADR-014)
909
+ // staTimeGroup: {
910
+ // eng: `Time grouping`,
911
+ // cht: `時間分組`,
912
+ // },
898
913
  staTimeHr: {
899
914
  eng: `Hourly`,
900
915
  cht: `每小時`,
@@ -923,6 +938,10 @@ let kpLang = {
923
938
  eng: `No event data`,
924
939
  cht: `尚無事件資料`,
925
940
  },
941
+ getDataError: {
942
+ eng: `Failed to load data`,
943
+ cht: `資料載入失敗`,
944
+ },
926
945
  staTableTitle: {
927
946
  eng: `Event Statistics`,
928
947
  cht: `事件統計表`,
@@ -77,6 +77,11 @@ function proc(opt = {}) {
77
77
  let cacheKey = `${timeLength}:${timeInterval}`
78
78
 
79
79
  let r = await ocGetStaEvent.getProxy(cacheKey, { fun: _getStaEvent, inputs: [timeLength, timeInterval], timeExpired: 30 * 1000 }) //快取30秒
80
+ if (!Array.isArray(r)) {
81
+ //_getStaEvent(worker) 失敗時 cache 之 .catch 會回 undefined 並把 undefined 快取 30s; 清除該快取避免窗內重試仍拿 undefined, 並 reject 讓上層 (kpFunExt) 記 err key
82
+ ocGetStaEvent.clear(cacheKey)
83
+ return Promise.reject('cannotGetStaEvent')
84
+ }
80
85
  return r
81
86
  }
82
87
 
@@ -98,6 +103,11 @@ function proc(opt = {}) {
98
103
  let getStaEventTable = async (userId) => {
99
104
 
100
105
  let r = await ocGetStaEventTable.getProxy('staEventTable', { fun: _getStaEventTable, inputs: [], timeExpired: 30 * 1000 }) //快取30秒
106
+ if (!Array.isArray(r)) {
107
+ //同 getStaEvent: staEventTable 失敗時 cache 回 undefined 並快取 30s; 清除避免窗內重試仍拿 undefined, 並 reject 讓上層記 err key
108
+ ocGetStaEventTable.clear('staEventTable')
109
+ return Promise.reject('cannotGetStaEventTable')
110
+ }
101
111
  return r
102
112
  }
103
113
 
@@ -0,0 +1,27 @@
1
+ import get from 'lodash-es/get.js'
2
+ import filter from 'lodash-es/filter.js'
3
+
4
+
5
+ //filterVpfsByWindow: 開檔前依「檔名時間窗」過濾 log 檔清單, 剔除窗外檔.
6
+ //w-syslog 檔名為 ISO 前綴 (hr: `YYYY-MM-DDTHH.log` / day: `YYYY-MM-DD.log`), 字典序 ≡ 時間序,
7
+ //故可用字串比較篩選 (w-syslog 自身 cleanLogs 亦依賴此性質, 見 node_modules/w-syslog/src/WSyslog.mjs:127-128).
8
+ //邊界檔一律保留, 其內 per-line 之時間判斷仍為最終判準 → 輸出語意零改變.
9
+ //fail-open: 檔名不符 ISO 前綴者一律保留 (寧可多讀, 不可漏讀).
10
+ //vpfs 為 fsTreeFolder 回傳之物件陣列, 每筆含 { path, name, isFolder, level }.
11
+ //【粒度自適應】比較時以「檔名長度」截取 keyStart: srLog 之 logInterval (決定檔名粒度) 與 staLogs 之
12
+ //timeInterval (決定 fmt) 為兩個獨立設定, 兩者不一定相同. 若檔名為 day 粒度 (10 字元) 而 fmt 為 hr 粒度
13
+ //(13 字元), 直接比較會把「當天檔」誤判為窗外 (`'2026-07-08' < '2026-07-08T21'`) → 漏讀整天資料.
14
+ //截取後 day 檔以日期比日期、hr 檔以小時比小時, 兩種粒度皆正確且一律偏保留 (fail-safe).
15
+ function filterVpfsByWindow(vpfs, tStart, fmt) {
16
+ let keyStart = tStart.format(fmt)
17
+ return filter(vpfs, (vpf) => {
18
+ let bn = get(vpf, 'name', '').replace(/\.log$/, '')
19
+ if (!/^\d{4}-\d{2}-\d{2}/.test(bn)) {
20
+ return true //fail-open: 非預期檔名一律保留
21
+ }
22
+ return bn >= keyStart.slice(0, bn.length)
23
+ })
24
+ }
25
+
26
+
27
+ export default filterVpfsByWindow
@@ -9,6 +9,7 @@ import genPm from 'wsemi/src/genPm.mjs'
9
9
  import isestr from 'wsemi/src/isestr.mjs'
10
10
  import fsTreeFolder from 'wsemi/src/fsTreeFolder.mjs'
11
11
  import fsBuildReadStreamText from 'wsemi/src/fsBuildReadStreamText.mjs'
12
+ import filterVpfsByWindow from './filterVpfsByWindow.mjs'
12
13
 
13
14
 
14
15
  //統計各 event 事件於各時間桶之發生頻率(鏡像 SSO staLogs/staToken.mjs;差異:不限特定 event,改依 event 名分組)。
@@ -40,6 +41,7 @@ async function staEvent(timeLength = 7, timeInterval = 'hr', opt = {}) {
40
41
 
41
42
  //vpfs
42
43
  let vpfs = fsTreeFolder(fdLog)
44
+ vpfs = filterVpfsByWindow(vpfs, tStart, fmt) //開檔前剔除窗外檔, 見該模組註解
43
45
 
44
46
  //logs
45
47
  let logs = []
@@ -5,6 +5,7 @@ import genPm from 'wsemi/src/genPm.mjs'
5
5
  import isestr from 'wsemi/src/isestr.mjs'
6
6
  import fsTreeFolder from 'wsemi/src/fsTreeFolder.mjs'
7
7
  import fsBuildReadStreamText from 'wsemi/src/fsBuildReadStreamText.mjs'
8
+ import filterVpfsByWindow from './filterVpfsByWindow.mjs'
8
9
 
9
10
 
10
11
  //統計各 event 事件於近 1日/8hr/4hr/1hr 各窗內之發生數量(鏡像 staEvent.mjs 的 NDJSON 讀取方式)。
@@ -26,6 +27,7 @@ async function staEventTable(opt = {}) {
26
27
 
27
28
  //vpfs
28
29
  let vpfs = fsTreeFolder(fdLog)
30
+ vpfs = filterVpfsByWindow(vpfs, now.subtract(25, 'hour'), 'YYYY-MM-DDTHH') //開檔前剔除窗外檔; 用 25h(非 24h)是因 diff('hour') 取整, 最舊可能到 now-25h, 多留一小時確保不漏讀
29
31
 
30
32
  //逐檔逐行讀取
31
33
  for (let vpf of vpfs) {
package/settings.json CHANGED
@@ -36,5 +36,8 @@
36
36
  // cht: `現有功能泛指實體的頁面、數據等,亦可為的功能或服務。`,
37
37
  // },
38
38
  // },
39
+
40
+ logFd: './logs',
41
+ logInterval: 'hr',
39
42
 
40
43
  }
@@ -167,7 +167,6 @@ export default {
167
167
 
168
168
  menuKey: 'mmTargets',
169
169
 
170
- panelWidth: 0,
171
170
  panelHeight: 0,
172
171
 
173
172
  drawer: true,
@@ -232,7 +231,6 @@ export default {
232
231
  let vo = this
233
232
 
234
233
  //save
235
- vo.panelWidth = msg.snew.clientWidth
236
234
  vo.panelHeight = msg.snew.clientHeight
237
235
  // console.log('vo.panelHeight', vo.panelHeight)
238
236
 
@@ -296,7 +296,6 @@ export default {
296
296
  mdiPencilOutline,
297
297
  mdiContentCopy,
298
298
 
299
- panelWidth: 100,
300
299
  panelHeight: 100,
301
300
  headHeight: 100,
302
301
 
@@ -624,7 +623,6 @@ export default {
624
623
  let vo = this
625
624
 
626
625
  //panelWidth, panelHeight
627
- vo.panelWidth = msg.snew.offsetWidth
628
626
  vo.panelHeight = msg.snew.offsetHeight
629
627
 
630
628
  },
@@ -296,7 +296,6 @@ export default {
296
296
  mdiPencilOutline,
297
297
  mdiContentCopy,
298
298
 
299
- panelWidth: 100,
300
299
  panelHeight: 100,
301
300
  headHeight: 100,
302
301
 
@@ -624,7 +623,6 @@ export default {
624
623
  let vo = this
625
624
 
626
625
  //panelWidth, panelHeight
627
- vo.panelWidth = msg.snew.offsetWidth
628
626
  vo.panelHeight = msg.snew.offsetHeight
629
627
 
630
628
  },
@@ -179,7 +179,6 @@ export default {
179
179
 
180
180
  dbc: debounce(300),
181
181
 
182
- panelWidth: 100,
183
182
  panelHeight: 100,
184
183
 
185
184
  firstLoading: true,
@@ -259,7 +258,6 @@ export default {
259
258
  let vo = this
260
259
 
261
260
  //panelWidth, panelHeight
262
- vo.panelWidth = msg.snew.offsetWidth
263
261
  vo.panelHeight = msg.snew.offsetHeight
264
262
 
265
263
  },