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.
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.85fce10c.js +1 -0
- package/dist/js/app.85fce10c.js +1 -0
- package/dist/js/{chunk-vendors-legacy.884ef407.js → chunk-vendors-legacy.b57e1cce.js} +2 -2
- package/dist/js/{chunk-vendors.884ef407.js → chunk-vendors.b57e1cce.js} +2 -2
- package/dist/w-web-perm.umd.js +2 -2
- package/dist/w-web-perm.umd.js.map +1 -1
- package/docs/WWebPerm.html +4 -4
- package/docs/global.html +610 -0
- package/docs/index.html +2 -2
- package/docs/{WWebPerm.mjs.html → server_WWebPerm.mjs.html} +84 -94
- package/docs/server_procCore.mjs.html +311 -0
- package/docs/src_fetchJson.mjs.html +113 -0
- package/package.json +18 -19
- package/server/WWebPerm.mjs +80 -90
- package/server/procCore.mjs +56 -4
- package/server/procLang.mjs +3 -3
- package/server/provideTabs.mjs +8 -9
- package/src/components/LayoutContentGrups.vue +5 -1
- package/src/components/LayoutContentPemis.vue +5 -1
- package/src/components/LayoutContentTargets.vue +5 -1
- package/src/components/LayoutContentUsers.vue +21 -8
- package/src/components/VeCgrups.vue +3 -3
- package/src/components/VeCpemis.vue +2 -2
- package/src/components/VeGrupBlngUsers.vue +2 -2
- package/src/components/VePemiBlngGrups.vue +2 -2
- package/src/fetchJson.mjs +41 -0
- package/src/getPerm.mjs +7 -7
- package/src/getPermUserInfor.mjs +7 -7
- package/src/main.js +1 -1
- package/src/perm.mjs +5 -4
- package/src/plugins/mUI.mjs +17 -0
- package/srv.mjs +8 -0
- package/tableTags-web-perm.json +1 -0
- package/dist/js/app-legacy.71fa8c3b.js +0 -1
- package/dist/js/app.71fa8c3b.js +0 -1
- 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 && 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 && 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.
|
|
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.
|
|
9
|
-
"w-component-vue": "^2.5.
|
|
10
|
-
"w-data-collector": "^1.0.
|
|
11
|
-
"w-echarts-vue": "^1.0.
|
|
12
|
-
"w-serv-hapi": "^1.0.
|
|
13
|
-
"w-serv-orm": "^1.0.
|
|
14
|
-
"w-syslog": "^1.0.
|
|
15
|
-
"w-ui-loginout": "^1.0.
|
|
16
|
-
"wsemi": "^1.8.
|
|
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.
|
|
32
|
-
"w-package-tools": "^1.1.
|
|
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.
|
|
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
|
|
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
|
}
|
package/server/WWebPerm.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
602
|
-
|
|
603
|
-
|
|
576
|
+
//from
|
|
577
|
+
let from = get(inp, 'from', '')
|
|
578
|
+
// console.log('from', from)
|
|
604
579
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
587
|
+
//rows
|
|
588
|
+
let rows = get(inp, 'rows', [])
|
|
589
|
+
// console.log(keyTable, rows)
|
|
615
590
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
625
|
-
|
|
599
|
+
//save from
|
|
600
|
+
rows = map(rows, (v, k) => {
|
|
626
601
|
// v.order = k + 1 //order大部分由外部給予, 不能複寫
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
602
|
+
v.from = from
|
|
603
|
+
return v
|
|
604
|
+
})
|
|
630
605
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
606
|
+
//ltdtmapping
|
|
607
|
+
rows = ltdtmapping(rows, ds[keyTable].keys)
|
|
608
|
+
// console.log('ltdtmapping rows', rows)
|
|
634
609
|
|
|
635
|
-
|
|
636
|
-
|
|
610
|
+
//delAll from
|
|
611
|
+
await woItems[keyTable].delAll({ from })
|
|
637
612
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
package/server/procCore.mjs
CHANGED
|
@@ -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
|
package/server/procLang.mjs
CHANGED
|
@@ -111,15 +111,15 @@ let kpLang = {
|
|
|
111
111
|
eng: `Reverse all`,
|
|
112
112
|
cht: `全反選使用`,
|
|
113
113
|
},
|
|
114
|
-
|
|
114
|
+
operEnable: { //關聯表 enable 欄表頭(是否使用)
|
|
115
115
|
eng: `Enable`,
|
|
116
116
|
cht: `是否使用`,
|
|
117
117
|
},
|
|
118
|
-
|
|
118
|
+
operPemiMode: { //關聯表 mode 欄表頭(權限之 OR/AND 合併模式)
|
|
119
119
|
eng: `Operator`,
|
|
120
120
|
cht: `合併權限模式`,
|
|
121
121
|
},
|
|
122
|
-
|
|
122
|
+
operGrupMode: { //關聯表 mode 欄表頭(權限群組之 OR/AND 合併模式)
|
|
123
123
|
eng: `Operator`,
|
|
124
124
|
cht: `合併權限群組模式`,
|
|
125
125
|
},
|
package/server/provideTabs.mjs
CHANGED
|
@@ -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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
98
|
+
body: JSON.stringify(rin),
|
|
99
99
|
})
|
|
100
|
-
.then((
|
|
101
|
-
// console.log('then',
|
|
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
|
|