w-web-perm 1.0.78 → 1.0.80

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 (37) hide show
  1. package/dist/index.tmp +1 -1
  2. package/dist/js/app-legacy.85fce10c.js +1 -0
  3. package/dist/js/app.85fce10c.js +1 -0
  4. package/dist/js/{chunk-vendors-legacy.884ef407.js → chunk-vendors-legacy.b57e1cce.js} +2 -2
  5. package/dist/js/{chunk-vendors.884ef407.js → chunk-vendors.b57e1cce.js} +2 -2
  6. package/dist/w-web-perm.umd.js +2 -2
  7. package/dist/w-web-perm.umd.js.map +1 -1
  8. package/docs/WWebPerm.html +4 -4
  9. package/docs/global.html +610 -0
  10. package/docs/index.html +2 -2
  11. package/docs/{WWebPerm.mjs.html → server_WWebPerm.mjs.html} +84 -94
  12. package/docs/server_procCore.mjs.html +311 -0
  13. package/docs/src_fetchJson.mjs.html +113 -0
  14. package/package.json +18 -19
  15. package/server/WWebPerm.mjs +80 -90
  16. package/server/procCore.mjs +56 -4
  17. package/server/procLang.mjs +3 -3
  18. package/server/provideTabs.mjs +8 -9
  19. package/src/components/LayoutContentGrups.vue +5 -1
  20. package/src/components/LayoutContentPemis.vue +5 -1
  21. package/src/components/LayoutContentTargets.vue +5 -1
  22. package/src/components/LayoutContentUsers.vue +21 -8
  23. package/src/components/VeCgrups.vue +3 -3
  24. package/src/components/VeCpemis.vue +2 -2
  25. package/src/components/VeGrupBlngUsers.vue +2 -2
  26. package/src/components/VePemiBlngGrups.vue +2 -2
  27. package/src/fetchJson.mjs +41 -0
  28. package/src/getPerm.mjs +7 -7
  29. package/src/getPermUserInfor.mjs +7 -7
  30. package/src/main.js +1 -1
  31. package/src/perm.mjs +5 -4
  32. package/src/plugins/mUI.mjs +17 -0
  33. package/srv.mjs +8 -0
  34. package/tableTags-web-perm.json +1 -0
  35. package/dist/js/app-legacy.71fa8c3b.js +0 -1
  36. package/dist/js/app.71fa8c3b.js +0 -1
  37. package/tableTags.json +0 -1
@@ -0,0 +1,113 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>src/fetchJson.mjs - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WWebPerm.html">WWebPerm</a></li></ul><h3>Global</h3><ul><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">src/fetchJson.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>/**
49
+ * 以瀏覽器與 node(18+) 內建之 fetch 取得 JSON 回應, 取代 axios(2026-09-07 起本套件不再依賴 axios)
50
+ *
51
+ * 與 axios 之語意對齊:
52
+ * - 網路錯誤或非 2xx: axios 會 reject, fetch 只在網路錯誤 reject, 故此處對 !res.ok 亦 reject, 統一為 { kind: 'request', status, message }
53
+ * - 回應非 JSON: axios 回原字串(呼叫端判 state 失敗), 此處 res.json() 失敗 reject { kind: 'parse', status, message }
54
+ * - 成功: resolve 解析後之 JSON(對應 axios 之 res.data)
55
+ * 注意: node 端 fetch(undici) 不讀 HTTP_PROXY / HTTPS_PROXY 環境變數(axios 會), 需走 proxy 之部署須自行以 opt.dispatcher 注入 undici Agent
56
+ *
57
+ * @param {String} url 輸入請求網址字串
58
+ * @param {Object} [opt={}] 輸入 fetch 選項物件(method / headers / body / signal / dispatcher 等原樣傳給 fetch), 預設 {}
59
+ * @returns {Promise} 回傳 Promise, resolve 為 JSON 物件, reject 為 { kind: 'request'|'parse', status, message }
60
+ */
61
+ async function fetchJson(url, opt = {}) {
62
+
63
+ //fetch
64
+ let res = null
65
+ try {
66
+ res = await fetch(url, opt)
67
+ }
68
+ catch (err) {
69
+ return Promise.reject({ kind: 'request', status: 0, message: String((err &amp;&amp; err.message) || err) })
70
+ }
71
+
72
+ //check status
73
+ if (!res.ok) {
74
+ return Promise.reject({ kind: 'request', status: res.status, message: `HTTP ${res.status}` })
75
+ }
76
+
77
+ //json
78
+ try {
79
+ return await res.json()
80
+ }
81
+ catch (err) {
82
+ return Promise.reject({ kind: 'parse', status: res.status, message: String((err &amp;&amp; err.message) || err) })
83
+ }
84
+
85
+ }
86
+
87
+
88
+ export default fetchJson
89
+ </code></pre>
90
+ </article>
91
+ </section>
92
+
93
+
94
+
95
+
96
+
97
+
98
+ </div>
99
+
100
+ <br class="clear">
101
+
102
+ <footer>
103
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Sep 13 2026 15:35:25 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
104
+ </footer>
105
+
106
+ <script>prettyPrint();</script>
107
+ <script src="scripts/polyfill.js"></script>
108
+ <script src="scripts/linenumber.js"></script>
109
+
110
+
111
+
112
+ </body>
113
+ </html>
package/package.json CHANGED
@@ -1,19 +1,18 @@
1
1
  {
2
2
  "name": "w-web-perm",
3
- "version": "1.0.78",
3
+ "version": "1.0.80",
4
4
  "main": "dist/w-web-perm.umd.js",
5
5
  "dependencies": {
6
- "axios": "^1.18.1",
7
6
  "json5": "^2.2.3",
8
- "w-aggrid-vue": "^2.0.85",
9
- "w-component-vue": "^2.5.12",
10
- "w-data-collector": "^1.0.36",
11
- "w-echarts-vue": "^1.0.10",
12
- "w-serv-hapi": "^1.0.86",
13
- "w-serv-orm": "^1.0.53",
14
- "w-syslog": "^1.0.25",
15
- "w-ui-loginout": "^1.0.24",
16
- "wsemi": "^1.8.85"
7
+ "w-aggrid-vue": "^2.0.86",
8
+ "w-component-vue": "^2.5.13",
9
+ "w-data-collector": "^1.0.37",
10
+ "w-echarts-vue": "^1.0.11",
11
+ "w-serv-hapi": "^1.0.88",
12
+ "w-serv-orm": "^1.0.54",
13
+ "w-syslog": "^1.0.26",
14
+ "w-ui-loginout": "^1.0.25",
15
+ "wsemi": "^1.8.92"
17
16
  },
18
17
  "devDependencies": {
19
18
  "@vue/cli-plugin-babel": "^5.0.8",
@@ -28,18 +27,15 @@
28
27
  "eslint-plugin-vue": "^9.10.0",
29
28
  "vue": "^2.7.16",
30
29
  "vuex": "^3.6.2",
31
- "w-orm-lmdb": "^1.0.30",
32
- "w-package-tools": "^1.1.15",
30
+ "w-orm-lmdb": "^1.0.31",
31
+ "w-package-tools": "^1.1.16",
33
32
  "w-package-tools-e2e": "^1.0.0",
34
- "w-screenctl": "^1.0.18"
33
+ "w-screenctl": "^1.0.19"
35
34
  },
36
35
  "scripts": {
37
36
  "serve": "vue-cli-service serve",
38
37
  "build": "vue-cli-service build",
39
- "test:unit": "mocha test/unit-*.test.mjs --timeout 300000",
40
- "test:apiunit": "mocha test/unit-*.test.mjs test/api-*.test.mjs test/getUserRules.test.mjs --timeout 300000",
41
- "test:e2e": "node test/run-e2e-isolated.mjs",
42
- "test": "npm run test:apiunit && npm run test:e2e",
38
+ "test": "mocha --no-parallel \"test/*.test.mjs\" --timeout 180000",
43
39
  "lint": "vue-cli-service lint",
44
40
  "deploy": "gh-pages -d docs"
45
41
  },
@@ -71,5 +67,8 @@
71
67
  "permission"
72
68
  ],
73
69
  "author": "yuda-lyu(semisphere)",
74
- "license": "MIT"
70
+ "license": "MIT",
71
+ "engines": {
72
+ "node": ">=18"
73
+ }
75
74
  }
@@ -41,7 +41,7 @@ import procStaInfor from './procStaInfor.mjs'
41
41
  * @param {String} url 輸入資料庫連線字串,例如w-orm-lmdb為'./db',或w-orm-mongodb為'mongodb://username:password@$127.0.0.1:27017'
42
42
  * @param {String} db 輸入資料庫名稱字串
43
43
  * @param {Function} getUserByToken 輸入處理函數,函數會傳入使用者token,通過此函數處理後並回傳使用者資訊物件,並至少須提供'id'、'email'、'name'、'isAdmin'欄位,且'isAdmin'限輸入'y'或'n'。注意: token 回傳之'isAdmin'僅供識別,權限系統一律以自身users表該使用者之'isAdmin'欄位值為權威,不會被 token 宣稱之值複寫
44
- * @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
44
+ * @param {Function} verifyClientUser 輸入驗證瀏覽使用者身份之處理函數,函數會傳入使用者資訊物件(perm users 表之列)與來源字串from,通過此函數識別後回傳布林值(可為Promise),允許使用者回傳true,反之回傳false。from 值域: 'getUserByToken'(登入)、'getPerm'(api/getPerm)、以及資料通道之 apiType 'main'(RPC 與 ORM 執行)、'upload-controller'、'upload-slice'、'download-get-filename'、'download-get-file'、'download';資料通道與登入採同一授權強度(反查 perm users 且 isActive='y' 後再經本函數)
45
45
  * @param {Function} verifyAppUser 輸入驗證應用程序使用者身份之處理函數,函數會傳入使用者資訊物件,通過此函數識別後回傳布林值,允許使用者回傳true,反之回傳false
46
46
  * @param {Object} [opt={}] 輸入設定物件,預設{}
47
47
  * @param {Integer} [opt.serverPort=11006] 輸入伺服器通訊port,預設11006
@@ -349,34 +349,6 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
349
349
  }
350
350
 
351
351
 
352
- // //getTargetsList
353
- // let getTargetsList = async (userId) => {
354
- // let rs = await woItems.targets.select()
355
- // return rs
356
- // }
357
-
358
-
359
- // //getPemisList
360
- // let getPemisList = async (userId) => {
361
- // let rs = await woItems.pemis.select()
362
- // return rs
363
- // }
364
-
365
-
366
- // //getGrupsList
367
- // let getGrupsList = async (userId) => {
368
- // let rs = await woItems.grups.select()
369
- // return rs
370
- // }
371
-
372
-
373
- // //getUsersList
374
- // let getUsersList = async (userId) => {
375
- // let rs = await woItems.users.select()
376
- // return rs
377
- // }
378
-
379
-
380
352
  //checkUser
381
353
  let checkUser = async(user) => {
382
354
  let id = get(user, 'id', '')
@@ -466,22 +438,6 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
466
438
  }
467
439
 
468
440
 
469
- //p
470
- let p = {
471
- checkToken: async(token) => {
472
-
473
- //getUserByToken
474
- let u = await getUserByToken(token)
475
- // console.log('u', u)
476
-
477
- //checkUser
478
- await checkUser(u)
479
-
480
- return true
481
- },
482
- }
483
-
484
-
485
441
  //getAndVerifyClientUser
486
442
  let getAndVerifyClientUser = async (token, from = '') => {
487
443
  //基於token查找client使用者, 會基於外部getUserByToken以及perm內部users進行查找並比對
@@ -544,6 +500,25 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
544
500
  }
545
501
 
546
502
 
503
+ //p
504
+ let p = {
505
+ //checkToken, 資料通道(/api/main 之 RPC 與 ORM 執行、上傳、下載)之連線驗證, 與 api/getUserByToken(登入)採同一授權強度:
506
+ //外部 getUserByToken → 欄位齊備 → 反查 perm users(mappingBy)且 isActive='y' → verifyClientUser(user, from)
507
+ //why: 2026-09-07 前僅驗外部 token 欄位齊備(checkUser), 不查 perm users 與 isActive, 亦不經 verifyClientUser,
508
+ //致不在 perm users 表之外部使用者、被停用(isActive='n')之使用者仍可經資料通道讀寫全部權限資料(實測 A3/A4, 見 tmp/probe-report.json);
509
+ //改為與登入入口對稱, 使 UI 之「是否有效」與部署層 verifyClientUser 政策對資料通道亦生效.
510
+ //from 為 w-converhp 之 apiType('main' / 'upload-controller' / 'upload-slice' / 'download-get-filename' / 'download-get-file' / 'download'),
511
+ //供部署層 verifyClientUser(user, from) 區分入口.
512
+ checkToken: async(token, from = '') => {
513
+
514
+ //getAndVerifyClientUser, reject 時 key: cannotFindUserFromToken / cannotGetUserFromPerm / userNoPermission 等
515
+ await getAndVerifyClientUser(token, from)
516
+
517
+ return true
518
+ },
519
+ }
520
+
521
+
547
522
  //parsePayload
548
523
  let parsePayload = async (req) => {
549
524
 
@@ -598,49 +573,49 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
598
573
  //序列化同表之並行整表 delAll+insert: 防 lost update (同 keyTable 序列化, 不同表並行)
599
574
  return await kmx('syncAndReplaceTabs:' + keyTable, async () => {
600
575
 
601
- //from
602
- let from = get(inp, 'from', '')
603
- // console.log('from', from)
576
+ //from
577
+ let from = get(inp, 'from', '')
578
+ // console.log('from', from)
604
579
 
605
- //check
606
- if (!isestr(from)) {
607
- console.log('inp', inp)
608
- srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidFrom', keyTable })
609
- return Promise.reject(`invalidFrom`)
610
- }
580
+ //check
581
+ if (!isestr(from)) {
582
+ console.log('inp', inp)
583
+ srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidFrom', keyTable })
584
+ return Promise.reject(`invalidFrom`)
585
+ }
611
586
 
612
- //rows
613
- let rows = get(inp, 'rows', [])
614
- // console.log(keyTable, rows)
587
+ //rows
588
+ let rows = get(inp, 'rows', [])
589
+ // console.log(keyTable, rows)
615
590
 
616
- //check
617
- if (!isearr(rows)) {
618
- console.log('inp', inp)
619
- console.log('keyTable', keyTable)
620
- srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidRows', keyTable })
621
- return Promise.reject(`invalidRows`)
622
- }
591
+ //check
592
+ if (!isearr(rows)) {
593
+ console.log('inp', inp)
594
+ console.log('keyTable', keyTable)
595
+ srLog.error({ event: 'syncAndReplaceTabs-error', msg: 'invalidRows', keyTable })
596
+ return Promise.reject(`invalidRows`)
597
+ }
623
598
 
624
- //save from
625
- rows = map(rows, (v, k) => {
599
+ //save from
600
+ rows = map(rows, (v, k) => {
626
601
  // v.order = k + 1 //order大部分由外部給予, 不能複寫
627
- v.from = from
628
- return v
629
- })
602
+ v.from = from
603
+ return v
604
+ })
630
605
 
631
- //ltdtmapping
632
- rows = ltdtmapping(rows, ds[keyTable].keys)
633
- // console.log('ltdtmapping rows', rows)
606
+ //ltdtmapping
607
+ rows = ltdtmapping(rows, ds[keyTable].keys)
608
+ // console.log('ltdtmapping rows', rows)
634
609
 
635
- //delAll from
636
- await woItems[keyTable].delAll({ from })
610
+ //delAll from
611
+ await woItems[keyTable].delAll({ from })
637
612
 
638
- //insert
639
- // let r = await woItems[keyTable].insert(rows)
640
- let r = await procOrm(userId, keyTable, 'insert', rows) //須使用procOrm才有辦法自動給予相關欄位
641
- // console.log('r', r)
613
+ //insert
614
+ // let r = await woItems[keyTable].insert(rows)
615
+ let r = await procOrm(userId, keyTable, 'insert', rows) //須使用procOrm才有辦法自動給予相關欄位
616
+ // console.log('r', r)
642
617
 
643
- return r
618
+ return r
644
619
 
645
620
  })
646
621
  }
@@ -1082,8 +1057,16 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1082
1057
  return false
1083
1058
  }
1084
1059
 
1085
- //checkToken
1086
- let b = await p.checkToken(token)
1060
+ //checkToken, 失敗一律回 false(傳輸層回 permission denied); reject 之 err key 記於 verifyConn log 之 msg
1061
+ let b = false
1062
+ let msg = ''
1063
+ await p.checkToken(token, apiType)
1064
+ .then(() => {
1065
+ b = true
1066
+ })
1067
+ .catch((err) => {
1068
+ msg = (typeof err === 'string') ? err : ((err && err.message) || 'checkTokenFail')
1069
+ })
1087
1070
  // console.log('b', b)
1088
1071
 
1089
1072
  //ip, 對齊sso慣例x-forwarded-for取第1個, fallback到req remoteAddress, 取不到則略過
@@ -1098,8 +1081,13 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1098
1081
  //referer
1099
1082
  let referer = get(headers, 'referer', '')
1100
1083
 
1101
- //info
1102
- srLog.info({ event: 'verifyConn', ok: b, apiType, ip, origin, referer })
1084
+ //info, 失敗時 msg 為 err key(cannotFindUserFromToken / cannotGetUserFromPerm / userNoPermission 等), 供資安稽核與統計
1085
+ if (b) {
1086
+ srLog.info({ event: 'verifyConn', ok: b, apiType, ip, origin, referer })
1087
+ }
1088
+ else {
1089
+ srLog.error({ event: 'verifyConn-error', ok: b, apiType, ip, origin, referer, msg })
1090
+ }
1103
1091
 
1104
1092
  return b
1105
1093
  },
@@ -1126,15 +1114,14 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1126
1114
 
1127
1115
  getWebInfor,
1128
1116
 
1129
- // getTargetsList,
1130
- // getPemisList,
1131
- // getGrupsList,
1132
- // getUsersList,
1133
-
1134
1117
  updateTargets: pc.updateTargets,
1135
1118
  updatePemis: pc.updatePemis,
1136
1119
  updateGrups: pc.updateGrups,
1137
- updateUsers: pc.updateUsers,
1120
+ //updateUsers, modeEditUsers='for:grups' 時 API 與 UI 一致只採納既有使用者之 cgrups 變更, 身分欄/新增/刪除/順序以資料庫為準
1121
+ updateUsers: (userId, rows) => {
1122
+ let opt = (modeEditUsers === 'for:grups') ? { pickKeysOnly: ['cgrups'] } : {}
1123
+ return pc.updateUsers(userId, rows, opt)
1124
+ },
1138
1125
 
1139
1126
  getStaEvent: async (userId, timeLength, timeInterval) => {
1140
1127
  try {
@@ -1161,7 +1148,10 @@ function WWebPerm(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser
1161
1148
  },
1162
1149
 
1163
1150
  },
1164
- fnTableTags: 'tableTags-web-perm.json',
1151
+ //fpTableTags: 各資料表時間戳檔案(供前端判定是否需重新同步)。
1152
+ //w-serv-hapi 1.0.87 起之正式名稱; 舊名 fnTableTags 於該版之前傳下去等同未給(w-serv-webdata 只讀 fpTableTags),
1153
+ //故 1.0.86 及之前實際寫的是預設之 ./tableTags.json, 升版後改用本檔名。
1154
+ fpTableTags: 'tableTags-web-perm.json',
1165
1155
  })
1166
1156
 
1167
1157
 
@@ -2,7 +2,9 @@ import get from 'lodash-es/get.js'
2
2
  import each from 'lodash-es/each.js'
3
3
  import map from 'lodash-es/map.js'
4
4
  import size from 'lodash-es/size.js'
5
+ import sortBy from 'lodash-es/sortBy.js'
5
6
  import isestr from 'wsemi/src/isestr.mjs'
7
+ import isearr from 'wsemi/src/isearr.mjs'
6
8
  import haskey from 'wsemi/src/haskey.mjs'
7
9
  import arrHas from 'wsemi/src/arrHas.mjs'
8
10
  import ltdtDiffByKey from 'wsemi/src/ltdtDiffByKey.mjs'
@@ -10,16 +12,63 @@ import ltdtmapping from 'wsemi/src/ltdtmapping.mjs'
10
12
  import ds from '../src/schema/index.mjs'
11
13
 
12
14
 
15
+ /**
16
+ * 僅採納既有列之指定欄位變更, 其餘欄位、新增列、缺列(刪除)與順序一律以資料庫現值為準
17
+ *
18
+ * 用於 modeEditUsers='for:grups': 使用者身分由外部單一登入系統同步, 本系統只擁有群組指派(cgrups), 故 API 層須與 UI 一致只接受 cgrups 變更
19
+ *
20
+ * @param {Array} ltdtDb 資料庫現有列(select 結果, 依 order 排序後為基底)
21
+ * @param {Array} rowsIn 前端/外部送入之整表列
22
+ * @param {String} keyDetect 對應列之鍵欄位(如 'id')
23
+ * @param {Array} pickKeys 允許採納之欄位名陣列(如 ['cgrups'])
24
+ * @returns {Array} 以資料庫列為基底、僅覆蓋 pickKeys 之新列陣列(順序同資料庫)
25
+ */
26
+ function mergePickKeysOnly(ltdtDb, rowsIn, keyDetect, pickKeys) {
27
+ let kpIn = {}
28
+ each(rowsIn, (r) => {
29
+ let v = get(r, keyDetect, '')
30
+ if (isestr(v)) {
31
+ kpIn[v] = r
32
+ }
33
+ })
34
+ let ltdt = sortBy(ltdtDb, 'order')
35
+ ltdt = map(ltdt, (u) => {
36
+ let r = { ...u }
37
+ let v = get(u, keyDetect, '')
38
+ let rin = get(kpIn, v, null)
39
+ if (rin !== null) {
40
+ each(pickKeys, (k) => {
41
+ if (haskey(rin, k)) {
42
+ r[k] = rin[k]
43
+ }
44
+ })
45
+ }
46
+ return r
47
+ })
48
+ return ltdt
49
+ }
50
+
51
+
13
52
  function proc(woItems, procOrm, { srLog, kmx }) {
14
53
 
15
54
 
16
55
  //updateTabItems
17
- let updateTabItems = async (keyTable, userId, rows, keyDetect) => {
56
+ let updateTabItems = async (keyTable, userId, rows, keyDetect, opt = {}) => {
18
57
  // console.log('updateTabItems', keyTable, userId, rows.length, keyDetect)
19
58
 
59
+ //pickKeysOnly, 僅採納既有列之指定欄位變更(見 mergePickKeysOnly), 未給則整表 diff 寫入
60
+ let pickKeysOnly = get(opt, 'pickKeysOnly', null)
61
+
20
62
  //序列化同表之並行整表批次寫入: 防 lost update (同 keyTable 序列化, 不同表並行)
21
63
  return await kmx('updateTabItems:' + keyTable, async () => {
22
64
 
65
+ //pickKeysOnly, 須於 mutex 內讀取資料庫為基底, 避免與並行寫入 race
66
+ if (isearr(pickKeysOnly)) {
67
+ let ltdtDb = await woItems[keyTable].select()
68
+ rows = mergePickKeysOnly(ltdtDb, rows, keyDetect, pickKeysOnly)
69
+ srLog.info({ event: 'updateTabItems-pickKeysOnly', keyTable, userId, pickKeysOnly })
70
+ }
71
+
23
72
  //ltdtmapping
24
73
  rows = ltdtmapping(rows, ds[keyTable].keys)
25
74
  // console.log('ltdtmapping rows', rows)
@@ -161,10 +210,10 @@ function proc(woItems, procOrm, { srLog, kmx }) {
161
210
  }
162
211
 
163
212
 
164
- //updateUsers
165
- let updateUsers = async (userId, rows) => {
213
+ //updateUsers, opt.pickKeysOnly 給 ['cgrups'] 時僅採納既有使用者之群組指派變更(modeEditUsers='for:grups')
214
+ let updateUsers = async (userId, rows, opt = {}) => {
166
215
  try {
167
- await updateTabItems('users', userId, rows, 'id')
216
+ await updateTabItems('users', userId, rows, 'id', opt)
168
217
  }
169
218
  catch (err) { errLog('updateUsers-error', userId, err); throw err }
170
219
  srLog.info({ event: 'updateUsers-success', userId })
@@ -184,4 +233,7 @@ function proc(woItems, procOrm, { srLog, kmx }) {
184
233
  }
185
234
 
186
235
 
236
+ export { mergePickKeysOnly }
237
+
238
+
187
239
  export default proc
@@ -111,15 +111,15 @@ let kpLang = {
111
111
  eng: `Reverse all`,
112
112
  cht: `全反選使用`,
113
113
  },
114
- operMode: {
114
+ operEnable: { //關聯表 enable 欄表頭(是否使用)
115
115
  eng: `Enable`,
116
116
  cht: `是否使用`,
117
117
  },
118
- operPemiEnable: {
118
+ operPemiMode: { //關聯表 mode 欄表頭(權限之 OR/AND 合併模式)
119
119
  eng: `Operator`,
120
120
  cht: `合併權限模式`,
121
121
  },
122
- operGrupEnable: {
122
+ operGrupMode: { //關聯表 mode 欄表頭(權限群組之 OR/AND 合併模式)
123
123
  eng: `Operator`,
124
124
  cht: `合併權限群組模式`,
125
125
  },
@@ -1,8 +1,8 @@
1
- import axios from 'axios'
2
1
  import get from 'lodash-es/get.js'
3
2
  import genPm from 'wsemi/src/genPm.mjs'
4
3
  import haskey from 'wsemi/src/haskey.mjs'
5
4
  import ltdtpick from 'wsemi/src/ltdtpick.mjs'
5
+ import fetchJson from '../src/fetchJson.mjs'
6
6
 
7
7
 
8
8
  //kpKs
@@ -90,16 +90,15 @@ async function provideTabs(url, keyTable, from, rows) {
90
90
  }
91
91
  // console.log('rin', rin)
92
92
 
93
- //axios
94
- await axios({
95
- method: 'post',
96
- url,
93
+ //post, 內建 fetch(不依賴 axios); fetchJson 直接 resolve JSON envelope(原 axios 之 res.data),
94
+ //網路錯誤 / 非 2xx / 非 JSON 回應 reject { kind, status, message }(原為 AxiosError)
95
+ await fetchJson(url, {
96
+ method: 'POST',
97
97
  headers: { 'Content-Type': 'application/json; charset=utf-8' },
98
- data: rin,
98
+ body: JSON.stringify(rin),
99
99
  })
100
- .then((res) => {
101
- // console.log('then', res)
102
- let data = get(res, 'data')
100
+ .then((data) => {
101
+ // console.log('then', data)
103
102
  let state = get(data, 'state')
104
103
  let msg = get(data, 'msg', '')
105
104
  if (state === 'success') {
@@ -301,6 +301,7 @@ export default {
301
301
 
302
302
  firstLoading: true,
303
303
  firstSetting: true,
304
+ systemProcing: false, //程式端載入/重載清單資料期間為 true, 用於排除非使用者操作之 rowsChange (見 $ui.markDataReload)
304
305
  showIsEditable: false,
305
306
  isEditable: false,
306
307
  isModified: false,
@@ -750,7 +751,7 @@ export default {
750
751
  // console.log('rowsChange cloneDeep(vo.opt.rows)', cloneDeep(vo.opt.rows))
751
752
 
752
753
  //check
753
- if (!vo.syncState || vo.firstLoading || vo.firstSetting) {
754
+ if (!vo.syncState || vo.firstLoading || vo.firstSetting || vo.systemProcing) {
754
755
  return
755
756
  }
756
757
 
@@ -771,6 +772,9 @@ export default {
771
772
 
772
773
  }
773
774
 
775
+ //markDataReload, 重建 opt 屬程式端寫入, 其後非同步觸發之 rowsChange 不得視為使用者變更
776
+ vo.$ui.markDataReload(vo)
777
+
774
778
  //save
775
779
  vo.opt = opt
776
780
 
@@ -301,6 +301,7 @@ export default {
301
301
 
302
302
  firstLoading: true,
303
303
  firstSetting: true,
304
+ systemProcing: false, //程式端載入/重載清單資料期間為 true, 用於排除非使用者操作之 rowsChange (見 $ui.markDataReload)
304
305
  showIsEditable: false,
305
306
  isEditable: false,
306
307
  isModified: false,
@@ -750,7 +751,7 @@ export default {
750
751
  // console.log('rowsChange cloneDeep(vo.opt.rows)', cloneDeep(vo.opt.rows))
751
752
 
752
753
  //check
753
- if (!vo.syncState || vo.firstLoading || vo.firstSetting) {
754
+ if (!vo.syncState || vo.firstLoading || vo.firstSetting || vo.systemProcing) {
754
755
  return
755
756
  }
756
757
 
@@ -771,6 +772,9 @@ export default {
771
772
 
772
773
  }
773
774
 
775
+ //markDataReload, 重建 opt 屬程式端寫入, 其後非同步觸發之 rowsChange 不得視為使用者變更
776
+ vo.$ui.markDataReload(vo)
777
+
774
778
  //save
775
779
  vo.opt = opt
776
780
 
@@ -289,6 +289,7 @@ export default {
289
289
 
290
290
  firstLoading: true,
291
291
  firstSetting: true,
292
+ systemProcing: false, //程式端載入/重載清單資料期間為 true, 用於排除非使用者操作之 rowsChange (見 $ui.markDataReload)
292
293
  showIsEditable: false,
293
294
  isEditable: false,
294
295
  isModified: false,
@@ -629,7 +630,7 @@ export default {
629
630
  // console.log('rowsChange cloneDeep(vo.opt.rows)', cloneDeep(vo.opt.rows))
630
631
 
631
632
  //check
632
- if (!vo.syncState || vo.firstLoading || vo.firstSetting) {
633
+ if (!vo.syncState || vo.firstLoading || vo.firstSetting || vo.systemProcing) {
633
634
  return
634
635
  }
635
636
 
@@ -650,6 +651,9 @@ export default {
650
651
 
651
652
  }
652
653
 
654
+ //markDataReload, 重建 opt 屬程式端寫入, 其後非同步觸發之 rowsChange 不得視為使用者變更
655
+ vo.$ui.markDataReload(vo)
656
+
653
657
  //save
654
658
  vo.opt = opt
655
659