w-web-api 1.0.53 → 1.0.56

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 (94) hide show
  1. package/README.md +1 -1
  2. package/dist/css/{app.fe69497a.css → app.cad4a1ca.css} +1 -1
  3. package/dist/index.tmp +1 -1
  4. package/dist/js/app-legacy.7687b23b.js +1 -0
  5. package/dist/js/app.7687b23b.js +1 -0
  6. package/dist/js/chunk-vendors-legacy.4c27cf51.js +15 -0
  7. package/dist/js/chunk-vendors.4c27cf51.js +15 -0
  8. package/dist/w-web-api.umd.js +2 -2
  9. package/dist/w-web-api.umd.js.map +1 -1
  10. package/docs/WWebApi.html +1 -1
  11. package/docs/index.html +1 -1
  12. package/docs/server_WWebApi.mjs.html +24 -34
  13. package/docs/src_components_Common_CheckYesNo.vue.html +1 -1
  14. package/logs/2026-06-19T16.log +6 -0
  15. package/logs/2026-06-20T11.log +4 -0
  16. package/logs/2026-06-20T14.log +2 -0
  17. package/logs/2026-06-21T20.log +1 -0
  18. package/logs/2026-06-21T21.log +4 -0
  19. package/logs/2026-06-22T07.log +12 -0
  20. package/logs/2026-06-22T08.log +808 -0
  21. package/logs/2026-06-22T09.log +104 -0
  22. package/logs/2026-06-22T10.log +26 -0
  23. package/logs/2026-06-22T11.log +860 -0
  24. package/logs/2026-06-22T12.log +430 -0
  25. package/logs/2026-06-22T23.log +1547 -0
  26. package/logs/2026-06-23T00.log +473 -0
  27. package/logs/2026-06-23T08.log +231 -0
  28. package/logs/2026-06-23T09.log +179 -0
  29. package/logs/2026-07-01T11.log +488 -0
  30. package/logs/2026-07-09T22.log +370 -0
  31. package/logs/2026-07-09T23.log +370 -0
  32. package/logs/2026-07-10T12.log +284 -0
  33. package/logs/2026-07-10T13.log +777 -0
  34. package/logs/2026-07-11T17.log +2 -0
  35. package/package.json +15 -14
  36. package/server/WWebApi.mjs +23 -33
  37. package/server/procLang.mjs +8 -88
  38. package/server/procProxy.mjs +89 -89
  39. package/server/srLog.mjs +2 -2
  40. package/server/staLogs/filterVpfsByWindow.mjs +27 -0
  41. package/server/staLogs/staEvent.mjs +4 -2
  42. package/server/staLogs/test_staEvent.mjs +67 -1
  43. package/settings.json +4 -0
  44. package/src/components/Layout.vue +1 -20
  45. package/src/components/LayoutContent.vue +0 -26
  46. package/src/components/LayoutContentEdit.vue +2 -2
  47. package/src/components/LayoutContentTest.vue +1 -19
  48. package/src/main.js +3 -8
  49. package/src/plugins/mUI.mjs +0 -35
  50. package/src/store/mutations.mjs +0 -14
  51. package/src/store/types.mjs +0 -3
  52. package/srv.mjs +6 -4
  53. package/tableTags.json +1 -1
  54. package/z/344/277/256/346/255/243/350/246/217/345/212/203/346/233/270.md +246 -0
  55. package/_gh-pages-deploy/.nojekyll +0 -0
  56. package/_gh-pages-deploy/WWebApi.html +0 -898
  57. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  58. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  59. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  60. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  61. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  62. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  63. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  64. package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  65. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  66. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -978
  67. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  68. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  69. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  70. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  71. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -1049
  72. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  73. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  74. package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  75. package/_gh-pages-deploy/index.html +0 -84
  76. package/_gh-pages-deploy/scripts/collapse.js +0 -39
  77. package/_gh-pages-deploy/scripts/commonNav.js +0 -28
  78. package/_gh-pages-deploy/scripts/linenumber.js +0 -25
  79. package/_gh-pages-deploy/scripts/nav.js +0 -12
  80. package/_gh-pages-deploy/scripts/polyfill.js +0 -4
  81. package/_gh-pages-deploy/scripts/prettify/Apache-License-2.0.txt +0 -202
  82. package/_gh-pages-deploy/scripts/prettify/lang-css.js +0 -2
  83. package/_gh-pages-deploy/scripts/prettify/prettify.js +0 -28
  84. package/_gh-pages-deploy/scripts/search.js +0 -99
  85. package/_gh-pages-deploy/server_WWebApi.mjs.html +0 -1012
  86. package/_gh-pages-deploy/src_components_Common_CheckYesNo.vue.html +0 -187
  87. package/_gh-pages-deploy/styles/jsdoc.css +0 -776
  88. package/_gh-pages-deploy/styles/prettify.css +0 -80
  89. package/dist/js/app-legacy.15819aa7.js +0 -1
  90. package/dist/js/app.15819aa7.js +0 -1
  91. package/dist/js/chunk-vendors-legacy.c1a32dd1.js +0 -15
  92. package/dist/js/chunk-vendors.c1a32dd1.js +0 -15
  93. package/src/plugins/mDataSelectorSchema.mjs +0 -30
  94. package/src/plugins/mShare.mjs +0 -44
@@ -0,0 +1,2 @@
1
+ {"level":30,"time":1783763274489,"pid":20076,"hostname":"semi-win-dev3","event":"verifyClientUser","from":"getUserByToken","userId":"id-for-admin"}
2
+ {"level":30,"time":1783763274490,"pid":20076,"hostname":"semi-win-dev3","event":"api/getUserByToken","userId":"id-for-admin"}
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "w-web-api",
3
- "version": "1.0.53",
3
+ "version": "1.0.56",
4
4
  "main": "dist/w-web-api.umd.js",
5
5
  "dependencies": {
6
6
  "@mdi/js": "^7.4.47",
7
- "axios": "^1.18.0",
7
+ "axios": "^1.18.1",
8
8
  "lodash-es": "^4.18.1",
9
9
  "showdown": "^2.1.0",
10
- "w-component-vue": "^2.4.66",
11
- "w-data-collector": "^1.0.22",
12
- "w-serv-hapi": "^1.0.72",
13
- "w-serv-orm": "^1.0.38",
14
- "w-ui-loginout": "^1.0.10",
15
- "w-syslog": "^1.0.8",
16
- "wsemi": "^1.8.61"
10
+ "w-component-vue": "^2.4.74",
11
+ "w-data-collector": "^1.0.23",
12
+ "w-serv-hapi": "^1.0.74",
13
+ "w-serv-orm": "^1.0.40",
14
+ "w-ui-loginout": "^1.0.11",
15
+ "w-syslog": "^1.0.10",
16
+ "wsemi": "^1.8.66"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@vue/cli-plugin-babel": "^5.0.8",
@@ -27,18 +27,19 @@
27
27
  "eslint-plugin-promise": "^6.1.1",
28
28
  "eslint-plugin-vue": "^9.10.0",
29
29
  "pixelmatch": "^7.2.0",
30
- "playwright": "^1.61.0",
30
+ "playwright": "^1.61.1",
31
31
  "pngjs": "^7.0.0",
32
32
  "vue": "^2.6.14",
33
33
  "vuex": "^3.6.2",
34
- "w-orm-lmdb": "^1.0.6",
35
- "w-screenctl": "^1.0.3",
36
- "w-package-tools": "^1.1.9"
34
+ "w-orm-lmdb": "^1.0.8",
35
+ "w-screenctl": "^1.0.4",
36
+ "w-package-tools": "^1.1.11"
37
37
  },
38
38
  "scripts": {
39
39
  "serve": "vue-cli-service serve",
40
40
  "build": "vue-cli-service build",
41
- "test": "mocha --timeout 300000",
41
+ "test:unit": "node server/staLogs/test_staEvent.mjs",
42
+ "test": "node server/staLogs/test_staEvent.mjs && mocha --timeout 300000",
42
43
  "lint": "vue-cli-service lint",
43
44
  "deploy": "gh-pages -d docs"
44
45
  },
@@ -230,7 +230,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
230
230
  //params
231
231
  let showLanguage = get(opt, 'showLanguage', 'y')
232
232
  let language = get(opt, 'language', 'eng')
233
- let logFd = get(opt, 'logFd', './_logs') //srLog 寫入 + staEvent 統計共用之 log 資料夾
233
+ let logFd = get(opt, 'logFd', './logs') //srLog 寫入 + staEvent 統計共用之 log 資料夾
234
234
 
235
235
 
236
236
  //kpLangExt
@@ -252,7 +252,9 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
252
252
  wp = WServOrm(ds, WOrm, url, db, optWServOrm)
253
253
  }
254
254
  catch (err) {
255
- console.log(err)
255
+ //ORM 為必要依賴:初始化失敗即 fail-fast,不以 wp={} 帶 undefined woItems/procOrm 殘缺啟動(與建構子其餘必要依賴一致)
256
+ console.log('WServOrm init failed', err)
257
+ throw err
256
258
  }
257
259
  let { woItems, procOrm } = wp
258
260
 
@@ -260,6 +262,9 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
260
262
  //srLog(後端 log;錯誤一律 log err-key 供識別,對齊 w-web-sso)
261
263
  let srLog = srLogInit(opt)
262
264
 
265
+ //maskTok:debug console.log 不印完整 token/authorization(憑證),僅前 4 碼 + ***,避免憑證落入 stdout
266
+ let maskTok = (t) => isestr(t) ? (strleft(t, 4) + '***') : t
267
+
263
268
  //kpFunExt 邊界統一處理:成功記 info(供 staEvent 統計呼叫頻率);catch 後端錯誤 → srLog 該 err-key →
264
269
  //原樣 re-reject(前端依 key 查自己 lang 顯示)。event 統一加 'kpfun-' 前綴 → 專用事件、與 api/verifyConn 區分。
265
270
  let wrapErrLog = (event, fn) => {
@@ -343,14 +348,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
343
348
  }
344
349
 
345
350
  //check
346
- if (true) {
347
- // if (!iseobj(userSelf)) {
348
- // console.log(`token`, token)
349
- // console.log(`can not find the user from token`)
350
- // return Promise.reject('errUserNotFound')
351
- // }
352
- await checkUser(userSelf)
353
- }
351
+ await checkUser(userSelf)
354
352
 
355
353
  return userSelf
356
354
  }
@@ -626,9 +624,8 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
626
624
  //check
627
625
  if (!isestr(token)) {
628
626
  console.log('req.query', get(req, 'query'))
629
- console.log('token', token)
627
+ console.log('token', maskTok(token))
630
628
  console.log('[API]getUserByToken/check token: invalid token')
631
- console.log(`token does not have permission`)
632
629
  return Promise.reject('errTokenNoPermission')
633
630
  }
634
631
 
@@ -637,9 +634,8 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
637
634
 
638
635
  //check
639
636
  if (!iseobj(user)) {
640
- console.log('token', token)
637
+ console.log('token', maskTok(token))
641
638
  console.log('[API]getUserByToken/check user: invalid user')
642
- console.log(`token does not have permission`)
643
639
  return Promise.reject('errTokenNoPermission')
644
640
  }
645
641
 
@@ -674,7 +670,6 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
674
670
  // console.log('req.query', get(req, 'query'))
675
671
  // console.log('token', token)
676
672
  // console.log('[API]getAPIsList/check token: invalid token')
677
- // console.log(`token does not have permission`)
678
673
  // return Promise.reject('errTokenNoPermission')
679
674
  // }
680
675
 
@@ -685,7 +680,6 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
685
680
  // if (!iseobj(user)) {
686
681
  // console.log('token', token)
687
682
  // console.log('[API]getAPIsList/check user: invalid user')
688
- // console.log(`token does not have permission`)
689
683
  // return Promise.reject('errTokenNoPermission')
690
684
  // }
691
685
 
@@ -723,7 +717,6 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
723
717
  // console.log('req.query', get(req, 'query'))
724
718
  // console.log('token', token)
725
719
  // console.log('[API]updateAPIs/check token: invalid token')
726
- // console.log(`token does not have permission`)
727
720
  // return Promise.reject('errTokenNoPermission')
728
721
  // }
729
722
 
@@ -734,7 +727,6 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
734
727
  // if (!iseobj(user)) {
735
728
  // console.log('token', token)
736
729
  // console.log('[API]updateAPIs/check user: invalid user')
737
- // console.log(`token does not have permission`)
738
730
  // return Promise.reject('errTokenNoPermission')
739
731
  // }
740
732
 
@@ -773,9 +765,8 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
773
765
  //check
774
766
  if (!isestr(token)) {
775
767
  console.log('req.query', get(req, 'query'))
776
- console.log('token', token)
768
+ console.log('token', maskTok(token))
777
769
  console.log('[API]syncAndReplaceTabs/check token: invalid token')
778
- console.log(`token does not have permission`)
779
770
  return Promise.reject('errTokenNoPermission')
780
771
  }
781
772
 
@@ -787,8 +778,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
787
778
  console.log('req.query', get(req, 'query'))
788
779
  console.log('keyTable', keyTable)
789
780
  console.log('[API]syncAndReplaceTabs/check keyTable: invalid keyTable')
790
- console.log(`token does not have permission`)
791
- return Promise.reject('errTokenNoPermission')
781
+ return Promise.reject('errKeyTableInvalid')
792
782
  }
793
783
 
794
784
  //check keyTable
@@ -803,9 +793,8 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
803
793
 
804
794
  //check
805
795
  if (!iseobj(user)) {
806
- console.log('token', token)
796
+ console.log('token', maskTok(token))
807
797
  console.log('[API]syncAndReplaceTabs/check user: invalid user')
808
- console.log(`token does not have permission`)
809
798
  return Promise.reject('errTokenNoPermission')
810
799
  }
811
800
 
@@ -814,10 +803,9 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
814
803
 
815
804
  //check userId
816
805
  if (!isestr(userId)) {
817
- console.log('token', token)
806
+ console.log('token', maskTok(token))
818
807
  console.log('user', user)
819
808
  console.log('[API]syncAndReplaceTabs/check userId: invalid userId')
820
- console.log(`token does not have permission`)
821
809
  return Promise.reject('errTokenNoPermission')
822
810
  }
823
811
 
@@ -855,7 +843,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
855
843
 
856
844
  //WServHapiServer
857
845
  instWServHapiServer = new WServHapiServer({
858
- port: opt.serverPort,
846
+ port: serverPort,
859
847
  pathStaticFiles,
860
848
  apiName: 'api',
861
849
  apis,
@@ -870,16 +858,18 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
870
858
  //check
871
859
  if (!isestr(token)) {
872
860
  console.log('apiType', apiType)
873
- console.log('authorization', authorization)
861
+ console.log('authorization', maskTok(authorization))
874
862
  console.log('invalid token')
875
863
  return false
876
864
  }
877
865
 
878
- //checkToken
866
+ //checkToken:套件層連線閘僅做「token 認證」(解析 user + 驗欄位齊備)。**授權**(如限管理員)屬
867
+ //套件使用者之責——由其提供之 getUserByToken(可對非授權者回 {})/ verifyClientUser / verifyAppUser,
868
+ //或 WServOrm 之 useCheckUser/useExcludeWhenNotAdmin 等鉤子決定;套件不對連線層強加特定授權政策(避免越界、
869
+ //保留使用者彈性)。詳見 spec/設計要點與取捨.md「套件授權責任」。
879
870
  let b = await p.checkToken(token)
880
- // console.log('b', b)
881
871
 
882
- srLog.info({ event: 'verifyConn', apiType }) //連線驗證通過(checkToken 失敗時會 throw 不到此)
872
+ srLog.info({ event: 'verifyConn', apiType }) //連線之 token 認證通過(checkToken 失敗會 throw 不到此)
883
873
  return b
884
874
  },
885
875
  getUserIdByToken: async (token) => { //可使用async或sync函數
@@ -915,8 +905,8 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
915
905
  return deleteApi(userId, id)
916
906
  }),
917
907
 
918
- //統計資訊頁:回傳各 event 之時段發生頻率(讀 logFd 之 log)。連線已由 verifyClientUser 限管理員,
919
- //故此處不另檢查(同 getApisList)。timeLength 天數 / timeInterval 'hr'|'day'。
908
+ //統計資訊頁:回傳各 event 之時段發生頻率(讀 logFd 之 log)。連線之 token 認證由 verifyConn 把關;
909
+ //授權(限管理員)屬套件使用者責任(見 verifyConn 註解與設計要點與取捨「套件授權責任」)。timeLength 天數 / timeInterval 'hr'|'day'。
920
910
  getStaEvent: wrapErrLog('getStaEvent', (userId, timeLength = 7, timeInterval = 'hr') => {
921
911
  return staEvent(timeLength, timeInterval, { fdLog: logFd })
922
912
  }),
@@ -26,10 +26,6 @@ let kpLang = {
26
26
  cht: '拒絕登入',
27
27
  },
28
28
 
29
- aggridLanguage: {
30
- eng: 'en',
31
- cht: 'zh-tw',
32
- },
33
29
 
34
30
  systemMessage: {
35
31
  eng: 'System message',
@@ -57,22 +53,10 @@ let kpLang = {
57
53
  cht: '等待數據中...',
58
54
  },
59
55
 
60
- isEditabled: {
61
- eng: 'Editable',
62
- cht: '是否編輯',
63
- },
64
- saveChanges: {
65
- eng: `Save changes`,
66
- cht: `儲存變更`,
67
- },
68
56
  id: {
69
57
  eng: `Id`,
70
58
  cht: `主鍵`,
71
59
  },
72
- from: {
73
- eng: `From`,
74
- cht: `來源`,
75
- },
76
60
  save: {
77
61
  eng: `Save`,
78
62
  cht: `儲存`,
@@ -89,38 +73,6 @@ let kpLang = {
89
73
  eng: `Cancel`,
90
74
  cht: `取消`,
91
75
  },
92
- screenFull: {
93
- eng: `Max size`,
94
- cht: `最大化尺寸`,
95
- },
96
- screenNormal: {
97
- eng: `Normal size`,
98
- cht: `預設尺寸`,
99
- },
100
- checkAllYes: {
101
- eng: `Check all`,
102
- cht: `全選使用`,
103
- },
104
- checkAllNo: {
105
- eng: `Uncheck all`,
106
- cht: `全不選使用`,
107
- },
108
- checkAllInv: {
109
- eng: `Reverse all`,
110
- cht: `全反選使用`,
111
- },
112
- operMode: {
113
- eng: `Enable`,
114
- cht: `是否使用`,
115
- },
116
- operPemiEnable: {
117
- eng: `Operator`,
118
- cht: `合併權限模式`,
119
- },
120
- operGrupEnable: {
121
- eng: `Operator`,
122
- cht: `合併權限群組模式`,
123
- },
124
76
 
125
77
  processing: {
126
78
  eng: `Processing...`,
@@ -130,10 +82,6 @@ let kpLang = {
130
82
  eng: `An unexpected error occurred, please contact the administrator`,
131
83
  cht: `發生非預期錯誤,請洽管理員`,
132
84
  },
133
- unknow: {
134
- eng: `Unknow`,
135
- cht: `未知`,
136
- },
137
85
 
138
86
  username: {
139
87
  eng: `Username`,
@@ -155,14 +103,6 @@ let kpLang = {
155
103
  // eng: `No description`,
156
104
  // cht: `無說明`,
157
105
  // },
158
- modeEdit: {
159
- eng: `Edit mode`,
160
- cht: `編輯模式`,
161
- },
162
- showTabCols: {
163
- eng: `Show columns`,
164
- cht: `顯示欄位`,
165
- },
166
106
 
167
107
  menuTreeShow: {
168
108
  eng: 'Show API list',
@@ -238,14 +178,6 @@ let kpLang = {
238
178
  cht: '回傳數據範例',
239
179
  },
240
180
 
241
- outputMenuTree: {
242
- eng: 'Tree',
243
- cht: '樹狀組件',
244
- },
245
- outputMenuRaw: {
246
- eng: 'Raw',
247
- cht: '原始數據',
248
- },
249
181
 
250
182
  //--- API 管理(展示/編輯/測試)---
251
183
 
@@ -294,14 +226,6 @@ let kpLang = {
294
226
  eng: 'Frequency of backend logged events (APIs, kpFunExt, connection verification).',
295
227
  cht: '後端各事件(API、kpFunExt、連線驗證)之發生頻率統計。',
296
228
  },
297
- eventFrequency: {
298
- eng: 'Event Frequency',
299
- cht: '事件發生頻率',
300
- },
301
- showIndividually: {
302
- eng: 'Show individually',
303
- cht: '個別顯示',
304
- },
305
229
  selectEvents: {
306
230
  eng: 'Select events',
307
231
  cht: '選擇事件',
@@ -338,10 +262,6 @@ let kpLang = {
338
262
  eng: 'Last 1 hr',
339
263
  cht: '最近1時',
340
264
  },
341
- countTotal: {
342
- eng: 'Total',
343
- cht: '總次數',
344
- },
345
265
  timeRange: {
346
266
  eng: 'Time range',
347
267
  cht: '時間範圍',
@@ -371,14 +291,18 @@ let kpLang = {
371
291
  cht: '取得資料失敗',
372
292
  },
373
293
 
294
+ //保留供日後擴充之 UI 標籤鍵(目前無 $t 引用,先以註解保留;需用時取消註解並於對應元件加 $t)。
295
+ //eventFrequency/countTotal/showIndividually 為統計頁可能擴充之標籤;editApiTitle/resStatus 值為依用途重建之起始值,啟用時請確認。
296
+ // eventFrequency: { eng: 'Event Frequency', cht: '事件發生頻率' },
297
+ // countTotal: { eng: 'Total', cht: '總次數' },
298
+ // showIndividually: { eng: 'Show individually', cht: '個別顯示' },
299
+ // editApiTitle: { eng: 'Edit API', cht: '編輯API' },
300
+ // resStatus: { eng: 'Status', cht: '狀態碼' },
301
+
374
302
  addApi: {
375
303
  eng: 'New API',
376
304
  cht: '新增API',
377
305
  },
378
- editApiTitle: {
379
- eng: 'Edit API',
380
- cht: '編輯API',
381
- },
382
306
  confirmDeleteApi: {
383
307
  eng: 'Are you sure you want to delete this API?',
384
308
  cht: '確定要刪除此API?',
@@ -537,10 +461,6 @@ let kpLang = {
537
461
  eng: 'Response',
538
462
  cht: '回應',
539
463
  },
540
- resStatus: {
541
- eng: 'Status',
542
- cht: '狀態碼',
543
- },
544
464
  resTime: {
545
465
  eng: 'Time',
546
466
  cht: '耗時',
@@ -13,101 +13,101 @@ function procProxy(opt = {}) {
13
13
  let isAllowTarget = get(opt, 'isAllowTarget', null)
14
14
 
15
15
 
16
- let proxyRequest = (inp = {}) => {
17
- return new Promise(async (resolve, reject) => {
18
-
19
- //method
20
- let method = get(inp, 'method', 'GET')
21
- if (!isestr(method)) {
22
- method = 'GET'
23
- }
24
- method = method.toUpperCase()
25
-
26
- //url
27
- let url = get(inp, 'url', '')
28
- if (!isestr(url)) {
29
- reject('errReqUrlInvalid')
30
- return
31
- }
32
- if (!url.startsWith('http://') && !url.startsWith('https://')) {
33
- reject('errReqUrlInvalid')
34
- return
35
- }
36
-
37
- //isAllowTarget check
38
- if (typeof isAllowTarget === 'function') {
39
- let allowed = isAllowTarget(url)
40
- if (allowed === false) {
41
- reject('errReqTargetNotAllowed')
42
- return
43
- }
16
+ let proxyRequest = async (inp = {}) => {
17
+
18
+ //method
19
+ let method = get(inp, 'method', 'GET')
20
+ if (!isestr(method)) {
21
+ method = 'GET'
22
+ }
23
+ method = method.toUpperCase()
24
+
25
+ //url
26
+ let url = get(inp, 'url', '')
27
+ if (!isestr(url)) {
28
+ return Promise.reject('errReqUrlInvalid')
29
+ }
30
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
31
+ return Promise.reject('errReqUrlInvalid')
32
+ }
33
+
34
+ //isAllowTarget check
35
+ if (typeof isAllowTarget === 'function') {
36
+ let allowed = isAllowTarget(url)
37
+ if (allowed === false) {
38
+ return Promise.reject('errReqTargetNotAllowed')
44
39
  }
45
-
46
- //headers
47
- let headers = get(inp, 'headers', {})
48
- if (!isobj(headers)) {
49
- headers = {}
50
- }
51
- //移除 host 與 content-length (交給 axios)
52
- delete headers['host']
53
- delete headers['Host']
54
- delete headers['content-length']
55
- delete headers['Content-Length']
56
-
57
- //query
58
- let query = get(inp, 'query', null)
59
- let params = null
60
- if (isobj(query) && Object.keys(query).length > 0) {
61
- params = query
62
- }
63
-
64
- //body
65
- let body = get(inp, 'body', undefined)
66
- let data = undefined
67
- if (method !== 'GET' && method !== 'HEAD') {
68
- if (body !== undefined) {
69
- data = body
70
- }
40
+ }
41
+
42
+ //headers
43
+ let headers = get(inp, 'headers', {})
44
+ if (!isobj(headers)) {
45
+ headers = {}
46
+ }
47
+
48
+ //移除 host 與 content-length (交給 axios)
49
+ delete headers['host']
50
+ delete headers['Host']
51
+ delete headers['content-length']
52
+ delete headers['Content-Length']
53
+
54
+ //query
55
+ let query = get(inp, 'query', null)
56
+ let params = null
57
+ if (isobj(query) && Object.keys(query).length > 0) {
58
+ params = query
59
+ }
60
+
61
+ //body
62
+ let body = get(inp, 'body', undefined)
63
+ let data = undefined
64
+ if (method !== 'GET' && method !== 'HEAD') {
65
+ if (body !== undefined) {
66
+ data = body
71
67
  }
72
-
73
- //timeout
74
- let timeout = get(inp, 'timeout', 30000)
75
- if (typeof timeout !== 'number' || timeout <= 0) {
76
- timeout = 30000
68
+ }
69
+
70
+ //timeout
71
+ let timeout = get(inp, 'timeout', 30000)
72
+ if (typeof timeout !== 'number' || timeout <= 0) {
73
+ timeout = 30000
74
+ }
75
+
76
+ //send
77
+ let r
78
+ let t0 = Date.now()
79
+ try {
80
+ let res = await axios({
81
+ method,
82
+ url,
83
+ headers,
84
+ params,
85
+ data,
86
+ timeout,
87
+ validateStatus: () => true,
88
+ })
89
+
90
+ let durationMs = Date.now() - t0
91
+
92
+ //r
93
+ r = {
94
+ status: res.status,
95
+ statusText: res.statusText,
96
+ headers: res.headers,
97
+ data: res.data,
98
+ durationMs,
77
99
  }
78
100
 
79
- //send
80
- let t0 = Date.now()
81
- try {
82
- let res = await axios({
83
- method,
84
- url,
85
- headers,
86
- params,
87
- data,
88
- timeout,
89
- validateStatus: () => true,
90
- })
91
-
92
- let durationMs = Date.now() - t0
93
-
94
- resolve({
95
- status: res.status,
96
- statusText: res.statusText,
97
- headers: res.headers,
98
- data: res.data,
99
- durationMs,
100
- })
101
- }
102
- catch (err) {
103
- //axios 例外(timeout/DNS 等):log 真實訊息供排查,但回前端只給 err-key(前端依 lang 顯示)
104
- if (srLog) {
105
- srLog.error({ event: 'proxyRequest-axios', msg: get(err, 'message', String(err)) })
106
- }
107
- reject('errProxyRequestFailed')
101
+ }
102
+ catch (err) {
103
+ //axios 例外(timeout/DNS 等):log 真實訊息供排查,但回前端只給 err-key(前端依 lang 顯示)
104
+ if (srLog) {
105
+ srLog.error({ event: 'proxyRequest-axios', msg: get(err, 'message', String(err)) })
108
106
  }
107
+ return Promise.reject('errProxyRequestFailed')
108
+ }
109
109
 
110
- })
110
+ return r
111
111
  }
112
112
 
113
113
 
package/server/srLog.mjs CHANGED
@@ -3,14 +3,14 @@ import isestr from 'wsemi/src/isestr.mjs'
3
3
  import WSyslog from 'w-syslog/src/WSyslog.mjs'
4
4
 
5
5
 
6
- //後端 log(比照 w-web-sso server/srLog.mjs):包 w-syslog,依時段輪檔寫入 ./_logs
6
+ //後端 log(比照 w-web-sso server/srLog.mjs):包 w-syslog,依時段輪檔寫入 ./logs
7
7
  //用法:srLog.info({ event, ... }) / srLog.warn({...}) / srLog.error({ event, err, ... })。
8
8
  //錯誤一律 log 其 err key(對齊「後端錯誤須 log err key」原則),供事後識別。
9
9
  let init = (opt = {}) => {
10
10
 
11
11
  let fdLog = get(opt, 'logFd', '')
12
12
  if (!isestr(fdLog)) {
13
- fdLog = './_logs'
13
+ fdLog = './logs'
14
14
  }
15
15
 
16
16
  let interval = get(opt, 'logInterval', '')
@@ -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 `t.isAfter(tStart)` 仍為最終判準 → 輸出語意零改變.
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