w-web-perm 1.1.11 → 1.1.13

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 (122) hide show
  1. package/README.md +158 -117
  2. package/dist/css/app.f09c7433.css +1 -0
  3. package/dist/index.tmp +1 -1
  4. package/dist/js/app-legacy.8af87052.js +1 -0
  5. package/dist/js/app.8af87052.js +1 -0
  6. package/dist/js/{chunk-vendors.3e9e3ef1.js → chunk-vendors-legacy.ece5a361.js} +1 -1
  7. package/dist/js/{chunk-vendors-legacy.3e9e3ef1.js → chunk-vendors.ece5a361.js} +1 -1
  8. package/dist/w-web-perm.umd.js +2 -2
  9. package/dist/w-web-perm.umd.js.map +1 -1
  10. package/docs/WWebPerm.html +84 -4
  11. package/docs/global.html +24747 -832
  12. package/docs/index.html +2 -2
  13. package/docs/server_WWebPerm.mjs.html +245 -32
  14. package/docs/server_cascadeWrite.mjs.html +225 -0
  15. package/docs/server_dataChannel.mjs.html +2 -2
  16. package/docs/server_keyCheck.mjs.html +353 -0
  17. package/docs/server_lockSave.mjs.html +2 -2
  18. package/docs/server_maskLog.mjs.html +2 -2
  19. package/docs/server_procCore.mjs.html +180 -41
  20. package/docs/server_staLogs_resolveShellWorker.mjs.html +2 -2
  21. package/docs/server_syncKeep.mjs.html +311 -0
  22. package/docs/server_tools_authRefsAudit.mjs.html +247 -0
  23. package/docs/server_tools_authRefsClean.mjs.html +335 -0
  24. package/docs/server_tools_fromTools.mjs.html +797 -0
  25. package/docs/server_tools_isMain.mjs.html +103 -0
  26. package/docs/server_tools_nameDupReport.mjs.html +280 -0
  27. package/docs/server_validateRows.mjs.html +15 -4
  28. package/docs/server_wrapInjected.mjs.html +3 -3
  29. package/docs/src_fetchJson.mjs.html +2 -2
  30. package/docs/src_js_cvKpToArr.mjs.html +125 -0
  31. package/docs/src_js_cvKpToObj.mjs.html +210 -0
  32. package/docs/src_js_draftSync.mjs.html +142 -0
  33. package/docs/src_js_eachOwn.mjs.html +3 -3
  34. package/docs/src_js_editorRefRows.mjs.html +356 -0
  35. package/docs/src_js_externalRows.mjs.html +338 -0
  36. package/docs/src_js_externalUi.mjs.html +230 -0
  37. package/docs/src_js_findRefRows.mjs.html +107 -0
  38. package/docs/src_js_findTouchedDup.mjs.html +150 -0
  39. package/docs/src_js_fmtLossyMsg.mjs.html +95 -0
  40. package/docs/src_js_fromAudit.mjs.html +489 -0
  41. package/docs/src_js_getAddImpact.mjs.html +269 -0
  42. package/docs/src_js_getAuthWarn.mjs.html +463 -0
  43. package/docs/src_js_getCascadePreview.mjs.html +258 -0
  44. package/docs/src_js_getDelRefCount.mjs.html +210 -0
  45. package/docs/src_js_getEffMode.mjs.html +92 -0
  46. package/docs/src_js_getKpLossy.mjs.html +155 -0
  47. package/docs/src_js_getNameErrs.mjs.html +266 -0
  48. package/docs/src_js_getOwn.mjs.html +3 -3
  49. package/docs/src_js_getPropKey.mjs.html +109 -0
  50. package/docs/src_js_getRuleEnable.mjs.html +2 -2
  51. package/docs/src_js_isAuthMode.mjs.html +92 -0
  52. package/docs/src_js_joinNames.mjs.html +96 -0
  53. package/docs/src_js_legendFit.mjs.html +2 -2
  54. package/docs/src_js_pairRowsToSnapshot.mjs.html +156 -0
  55. package/docs/src_js_refAudit.mjs.html +359 -0
  56. package/docs/src_js_refCascade.mjs.html +572 -0
  57. package/docs/src_js_relDialogSend.mjs.html +199 -0
  58. package/docs/src_js_rowDigest.mjs.html +265 -0
  59. package/docs/src_plugins_dataReloadGuard.mjs.html +109 -0
  60. package/docs/src_plugins_reuseDeletedIds.mjs.html +2 -2
  61. package/docs/src_plugins_submitGuard.mjs.html +2 -2
  62. package/g_authRefsClean.mjs +79 -0
  63. package/g_fixFromType.mjs +92 -0
  64. package/g_initialData.mjs +3 -3
  65. package/g_purgeFrom.mjs +127 -0
  66. package/package.json +1 -1
  67. package/server/WWebPerm.mjs +243 -30
  68. package/server/cascadeWrite.mjs +153 -0
  69. package/server/keyCheck.mjs +281 -0
  70. package/server/procCore.mjs +178 -39
  71. package/server/procLang.mjs +271 -0
  72. package/server/syncKeep.mjs +239 -0
  73. package/server/tools/authRefsAudit.mjs +175 -0
  74. package/server/tools/authRefsClean.mjs +263 -0
  75. package/server/tools/fromTools.mjs +725 -0
  76. package/server/tools/isMain.mjs +31 -0
  77. package/server/tools/nameDupReport.mjs +208 -0
  78. package/server/validateRows.mjs +13 -2
  79. package/server/wrapInjected.mjs +1 -1
  80. package/src/components/LayoutContentGrups.vue +399 -77
  81. package/src/components/LayoutContentPemis.vue +400 -78
  82. package/src/components/LayoutContentTargets.vue +261 -25
  83. package/src/components/LayoutContentUsers.vue +336 -98
  84. package/src/components/VeCgrups.vue +119 -115
  85. package/src/components/VeCpemis.vue +117 -115
  86. package/src/components/VeCrules.vue +18 -2
  87. package/src/components/VeGrupBlngUsers.vue +240 -73
  88. package/src/components/VePemiBlngGrups.vue +247 -83
  89. package/src/js/cvKpToArr.mjs +53 -0
  90. package/src/js/cvKpToObj.mjs +138 -0
  91. package/src/js/draftSync.mjs +70 -0
  92. package/src/js/eachOwn.mjs +1 -1
  93. package/src/js/editorRefRows.mjs +284 -0
  94. package/src/js/externalRows.mjs +266 -0
  95. package/src/js/externalUi.mjs +158 -0
  96. package/src/js/findRefRows.mjs +35 -0
  97. package/src/js/findTouchedDup.mjs +78 -0
  98. package/src/js/fmtLossyMsg.mjs +23 -0
  99. package/src/js/fromAudit.mjs +417 -0
  100. package/src/js/getAddImpact.mjs +197 -0
  101. package/src/js/getAuthWarn.mjs +391 -0
  102. package/src/js/getCascadePreview.mjs +186 -0
  103. package/src/js/getDelRefCount.mjs +138 -0
  104. package/src/js/getEffMode.mjs +20 -0
  105. package/src/js/getKpLossy.mjs +83 -0
  106. package/src/js/getNameErrs.mjs +194 -0
  107. package/src/js/getOwn.mjs +1 -1
  108. package/src/js/getPropKey.mjs +37 -0
  109. package/src/js/isAuthMode.mjs +20 -0
  110. package/src/js/joinNames.mjs +24 -0
  111. package/src/js/pairRowsToSnapshot.mjs +84 -0
  112. package/src/js/refAudit.mjs +287 -0
  113. package/src/js/refCascade.mjs +500 -0
  114. package/src/js/relDialogSend.mjs +127 -0
  115. package/src/js/rowDigest.mjs +193 -0
  116. package/src/plugins/dataReloadGuard.mjs +37 -0
  117. package/src/plugins/mShare.mjs +147 -174
  118. package/src/plugins/mUI.mjs +3 -8
  119. package/src/schema/tables/users.mjs +152 -152
  120. package/dist/css/app.6918ec45.css +0 -1
  121. package/dist/js/app-legacy.963e800c.js +0 -1
  122. package/dist/js/app.963e800c.js +0 -1
@@ -0,0 +1,463 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>src/js/getAuthWarn.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#applyPurgePlan">applyPurgePlan</a></li><li><a href="global.html#auditFrom">auditFrom</a></li><li><a href="global.html#auditRefs">auditRefs</a></li><li><a href="global.html#buildReport">buildReport</a></li><li><a href="global.html#buildRowsSend">buildRowsSend</a></li><li><a href="global.html#buildSyncRowsNew">buildSyncRowsNew</a></li><li><a href="global.html#checkBaseline">checkBaseline</a></li><li><a href="global.html#checkCascadePlan">checkCascadePlan</a></li><li><a href="global.html#checkDbNotInUse">checkDbNotInUse</a></li><li><a href="global.html#checkExternalWrite">checkExternalWrite</a></li><li><a href="global.html#checkMapKeyBatch">checkMapKeyBatch</a></li><li><a href="global.html#checkUsersWrite">checkUsersWrite</a></li><li><a href="global.html#cleanOutOf">cleanOutOf</a></li><li><a href="global.html#compareBaseline">compareBaseline</a></li><li><a href="global.html#countAndRefs">countAndRefs</a></li><li><a href="global.html#countExternalRows">countExternalRows</a></li><li><a href="global.html#countIdRenumber">countIdRenumber</a></li><li><a href="global.html#createAuthorizeExec">createAuthorizeExec</a></li><li><a href="global.html#createLegendMeasurer">createLegendMeasurer</a></li><li><a href="global.html#createLockSave">createLockSave</a></li><li><a href="global.html#cvKpToArr">cvKpToArr</a></li><li><a href="global.html#cvKpToObj">cvKpToObj</a></li><li><a href="global.html#decideLegendType">decideLegendType</a></li><li><a href="global.html#describeUpstream">describeUpstream</a></li><li><a href="global.html#diffRefTargets">diffRefTargets</a></li><li><a href="global.html#dropServerOnlyDiff">dropServerOnlyDiff</a></li><li><a href="global.html#eachOwn">eachOwn</a></li><li><a href="global.html#fetchJson">fetchJson</a></li><li><a href="global.html#fillIdlessIds">fillIdlessIds</a></li><li><a href="global.html#findDupIndex">findDupIndex</a></li><li><a href="global.html#findKeyOtherFrom">findKeyOtherFrom</a></li><li><a href="global.html#findRefRows">findRefRows</a></li><li><a href="global.html#findTouchedDup">findTouchedDup</a></li><li><a href="global.html#fmtLossyMsg">fmtLossyMsg</a></li><li><a href="global.html#fmtVars">fmtVars</a></li><li><a href="global.html#fmtWarn">fmtWarn</a></li><li><a href="global.html#formatFixReport">formatFixReport</a></li><li><a href="global.html#formatFromAudit">formatFromAudit</a></li><li><a href="global.html#formatPurgeReport">formatPurgeReport</a></li><li><a href="global.html#genLegend">genLegend</a></li><li><a href="global.html#getAddConfirmText">getAddConfirmText</a></li><li><a href="global.html#getAddImpact">getAddImpact</a></li><li><a href="global.html#getBannerLines">getBannerLines</a></li><li><a href="global.html#getBaselineDigest">getBaselineDigest</a></li><li><a href="global.html#getCascadePreview">getCascadePreview</a></li><li><a href="global.html#getChangedIdx">getChangedIdx</a></li><li><a href="global.html#getDelConfirmText">getDelConfirmText</a></li><li><a href="global.html#getDelImpact">getDelImpact</a></li><li><a href="global.html#getDelRefCount">getDelRefCount</a></li><li><a href="global.html#getDigestFields">getDigestFields</a></li><li><a href="global.html#getDupRowsInconsistent">getDupRowsInconsistent</a></li><li><a href="global.html#getEffMode">getEffMode</a></li><li><a href="global.html#getExternalEdits">getExternalEdits</a></li><li><a href="global.html#getItemWarnText">getItemWarnText</a></li><li><a href="global.html#getKpLossy">getKpLossy</a></li><li><a href="global.html#getLockFields">getLockFields</a></li><li><a href="global.html#getNameErrs">getNameErrs</a></li><li><a href="global.html#getOwn">getOwn</a></li><li><a href="global.html#getPropKey">getPropKey</a></li><li><a href="global.html#getRawModeMap">getRawModeMap</a></li><li><a href="global.html#getRefRowsExtra">getRefRowsExtra</a></li><li><a href="global.html#getRefState">getRefState</a></li><li><a href="global.html#getRefWarnText">getRefWarnText</a></li><li><a href="global.html#getRefWarns">getRefWarns</a></li><li><a href="global.html#getRowIdxs">getRowIdxs</a></li><li><a href="global.html#getRowState">getRowState</a></li><li><a href="global.html#getRuleEnable">getRuleEnable</a></li><li><a href="global.html#getRuleWarnText">getRuleWarnText</a></li><li><a href="global.html#getRuleWarns">getRuleWarns</a></li><li><a href="global.html#getSavePreview">getSavePreview</a></li><li><a href="global.html#getSyncWarnLines">getSyncWarnLines</a></li><li><a href="global.html#hasDupKey">hasDupKey</a></li><li><a href="global.html#hasExternalDiff">hasExternalDiff</a></li><li><a href="global.html#isAuthMode">isAuthMode</a></li><li><a href="global.html#isBaselineStale">isBaselineStale</a></li><li><a href="global.html#isBaselineValid">isBaselineValid</a></li><li><a href="global.html#isDraftStale">isDraftStale</a></li><li><a href="global.html#isExternalRow">isExternalRow</a></li><li><a href="global.html#isJsonLossy">isJsonLossy</a></li><li><a href="global.html#isMain">isMain</a></li><li><a href="global.html#joinList">joinList</a></li><li><a href="global.html#joinNames">joinNames</a></li><li><a href="global.html#keepAssignByEntity">keepAssignByEntity</a></li><li><a href="global.html#keepAssignOnMissing">keepAssignOnMissing</a></li><li><a href="global.html#makeBaseline">makeBaseline</a></li><li><a href="global.html#makeRowStates">makeRowStates</a></li><li><a href="global.html#mapKeyOf">mapKeyOf</a></li><li><a href="global.html#mapNameKp">mapNameKp</a></li><li><a href="global.html#markDataReload">markDataReload</a></li><li><a href="global.html#markHasKey">markHasKey</a></li><li><a href="global.html#maskQuery">maskQuery</a></li><li><a href="global.html#maskTok">maskTok</a></li><li><a href="global.html#maskUrl">maskUrl</a></li><li><a href="global.html#mergePickKeysOnly">mergePickKeysOnly</a></li><li><a href="global.html#pairRowsToSnapshot">pairRowsToSnapshot</a></li><li><a href="global.html#pickUniqueUser">pickUniqueUser</a></li><li><a href="global.html#planDraftSync">planDraftSync</a></li><li><a href="global.html#planFixFromType">planFixFromType</a></li><li><a href="global.html#planPurgeChain">planPurgeChain</a></li><li><a href="global.html#planPurgeFrom">planPurgeFrom</a></li><li><a href="global.html#planRefChanges">planRefChanges</a></li><li><a href="global.html#putRefEntry">putRefEntry</a></li><li><a href="global.html#putRowsToKp">putRowsToKp</a></li><li><a href="global.html#refsChanged">refsChanged</a></li><li><a href="global.html#resolveShellWorker">resolveShellWorker</a></li><li><a href="global.html#restoreExternalOrder">restoreExternalOrder</a></li><li><a href="global.html#restoreServerFields">restoreServerFields</a></li><li><a href="global.html#reuseDeletedIds">reuseDeletedIds</a></li><li><a href="global.html#rewriteRefs">rewriteRefs</a></li><li><a href="global.html#rowDigest">rowDigest</a></li><li><a href="global.html#runAuditFrom">runAuditFrom</a></li><li><a href="global.html#runClean">runClean</a></li><li><a href="global.html#runFixFromType">runFixFromType</a></li><li><a href="global.html#runPurgeFrom">runPurgeFrom</a></li><li><a href="global.html#splitCheckedRows">splitCheckedRows</a></li><li><a href="global.html#submitGuard">submitGuard</a></li><li><a href="global.html#tabWriteKey">tabWriteKey</a></li><li><a href="global.html#validateRowsBeforeWrite">validateRowsBeforeWrite</a></li><li><a href="global.html#withTabLocks">withTabLocks</a></li><li><a href="global.html#wrapInjected">wrapInjected</a></li><li><a href="global.html#wrapOperOrm">wrapOperOrm</a></li><li><a href="global.html#writeWithCascade">writeWithCascade</a></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">src/js/getAuthWarn.mjs</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>import JSON5 from 'json5'
49
+ import eachOwn from './eachOwn.mjs'
50
+ import cvKpToArr from './cvKpToArr.mjs'
51
+ import findRefRows from './findRefRows.mjs'
52
+ import getEffMode from './getEffMode.mjs'
53
+ import getPropKey from './getPropKey.mjs'
54
+ import isAuthMode from './isAuthMode.mjs'
55
+ import joinNames from './joinNames.mjs'
56
+
57
+
58
+ //授權資料「衝突提示」之偵測與文字, 單一擁有者(規則帳本 R-PRINCIPLE-00 原則 3「遇衝突:收嚴+提示」、R-AUTH-WARN-01):
59
+ // 伺服端 getUserRules 對衝突資料已收嚴(src/plugins/mShare.mjs); 本檔以相同判準(findRefRows/getEffMode/getPropKey)找出「哪裡有衝突」,
60
+ // 供清單頁(cgrups/cpemis/crules 儲存格)與五個編輯對話框於該處顯示警告圖示(三角形驚嘆號, $ui.getIcon('warning'))與 tooltip. 純函數, 不依賴 Vue/DOM.
61
+ //
62
+ // 警告碼(引用清單 cgrups/cpemis, 僅 isActive 為 'y' 之項目才有警告——停用項目不參與合併):
63
+ // dangling (c) 引用之名稱查無實體(懸空): 仍以其 mode 參與、內容全 n
64
+ // ambiguous (d) 引用之名稱對到多筆(歷史同名): 內容取各筆之交集
65
+ // modeInvalid(e) mode 缺或非 OR/AND: 視為 AND 限制項(只當交集項、不單獨授予)
66
+ // bothModes (b) 同名同時有 OR 與 AND 之啟用項目: 物件形式無法無損表示, 編輯器拒存(getKpLossy)
67
+ // emptyName (f) 名稱為空字串: 照原樣保留(與名稱為 '' 之列相符, 否則為懸空)
68
+ // noName (f) 名稱不是字串(陣列元素無 name 等): 物件形式無法表示, 編輯器拒存(getKpLossy)
69
+ // 規則清單 crules: ruleConflict (a) 同一管理對象被列兩次以上且有勾與沒勾並存: 所有列皆勾才勾(收嚴)
70
+
71
+
72
+ let CODES = ['dangling', 'ambiguous', 'modeInvalid', 'bothModes', 'emptyName', 'noName', 'unparsable']
73
+
74
+
75
+ let parseKp = (c) => {
76
+ if (typeof c === 'string') {
77
+ try {
78
+ return JSON5.parse(c)
79
+ }
80
+ catch (err) {
81
+ return null //損壞字串: 伺服端視為空(全部 n), 無從指出個別項目
82
+ }
83
+ }
84
+ return c
85
+ }
86
+
87
+
88
+ //isBadData: 非空字串而無法解析為物件/陣列(JSON5 解析失敗, 或解出 null/純量)——格式無法判讀, 伺服端視為空(全部 n, 收嚴), 頁面須提示(R-PRINCIPLE-00 原則 3; 外審 sol R8).
89
+ //空字串/空白(新增列之預設值)與非字串輸入不算損壞
90
+ let isBadData = (c) => {
91
+ if (typeof c !== 'string' || c.trim() === '') {
92
+ return false
93
+ }
94
+ try {
95
+ let kp = JSON5.parse(c)
96
+ return kp === null || typeof kp !== 'object'
97
+ }
98
+ catch (err) {
99
+ return true
100
+ }
101
+ }
102
+
103
+
104
+ /**
105
+ * 偵測引用清單(使用者 cgrups、群組 cpemis)中各項目之衝突
106
+ *
107
+ * @param {String|Object|Array} c 輸入 cgrups/cpemis 字串(JSON5)或已解析之結果
108
+ * @param {Array} rows 輸入被引用的表列(cgrups 對應 grups 表; cpemis 對應 pemis 表), 以 name 精確比對(findRefRows)
109
+ * @returns {Object} 回傳 { items, kpWarns, byCode, count, hasWarn }:
110
+ * items 每個解碼出之項目 [{ name, mode, effMode, isActive, active, nMatch, warns: [警告碼] }](nMatch 僅啟用且名稱為字串者有值, 其餘 -1)
111
+ * kpWarns 名稱 → 警告碼陣列(無原型物件; 僅名稱為字串之啟用項目)
112
+ * byCode 警告碼 → 受影響之名稱陣列(不重複; noName 以 #&lt;序號>)
113
+ * count 有警告之項目數; hasWarn 是否有任何警告
114
+ * @example
115
+ *
116
+ * console.log(getRefWarns('{"G1":{"mode":"AND","isActive":"y"}}', []).byCode.dangling)
117
+ * // => ['G1']
118
+ *
119
+ */
120
+ function getRefWarns(c, rows) {
121
+ let arr = cvKpToArr(parseKp(c))
122
+
123
+ //items
124
+ let items = []
125
+ let kpModes = Object.create(null)
126
+ let k = 0
127
+ eachOwn(arr, (r) => {
128
+ k++
129
+ let active = r.isActive === 'y'
130
+ let it = {
131
+ name: r.name,
132
+ mode: r.mode,
133
+ effMode: getEffMode(r.mode),
134
+ isActive: r.isActive,
135
+ active,
136
+ nMatch: -1,
137
+ warns: [],
138
+ _k: k,
139
+ }
140
+ if (active) {
141
+ if (typeof r.name !== 'string') {
142
+ it.warns.push('noName')
143
+ }
144
+ else {
145
+ it.nMatch = findRefRows(rows, r.name).length
146
+ if (it.nMatch === 0) {
147
+ it.warns.push('dangling')
148
+ }
149
+ else if (it.nMatch >= 2) {
150
+ it.warns.push('ambiguous')
151
+ }
152
+ if (!isAuthMode(r.mode)) {
153
+ it.warns.push('modeInvalid')
154
+ }
155
+ if (r.name === '') {
156
+ it.warns.push('emptyName')
157
+ }
158
+ if (!(r.name in kpModes)) {
159
+ kpModes[r.name] = []
160
+ }
161
+ if (!kpModes[r.name].includes(it.effMode)) {
162
+ kpModes[r.name].push(it.effMode)
163
+ }
164
+ }
165
+ }
166
+ items.push(it)
167
+ })
168
+
169
+ //bothModes
170
+ eachOwn(items, (it) => {
171
+ if (it.active &amp;&amp; typeof it.name === 'string' &amp;&amp; kpModes[it.name].length > 1) {
172
+ it.warns.push('bothModes')
173
+ }
174
+ })
175
+
176
+ //kpWarns, byCode
177
+ let kpWarns = Object.create(null)
178
+ let byCode = {}
179
+ eachOwn(CODES, (code) => {
180
+ byCode[code] = []
181
+ })
182
+ let count = 0
183
+ eachOwn(items, (it) => {
184
+ if (it.warns.length === 0) {
185
+ return true //跳出換下一個
186
+ }
187
+ count++
188
+ let label = (typeof it.name === 'string') ? (it.name === '' ? '""' : it.name) : `#${it._k}` //空字串名稱顯示為 "", 否則 tooltip 中看不到
189
+ eachOwn(it.warns, (code) => {
190
+ if (!byCode[code].includes(label)) {
191
+ byCode[code].push(label)
192
+ }
193
+ if (typeof it.name === 'string') {
194
+ if (!(it.name in kpWarns)) {
195
+ kpWarns[it.name] = []
196
+ }
197
+ if (!kpWarns[it.name].includes(code)) {
198
+ kpWarns[it.name].push(code)
199
+ }
200
+ }
201
+ })
202
+ })
203
+ eachOwn(items, (it) => {
204
+ delete it._k
205
+ })
206
+
207
+ //unparsable: 整個字串無法判讀(無個別項目可掛), 以占位名稱 '-' 計入
208
+ if (isBadData(c)) {
209
+ byCode.unparsable.push('-')
210
+ count++
211
+ }
212
+
213
+ return {
214
+ items,
215
+ kpWarns,
216
+ byCode,
217
+ count,
218
+ hasWarn: count > 0,
219
+ }
220
+ }
221
+
222
+
223
+ /**
224
+ * 偵測規則清單(權限 crules)中「同一管理對象被列兩次以上且有勾與沒勾並存」(R-PRINCIPLE-00 a)
225
+ *
226
+ * 物件形式名稱即鍵、不會重複; 僅陣列形式 [{ name, isActive }] 會發生. 伺服端與編輯器解碼皆為「所有列皆 y 才 y」(收嚴).
227
+ * 值只分 y(isActive 為 'y')與 n(其他一律視為 n); 值全相同(全 y 或全 n)的重複不算衝突.
228
+ *
229
+ * @param {String|Object|Array} c 輸入 crules 字串(JSON5)或已解析之結果
230
+ * @returns {Object} 回傳 { conflicts, names, kpConflict, count, hasWarn }:
231
+ * conflicts [{ name(屬性鍵), n(列數), nY(勾選列數) }]; names 受影響之管理對象名稱陣列; kpConflict 名稱 → true(無原型物件)
232
+ * @example
233
+ *
234
+ * console.log(getRuleWarns('[{"name":"T1","isActive":"y"},{"name":"T1","isActive":"n"}]').names)
235
+ * // => ['T1']
236
+ *
237
+ */
238
+ function getRuleWarns(c) {
239
+ let arr = cvKpToArr(parseKp(c))
240
+ let kpCnt = Object.create(null)
241
+ eachOwn(arr, (r) => {
242
+ let key = getPropKey(r.name)
243
+ if (key === null) {
244
+ return true //跳出換下一個
245
+ }
246
+ if (!(key in kpCnt)) {
247
+ kpCnt[key] = { n: 0, nY: 0 }
248
+ }
249
+ kpCnt[key].n++
250
+ if (r.isActive === 'y') {
251
+ kpCnt[key].nY++
252
+ }
253
+ })
254
+ let conflicts = []
255
+ let kpConflict = Object.create(null)
256
+ eachOwn(kpCnt, (v, key) => {
257
+ if (v.n >= 2 &amp;&amp; v.nY > 0 &amp;&amp; v.nY &lt; v.n) {
258
+ conflicts.push({ name: key, n: v.n, nY: v.nY })
259
+ kpConflict[key] = true
260
+ }
261
+ })
262
+ let names = conflicts.map((x) => x.name)
263
+ let unparsable = isBadData(c)
264
+ return {
265
+ conflicts,
266
+ names,
267
+ kpConflict,
268
+ unparsable,
269
+ count: conflicts.length + (unparsable ? 1 : 0),
270
+ hasWarn: conflicts.length > 0 || unparsable,
271
+ }
272
+ }
273
+
274
+
275
+ /**
276
+ * 計算「以 AND 引用被刪名稱且刪除後該名稱完全消失」之引用者數(R-PRINCIPLE-00 c: 刪除被 AND 引用之群組/權限前提示影響人數;
277
+ * run06 起刪除連帶清理會把該引用自引用端移除, 其 AND 限制隨之解除、授權可能變多, 見 ADR-036; run05 前為「AND 項變懸空、授權被收回」),
278
+ * 以及「刪除歷史同名之一列後其授權可能變多」之引用者數(R-PRINCIPLE-00 d: 同名多筆取各候選之交集, 候選變少則交集變寬)
279
+ *
280
+ * n/holders(既有): 引用者(使用者的 cgrups、群組的 cpemis)中, 有 isActive 為 'y'、有效模式為 AND(mode 缺或非法者亦視為 AND)、名稱屬於被刪名稱,
281
+ * 且刪除後被引用表已無該名稱之列者(刪除後該引用被連帶清理, 其 AND 限制解除).
282
+ * nWiden/holdersWiden(新增; 須給 rowsBefore): 引用者中有 isActive 為 'y' 之引用(任何 mode)、名稱屬於被刪名稱,
283
+ * 且刪除後該名稱仍有 ≥ 1 筆剩餘列但筆數比刪除前少者(歷史同名只刪其一), 該引用者之授權可能變多(少一個較嚴之候選).
284
+ * 未給 rowsBefore(非陣列)則不計, nWiden 為 0.
285
+ *
286
+ * @param {Array} holders 輸入引用者列(users 或 grups)
287
+ * @param {String} field 輸入引用欄位名('cgrups' 或 'cpemis')
288
+ * @param {Array} delNames 輸入被刪除之名稱陣列
289
+ * @param {Array} rowsRemain 輸入刪除後被引用表之剩餘列(grups 或 pemis)
290
+ * @param {Array} [rowsBefore] 輸入刪除前被引用表之列(基準, 已儲存者), 供計 nWiden
291
+ * @returns {Object} 回傳 { n, holders, nWiden, holdersWiden }: n 為以 AND 引用且名稱完全消失之引用者數(其 AND 限制將隨引用被清理而解除), holders 為其列; nWiden 為授權可能變多之引用者數, holdersWiden 為其列
292
+ */
293
+ function countAndRefs(holders, field, delNames, rowsRemain, rowsBefore) {
294
+ let setDel = new Set(delNames)
295
+ let canWiden = Array.isArray(rowsBefore)
296
+ let hit = []
297
+ let hitWiden = []
298
+ eachOwn(holders, (h) => {
299
+ let arr = cvKpToArr(parseKp(h ? h[field] : ''))
300
+ let b = false
301
+ let w = false
302
+ eachOwn(arr, (r) => {
303
+ if (r.isActive !== 'y' || typeof r.name !== 'string' || !setDel.has(r.name)) {
304
+ return true //跳出換下一個
305
+ }
306
+ let nAfter = findRefRows(rowsRemain, r.name).length
307
+ if (getEffMode(r.mode) === 'AND' &amp;&amp; nAfter === 0) {
308
+ b = true
309
+ }
310
+ if (canWiden &amp;&amp; nAfter >= 1 &amp;&amp; nAfter &lt; findRefRows(rowsBefore, r.name).length) {
311
+ w = true
312
+ }
313
+ if (b &amp;&amp; (w || !canWiden)) {
314
+ return false //跳出
315
+ }
316
+ })
317
+ if (b) {
318
+ hit.push(h)
319
+ }
320
+ if (w) {
321
+ hitWiden.push(h)
322
+ }
323
+ })
324
+ return {
325
+ n: hit.length,
326
+ holders: hit,
327
+ nWiden: hitWiden.length,
328
+ holdersWiden: hitWiden,
329
+ }
330
+ }
331
+
332
+
333
+ /**
334
+ * 把訊息範本之 {names}、{kind}、{n}、{m}、{w}、{e}、{list} 代入(以 split/join 之等價單趟掃描, 名稱可含 $&amp; 等字樣, 原樣顯示)
335
+ *
336
+ * names 以 joinNames 串接(至多 5 個, 超過加省略號); list 為呼叫端已組好之字串(如 getCascadePreview 之「舊→新」清單, 自訂截斷文字), 原樣代入;
337
+ * n、m、w、e 為筆數(刪除/改名確認框: n 為受影響之引用端列數, m 為其中以 AND 引用者, w 為歷史同名致授權可能變多者, e 為其中來源為外部應用者). 未給之佔位符原樣保留.
338
+ *
339
+ * @param {String} tpl 輸入範本
340
+ * @param {Object} vars 輸入 { names, kind, n, m, w, e, list }
341
+ * @returns {String} 回傳代入後之文字
342
+ */
343
+ function fmtWarn(tpl, vars = {}) {
344
+ //只掃描原始範本一次, 代入結果不再解析(名稱可含 {kind}、{n}、{names} 等字樣, 須原樣顯示; 外審 sol R11)
345
+ let kp = Object.create(null)
346
+ if ('names' in vars) {
347
+ kp.names = joinNames(vars.names)
348
+ }
349
+ if ('kind' in vars) {
350
+ kp.kind = String(vars.kind)
351
+ }
352
+ for (let k of ['n', 'm', 'w', 'e', 'list']) {
353
+ if (k in vars) {
354
+ kp[k] = String(vars[k])
355
+ }
356
+ }
357
+ return String(tpl).replace(/\{(names|kind|n|m|w|e|list)\}/g, (hit, k) => (k in kp ? kp[k] : hit))
358
+ }
359
+
360
+
361
+ let KEY_OF_CODE = {
362
+ dangling: 'warnRefDangling',
363
+ ambiguous: 'warnRefAmbiguous',
364
+ modeInvalid: 'warnRefModeInvalid',
365
+ bothModes: 'warnRefBothModes',
366
+ emptyName: 'warnRefEmptyName',
367
+ noName: 'warnRefNoName',
368
+ unparsable: 'warnDataUnparsable',
369
+ }
370
+
371
+
372
+ /**
373
+ * 引用清單警告之 tooltip 文字(整個儲存格; 每種警告一行, 換行以 \n)
374
+ *
375
+ * @param {Object} res 輸入 getRefWarns 之結果
376
+ * @param {Function} $t 輸入語系函數(key → 文字)
377
+ * @param {String} kindKey 輸入被引用者之種類語系鍵('warnKindGrup' 或 'warnKindPemi')
378
+ * @returns {String} 回傳 tooltip 文字, 無警告為空字串
379
+ */
380
+ function getRefWarnText(res, $t, kindKey) {
381
+ let kind = $t(kindKey)
382
+ let lines = []
383
+ eachOwn(CODES, (code) => {
384
+ let names = res.byCode[code]
385
+ if (names.length === 0) {
386
+ return true //跳出換下一個
387
+ }
388
+ lines.push(fmtWarn($t(KEY_OF_CODE[code]), { names, kind }))
389
+ })
390
+ return lines.join('\n')
391
+ }
392
+
393
+
394
+ /**
395
+ * 單一項目(編輯對話框之一列)警告之 tooltip 文字
396
+ *
397
+ * @param {Array} codes 輸入該項目之警告碼陣列(getRefWarns 的 kpWarns[name])
398
+ * @param {String} name 輸入該項目之名稱
399
+ * @param {Function} $t 輸入語系函數
400
+ * @param {String} kindKey 輸入被引用者之種類語系鍵
401
+ * @returns {String} 回傳 tooltip 文字, 無警告為空字串
402
+ */
403
+ function getItemWarnText(codes, name, $t, kindKey) {
404
+ let kind = $t(kindKey)
405
+ let lines = []
406
+ eachOwn(CODES, (code) => {
407
+ if (!Array.isArray(codes) || !codes.includes(code)) {
408
+ return true //跳出換下一個
409
+ }
410
+ lines.push(fmtWarn($t(KEY_OF_CODE[code]), { names: [name === '' ? '""' : name], kind }))
411
+ })
412
+ return lines.join('\n')
413
+ }
414
+
415
+
416
+ /**
417
+ * 規則清單警告之 tooltip 文字
418
+ *
419
+ * @param {Object} res 輸入 getRuleWarns 之結果
420
+ * @param {Function} $t 輸入語系函數
421
+ * @returns {String} 回傳 tooltip 文字, 無警告為空字串
422
+ */
423
+ function getRuleWarnText(res, $t) {
424
+ if (!res.hasWarn) {
425
+ return ''
426
+ }
427
+ let lines = []
428
+ if (res.names.length > 0) {
429
+ lines.push(fmtWarn($t('warnRuleConflict'), { names: res.names }))
430
+ }
431
+ if (res.unparsable) {
432
+ lines.push($t('warnDataUnparsable'))
433
+ }
434
+ return lines.join('\n')
435
+ }
436
+
437
+
438
+ export { CODES, getRefWarns, getRuleWarns, countAndRefs, fmtWarn, getRefWarnText, getItemWarnText, getRuleWarnText }
439
+ </code></pre>
440
+ </article>
441
+ </section>
442
+
443
+
444
+
445
+
446
+
447
+
448
+ </div>
449
+
450
+ <br class="clear">
451
+
452
+ <footer>
453
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Fri Oct 23 2026 10:56:39 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
454
+ </footer>
455
+
456
+ <script>prettyPrint();</script>
457
+ <script src="scripts/polyfill.js"></script>
458
+ <script src="scripts/linenumber.js"></script>
459
+
460
+
461
+
462
+ </body>
463
+ </html>