w-web-api 1.0.52 → 1.0.55
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.cad4a1ca.css +1 -0
- package/dist/index.tmp +4 -2
- 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 +2 -2
- package/docs/index.html +1 -1
- package/docs/{WWebApi.mjs.html → server_WWebApi.mjs.html} +113 -66
- package/docs/src_components_Common_CheckYesNo.vue.html +187 -0
- package/g.getSettings.mjs +3 -0
- 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 +20 -15
- package/public/index.html +9 -2
- package/server/WWebApi.mjs +110 -63
- package/server/procApis.mjs +62 -0
- package/server/procLang.mjs +372 -66
- package/server/procProxy.mjs +120 -0
- package/server/srLog.mjs +30 -0
- package/server/staLogs/filterVpfsByWindow.mjs +27 -0
- package/server/staLogs/staEvent.mjs +131 -0
- package/server/staLogs/test_staEvent.mjs +198 -0
- package/settings.json +18 -0
- package/src/App.vue +11 -7
- package/src/assets/scalar-ui.css +113 -0
- package/src/components/Common/CheckYes.vue +102 -0
- package/src/components/Common/CheckYesNo.vue +115 -0
- package/src/components/Common/LoadingWinBar.vue +114 -0
- package/src/components/Layout.vue +8 -26
- package/src/components/LayoutContent.vue +299 -350
- package/src/components/LayoutContentEdit.vue +577 -0
- package/src/components/LayoutContentStats.vue +485 -0
- package/src/components/LayoutContentTest.vue +781 -0
- package/src/components/MdPanel.vue +80 -58
- package/src/main.js +11 -8
- package/src/plugins/mUI.mjs +19 -39
- package/src/schema/tables/apis.mjs +30 -0
- package/src/store/mutations.mjs +0 -14
- package/src/store/types.mjs +0 -3
- package/srv.mjs +50 -20
- 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/.github/workflows/ci-test.yml +0 -24
- package/.vscode/launch.json +0 -18
- package/db/wapis/apis/data.mdb +0 -0
- package/db/wapis/apis/lock.mdb +0 -0
- package/dist/css/app.b5cf2ef5.css +0 -1
- package/dist/js/app-legacy.927b5679.js +0 -1
- package/dist/js/app.927b5679.js +0 -1
- package/dist/js/chunk-vendors-legacy.ffb0c6c5.js +0 -13
- package/dist/js/chunk-vendors.ffb0c6c5.js +0 -13
- package/src/plugins/mDataSelectorSchema.mjs +0 -30
- package/src/plugins/mShare.mjs +0 -44
- package/tableTags-web-api.json +0 -1
- package/test/all.test.mjs +0 -10
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,13 +178,379 @@ let kpLang = {
|
|
|
238
178
|
cht: '回傳數據範例',
|
|
239
179
|
},
|
|
240
180
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
181
|
+
|
|
182
|
+
//--- API 管理(展示/編輯/測試)---
|
|
183
|
+
|
|
184
|
+
docInput: {
|
|
185
|
+
eng: 'Input',
|
|
186
|
+
cht: '輸入參數',
|
|
187
|
+
},
|
|
188
|
+
docOutputFields: {
|
|
189
|
+
eng: 'Output fields',
|
|
190
|
+
cht: '回傳欄位',
|
|
191
|
+
},
|
|
192
|
+
docRequest: {
|
|
193
|
+
eng: 'Request',
|
|
194
|
+
cht: '請求',
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
tabDocs: {
|
|
198
|
+
eng: 'Docs',
|
|
199
|
+
cht: '文件',
|
|
200
|
+
},
|
|
201
|
+
tabEdit: {
|
|
202
|
+
eng: 'Edit',
|
|
203
|
+
cht: '編輯',
|
|
204
|
+
},
|
|
205
|
+
tabTest: {
|
|
206
|
+
eng: 'Test',
|
|
207
|
+
cht: '測試',
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
//左側垂直主選單
|
|
211
|
+
mmApi: {
|
|
212
|
+
eng: 'API',
|
|
213
|
+
cht: 'API',
|
|
214
|
+
},
|
|
215
|
+
mmStaInfor: {
|
|
216
|
+
eng: 'Statistics',
|
|
217
|
+
cht: '統計資訊',
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
//統計資訊頁
|
|
221
|
+
statisticsInformation: {
|
|
222
|
+
eng: 'Statistics Information',
|
|
223
|
+
cht: '統計資訊',
|
|
224
|
+
},
|
|
225
|
+
statisticsInformationDescription: {
|
|
226
|
+
eng: 'Frequency of backend logged events (APIs, kpFunExt, connection verification).',
|
|
227
|
+
cht: '後端各事件(API、kpFunExt、連線驗證)之發生頻率統計。',
|
|
228
|
+
},
|
|
229
|
+
selectEvents: {
|
|
230
|
+
eng: 'Select events',
|
|
231
|
+
cht: '選擇事件',
|
|
232
|
+
},
|
|
233
|
+
selectAll: {
|
|
234
|
+
eng: 'All',
|
|
235
|
+
cht: '全選',
|
|
236
|
+
},
|
|
237
|
+
selectNone: {
|
|
238
|
+
eng: 'Clear',
|
|
239
|
+
cht: '清除',
|
|
240
|
+
},
|
|
241
|
+
eventStatsTable: {
|
|
242
|
+
eng: 'Event Statistics',
|
|
243
|
+
cht: '事件統計表',
|
|
244
|
+
},
|
|
245
|
+
colEvent: {
|
|
246
|
+
eng: 'Event',
|
|
247
|
+
cht: '事件',
|
|
248
|
+
},
|
|
249
|
+
colRecent1day: {
|
|
250
|
+
eng: 'Last 1 day',
|
|
251
|
+
cht: '最近1日',
|
|
252
|
+
},
|
|
253
|
+
colRecent8hr: {
|
|
254
|
+
eng: 'Last 8 hr',
|
|
255
|
+
cht: '最近8時',
|
|
256
|
+
},
|
|
257
|
+
colRecent4hr: {
|
|
258
|
+
eng: 'Last 4 hr',
|
|
259
|
+
cht: '最近4時',
|
|
260
|
+
},
|
|
261
|
+
colRecent1hr: {
|
|
262
|
+
eng: 'Last 1 hr',
|
|
263
|
+
cht: '最近1時',
|
|
264
|
+
},
|
|
265
|
+
timeRange: {
|
|
266
|
+
eng: 'Time range',
|
|
267
|
+
cht: '時間範圍',
|
|
268
|
+
},
|
|
269
|
+
selectItem1hr: {
|
|
270
|
+
eng: '1 hour',
|
|
271
|
+
cht: '1小時',
|
|
272
|
+
},
|
|
273
|
+
selectItem4hr: {
|
|
274
|
+
eng: '4 hours',
|
|
275
|
+
cht: '4小時',
|
|
276
|
+
},
|
|
277
|
+
selectItem8hr: {
|
|
278
|
+
eng: '8 hours',
|
|
279
|
+
cht: '8小時',
|
|
280
|
+
},
|
|
281
|
+
selectItem1day: {
|
|
282
|
+
eng: '1 day',
|
|
283
|
+
cht: '1天',
|
|
284
|
+
},
|
|
285
|
+
noStaData: {
|
|
286
|
+
eng: 'No statistics data',
|
|
287
|
+
cht: '尚無統計資料',
|
|
288
|
+
},
|
|
289
|
+
getDataError: {
|
|
290
|
+
eng: 'Failed to get data',
|
|
291
|
+
cht: '取得資料失敗',
|
|
292
|
+
},
|
|
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
|
+
|
|
302
|
+
addApi: {
|
|
303
|
+
eng: 'New API',
|
|
304
|
+
cht: '新增API',
|
|
305
|
+
},
|
|
306
|
+
confirmDeleteApi: {
|
|
307
|
+
eng: 'Are you sure you want to delete this API?',
|
|
308
|
+
cht: '確定要刪除此API?',
|
|
309
|
+
},
|
|
310
|
+
saveSuccess: {
|
|
311
|
+
eng: 'Saved',
|
|
312
|
+
cht: '已儲存',
|
|
313
|
+
},
|
|
314
|
+
deleteSuccess: {
|
|
315
|
+
eng: 'Deleted',
|
|
316
|
+
cht: '已刪除',
|
|
317
|
+
},
|
|
318
|
+
searchApiPlaceholder: {
|
|
319
|
+
eng: 'Search API...',
|
|
320
|
+
cht: '搜尋API...',
|
|
321
|
+
},
|
|
322
|
+
|
|
323
|
+
fieldMethod: {
|
|
324
|
+
eng: 'Method',
|
|
325
|
+
cht: '方法',
|
|
326
|
+
},
|
|
327
|
+
fieldGroup: {
|
|
328
|
+
eng: 'Group',
|
|
329
|
+
cht: '群組',
|
|
330
|
+
},
|
|
331
|
+
fieldOrder: {
|
|
332
|
+
eng: 'Order',
|
|
333
|
+
cht: '順序',
|
|
334
|
+
},
|
|
335
|
+
fieldTestBaseUrl: {
|
|
336
|
+
eng: 'Test base URL',
|
|
337
|
+
cht: '測試基底網址',
|
|
338
|
+
},
|
|
339
|
+
fieldAuthType: {
|
|
340
|
+
eng: 'Auth type',
|
|
341
|
+
cht: '認證類型',
|
|
342
|
+
},
|
|
343
|
+
fieldAuthConfigJson: {
|
|
344
|
+
eng: 'Auth config (JSON)',
|
|
345
|
+
cht: '認證設定(JSON)',
|
|
346
|
+
},
|
|
347
|
+
fieldDefaultHeadersJson: {
|
|
348
|
+
eng: 'Default headers (JSON)',
|
|
349
|
+
cht: '預設標頭(JSON)',
|
|
350
|
+
},
|
|
351
|
+
fieldDefaultQueryJson: {
|
|
352
|
+
eng: 'Default query (JSON)',
|
|
353
|
+
cht: '預設查詢參數(JSON)',
|
|
354
|
+
},
|
|
355
|
+
fieldDefaultBodyJson: {
|
|
356
|
+
eng: 'Default body (JSON)',
|
|
357
|
+
cht: '預設請求內容(JSON)',
|
|
358
|
+
},
|
|
359
|
+
fieldContentType: {
|
|
360
|
+
eng: 'Content type',
|
|
361
|
+
cht: '內容類型',
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
hintTestBaseUrl: {
|
|
365
|
+
eng: 'Fallback request URL, used only when API url is empty. e.g. https://api.example.com',
|
|
366
|
+
cht: '送請求的後備網址:「API url」有填則優先,留空時才用此值。範例:https://api.example.com',
|
|
367
|
+
},
|
|
368
|
+
hintAuthType: {
|
|
369
|
+
eng: 'Auth auto-applied when sending. none = none; bearer / apikey / basic are built from the Auth config below.',
|
|
370
|
+
cht: '送出時自動帶入的認證方式。none=不帶;bearer / apikey / basic 依下方「認證設定(JSON)」組出。',
|
|
371
|
+
},
|
|
372
|
+
hintAuthConfigJson: {
|
|
373
|
+
eng: 'JSON by auth type:\nbearer → {"token":"your-token"}\napikey → {"name":"X-API-Key","value":"your-key","in":"header"} (in: header or query)\nbasic → {"username":"user","password":"pass"}',
|
|
374
|
+
cht: '依認證類型填 JSON:\nbearer → {"token":"你的token"}(留空則退用上方 tokens 欄第一個)\napikey → {"name":"X-API-Key","value":"你的key","in":"header"}(in 可為 header 或 query)\nbasic → {"username":"帳號","password":"密碼"}',
|
|
375
|
+
},
|
|
376
|
+
hintDefaultHeadersJson: {
|
|
377
|
+
eng: 'Default request headers, a JSON object. e.g. {"Accept":"application/json"}',
|
|
378
|
+
cht: '預設請求標頭,JSON 物件。範例:{"Accept":"application/json"}',
|
|
379
|
+
},
|
|
380
|
+
hintDefaultQueryJson: {
|
|
381
|
+
eng: 'Default query params, a JSON object. e.g. {"page":"1","size":"20"}',
|
|
382
|
+
cht: '預設查詢參數,JSON 物件。範例:{"page":"1","size":"20"}',
|
|
383
|
+
},
|
|
384
|
+
hintDefaultBodyJson: {
|
|
385
|
+
eng: 'Default request body (for POST/PUT); falls back to Input example if empty. e.g. {"name":"foo"}',
|
|
386
|
+
cht: '預設請求內容(POST/PUT 用),留空則退用「輸入範例」。範例:{"name":"小明"}',
|
|
387
|
+
},
|
|
388
|
+
hintContentType: {
|
|
389
|
+
eng: 'Request content type, added as a Content-Type header. e.g. application/json',
|
|
390
|
+
cht: '請求內容類型,會帶成 Content-Type 標頭。範例:application/json',
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
authNone: {
|
|
394
|
+
eng: 'None',
|
|
395
|
+
cht: '無',
|
|
396
|
+
},
|
|
397
|
+
authBearer: {
|
|
398
|
+
eng: 'Bearer token',
|
|
399
|
+
cht: 'Bearer token',
|
|
400
|
+
},
|
|
401
|
+
authApikey: {
|
|
402
|
+
eng: 'API key',
|
|
403
|
+
cht: 'API key',
|
|
244
404
|
},
|
|
245
|
-
|
|
246
|
-
eng: '
|
|
247
|
-
cht: '
|
|
405
|
+
authBasic: {
|
|
406
|
+
eng: 'Basic',
|
|
407
|
+
cht: 'Basic',
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
secBasic: {
|
|
411
|
+
eng: 'Basic',
|
|
412
|
+
cht: '基本資訊',
|
|
413
|
+
},
|
|
414
|
+
secDocs: {
|
|
415
|
+
eng: 'Documentation',
|
|
416
|
+
cht: '文件說明',
|
|
417
|
+
},
|
|
418
|
+
secTest: {
|
|
419
|
+
eng: 'Test settings',
|
|
420
|
+
cht: '測試設定',
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
reqSend: {
|
|
424
|
+
eng: 'Send',
|
|
425
|
+
cht: '送出請求',
|
|
426
|
+
},
|
|
427
|
+
reqUrlPlaceholder: {
|
|
428
|
+
eng: 'Request URL',
|
|
429
|
+
cht: '請求網址',
|
|
430
|
+
},
|
|
431
|
+
reqQuery: {
|
|
432
|
+
eng: 'Query params',
|
|
433
|
+
cht: '查詢參數',
|
|
434
|
+
},
|
|
435
|
+
reqHeaders: {
|
|
436
|
+
eng: 'Headers',
|
|
437
|
+
cht: '標頭',
|
|
438
|
+
},
|
|
439
|
+
reqBody: {
|
|
440
|
+
eng: 'Body',
|
|
441
|
+
cht: '請求內容',
|
|
442
|
+
},
|
|
443
|
+
reqAddRow: {
|
|
444
|
+
eng: 'Add',
|
|
445
|
+
cht: '新增一列',
|
|
446
|
+
},
|
|
447
|
+
colKey: {
|
|
448
|
+
eng: 'Key',
|
|
449
|
+
cht: '鍵',
|
|
450
|
+
},
|
|
451
|
+
colValue: {
|
|
452
|
+
eng: 'Value',
|
|
453
|
+
cht: '值',
|
|
454
|
+
},
|
|
455
|
+
colOn: {
|
|
456
|
+
eng: 'On',
|
|
457
|
+
cht: '啟用',
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
resTitle: {
|
|
461
|
+
eng: 'Response',
|
|
462
|
+
cht: '回應',
|
|
463
|
+
},
|
|
464
|
+
resTime: {
|
|
465
|
+
eng: 'Time',
|
|
466
|
+
cht: '耗時',
|
|
467
|
+
},
|
|
468
|
+
resHeaders: {
|
|
469
|
+
eng: 'Response headers',
|
|
470
|
+
cht: '回應標頭',
|
|
471
|
+
},
|
|
472
|
+
resBody: {
|
|
473
|
+
eng: 'Response body',
|
|
474
|
+
cht: '回應內容',
|
|
475
|
+
},
|
|
476
|
+
resEmpty: {
|
|
477
|
+
eng: 'No response yet. Click Send to test.',
|
|
478
|
+
cht: '尚無回應,點擊送出請求進行測試。',
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
valRequired: {
|
|
482
|
+
eng: 'This field is required',
|
|
483
|
+
cht: '此欄位必填',
|
|
484
|
+
},
|
|
485
|
+
valInvalidJson: {
|
|
486
|
+
eng: 'Invalid JSON format',
|
|
487
|
+
cht: 'JSON格式錯誤',
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
//後端錯誤 err-key(後端 reject 此 key、srLog 此 key;前端依當前 lang 反查下列文字顯示)
|
|
491
|
+
errReqUrlInvalid: {
|
|
492
|
+
eng: 'The request URL is invalid (must start with http:// or https://)',
|
|
493
|
+
cht: '請求網址無效(須以 http:// 或 https:// 開頭)',
|
|
494
|
+
},
|
|
495
|
+
errReqTargetNotAllowed: {
|
|
496
|
+
eng: 'The target address is not allowed',
|
|
497
|
+
cht: '目標位址不被允許',
|
|
498
|
+
},
|
|
499
|
+
errProxyRequestFailed: {
|
|
500
|
+
eng: 'The request failed to send',
|
|
501
|
+
cht: '請求發送失敗',
|
|
502
|
+
},
|
|
503
|
+
errApiRowInvalid: {
|
|
504
|
+
eng: 'Invalid API data',
|
|
505
|
+
cht: 'API 資料無效',
|
|
506
|
+
},
|
|
507
|
+
errApiIdInvalid: {
|
|
508
|
+
eng: 'Invalid API id',
|
|
509
|
+
cht: 'API id 無效',
|
|
510
|
+
},
|
|
511
|
+
errUserIdMissing: {
|
|
512
|
+
eng: 'Unable to get user id',
|
|
513
|
+
cht: '無法取得使用者 id',
|
|
514
|
+
},
|
|
515
|
+
errUserEmailMissing: {
|
|
516
|
+
eng: 'Unable to get user email',
|
|
517
|
+
cht: '無法取得使用者 email',
|
|
518
|
+
},
|
|
519
|
+
errUserNameMissing: {
|
|
520
|
+
eng: 'Unable to get user name',
|
|
521
|
+
cht: '無法取得使用者名稱',
|
|
522
|
+
},
|
|
523
|
+
errUserRoleMissing: {
|
|
524
|
+
eng: 'Unable to get user role',
|
|
525
|
+
cht: '無法取得使用者權限',
|
|
526
|
+
},
|
|
527
|
+
errUserNoPermission: {
|
|
528
|
+
eng: 'User does not have permission',
|
|
529
|
+
cht: '使用者沒有權限',
|
|
530
|
+
},
|
|
531
|
+
errUserNotFound: {
|
|
532
|
+
eng: 'Unable to find user from token',
|
|
533
|
+
cht: '無法依 token 找到使用者',
|
|
534
|
+
},
|
|
535
|
+
errPayloadInvalid: {
|
|
536
|
+
eng: 'Invalid request payload',
|
|
537
|
+
cht: '請求內容無效',
|
|
538
|
+
},
|
|
539
|
+
errGroupInvalid: {
|
|
540
|
+
eng: 'Invalid group',
|
|
541
|
+
cht: '群組無效',
|
|
542
|
+
},
|
|
543
|
+
errRowsInvalid: {
|
|
544
|
+
eng: 'Invalid rows',
|
|
545
|
+
cht: '資料列無效',
|
|
546
|
+
},
|
|
547
|
+
errTokenNoPermission: {
|
|
548
|
+
eng: 'Token does not have permission',
|
|
549
|
+
cht: 'token 沒有權限',
|
|
550
|
+
},
|
|
551
|
+
errKeyTableInvalid: {
|
|
552
|
+
eng: 'Invalid table',
|
|
553
|
+
cht: '資料表無效',
|
|
248
554
|
},
|
|
249
555
|
|
|
250
556
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import get from 'lodash-es/get.js'
|
|
2
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
3
|
+
import isobj from 'wsemi/src/isobj.mjs'
|
|
4
|
+
import axios from 'axios'
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
function procProxy(opt = {}) {
|
|
8
|
+
|
|
9
|
+
//srLog(後端 log;錯誤一律 log err-key,axios 例外另 log 真實訊息供排查)
|
|
10
|
+
let srLog = get(opt, 'srLog', null)
|
|
11
|
+
|
|
12
|
+
//isAllowTarget
|
|
13
|
+
let isAllowTarget = get(opt, 'isAllowTarget', null)
|
|
14
|
+
|
|
15
|
+
|
|
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')
|
|
39
|
+
}
|
|
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
|
|
67
|
+
}
|
|
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,
|
|
99
|
+
}
|
|
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
|
+
return Promise.reject('errProxyRequestFailed')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return r
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
proxyRequest,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
export default procProxy
|
package/server/srLog.mjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import get from 'lodash-es/get.js'
|
|
2
|
+
import isestr from 'wsemi/src/isestr.mjs'
|
|
3
|
+
import WSyslog from 'w-syslog/src/WSyslog.mjs'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
//後端 log(比照 w-web-sso server/srLog.mjs):包 w-syslog,依時段輪檔寫入 ./logs。
|
|
7
|
+
//用法:srLog.info({ event, ... }) / srLog.warn({...}) / srLog.error({ event, err, ... })。
|
|
8
|
+
//錯誤一律 log 其 err key(對齊「後端錯誤須 log err key」原則),供事後識別。
|
|
9
|
+
let init = (opt = {}) => {
|
|
10
|
+
|
|
11
|
+
let fdLog = get(opt, 'logFd', '')
|
|
12
|
+
if (!isestr(fdLog)) {
|
|
13
|
+
fdLog = './logs'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let interval = get(opt, 'logInterval', '')
|
|
17
|
+
if (!isestr(interval)) {
|
|
18
|
+
interval = 'hr'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let srLog = WSyslog({
|
|
22
|
+
fdLog,
|
|
23
|
+
interval,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return srLog
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export default init
|
|
@@ -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
|