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.
- package/README.md +1 -1
- package/dist/css/{app.fe69497a.css → app.cad4a1ca.css} +1 -1
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.7687b23b.js +1 -0
- package/dist/js/app.7687b23b.js +1 -0
- package/dist/js/chunk-vendors-legacy.4c27cf51.js +15 -0
- package/dist/js/chunk-vendors.4c27cf51.js +15 -0
- package/dist/w-web-api.umd.js +2 -2
- package/dist/w-web-api.umd.js.map +1 -1
- package/docs/WWebApi.html +1 -1
- package/docs/index.html +1 -1
- package/docs/server_WWebApi.mjs.html +24 -34
- package/docs/src_components_Common_CheckYesNo.vue.html +1 -1
- package/logs/2026-06-19T16.log +6 -0
- package/logs/2026-06-20T11.log +4 -0
- package/logs/2026-06-20T14.log +2 -0
- package/logs/2026-06-21T20.log +1 -0
- package/logs/2026-06-21T21.log +4 -0
- package/logs/2026-06-22T07.log +12 -0
- package/logs/2026-06-22T08.log +808 -0
- package/logs/2026-06-22T09.log +104 -0
- package/logs/2026-06-22T10.log +26 -0
- package/logs/2026-06-22T11.log +860 -0
- package/logs/2026-06-22T12.log +430 -0
- package/logs/2026-06-22T23.log +1547 -0
- package/logs/2026-06-23T00.log +473 -0
- package/logs/2026-06-23T08.log +231 -0
- package/logs/2026-06-23T09.log +179 -0
- package/logs/2026-07-01T11.log +488 -0
- package/logs/2026-07-09T22.log +370 -0
- package/logs/2026-07-09T23.log +370 -0
- package/logs/2026-07-10T12.log +284 -0
- package/logs/2026-07-10T13.log +777 -0
- package/logs/2026-07-11T17.log +2 -0
- package/package.json +15 -14
- package/server/WWebApi.mjs +23 -33
- package/server/procLang.mjs +8 -88
- package/server/procProxy.mjs +89 -89
- package/server/srLog.mjs +2 -2
- package/server/staLogs/filterVpfsByWindow.mjs +27 -0
- package/server/staLogs/staEvent.mjs +4 -2
- package/server/staLogs/test_staEvent.mjs +67 -1
- package/settings.json +4 -0
- package/src/components/Layout.vue +1 -20
- package/src/components/LayoutContent.vue +0 -26
- package/src/components/LayoutContentEdit.vue +2 -2
- package/src/components/LayoutContentTest.vue +1 -19
- package/src/main.js +3 -8
- package/src/plugins/mUI.mjs +0 -35
- package/src/store/mutations.mjs +0 -14
- package/src/store/types.mjs +0 -3
- package/srv.mjs +6 -4
- package/tableTags.json +1 -1
- package/z/344/277/256/346/255/243/350/246/217/345/212/203/346/233/270.md +246 -0
- package/_gh-pages-deploy/.nojekyll +0 -0
- package/_gh-pages-deploy/WWebApi.html +0 -898
- 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 +0 -978
- 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 +0 -1049
- 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 +0 -84
- package/_gh-pages-deploy/scripts/collapse.js +0 -39
- package/_gh-pages-deploy/scripts/commonNav.js +0 -28
- package/_gh-pages-deploy/scripts/linenumber.js +0 -25
- package/_gh-pages-deploy/scripts/nav.js +0 -12
- package/_gh-pages-deploy/scripts/polyfill.js +0 -4
- package/_gh-pages-deploy/scripts/prettify/Apache-License-2.0.txt +0 -202
- package/_gh-pages-deploy/scripts/prettify/lang-css.js +0 -2
- package/_gh-pages-deploy/scripts/prettify/prettify.js +0 -28
- package/_gh-pages-deploy/scripts/search.js +0 -99
- package/_gh-pages-deploy/server_WWebApi.mjs.html +0 -1012
- package/_gh-pages-deploy/src_components_Common_CheckYesNo.vue.html +0 -187
- package/_gh-pages-deploy/styles/jsdoc.css +0 -776
- package/_gh-pages-deploy/styles/prettify.css +0 -80
- package/dist/js/app-legacy.15819aa7.js +0 -1
- package/dist/js/app.15819aa7.js +0 -1
- package/dist/js/chunk-vendors-legacy.c1a32dd1.js +0 -15
- package/dist/js/chunk-vendors.c1a32dd1.js +0 -15
- package/src/plugins/mDataSelectorSchema.mjs +0 -30
- package/src/plugins/mShare.mjs +0 -44
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "w-web-api",
|
|
3
|
-
"version": "1.0.
|
|
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.
|
|
7
|
+
"axios": "^1.18.1",
|
|
8
8
|
"lodash-es": "^4.18.1",
|
|
9
9
|
"showdown": "^2.1.0",
|
|
10
|
-
"w-component-vue": "^2.4.
|
|
11
|
-
"w-data-collector": "^1.0.
|
|
12
|
-
"w-serv-hapi": "^1.0.
|
|
13
|
-
"w-serv-orm": "^1.0.
|
|
14
|
-
"w-ui-loginout": "^1.0.
|
|
15
|
-
"w-syslog": "^1.0.
|
|
16
|
-
"wsemi": "^1.8.
|
|
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.
|
|
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.
|
|
35
|
-
"w-screenctl": "^1.0.
|
|
36
|
-
"w-package-tools": "^1.1.
|
|
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": "
|
|
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
|
},
|
package/server/WWebApi.mjs
CHANGED
|
@@ -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', './
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 })
|
|
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
|
|
919
|
-
|
|
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
|
}),
|
package/server/procLang.mjs
CHANGED
|
@@ -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: '耗時',
|
package/server/procProxy.mjs
CHANGED
|
@@ -13,101 +13,101 @@ function procProxy(opt = {}) {
|
|
|
13
13
|
let isAllowTarget = get(opt, 'isAllowTarget', null)
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
let proxyRequest = (inp = {}) => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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,依時段輪檔寫入 ./
|
|
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 = './
|
|
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
|