w-web-perm 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/css/{chunk-vendors.65775501.css → chunk-vendors.1c6c57f9.css} +1 -1
- package/dist/index.tmp +1 -1
- package/dist/js/app-legacy.317c7b7b.js +2 -0
- package/dist/js/app-legacy.317c7b7b.js.map +1 -0
- package/dist/js/app.317c7b7b.js +2 -0
- package/dist/js/app.317c7b7b.js.map +1 -0
- package/dist/js/chunk-vendors-legacy.a074d0c0.js +13 -0
- package/dist/js/chunk-vendors-legacy.a074d0c0.js.map +1 -0
- package/dist/js/chunk-vendors.a074d0c0.js +13 -0
- package/dist/js/chunk-vendors.a074d0c0.js.map +1 -0
- package/dist/w-web-perm.umd.js +2 -2
- package/dist/w-web-perm.umd.js.map +1 -1
- package/docs/WWebPerm.html +1 -1
- package/docs/WWebPerm.mjs.html +58 -8
- package/docs/index.html +1 -1
- package/g.mInitialData.mjs +147 -0
- package/package.json +4 -3
- package/server/WWebPerm.mjs +57 -7
- package/src/components/LayoutContentGroups.vue +3 -2
- package/src/components/LayoutContentTargets.vue +9 -2
- package/src/components/VeUser.vue +5 -4
- package/src/components/ruleGroup.vue +6 -28
- package/src/main.js +5 -10
- package/src/perm.mjs +85 -47
- package/src/plugins/mDataSupport.mjs +3 -46
- package/src/plugins/mShare.mjs +25 -5
- package/src/schema/tables/ruleGroups.mjs +1 -2
- package/src/schema/tables/targets.mjs +21 -28
- package/src/store/mutations.mjs +1 -1
- package/srv.mjs +24 -7
- package/tableTags-web-perm.json +1 -1
- package/dist/js/app-legacy.a31aa3a9.js +0 -2
- package/dist/js/app-legacy.a31aa3a9.js.map +0 -1
- package/dist/js/app.a31aa3a9.js +0 -2
- package/dist/js/app.a31aa3a9.js.map +0 -1
- package/dist/js/chunk-vendors-legacy.f5f1a231.js +0 -13
- package/dist/js/chunk-vendors-legacy.f5f1a231.js.map +0 -1
- package/dist/js/chunk-vendors.f5f1a231.js +0 -13
- package/dist/js/chunk-vendors.f5f1a231.js.map +0 -1
package/src/perm.mjs
CHANGED
|
@@ -1,65 +1,103 @@
|
|
|
1
1
|
import axios from 'axios'
|
|
2
|
+
import get from 'lodash/get'
|
|
2
3
|
import isestr from 'wsemi/src/isestr.mjs'
|
|
4
|
+
import isbol from 'wsemi/src/isbol.mjs'
|
|
5
|
+
import isfun from 'wsemi/src/isfun.mjs'
|
|
6
|
+
import genPm from 'wsemi/src/genPm.mjs'
|
|
7
|
+
import pmInvResolve from 'wsemi/src/pmInvResolve.mjs'
|
|
3
8
|
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
function perm() {
|
|
11
|
+
//供外部系統引用並進行取得權限之操作
|
|
6
12
|
|
|
13
|
+
//pmd
|
|
14
|
+
let pmd = null
|
|
7
15
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
16
|
+
//conn
|
|
17
|
+
let conn = (url, opt = {}) => {
|
|
11
18
|
|
|
19
|
+
//check
|
|
20
|
+
if (!isestr(url)) {
|
|
21
|
+
throw new Error(`invalid url`)
|
|
22
|
+
}
|
|
23
|
+
if (url.indexOf('token=') === 0) {
|
|
24
|
+
throw new Error(`url does not include 'token='`)
|
|
25
|
+
}
|
|
26
|
+
if (url.indexOf('userId=') === 0) {
|
|
27
|
+
throw new Error(`url does not include 'userId='`)
|
|
28
|
+
}
|
|
12
29
|
|
|
13
|
-
|
|
30
|
+
//useAsync
|
|
31
|
+
let useAsync = get(opt, 'useAsync')
|
|
32
|
+
if (!isbol(useAsync)) {
|
|
33
|
+
useAsync = false
|
|
34
|
+
}
|
|
14
35
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
throw new Error(`invalid url, need to use iniPerm to set url`)
|
|
18
|
-
}
|
|
36
|
+
//permSuccess
|
|
37
|
+
let permSuccess = get(opt, 'permSuccess')
|
|
19
38
|
|
|
20
|
-
|
|
21
|
-
|
|
39
|
+
//permError
|
|
40
|
+
let permError = get(opt, 'permError')
|
|
22
41
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
else {
|
|
29
|
-
url = _url
|
|
30
|
-
}
|
|
42
|
+
//pm
|
|
43
|
+
let pm = null
|
|
44
|
+
if (useAsync) {
|
|
45
|
+
pm = genPm()
|
|
46
|
+
}
|
|
31
47
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
//get
|
|
49
|
+
let pms = axios.get(url)
|
|
50
|
+
pms = pmInvResolve(pms, { thenExtractData: true })
|
|
51
|
+
pms
|
|
52
|
+
.then((res) => {
|
|
53
|
+
// console.log('perm.getPerm then', res)
|
|
54
|
+
pmd = res
|
|
55
|
+
if (isfun(permSuccess)) {
|
|
56
|
+
permSuccess(pmd)
|
|
57
|
+
}
|
|
58
|
+
if (useAsync) {
|
|
59
|
+
pm.resolve(kp)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
.catch((err) => {
|
|
63
|
+
// console.log('perm.getPerm catch', err)
|
|
64
|
+
if (isfun(permError)) {
|
|
65
|
+
permError(err)
|
|
66
|
+
}
|
|
67
|
+
if (useAsync) {
|
|
68
|
+
pm.reject()
|
|
69
|
+
}
|
|
70
|
+
})
|
|
55
71
|
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
if (useAsync) {
|
|
73
|
+
return pm
|
|
74
|
+
}
|
|
75
|
+
return kp
|
|
76
|
+
}
|
|
58
77
|
|
|
78
|
+
//kp
|
|
79
|
+
let kp = {
|
|
80
|
+
conn,
|
|
81
|
+
getUser: () => {
|
|
82
|
+
let u = get(pmd, `user`, {})
|
|
83
|
+
// console.log('u', u)
|
|
84
|
+
return u
|
|
85
|
+
},
|
|
86
|
+
getRules: () => {
|
|
87
|
+
return get(pmd, `rules`, {})
|
|
88
|
+
},
|
|
89
|
+
rule: (key) => {
|
|
90
|
+
return get(pmd, `rules.${key}`, {})
|
|
91
|
+
},
|
|
92
|
+
show: (key) => {
|
|
93
|
+
return get(pmd, `rules.${key}.show`, 'n')
|
|
94
|
+
},
|
|
95
|
+
active: (key) => {
|
|
96
|
+
return get(pmd, `rules.${key}.active`, 'n')
|
|
97
|
+
},
|
|
98
|
+
}
|
|
59
99
|
|
|
60
|
-
|
|
61
|
-
iniPerm,
|
|
62
|
-
getPerm,
|
|
100
|
+
return kp
|
|
63
101
|
}
|
|
64
102
|
|
|
65
103
|
|
|
@@ -1,63 +1,20 @@
|
|
|
1
|
-
// import min from 'lodash/min'
|
|
2
|
-
// import size from 'lodash/size'
|
|
3
|
-
// import take from 'lodash/take'
|
|
4
|
-
// import isEqual from 'lodash/isEqual'
|
|
5
|
-
// import uniq from 'lodash/uniq'
|
|
6
|
-
// import set from 'lodash/set'
|
|
7
1
|
// import get from 'lodash/get'
|
|
8
2
|
// import each from 'lodash/each'
|
|
9
3
|
// import map from 'lodash/map'
|
|
10
|
-
// import filter from 'lodash/filter'
|
|
11
|
-
// import last from 'lodash/last'
|
|
12
|
-
// import split from 'lodash/split'
|
|
13
|
-
// import join from 'lodash/join'
|
|
14
|
-
// import drop from 'lodash/drop'
|
|
15
|
-
// import dropRight from 'lodash/dropRight'
|
|
16
|
-
// import groupBy from 'lodash/groupBy'
|
|
17
|
-
// import sortBy from 'lodash/sortBy'
|
|
18
4
|
// import cloneDeep from 'lodash/cloneDeep'
|
|
19
|
-
// import cint from 'wsemi/src/cint.mjs'
|
|
20
|
-
// import delay from 'wsemi/src/delay.mjs'
|
|
21
|
-
// import isarr from 'wsemi/src/isarr.mjs'
|
|
22
5
|
// import isestr from 'wsemi/src/isestr.mjs'
|
|
23
6
|
// import isfun from 'wsemi/src/isfun.mjs'
|
|
24
7
|
// import iseobj from 'wsemi/src/iseobj.mjs'
|
|
25
8
|
// import isearr from 'wsemi/src/isearr.mjs'
|
|
26
|
-
// import
|
|
27
|
-
// import strleft from 'wsemi/src/strleft.mjs'
|
|
28
|
-
// import strdelleft from 'wsemi/src/strdelleft.mjs'
|
|
29
|
-
// import sep from 'wsemi/src/sep.mjs'
|
|
30
|
-
// import aes2str from 'wsemi/src/aes2str.mjs'
|
|
31
|
-
// import str2aes from 'wsemi/src/str2aes.mjs'
|
|
32
|
-
// import haskey from 'wsemi/src/haskey.mjs'
|
|
33
|
-
// import genPm from 'wsemi/src/genPm.mjs'
|
|
34
|
-
// import pmSeries from 'wsemi/src/pmSeries.mjs'
|
|
35
|
-
// import waitFun from 'wsemi/src/waitFun.mjs'
|
|
36
|
-
// import browserView from 'wsemi/src/browserView.mjs'
|
|
37
|
-
// import arrHas from 'wsemi/src/arrHas.mjs'
|
|
38
|
-
// import arrSort from 'wsemi/src/arrSort.mjs'
|
|
39
|
-
// import arrInsert from 'wsemi/src/arrInsert.mjs'
|
|
40
|
-
// import replace from 'wsemi/src/replace.mjs'
|
|
41
|
-
// import timemsTZ2past from 'wsemi/src/timemsTZ2past.mjs'
|
|
42
|
-
// import convertToTree from 'wsemi/src/convertToTree.mjs'
|
|
43
|
-
// import html2picDyn from 'wsemi/src/html2picDyn.mjs'
|
|
44
|
-
// import urlParse from 'wsemi/src/urlParse.mjs'
|
|
45
|
-
// import domConvertToPicDyn from 'wsemi/src/domConvertToPicDyn.mjs'
|
|
46
|
-
// import ltdtkeysheads2mat from 'wsemi/src/ltdtkeysheads2mat.mjs'
|
|
47
|
-
// import downloadFileFromB64 from 'wsemi/src/downloadFileFromB64.mjs'
|
|
48
|
-
// import downloadExcelFileFromDataDyn from 'wsemi/src/downloadExcelFileFromDataDyn.mjs'
|
|
49
|
-
// import getFileAccept from 'wsemi/src/getFileAccept.mjs'
|
|
50
|
-
// import downloadFileFromU8Arr from 'wsemi/src/downloadFileFromU8Arr.mjs'
|
|
9
|
+
// import cint from 'wsemi/src/cint.mjs'
|
|
51
10
|
|
|
52
11
|
|
|
53
|
-
function mDataSupport(vo
|
|
12
|
+
function mDataSupport(vo) {
|
|
54
13
|
|
|
55
14
|
|
|
56
15
|
return {
|
|
57
16
|
|
|
58
|
-
//
|
|
59
|
-
// mdGetTextByKey,
|
|
60
|
-
// mdGetTextByKeyIndAndKey,
|
|
17
|
+
// fun,
|
|
61
18
|
|
|
62
19
|
}
|
|
63
20
|
}
|
package/src/plugins/mShare.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//前後端共用函數區
|
|
2
|
+
import JSON5 from 'json5'
|
|
2
3
|
import get from 'lodash/get'
|
|
3
4
|
import each from 'lodash/each'
|
|
4
5
|
import map from 'lodash/map'
|
|
@@ -14,16 +15,20 @@ import haskey from 'wsemi/src/haskey.mjs'
|
|
|
14
15
|
import composeToTree from 'wsemi/src/composeToTree.mjs'
|
|
15
16
|
|
|
16
17
|
|
|
18
|
+
//dlm
|
|
19
|
+
let dlm = '/'
|
|
20
|
+
|
|
21
|
+
|
|
17
22
|
function getAllBlocks(targets) {
|
|
18
23
|
|
|
19
24
|
//rs
|
|
20
25
|
let rs = map(targets, (v) => {
|
|
21
26
|
|
|
22
27
|
//text, parentId
|
|
23
|
-
let s = sep(v.id,
|
|
28
|
+
let s = sep(v.id, dlm) //[tag:dlm]
|
|
24
29
|
let text = get(takeRight(s), 0) //取最後節點
|
|
25
30
|
s = dropRight(s, 1)
|
|
26
|
-
let parentId = join(s,
|
|
31
|
+
let parentId = join(s, dlm) //[tag:dlm] 取父節點
|
|
27
32
|
|
|
28
33
|
return {
|
|
29
34
|
...v,
|
|
@@ -32,9 +37,11 @@ function getAllBlocks(targets) {
|
|
|
32
37
|
text,
|
|
33
38
|
}
|
|
34
39
|
})
|
|
40
|
+
// console.log('rs', rs)
|
|
35
41
|
|
|
36
42
|
//sortBy
|
|
37
43
|
rs = sortBy(rs, 'order')
|
|
44
|
+
// console.log('rs sortBy', rs)
|
|
38
45
|
|
|
39
46
|
return rs
|
|
40
47
|
}
|
|
@@ -44,6 +51,7 @@ function getTreeBlocks(targets) {
|
|
|
44
51
|
|
|
45
52
|
//getAllBlocks
|
|
46
53
|
let blocks = getAllBlocks(targets)
|
|
54
|
+
// console.log('blocks', blocks)
|
|
47
55
|
|
|
48
56
|
//opt
|
|
49
57
|
let opt = {
|
|
@@ -86,7 +94,7 @@ function getUserGroups(user, ruleGroups) {
|
|
|
86
94
|
|
|
87
95
|
//add rules
|
|
88
96
|
gs = map(gs, (g) => {
|
|
89
|
-
let rules =
|
|
97
|
+
let rules = JSON5.parse(g.crules)
|
|
90
98
|
g.rules = rules
|
|
91
99
|
return g
|
|
92
100
|
})
|
|
@@ -107,7 +115,7 @@ function getTargetsByGroup(targets, group) {
|
|
|
107
115
|
group = cloneDeep(group)
|
|
108
116
|
|
|
109
117
|
//rules
|
|
110
|
-
let rules =
|
|
118
|
+
let rules = JSON5.parse(group.crules)
|
|
111
119
|
|
|
112
120
|
//rulesDisplay
|
|
113
121
|
let rulesDisplay = {}
|
|
@@ -157,11 +165,19 @@ function getTargetsByGroup(targets, group) {
|
|
|
157
165
|
|
|
158
166
|
|
|
159
167
|
function getUserRules(user, ruleGroups, targets) {
|
|
168
|
+
//依照使用者所允許之ruleGroups轉出對全部targets權限
|
|
169
|
+
|
|
170
|
+
//ruleGroupsIds
|
|
171
|
+
let ruleGroupsIds = get(user, 'ruleGroupsIds', '')
|
|
160
172
|
|
|
161
173
|
//getUserGroups
|
|
162
174
|
let gs = getUserGroups(user, ruleGroups)
|
|
163
175
|
// console.log('gs', gs)
|
|
164
176
|
|
|
177
|
+
//ruleGroupsNames
|
|
178
|
+
let ruleGroupsNames = map(gs, 'name')
|
|
179
|
+
ruleGroupsNames = join(ruleGroupsNames, ';')
|
|
180
|
+
|
|
165
181
|
//getTargetsByGroup
|
|
166
182
|
let rulesDisplay = {}
|
|
167
183
|
each(gs, (g) => {
|
|
@@ -196,7 +212,11 @@ function getUserRules(user, ruleGroups, targets) {
|
|
|
196
212
|
})
|
|
197
213
|
// console.log('rulesDisplay', rulesDisplay)
|
|
198
214
|
|
|
199
|
-
return
|
|
215
|
+
return {
|
|
216
|
+
ruleGroupsIds,
|
|
217
|
+
ruleGroupsNames,
|
|
218
|
+
rules: rulesDisplay,
|
|
219
|
+
}
|
|
200
220
|
}
|
|
201
221
|
|
|
202
222
|
|
|
@@ -11,6 +11,7 @@ let keyTable = 'ruleGroups'
|
|
|
11
11
|
let tableNameCht = '權限群組'
|
|
12
12
|
let tableNameEng = 'Rule groups'
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
let settings = {
|
|
15
16
|
id: {
|
|
16
17
|
pk: true,
|
|
@@ -70,8 +71,6 @@ let funTest = () => {
|
|
|
70
71
|
['權限群組1', `{ "___all___": { "show": "y", "active": "n" }, "專案A.頁A": { "show": "y", "active": "y" }, "專案A.頁B": { "show": "n", "active": "n" } }`],
|
|
71
72
|
['權限群組2', `{ "___all___": { "show": "y", "active": "n" } }`],
|
|
72
73
|
['權限群組3', `{ "___all___": { "show": "y", "active": "y" } }`],
|
|
73
|
-
// ['L0權限群組', `{ "___all___": { "show": "n", "active": "n" }, "主題列表": { "show": "y", "active": "n" } }`],
|
|
74
|
-
// ['L1權限群組', `{ "___all___": { "show": "y", "active": "y" } }`],
|
|
75
74
|
]
|
|
76
75
|
rs = map(rs, ([g, crules], k) => {
|
|
77
76
|
let v = funNew({ userId: 'id-for-admin', crules })
|
|
@@ -11,6 +11,7 @@ let keyTable = 'targets'
|
|
|
11
11
|
let tableNameCht = '管控對象'
|
|
12
12
|
let tableNameEng = 'Targets'
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
let settings = {
|
|
15
16
|
id: {
|
|
16
17
|
pk: true,
|
|
@@ -47,37 +48,29 @@ let funNew = (ndata = {}) => {
|
|
|
47
48
|
let funTest = () => {
|
|
48
49
|
let rs = [
|
|
49
50
|
'專案A',
|
|
50
|
-
'專案A
|
|
51
|
-
'專案A
|
|
52
|
-
'專案A
|
|
53
|
-
'專案A
|
|
54
|
-
'專案A
|
|
55
|
-
'專案A
|
|
56
|
-
'專案A
|
|
57
|
-
'專案A
|
|
58
|
-
'專案A
|
|
59
|
-
'專案A
|
|
60
|
-
'專案A
|
|
51
|
+
'專案A/頁A',
|
|
52
|
+
'專案A/頁A/區塊A',
|
|
53
|
+
'專案A/頁A/區塊A/執行按鈕',
|
|
54
|
+
'專案A/頁A/區塊A/分析按鈕',
|
|
55
|
+
'專案A/頁B',
|
|
56
|
+
'專案A/頁B/區塊A',
|
|
57
|
+
'專案A/頁B/區塊A/執行按鈕',
|
|
58
|
+
'專案A/頁B/區塊A/分析按鈕',
|
|
59
|
+
'專案A/頁B/區塊B',
|
|
60
|
+
'專案A/頁B/區塊B/展開按鈕',
|
|
61
|
+
'專案A/頁B/區塊B/下載按鈕',
|
|
61
62
|
'專案B',
|
|
62
|
-
'專案B
|
|
63
|
-
'專案B
|
|
64
|
-
'專案B
|
|
65
|
-
'專案B
|
|
66
|
-
'專案B
|
|
67
|
-
'專案B
|
|
68
|
-
'專案B
|
|
69
|
-
'專案B
|
|
70
|
-
// '主題列表',
|
|
71
|
-
// '主題列表.隱藏已刪除',
|
|
72
|
-
// '主題列表.新增主題',
|
|
73
|
-
// '主題列表.主題排序',
|
|
74
|
-
// '主題列表.編輯主題',
|
|
75
|
-
// '主題列表.刪除主題',
|
|
76
|
-
// '後台管理',
|
|
77
|
-
// '權限管理',
|
|
63
|
+
'專案B/頁A',
|
|
64
|
+
'專案B/頁A/區塊A',
|
|
65
|
+
'專案B/頁A/區塊A/下載報表按鈕',
|
|
66
|
+
'專案B/頁A/區塊A/下載數據按鈕',
|
|
67
|
+
'專案B/頁B/區塊A',
|
|
68
|
+
'專案B/頁B/區塊B',
|
|
69
|
+
'專案B/頁B/區塊B/顯示後台按鈕',
|
|
70
|
+
'專案B/頁B/區塊B/轉跳主站按鈕',
|
|
78
71
|
]
|
|
79
72
|
rs = map(rs, (key, k) => {
|
|
80
|
-
let v = funNew({
|
|
73
|
+
let v = funNew({ description: key })
|
|
81
74
|
v.id = `id-for-${key}`
|
|
82
75
|
v = dtpick(v, keys(settings))
|
|
83
76
|
return v
|
package/src/store/mutations.mjs
CHANGED
package/srv.mjs
CHANGED
|
@@ -30,15 +30,32 @@ let opt = {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
let getUserByToken = async (token) => {
|
|
33
|
-
if (token
|
|
34
|
-
return {
|
|
33
|
+
if (token === '{token-for-application}') { //提供外部應用系統作為存取使用者
|
|
34
|
+
return {
|
|
35
|
+
id: 'id-for-application',
|
|
36
|
+
name: 'application',
|
|
37
|
+
email: 'admin@example.com',
|
|
38
|
+
isAdmin: 'y',
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
// if (token === 'token-for-sys-application') { //將主系統視為外部應用系統, 依據指定token作為授權使用者
|
|
42
|
+
// return {
|
|
43
|
+
// id: 'id-for-application',
|
|
44
|
+
// name: 'application',
|
|
45
|
+
// email: 'admin@example.com',
|
|
46
|
+
// isAdmin: 'y',
|
|
47
|
+
// }
|
|
48
|
+
// }
|
|
49
|
+
if (token === 'sys') { //開發階段w-ui-loginout自動給予browser使用者(且位於localhost)的token為sys
|
|
50
|
+
return {
|
|
51
|
+
id: 'id-for-admin',
|
|
52
|
+
name: 'tester',
|
|
53
|
+
email: 'admin@example.com',
|
|
54
|
+
isAdmin: 'y',
|
|
55
|
+
}
|
|
41
56
|
}
|
|
57
|
+
console.log('invalid token', token)
|
|
58
|
+
return {}
|
|
42
59
|
}
|
|
43
60
|
|
|
44
61
|
//WWebPerm
|
package/tableTags-web-perm.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"ruleGroups":"
|
|
1
|
+
{"ruleGroups":"lawH1q","targets":"ajtpXf","users":"t4rBhb"}
|