w-web-api 1.0.82 → 1.0.84
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/dist/css/{app.9fbdb016.css → app.6da12a32.css} +1 -1
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.bc36c734.js +1 -0
- package/dist/js/app.bc36c734.js +1 -0
- package/dist/js/{chunk-vendors.de0404f6.js → chunk-vendors-legacy.ef3795e3.js} +3 -3
- package/dist/js/{chunk-vendors-legacy.de0404f6.js → chunk-vendors.ef3795e3.js} +3 -3
- package/dist/w-web-api.umd.js +2 -2
- package/dist/w-web-api.umd.js.map +1 -1
- package/docs/WWebApi.html +42 -2
- package/docs/index.html +1 -1
- package/docs/server_WWebApi.mjs.html +101 -18
- package/docs/src_components_Common_CheckYesNo.vue.html +1 -1
- package/package.json +14 -18
- package/server/WWebApi.mjs +100 -17
- package/server/procApis.mjs +3 -1
- package/server/procLang.mjs +12 -0
- package/server/procProxy.mjs +183 -41
- package/server/provideTabs.mjs +9 -8
- package/server/staLogs/staLogsCore.mjs +441 -420
- package/src/components/LayoutContent.vue +25 -20
- package/src/components/LayoutContentEdit.vue +42 -13
- package/src/components/LayoutContentStats.vue +81 -22
- package/src/components/LayoutContentTest.vue +63 -91
- package/src/main.js +3 -1
- package/src/plugins/mShare.mjs +105 -0
- package/src/store/mutations.mjs +1 -0
- package/srv.mjs +9 -4
- package/tableTags-web-api.json +1 -0
- package/dist/js/app-legacy.075a8cf9.js +0 -1
- package/dist/js/app.075a8cf9.js +0 -1
- package/server/staLogs/test_staEvent.mjs +0 -214
- package/tableTags.json +0 -1
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
|
|
65
65
|
<div
|
|
66
66
|
v-for="(row, idx) in req.query"
|
|
67
|
-
:key="
|
|
67
|
+
:key="row.id"
|
|
68
68
|
class="kv-row"
|
|
69
69
|
>
|
|
70
70
|
<div class="kv-col-ck kv-center">
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
|
|
124
124
|
<div
|
|
125
125
|
v-for="(row, idx) in req.headers"
|
|
126
|
-
:key="
|
|
126
|
+
:key="row.id"
|
|
127
127
|
class="kv-row"
|
|
128
128
|
>
|
|
129
129
|
<div class="kv-col-ck kv-center">
|
|
@@ -239,6 +239,15 @@ let kpColorMethod = {
|
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
|
|
242
|
+
//mkRow:Query/Headers 表列之唯一建立點(改造前 10 處各自手寫 `{ on, key, value }` 字面值)。
|
|
243
|
+
//id 為單調遞增序號、供 v-for :key(改造前 :key=idx——增刪列後 Vue 依索引重用 DOM,列之瞬時 DOM 狀態(焦點/IME 組字)會錯位)。
|
|
244
|
+
let seqKvRow = 0
|
|
245
|
+
function mkRow(key = '', value = '') {
|
|
246
|
+
seqKvRow += 1
|
|
247
|
+
return { id: seqKvRow, on: true, key, value }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
242
251
|
export default {
|
|
243
252
|
components: {
|
|
244
253
|
WTextSelect,
|
|
@@ -246,7 +255,9 @@ export default {
|
|
|
246
255
|
props: {
|
|
247
256
|
item: {
|
|
248
257
|
type: Object,
|
|
249
|
-
default: function() {
|
|
258
|
+
default: function() {
|
|
259
|
+
return {}
|
|
260
|
+
},
|
|
250
261
|
},
|
|
251
262
|
height: {
|
|
252
263
|
type: Number,
|
|
@@ -255,12 +266,12 @@ export default {
|
|
|
255
266
|
},
|
|
256
267
|
data: function() {
|
|
257
268
|
return {
|
|
258
|
-
methodItems:
|
|
269
|
+
methodItems: this.$s.METHODS.map((m) => m.toUpperCase()), //顯示層大寫;單一來源 mShare.METHODS(與編輯表單共用)
|
|
259
270
|
req: {
|
|
260
271
|
method: 'GET',
|
|
261
272
|
url: '',
|
|
262
|
-
query: [
|
|
263
|
-
headers: [
|
|
273
|
+
query: [mkRow()],
|
|
274
|
+
headers: [mkRow()],
|
|
264
275
|
body: '',
|
|
265
276
|
},
|
|
266
277
|
res: null,
|
|
@@ -320,48 +331,14 @@ export default {
|
|
|
320
331
|
if (vo.res === null) {
|
|
321
332
|
return ''
|
|
322
333
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
catch (e) {
|
|
330
|
-
raw = String(vo.res.data)
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
raw = String(vo.res.data)
|
|
334
|
+
//物件/陣列 → 縮排 JSON + 語法高亮;其餘(HTML/純文字/數字…)→ 原文(僅 escape、不上色:純文字中的數字/true 不是 JSON token)。
|
|
335
|
+
//型別表見 spec/流程_測試API.md「回應 data 型別 → 顯示形式」;高亮實作與 docs 分頁共用 mShare.jsonHighlight
|
|
336
|
+
//(改造前本處自寫規則以 " 比對但 escape 未換 ",鍵值永遠不上色、只有數字/布林被上色,且對 HTML 原文亦上色)
|
|
337
|
+
let raw = vo.$s.dataToText(vo.res.data)
|
|
338
|
+
if (!vo.$s.isJsonContainer(vo.res.data)) {
|
|
339
|
+
return vo.$s.escapeHtml(raw)
|
|
335
340
|
}
|
|
336
|
-
|
|
337
|
-
let escaped = raw
|
|
338
|
-
.replace(/&/g, '&')
|
|
339
|
-
.replace(/</g, '<')
|
|
340
|
-
.replace(/>/g, '>')
|
|
341
|
-
// 簡易 JSON 語法高亮(逐 token 替換)
|
|
342
|
-
// key: "xxx":
|
|
343
|
-
// string value: "xxx"
|
|
344
|
-
// number: bare number
|
|
345
|
-
// bool/null: true/false/null
|
|
346
|
-
let highlighted = escaped.replace(
|
|
347
|
-
/("[^&]*")\s*(:)|("[^&]*")|(\b-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)|(\b(?:true|false|null)\b)/g,
|
|
348
|
-
function(match, keyStr, colon, strVal, numVal, boolVal) {
|
|
349
|
-
if (keyStr && colon) {
|
|
350
|
-
return '<span class="k">' + keyStr + '</span>' + colon
|
|
351
|
-
}
|
|
352
|
-
if (strVal) {
|
|
353
|
-
return '<span class="s">' + strVal + '</span>'
|
|
354
|
-
}
|
|
355
|
-
if (numVal !== undefined) {
|
|
356
|
-
return '<span class="n">' + numVal + '</span>'
|
|
357
|
-
}
|
|
358
|
-
if (boolVal !== undefined) {
|
|
359
|
-
return '<span class="b">' + boolVal + '</span>'
|
|
360
|
-
}
|
|
361
|
-
return match
|
|
362
|
-
}
|
|
363
|
-
)
|
|
364
|
-
return highlighted
|
|
341
|
+
return vo.$s.jsonHighlight(raw)
|
|
365
342
|
},
|
|
366
343
|
|
|
367
344
|
},
|
|
@@ -383,7 +360,7 @@ export default {
|
|
|
383
360
|
return rows
|
|
384
361
|
}
|
|
385
362
|
each(obj, function(v, k) {
|
|
386
|
-
rows.push(
|
|
363
|
+
rows.push(mkRow(k, String(v)))
|
|
387
364
|
})
|
|
388
365
|
return rows
|
|
389
366
|
},
|
|
@@ -416,7 +393,7 @@ export default {
|
|
|
416
393
|
// { "token": "xxx" };留空則退用 tokens 欄第一個(向後相容舊資料)
|
|
417
394
|
let token = isestr(authCfg.token) ? authCfg.token : (isestr(itm.tokens) ? itm.tokens.split(';')[0] : '')
|
|
418
395
|
if (isestr(token)) {
|
|
419
|
-
headerRows.push(
|
|
396
|
+
headerRows.push(mkRow('Authorization', 'Bearer ' + token))
|
|
420
397
|
}
|
|
421
398
|
}
|
|
422
399
|
else if (authType === 'apikey') {
|
|
@@ -425,10 +402,10 @@ export default {
|
|
|
425
402
|
let value = isestr(authCfg.value) ? authCfg.value : ''
|
|
426
403
|
if (isestr(value)) {
|
|
427
404
|
if (authCfg.in === 'query') {
|
|
428
|
-
queryRows.push(
|
|
405
|
+
queryRows.push(mkRow(name, value))
|
|
429
406
|
}
|
|
430
407
|
else {
|
|
431
|
-
headerRows.push(
|
|
408
|
+
headerRows.push(mkRow(name, value))
|
|
432
409
|
}
|
|
433
410
|
}
|
|
434
411
|
}
|
|
@@ -437,22 +414,25 @@ export default {
|
|
|
437
414
|
let u = isestr(authCfg.username) ? authCfg.username : ''
|
|
438
415
|
let p = isestr(authCfg.password) ? authCfg.password : ''
|
|
439
416
|
if (isestr(u) || isestr(p)) {
|
|
440
|
-
headerRows.push(
|
|
417
|
+
headerRows.push(mkRow('Authorization', 'Basic ' + btoa(u + ':' + p)))
|
|
441
418
|
}
|
|
442
419
|
}
|
|
443
420
|
|
|
444
|
-
// content-type
|
|
445
|
-
|
|
446
|
-
|
|
421
|
+
// content-type 標頭:預設標頭(defaultHeadersJson)已明給 Content-Type(不分大小寫)者以其為準、不再補一列
|
|
422
|
+
//(對標 Postman:使用者明給之標頭優先於自動標頭。改造前雙列並存:送出時同鍵收斂使後列覆蓋前列,
|
|
423
|
+
//改為有序鍵值對後兩列會被 fetch 合併成 'a, b',皆非使用者所見)
|
|
424
|
+
let hasContentType = headerRows.some((r) => r.key.toLowerCase() === 'content-type')
|
|
425
|
+
if (isestr(itm.contentType) && !hasContentType) {
|
|
426
|
+
headerRows.push(mkRow('Content-Type', itm.contentType))
|
|
447
427
|
}
|
|
448
428
|
|
|
449
429
|
if (headerRows.length === 0) {
|
|
450
|
-
headerRows.push(
|
|
430
|
+
headerRows.push(mkRow())
|
|
451
431
|
}
|
|
452
432
|
vo.req.headers = headerRows
|
|
453
433
|
|
|
454
434
|
if (queryRows.length === 0) {
|
|
455
|
-
queryRows.push(
|
|
435
|
+
queryRows.push(mkRow())
|
|
456
436
|
}
|
|
457
437
|
vo.req.query = queryRows
|
|
458
438
|
|
|
@@ -474,27 +454,27 @@ export default {
|
|
|
474
454
|
|
|
475
455
|
addQueryRow: function() {
|
|
476
456
|
let vo = this
|
|
477
|
-
vo.req.query.push(
|
|
457
|
+
vo.req.query.push(mkRow())
|
|
478
458
|
},
|
|
479
459
|
|
|
480
460
|
removeQueryRow: function(idx) {
|
|
481
461
|
let vo = this
|
|
482
462
|
vo.req.query.splice(idx, 1)
|
|
483
463
|
if (vo.req.query.length === 0) {
|
|
484
|
-
vo.req.query.push(
|
|
464
|
+
vo.req.query.push(mkRow())
|
|
485
465
|
}
|
|
486
466
|
},
|
|
487
467
|
|
|
488
468
|
addHeaderRow: function() {
|
|
489
469
|
let vo = this
|
|
490
|
-
vo.req.headers.push(
|
|
470
|
+
vo.req.headers.push(mkRow())
|
|
491
471
|
},
|
|
492
472
|
|
|
493
473
|
removeHeaderRow: function(idx) {
|
|
494
474
|
let vo = this
|
|
495
475
|
vo.req.headers.splice(idx, 1)
|
|
496
476
|
if (vo.req.headers.length === 0) {
|
|
497
|
-
vo.req.headers.push(
|
|
477
|
+
vo.req.headers.push(mkRow())
|
|
498
478
|
}
|
|
499
479
|
},
|
|
500
480
|
|
|
@@ -523,45 +503,33 @@ export default {
|
|
|
523
503
|
return
|
|
524
504
|
}
|
|
525
505
|
|
|
526
|
-
// 3)
|
|
506
|
+
// 3) 開 loading:本地 sending(送出鈕禁用)+ 頁面層全頁 overlay(CLAUDE.md 三層雙擊防護之頁面層;
|
|
507
|
+
// 送出對目標可能有副作用(POST/PUT/DELETE),與編輯分頁之存/刪同級;改造前只有本地 sending)
|
|
527
508
|
vo.sending = true
|
|
509
|
+
vo.$ui.updateLoading(true)
|
|
528
510
|
|
|
529
|
-
// 4) 組 spec
|
|
530
|
-
let method = vo.req.method
|
|
531
|
-
|
|
532
|
-
let query = {}
|
|
533
|
-
each(vo.req.query, function(row) {
|
|
534
|
-
if (row.on && isestr(row.key)) {
|
|
535
|
-
query[row.key] = row.value
|
|
536
|
-
}
|
|
537
|
-
})
|
|
511
|
+
// 4) 組 spec 並送出(方法代號 → HTTP 標準動詞;顯示層之 DEL 須送 DELETE,見 mShare 說明)
|
|
512
|
+
let method = vo.$s.methodToHttpVerb(vo.req.method)
|
|
538
513
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
})
|
|
514
|
+
//Query/Headers:僅 on 且鍵非空之列,以有序鍵值對 [[key, value], ...] 送出、重複鍵各自保留
|
|
515
|
+
//(mShare.kvRowsToPairs;改造前以物件收斂使同鍵後列靜默覆蓋前列)
|
|
516
|
+
let query = vo.$s.kvRowsToPairs(vo.req.query)
|
|
517
|
+
let headers = vo.$s.kvRowsToPairs(vo.req.headers)
|
|
545
518
|
|
|
546
|
-
|
|
519
|
+
//請求內容原文送出(同 Scalar / Postman:打什麼送什麼,不解析、不驗證、不代換;Content-Type 以標頭表為準),
|
|
520
|
+
//空白則不帶 body;GET/HEAD 不帶。見 spec/流程_測試API.md「規則摘要」契約與 ADR-029。
|
|
521
|
+
let body
|
|
547
522
|
if (method !== 'GET' && method !== 'HEAD') {
|
|
548
523
|
let rawBody = vo.req.body
|
|
549
|
-
|
|
550
|
-
if (isestr(ct) && ct.toLowerCase().indexOf('json') >= 0) {
|
|
551
|
-
let parsed = j2o(rawBody)
|
|
552
|
-
body = isobj(parsed) ? parsed : rawBody
|
|
553
|
-
}
|
|
554
|
-
else {
|
|
555
|
-
body = rawBody
|
|
556
|
-
}
|
|
524
|
+
body = isestr(rawBody) ? rawBody : undefined
|
|
557
525
|
}
|
|
558
526
|
|
|
559
527
|
let spec = {
|
|
560
|
-
method
|
|
528
|
+
method,
|
|
561
529
|
url: vo.req.url,
|
|
562
|
-
headers
|
|
563
|
-
query
|
|
564
|
-
body
|
|
530
|
+
headers,
|
|
531
|
+
query,
|
|
532
|
+
body,
|
|
565
533
|
timeout: 30000,
|
|
566
534
|
}
|
|
567
535
|
|
|
@@ -586,11 +554,15 @@ export default {
|
|
|
586
554
|
|
|
587
555
|
core()
|
|
588
556
|
.catch(function(err) {
|
|
557
|
+
//非預期例外:先關 overlay、再以 showCheckYes modal 通知(CLAUDE.md:失敗通知走 showCheckYes、之前先 updateLoading(false);
|
|
558
|
+
//非 success 之 type 顯示警示圖標);不用會自動消失之 $alert toast(ADR-005 modal 政策)
|
|
589
559
|
console.log('catch', err)
|
|
590
|
-
vo.$
|
|
560
|
+
vo.$ui.updateLoading(false)
|
|
561
|
+
vo.$dg.showCheckYes(vo.$t('anUnexpectedErrorOccurred'), { type: 'error' })
|
|
591
562
|
})
|
|
592
563
|
.finally(function() {
|
|
593
564
|
vo.sending = false
|
|
565
|
+
vo.$ui.updateLoading(false)
|
|
594
566
|
})
|
|
595
567
|
|
|
596
568
|
},
|
package/src/main.js
CHANGED
|
@@ -13,6 +13,7 @@ import domDragDrop from 'w-component-vue/src/js/domDragDrop.mjs'
|
|
|
13
13
|
import App from './App.vue'
|
|
14
14
|
import store from './store/index.mjs'
|
|
15
15
|
import ui from './plugins/mUI.mjs'
|
|
16
|
+
import * as s from './plugins/mShare.mjs'
|
|
16
17
|
import ds from './schema/index.mjs'
|
|
17
18
|
// console.log('globalState', globalState)
|
|
18
19
|
// console.log('ds', ds)
|
|
@@ -33,6 +34,7 @@ Vue.prototype.$alert = function() {
|
|
|
33
34
|
//prototype
|
|
34
35
|
Vue.prototype.$ui = ui
|
|
35
36
|
Vue.prototype.$t = ui.getKpText
|
|
37
|
+
Vue.prototype.$s = s
|
|
36
38
|
//$transErr:後端 reject 之 err-key → 依當前 lang 反查顯示文字。
|
|
37
39
|
//非字串或查無對應 key(getKpText 查不到會原樣回 key)則 fallback 'anUnexpectedErrorOccurred',避免顯示生 key。
|
|
38
40
|
Vue.prototype.$transErr = (err) => {
|
|
@@ -51,7 +53,7 @@ Vue.directive('domdragdrop', domDragDrop())
|
|
|
51
53
|
//WServHapiClient
|
|
52
54
|
// let bFirstSync = false //不需要bFirstSync, 由getWebInfor結束代表第1次完成同步
|
|
53
55
|
WServHapiClient({
|
|
54
|
-
|
|
56
|
+
useShowLog: false,
|
|
55
57
|
url: window.location.origin + window.location.pathname,
|
|
56
58
|
useWaitToken: true,
|
|
57
59
|
apiName: 'api',
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
//前後端共用函數區
|
|
2
|
+
import isobj from 'wsemi/src/isobj.mjs'
|
|
3
|
+
import isarr from 'wsemi/src/isarr.mjs'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
function isJsonContainer(v) {
|
|
7
|
+
//值是否為 JSON 容器(純物件或陣列)。
|
|
8
|
+
//why 不只用 isobj:wsemi 之 isobj 以 Object.prototype.toString 判 '[object Object]',陣列為 '[object Array]' 回 false;
|
|
9
|
+
//測試分頁「回應內容」與「請求 body 解析」兩站點都要把陣列與物件同等看待(見 spec/流程_測試API.md「回應 data 型別 → 顯示形式」)。
|
|
10
|
+
return isobj(v) || isarr(v)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
function dataToText(v) {
|
|
15
|
+
//回應 data → 顯示用純文字。容器(物件/陣列)→ 縮排 2 之 JSON;其餘(字串/數字/布林/null/空)→ String(v)。
|
|
16
|
+
//JSON.stringify 拋錯(如循環參照;經 JSON 傳輸之回應實務上不可達)時退回 String(v)。
|
|
17
|
+
if (isJsonContainer(v)) {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.stringify(v, null, 2)
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
return String(v)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return String(v)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
//methodToHttpVerb:把 UI/資料層的方法代號轉成 HTTP 標準動詞。
|
|
30
|
+
//why:本系統以 'del'(資料層)/'DEL'(顯示層)表示刪除,但 `DEL` **不是** HTTP 動詞——實測以 fetch 送出 `DEL`
|
|
31
|
+
//時目標伺服器無法解析、於 HTTP 層直接回 400 且 handler 完全不會執行,導致所有刪除類 API 無法測試(種子資料即有
|
|
32
|
+
//3 支)。故送出前與產生 cURL 前一律經此轉換。其餘動詞僅正規化為大寫;空值或非字串退回 'GET'(同 proxy 之預設)。
|
|
33
|
+
function methodToHttpVerb(v) {
|
|
34
|
+
if (typeof v !== 'string' || v === '') {
|
|
35
|
+
return 'GET'
|
|
36
|
+
}
|
|
37
|
+
let m = v.toUpperCase()
|
|
38
|
+
return (m === 'DEL') ? 'DELETE' : m
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
//METHODS:本系統之方法代號清單(資料層小寫;顯示層以 toUpperCase 呈現;送出時經 methodToHttpVerb 轉 HTTP 動詞)。
|
|
43
|
+
//單一來源:編輯表單與測試分頁之方法下拉共用(改造前兩處各自手寫一份清單)。
|
|
44
|
+
let METHODS = ['get', 'post', 'put', 'del']
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
//escapeHtml:文字 → 可放入 HTML 文字節點之字串(只 escape & < > 三者;文字節點內之引號不需 escape,
|
|
48
|
+
//且 jsonHighlight 之 token 規則依賴保留原始 ")。
|
|
49
|
+
function escapeHtml(text) {
|
|
50
|
+
let s = (text === null || text === undefined) ? '' : String(text)
|
|
51
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
//jsonHighlight:JSON 文字 → 語法高亮 HTML(class k=鍵、s=字串值、n=數字、b=true/false/null;樣式為 src/assets/scalar-ui.css 之 .code)。
|
|
56
|
+
//docs 分頁(outputExample)與測試分頁(回應內容)共用同一實作,兩處行為一致。
|
|
57
|
+
//why 單趟 tokenizer:改造前兩處各自手寫——測試分頁以 " 比對但 escape 只換 & < >,字串永遠不命中、鍵值一律不上色;
|
|
58
|
+
//docs 版逐規則多次 replace,字串內之數字/true 亦被誤上色。此處字串 token(含 \" 跳脫)優先吞掉整段,
|
|
59
|
+
//字串內容不再被後續規則比對;字串後緊接 : 者為鍵,否則為值。
|
|
60
|
+
function jsonHighlight(text) {
|
|
61
|
+
let s = escapeHtml(text)
|
|
62
|
+
return s.replace(/("(?:[^"\\]|\\.)*")(\s*:)?|(-?\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)|\b(true|false|null)\b/g, function(m, str, colon, num, kw) {
|
|
63
|
+
if (str !== undefined) {
|
|
64
|
+
if (colon !== undefined) {
|
|
65
|
+
return '<span class="k">' + str + '</span>' + colon
|
|
66
|
+
}
|
|
67
|
+
return '<span class="s">' + str + '</span>'
|
|
68
|
+
}
|
|
69
|
+
if (num !== undefined) {
|
|
70
|
+
return '<span class="n">' + num + '</span>'
|
|
71
|
+
}
|
|
72
|
+
return '<span class="b">' + kw + '</span>'
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
//kvRowsToPairs:請求建構器之 Query/Headers 表列([{ on, key, value }])→ 有序鍵值對 [[key, value], ...]。
|
|
78
|
+
//規則(spec/流程_測試API.md「規則摘要」):僅 on 且鍵非空之列納入;重複鍵各自保留一對、不收斂成物件
|
|
79
|
+
//(重複之 query 鍵須各自送達目標,如 ?id=1&id=2;重複之標頭由 proxy 依 Fetch 標準合併);值一律轉字串。
|
|
80
|
+
//why:改造前以 `obj[key] = value` 收斂,同鍵之後列靜默覆蓋前列,使用者看到兩列卻只送出一列。
|
|
81
|
+
function kvRowsToPairs(rows) {
|
|
82
|
+
let pairs = []
|
|
83
|
+
if (!isarr(rows)) {
|
|
84
|
+
return pairs
|
|
85
|
+
}
|
|
86
|
+
for (let row of rows) {
|
|
87
|
+
if (!isobj(row) || !row.on || typeof row.key !== 'string' || row.key === '') {
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
let v = (row.value === null || row.value === undefined) ? '' : String(row.value)
|
|
91
|
+
pairs.push([row.key, v])
|
|
92
|
+
}
|
|
93
|
+
return pairs
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
METHODS,
|
|
99
|
+
isJsonContainer,
|
|
100
|
+
dataToText,
|
|
101
|
+
methodToHttpVerb,
|
|
102
|
+
escapeHtml,
|
|
103
|
+
jsonHighlight,
|
|
104
|
+
kvRowsToPairs
|
|
105
|
+
}
|
package/src/store/mutations.mjs
CHANGED
package/srv.mjs
CHANGED
|
@@ -3,7 +3,6 @@ import WWebApi from './server/WWebApi.mjs'
|
|
|
3
3
|
import getSettings from './g_getSettings.mjs'
|
|
4
4
|
import get from 'lodash-es/get.js'
|
|
5
5
|
import iseobj from 'wsemi/src/iseobj.mjs'
|
|
6
|
-
import axios from 'axios'
|
|
7
6
|
import JSON5 from 'json5'
|
|
8
7
|
import fs from 'fs'
|
|
9
8
|
|
|
@@ -39,6 +38,7 @@ let opt = {
|
|
|
39
38
|
//log 資料夾與輪檔間隔(srLog 寫入 + staEvent 統計皆讀此)。e2e 可用 genTempSettings({logFd}) 指向合成 log 達 hermetic。
|
|
40
39
|
logFd: get(stApp, 'logFd', './logs'),
|
|
41
40
|
logInterval: get(stApp, 'logInterval', 'hr'),
|
|
41
|
+
logNumKeep: get(stApp, 'logNumKeep', null), //null=未給, srLog 採 w-syslog 預設; 給非正整數則 srLog 啟動即 throw (fail-fast)
|
|
42
42
|
|
|
43
43
|
webName: {
|
|
44
44
|
'eng': 'API Service',
|
|
@@ -68,9 +68,14 @@ let getUserByToken = async (token) => {
|
|
|
68
68
|
//呼叫 SSO 解析 token -> user
|
|
69
69
|
try {
|
|
70
70
|
let url = `${ssoBaseUrl}/api/getSsoUserInfor?token=${encodeURIComponent(ssoAppToken)}&key=token&value=${encodeURIComponent(token)}`
|
|
71
|
-
let res = await
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
let res = await fetch(url) //Node 內建 fetch(改造前 axios);非 2xx 視同失敗
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
console.log('SSO getSsoUserInfor HTTP', res.status)
|
|
74
|
+
return {}
|
|
75
|
+
}
|
|
76
|
+
let data = await res.json()
|
|
77
|
+
let state = get(data, 'state', '')
|
|
78
|
+
let u = get(data, 'msg', null)
|
|
74
79
|
if (state !== 'success' || !iseobj(u)) {
|
|
75
80
|
console.log('SSO getSsoUserInfor 失敗', state)
|
|
76
81
|
return {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"apis":"2026-09-13T00:43:23+08:00|NCZLhv"}
|