@rpcbase/client 0.214.0 → 0.216.0
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/access-control/ACLForm/components/GrantField/OpSelector.tsx +129 -0
- package/access-control/ACLForm/components/GrantField/ResourceSelector.tsx +86 -0
- package/access-control/ACLForm/components/GrantField/UsersSelector.tsx +96 -0
- package/access-control/ACLForm/components/GrantField/grant-field.scss +26 -0
- package/access-control/ACLForm/components/GrantField/icons/CheckMark.tsx +16 -0
- package/access-control/ACLForm/components/GrantField/icons/CollapseArrow.tsx +14 -0
- package/access-control/ACLForm/components/GrantField/icons/ExpandArrow.tsx +14 -0
- package/access-control/ACLForm/components/GrantField/index.tsx +91 -0
- package/access-control/ACLForm/components/GrantsList.tsx +48 -0
- package/access-control/ACLForm/components/RoleForm.tsx +134 -0
- package/access-control/ACLForm/components/RoleView.tsx +115 -0
- package/access-control/ACLForm/components/RolesList.tsx +79 -0
- package/access-control/ACLForm/components/constants.tsx +1 -0
- package/access-control/ACLForm/components/resolver.ts +57 -0
- package/access-control/ACLForm/components/role-form.scss +19 -0
- package/access-control/ACLForm/index.tsx +48 -0
- package/access-control/ACLModal/acl-modal.scss +7 -0
- package/access-control/ACLModal/index.tsx +66 -0
- package/access-control/PolicyEditor/TargetSelector/QueryBuilder.tsx +48 -0
- package/access-control/PolicyEditor/TargetSelector/index.tsx +5 -0
- package/access-control/PolicyEditor/TargetSelector/query-builder.scss +9 -0
- package/access-control/PolicyEditor/index.tsx +70 -0
- package/access-control/index.ts +3 -0
- package/firebase/index.js +1 -1
- package/firebase/sw.js +1 -1
- package/package.json +10 -10
- package/ui/SelectPills/index.tsx +96 -0
- package/ui/SelectPills/select-pills.scss +66 -0
- package/ui/icons/Close.tsx +14 -0
- package/ui/icons/index.tsx +1 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import assert from "assert"
|
|
2
|
+
import {useForm, FormProvider} from "react-hook-form"
|
|
3
|
+
|
|
4
|
+
import FloatingLabel from "react-bootstrap/FloatingLabel"
|
|
5
|
+
import Form from "react-bootstrap/Form"
|
|
6
|
+
|
|
7
|
+
// import {useEnvContext} from "helpers/EnvContext"
|
|
8
|
+
import {CloseIcon} from "../../../ui/icons/Close"
|
|
9
|
+
import {View} from "../../../ui/View"
|
|
10
|
+
|
|
11
|
+
import {resolver} from "./resolver"
|
|
12
|
+
import {GrantsList} from "./GrantsList"
|
|
13
|
+
|
|
14
|
+
// import create_role from "rpc!server/access-control/create_role"
|
|
15
|
+
// import delete_role from "rpc!server/access-control/delete_role"
|
|
16
|
+
|
|
17
|
+
import "./role-form.scss"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export const RoleView = ({role}) => {
|
|
21
|
+
// const envContext = useEnvContext()
|
|
22
|
+
|
|
23
|
+
// const [isLoading, setIsLoading] = useState(false)
|
|
24
|
+
// TODO:
|
|
25
|
+
// react hook form submit on edit
|
|
26
|
+
// https://stackoverflow.com/a/70119332
|
|
27
|
+
const form = useForm({
|
|
28
|
+
defaultValues: {
|
|
29
|
+
...role,
|
|
30
|
+
_isEditing: true,
|
|
31
|
+
},
|
|
32
|
+
resolver,
|
|
33
|
+
reValidateMode: "onChange",
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
register,
|
|
38
|
+
handleSubmit,
|
|
39
|
+
// watch,
|
|
40
|
+
// getValues,
|
|
41
|
+
formState: {errors},
|
|
42
|
+
} = form
|
|
43
|
+
|
|
44
|
+
const onSubmit = async(data) => {
|
|
45
|
+
const {groupId} = envContext
|
|
46
|
+
console.log("roleview submit", groupId, data)
|
|
47
|
+
// setIsLoading(true)
|
|
48
|
+
// const res = await create_role({group_id: groupId, ...data})
|
|
49
|
+
// assert(res.status === "ok")
|
|
50
|
+
// // TODO: error handling here
|
|
51
|
+
// setIsLoading(false)
|
|
52
|
+
// onDone()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const onClickRemove = async() => {
|
|
56
|
+
// const res = await delete_role({role_id: role._id})
|
|
57
|
+
// assert(res.status === "ok")
|
|
58
|
+
console.log("NYI DELETE ROLE", role._id)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// useEffect(() => {
|
|
62
|
+
// console.log("le vals", getValues())
|
|
63
|
+
// }, [formState])
|
|
64
|
+
|
|
65
|
+
// TODO: WARNING: DANGER: input should be mapped to field id + index to avoid duplicates in form
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<FormProvider {...form}>
|
|
69
|
+
<div className="w-100" style={{position: "relative"}}>
|
|
70
|
+
<form className="mb-1 me-4 card" onSubmit={handleSubmit(onSubmit)}>
|
|
71
|
+
<div className="p-2">
|
|
72
|
+
<div className="d-flex flex-row w-100 mt-1">
|
|
73
|
+
<FloatingLabel controlId="input-name" label="Name" className="me-2">
|
|
74
|
+
<Form.Control type="text" {...register("name")} />
|
|
75
|
+
{errors.name && <div className="text-danger">{errors.name.message}</div>}
|
|
76
|
+
</FloatingLabel>
|
|
77
|
+
|
|
78
|
+
<FloatingLabel
|
|
79
|
+
controlId="input-description"
|
|
80
|
+
label="Description"
|
|
81
|
+
className="flex-grow-1"
|
|
82
|
+
>
|
|
83
|
+
<Form.Control type="text" {...register("description")} />
|
|
84
|
+
{errors.description && (
|
|
85
|
+
<div className="text-danger">{errors.description.message}</div>
|
|
86
|
+
)}
|
|
87
|
+
</FloatingLabel>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{/* Grants list */}
|
|
91
|
+
<GrantsList />
|
|
92
|
+
</div>
|
|
93
|
+
</form>
|
|
94
|
+
<View
|
|
95
|
+
className="role-remove-button mt-1 me-1"
|
|
96
|
+
onClick={onClickRemove}
|
|
97
|
+
tooltip="Remove Role"
|
|
98
|
+
>
|
|
99
|
+
<CloseIcon size={14} />
|
|
100
|
+
</View>
|
|
101
|
+
</div>
|
|
102
|
+
</FormProvider>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// <label htmlFor="input-role-name" className="h6">
|
|
107
|
+
// Name
|
|
108
|
+
// </label>
|
|
109
|
+
// <input
|
|
110
|
+
// type="text"
|
|
111
|
+
// className={cx("form-control", {"is-invalid": !!errors.name})}
|
|
112
|
+
// id="input-role-name"
|
|
113
|
+
// {...register("name")}
|
|
114
|
+
// placeholder="Role Name"
|
|
115
|
+
// />
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {useState} from "react"
|
|
2
|
+
// import {arrayMoveImmutable} from "array-move"
|
|
3
|
+
|
|
4
|
+
import {DragHandle, SortableContainer, SortableElement} from "../../../ui/sortable-hoc"
|
|
5
|
+
import {useQuery} from "../../../rts"
|
|
6
|
+
|
|
7
|
+
import {RoleForm} from "./RoleForm"
|
|
8
|
+
import {RoleView} from "./RoleView"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const SortableItem = SortableElement(({value, index, active, onClick}) => (
|
|
12
|
+
<div id={`${value._id}-role-list-item-${index}`} className="d-block px-1 py-2">
|
|
13
|
+
<div className={cx(["d-flex", "align-items-start", {active}])} onClick={onClick}>
|
|
14
|
+
<DragHandle variant="dots" className="mt-1" />
|
|
15
|
+
<RoleView role={value} />
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
))
|
|
19
|
+
|
|
20
|
+
const SortableList = SortableContainer(({ref, items, onClickHandler, activeTab, className = ""}) => {
|
|
21
|
+
return (
|
|
22
|
+
<div ref={ref} className={cx(className)}>
|
|
23
|
+
{items.map((value, index) => (
|
|
24
|
+
<SortableItem
|
|
25
|
+
key={`item-${value._id}`}
|
|
26
|
+
index={index}
|
|
27
|
+
value={value}
|
|
28
|
+
active={`${index}` === activeTab}
|
|
29
|
+
onClick={onClickHandler(`${index}`)}
|
|
30
|
+
/>
|
|
31
|
+
))}
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export const RolesList = () => {
|
|
37
|
+
// const [roles, setRoles] = useState([])
|
|
38
|
+
// const rolesQuery = useQuery("ACLRole", {})
|
|
39
|
+
const rolesQuery = {}
|
|
40
|
+
|
|
41
|
+
const [isAddingRole, setIsAddingRole] = useState(false)
|
|
42
|
+
|
|
43
|
+
const onClickAddRole = () => setIsAddingRole(true)
|
|
44
|
+
|
|
45
|
+
const onSortEnd = ({oldIndex, newIndex}) => {
|
|
46
|
+
// setItems(arrayMoveImmutable(items, oldIndex, newIndex))
|
|
47
|
+
console.log("update sort roles", oldIndex, newIndex)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const getOnClickHandler = (arg) => () => {
|
|
51
|
+
console.log("sortable on click handler", arg)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="d-block bg-white mb-2">
|
|
56
|
+
{isAddingRole && <RoleForm onDone={() => setIsAddingRole(false)} />}
|
|
57
|
+
|
|
58
|
+
{!isAddingRole && (
|
|
59
|
+
<button className="btn btn-link ms-1" onClick={onClickAddRole}>
|
|
60
|
+
+ Add Role
|
|
61
|
+
</button>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
{rolesQuery.data && (
|
|
65
|
+
<SortableList
|
|
66
|
+
className="mt-3"
|
|
67
|
+
axis="y"
|
|
68
|
+
lockAxis={"y"}
|
|
69
|
+
// distance={2} // PUTAIN
|
|
70
|
+
items={rolesQuery.data}
|
|
71
|
+
onSortEnd={onSortEnd}
|
|
72
|
+
onClickHandler={getOnClickHandler}
|
|
73
|
+
transitionDuration={200}
|
|
74
|
+
useDragHandle
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const GRANTS_FIELD = "grants"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import _set from "lodash/set"
|
|
2
|
+
|
|
3
|
+
import {GRANTS_FIELD} from "./constants"
|
|
4
|
+
|
|
5
|
+
const validateGrants = (grants, errors) => {
|
|
6
|
+
grants.forEach((grant, index) => {
|
|
7
|
+
const fieldKey = `${GRANTS_FIELD}.${index}`
|
|
8
|
+
|
|
9
|
+
// Ops
|
|
10
|
+
const ops = grant.ops || {}
|
|
11
|
+
const hasOps = Object.keys(ops).length > 0
|
|
12
|
+
if (!hasOps) {
|
|
13
|
+
const opsErrKey = `${fieldKey}.ops`
|
|
14
|
+
_set(errors, opsErrKey, {type: "error", message: "You must select at least one Operation"})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Resources
|
|
18
|
+
const resources = grant.resources || {}
|
|
19
|
+
const hasResources = Object.keys(resources).length > 0
|
|
20
|
+
|
|
21
|
+
if (!hasResources) {
|
|
22
|
+
const resErrKey = `${fieldKey}.resources`
|
|
23
|
+
_set(errors, resErrKey, {type: "error", message: "You must select at least one Resource"})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Users
|
|
27
|
+
const users = grant.users || {}
|
|
28
|
+
const hasUsers = Object.keys(users).length > 0
|
|
29
|
+
|
|
30
|
+
if (!hasUsers) {
|
|
31
|
+
const usersErrKey = `${fieldKey}.users`
|
|
32
|
+
_set(errors, usersErrKey, {type: "error", message: "You must select at least one User"})
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const resolver = (values) => {
|
|
38
|
+
const errors = {}
|
|
39
|
+
|
|
40
|
+
if (!values.name) {
|
|
41
|
+
errors.name = {message: "Please enter a valid name"}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!values.description) {
|
|
45
|
+
errors.description = {message: "Please enter a valid description"}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
validateGrants(values.grants, errors)
|
|
49
|
+
if (values.grants.length === 0) {
|
|
50
|
+
errors.grants = {message: "You must add at least one Grant"}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
values,
|
|
55
|
+
errors,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
@import "helpers";
|
|
2
|
+
|
|
3
|
+
.role-remove-button {
|
|
4
|
+
position: absolute;
|
|
5
|
+
width: 14px;
|
|
6
|
+
height: 14px;
|
|
7
|
+
right: 0;
|
|
8
|
+
top: 0;
|
|
9
|
+
cursor: pointer;
|
|
10
|
+
color: $gray-500;
|
|
11
|
+
|
|
12
|
+
&:hover {
|
|
13
|
+
color: $gray-700;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&:active {
|
|
17
|
+
color: $gray-900;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {useState} from "react"
|
|
2
|
+
|
|
3
|
+
import {SelectPills} from "../../ui/SelectPills"
|
|
4
|
+
|
|
5
|
+
import {RolesList} from "./components/RolesList"
|
|
6
|
+
|
|
7
|
+
const VISIBILITY_ITEMS = [
|
|
8
|
+
{
|
|
9
|
+
name: "Private",
|
|
10
|
+
key: "private",
|
|
11
|
+
description: "Members cannot discover this group and have to be added manually",
|
|
12
|
+
icon: "/static/icons/acl/acl-visibility-private.svg",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "Public",
|
|
16
|
+
key: "public",
|
|
17
|
+
description: "Members of your organisation will be able to see this group",
|
|
18
|
+
icon: "/static/icons/acl/acl-visibility-public.svg",
|
|
19
|
+
},
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
export const ACLForm = () => {
|
|
23
|
+
const [activeVisibilityKey, setActiveVisibilityKey] = useState("private")
|
|
24
|
+
|
|
25
|
+
const onUpdateGroupVisibility = (k) => setActiveVisibilityKey(k)
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div>
|
|
29
|
+
{/* Visibility */}
|
|
30
|
+
<div className="mx-3">
|
|
31
|
+
<label className="fw-bold mb-1">Group Visibility</label>
|
|
32
|
+
<SelectPills
|
|
33
|
+
direction="row"
|
|
34
|
+
size="sm"
|
|
35
|
+
items={VISIBILITY_ITEMS}
|
|
36
|
+
activeKey={activeVisibilityKey}
|
|
37
|
+
onChange={onUpdateGroupVisibility}
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{/* Roles */}
|
|
42
|
+
<div>
|
|
43
|
+
<label className="fw-bold mb-1 ms-3">Roles</label>
|
|
44
|
+
<RolesList />
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {useState, useEffect} from "react"
|
|
2
|
+
|
|
3
|
+
import {useHashState} from "../../hashState"
|
|
4
|
+
import Modal from "../../ui/Modal"
|
|
5
|
+
|
|
6
|
+
import {ACLForm} from "../ACLForm"
|
|
7
|
+
|
|
8
|
+
import "./acl-modal.scss"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export const ACLModal = ({}) => {
|
|
12
|
+
const {hashState, serializeHashState} = useHashState()
|
|
13
|
+
|
|
14
|
+
const [isShown, setIsShown] = useState(false)
|
|
15
|
+
|
|
16
|
+
// useEffect(() => {
|
|
17
|
+
// console.log("GOURP", envContext)
|
|
18
|
+
// }, [envContext])
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (hashState.showGroupAccessControlModal) {
|
|
22
|
+
setIsShown(true)
|
|
23
|
+
} else {
|
|
24
|
+
setIsShown(false)
|
|
25
|
+
}
|
|
26
|
+
}, [hashState, setIsShown])
|
|
27
|
+
|
|
28
|
+
const onHide = () => {
|
|
29
|
+
serializeHashState({
|
|
30
|
+
showGroupAccessControlModal: null,
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!isShown) return null
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Modal className="group-acl-modal" onHide={onHide} size="large">
|
|
38
|
+
<Modal.Header className="close-top" closeButton>
|
|
39
|
+
<img
|
|
40
|
+
width={26}
|
|
41
|
+
height={26}
|
|
42
|
+
style={{marginTop: 0}}
|
|
43
|
+
className="me-2 align-self-start"
|
|
44
|
+
src={`/static/icons/acl-shield.svg`}
|
|
45
|
+
/>
|
|
46
|
+
<div className="">
|
|
47
|
+
<div>
|
|
48
|
+
Permissions for <kbd>wow group name</kbd>
|
|
49
|
+
</div>
|
|
50
|
+
<small className="text-secondary fw-normal">
|
|
51
|
+
Permissions and Grants that control who can see what in your Group
|
|
52
|
+
</small>
|
|
53
|
+
</div>
|
|
54
|
+
</Modal.Header>
|
|
55
|
+
<Modal.Body className="px-0">
|
|
56
|
+
<ACLForm />
|
|
57
|
+
</Modal.Body>
|
|
58
|
+
<Modal.Footer className="justify-content-start">
|
|
59
|
+
Learn more about Access Control and Permissions in
|
|
60
|
+
<a target="_blank" rel="noopener noreferrer" href={`/docs/access-control`}>
|
|
61
|
+
/docs/access-control
|
|
62
|
+
</a>
|
|
63
|
+
</Modal.Footer>
|
|
64
|
+
</Modal>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
QueryBuilderBootstrap,
|
|
3
|
+
bootstrapControlClassnames,
|
|
4
|
+
bootstrapControlElements,
|
|
5
|
+
} from "@react-querybuilder/bootstrap"
|
|
6
|
+
import {QueryBuilder as ReactQueryBuilder} from "react-querybuilder"
|
|
7
|
+
import {QueryBuilderDnD as ReactQueryBuilderDnD} from "@react-querybuilder/dnd"
|
|
8
|
+
import * as ReactDnD from "react-dnd"
|
|
9
|
+
import * as ReactDnDHtml5Backend from "react-dnd-html5-backend"
|
|
10
|
+
|
|
11
|
+
// import FieldSelector from "./FieldSelector"
|
|
12
|
+
// import OperatorSelector from "./OperatorSelector"
|
|
13
|
+
// import ValueEditor from "./ValueEditor"
|
|
14
|
+
|
|
15
|
+
import "./query-builder.scss"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
const controlClassnames = {...bootstrapControlClassnames}
|
|
19
|
+
controlClassnames.queryBuilder += " queryBuilder-branches"
|
|
20
|
+
|
|
21
|
+
const QueryBuilder = ({controlElements = {}, ...props}) => {
|
|
22
|
+
return (
|
|
23
|
+
<ReactQueryBuilderDnD dnd={{...ReactDnD, ...ReactDnDHtml5Backend}}>
|
|
24
|
+
<QueryBuilderBootstrap>
|
|
25
|
+
<ReactQueryBuilder
|
|
26
|
+
addRuleToNewGroups
|
|
27
|
+
// TODO: why is DnD not working??
|
|
28
|
+
// https://react-querybuilder.js.org/docs/components/querybuilder#enabledraganddrop
|
|
29
|
+
enableDragAndDrop
|
|
30
|
+
showCombinatorsBetweenRules={false}
|
|
31
|
+
showNotToggle
|
|
32
|
+
showCloneButtons
|
|
33
|
+
controlClassnames={controlClassnames}
|
|
34
|
+
controlElements={{
|
|
35
|
+
...bootstrapControlElements,
|
|
36
|
+
...controlElements,
|
|
37
|
+
// fieldSelector: FieldSelector,
|
|
38
|
+
// operatorSelector: OperatorSelector,
|
|
39
|
+
// valueEditor: ValueEditor,
|
|
40
|
+
}}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
</QueryBuilderBootstrap>
|
|
44
|
+
</ReactQueryBuilderDnD>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default QueryBuilder
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {useState} from "react"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const TARGET_TYPES = [
|
|
5
|
+
{
|
|
6
|
+
name: "Collection:",
|
|
7
|
+
key: "collection",
|
|
8
|
+
description: "Applies to all documents within the collection.",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: "Document:",
|
|
12
|
+
key: "document",
|
|
13
|
+
description: "Applies only to certain specific documents.",
|
|
14
|
+
},
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
export const PolicyEditor = () => {
|
|
18
|
+
const [targetType, setTargetType] = useState("collection")
|
|
19
|
+
|
|
20
|
+
const onChangeTargetType = (event) => {
|
|
21
|
+
setTargetType(event.target.value)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div>
|
|
26
|
+
<h6>Policy Editor</h6>
|
|
27
|
+
<br />
|
|
28
|
+
<div className="d-flex flex-row">
|
|
29
|
+
<div className="me-2">
|
|
30
|
+
<h6>Target Type:</h6>
|
|
31
|
+
<div style={{maxWidth: 300}}>
|
|
32
|
+
{TARGET_TYPES.map((type) => (
|
|
33
|
+
<div key={type.key} className="d-flex flex-row mb-1">
|
|
34
|
+
<input
|
|
35
|
+
className="form-check-input"
|
|
36
|
+
type="radio"
|
|
37
|
+
name="targetTypeOptions"
|
|
38
|
+
id={type.key}
|
|
39
|
+
value={type.key}
|
|
40
|
+
checked={targetType === type.key}
|
|
41
|
+
onChange={onChangeTargetType}
|
|
42
|
+
/>
|
|
43
|
+
<label
|
|
44
|
+
className="form-check-label cursor-pointer ps-2"
|
|
45
|
+
htmlFor={type.key}
|
|
46
|
+
>
|
|
47
|
+
<b>{type.name}</b> {type.description}
|
|
48
|
+
</label>
|
|
49
|
+
</div>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div>
|
|
55
|
+
<h6>Targets:</h6>
|
|
56
|
+
<div>
|
|
57
|
+
<input type="text" />
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
grant / deny
|
|
62
|
+
<br />
|
|
63
|
+
Scope: document, field
|
|
64
|
+
<br />
|
|
65
|
+
operation: create read write delete
|
|
66
|
+
<br />
|
|
67
|
+
to attributes / conditions add support for expiry date
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
}
|