mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b595e0d6a8 | ||
|
|
33621cae5d | ||
|
|
360d807008 | ||
|
|
6f8897ffa5 | ||
|
|
75d5f804c5 | ||
|
|
f7cac02a2d | ||
|
|
ec40730cb2 | ||
|
|
7f3648861b | ||
|
|
b50464db43 | ||
|
|
1eb5ccc131 |
No files matched your search
+438
-355
File diff suppressed because it is too large.
Load diff
+163
-107
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
Badge,
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Button, Card,
|
||||
Col,
|
||||
Divider,
|
||||
Form,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Row,
|
||||
Select,
|
||||
Skeleton,
|
||||
Space, Table,
|
||||
Space, Switch, Table,
|
||||
Tag, Typography
|
||||
} from "antd";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
@@ -33,6 +33,7 @@ import Column from "antd/lib/table/Column";
|
||||
import {useOidcUser} from "@axa-fr/react-oidc";
|
||||
|
||||
const {Option} = Select;
|
||||
const {Meta} = Card;
|
||||
const {Title, Paragraph, Text} = Typography;
|
||||
|
||||
interface TokenDataTable extends PersonalAccessToken {
|
||||
@@ -62,6 +63,7 @@ const UserEdit = () => {
|
||||
|
||||
const [formUser, setFormUser] = useState({} as FormUser)
|
||||
const [form] = Form.useForm()
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
||||
const [confirmModal, confirmModalContextHolder] = Modal.useModal();
|
||||
|
||||
@@ -86,10 +88,20 @@ const UserEdit = () => {
|
||||
name: values.name,
|
||||
groupsToCreate: groupsToCreate,
|
||||
auto_groups: autoGroups,
|
||||
is_service_user: isServiceUser
|
||||
is_service_user: isServiceUser,
|
||||
is_blocked: !values.is_active
|
||||
} as UserToSave
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(users) {
|
||||
let currentUser = users.find((user) => user.is_current)
|
||||
if(currentUser) {
|
||||
setIsAdmin(currentUser.role === "admin");
|
||||
}
|
||||
}
|
||||
}, [users])
|
||||
|
||||
const handleFormSubmit = () => {
|
||||
form.validateFields()
|
||||
.then((values) => {
|
||||
@@ -101,18 +113,6 @@ const UserEdit = () => {
|
||||
dispatch(userActions.setEditUserPopupVisible(false));
|
||||
dispatch(userActions.setUser(null as unknown as User))
|
||||
dispatch(personalAccessTokenActions.resetPersonalAccessTokens(null))
|
||||
dispatch(groupActions.getGroups.request({
|
||||
getAccessTokenSilently: getTokenSilently,
|
||||
payload: null
|
||||
}));
|
||||
dispatch(userActions.getServiceUsers.request({
|
||||
getAccessTokenSilently: getTokenSilently,
|
||||
payload: null
|
||||
}))
|
||||
dispatch(userActions.getUsers.request({
|
||||
getAccessTokenSilently: getTokenSilently,
|
||||
payload: null
|
||||
}))
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
console.log('errorInfo', errorInfo)
|
||||
@@ -208,14 +208,15 @@ const UserEdit = () => {
|
||||
}
|
||||
|
||||
const transformTokenTable = (d: PersonalAccessToken[]): TokenDataTable[] => {
|
||||
if(!d) {
|
||||
if (!d) {
|
||||
return []
|
||||
}
|
||||
return d.map(p => ({
|
||||
key: p.id,
|
||||
status: Date.parse(p.expiration_date) > Date.now() ? "valid" : "expired",
|
||||
created_by_email: getEmail(p),
|
||||
...p} as TokenDataTable))
|
||||
...p
|
||||
} as TokenDataTable))
|
||||
}
|
||||
|
||||
const getEmail = (token: PersonalAccessToken): string => {
|
||||
@@ -271,8 +272,10 @@ const UserEdit = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (user.is_current || user.is_service_user) {
|
||||
dispatch(personalAccessTokenActions.getPersonalAccessTokens.request({getAccessTokenSilently: getTokenSilently,
|
||||
payload: user.id}))
|
||||
dispatch(personalAccessTokenActions.getPersonalAccessTokens.request({
|
||||
getAccessTokenSilently: getTokenSilently,
|
||||
payload: user.id
|
||||
}))
|
||||
}
|
||||
}, [user])
|
||||
|
||||
@@ -282,29 +285,13 @@ const UserEdit = () => {
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
email: user.email,
|
||||
is_blocked: user.is_blocked,
|
||||
is_active: !user.is_blocked,
|
||||
autoGroupsNames: currentGroups,
|
||||
})
|
||||
}
|
||||
}, [form, user, currentGroups])
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
key: '1',
|
||||
label: (
|
||||
<Text onClick={() => onBreadcrumbUsersClick("Users")}>
|
||||
Users
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: (
|
||||
<Text onClick={() => onBreadcrumbUsersClick("Service Users")}>
|
||||
Service Users
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const isUserAdmin = (userId: string): boolean => {
|
||||
return users.find(u => u.id === userId)?.role === "admin"
|
||||
@@ -316,10 +303,10 @@ const UserEdit = () => {
|
||||
<Breadcrumb style={{marginBottom: "30px"}}
|
||||
items={[
|
||||
{
|
||||
title: <Text onClick={() => onBreadcrumbUsersClick("Users")}>All Users</Text>,
|
||||
title: <a onClick={() => onBreadcrumbUsersClick("Users")}>All Users</a>,
|
||||
},
|
||||
{
|
||||
title: <Text onClick={() => onBreadcrumbUsersClick(tab)}>{tab}</Text>,
|
||||
title: <a onClick={() => onBreadcrumbUsersClick(tab)}>{tab}</a>,
|
||||
// menu: { items: menuItems },
|
||||
},
|
||||
{
|
||||
@@ -327,35 +314,43 @@ const UserEdit = () => {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Container style={{backgroundColor: "white", padding: "20px", borderRadius: "4px", boxSizing: "border-box", border: "0.5px solid #D9D9D9", marginBottom: "7px"}}>
|
||||
|
||||
<Card
|
||||
bordered={true}
|
||||
title={user.name}
|
||||
loading={loading}
|
||||
style={{marginBottom: "7px"}}
|
||||
>
|
||||
<div style={{maxWidth: "800px"}}>
|
||||
<Paragraph style={{textAlign: "left", whiteSpace: "pre-line", fontSize: "1.5em"}}>{user.name}</Paragraph>
|
||||
<Form layout="vertical" hideRequiredMark form={form}
|
||||
initialValues={{
|
||||
name: formUser.name,
|
||||
role: formUser.role,
|
||||
email: formUser.email,
|
||||
is_blocked: formUser.is_blocked,
|
||||
is_active: !formUser.is_blocked,
|
||||
autoGroupsNames: formUser.autoGroupsNames,
|
||||
}}
|
||||
>
|
||||
<Row style={{ paddingBottom: "15px"}}>
|
||||
{!user.is_service_user && <Col xs={24} sm={24} md={11} lg={11} xl={11} xxl={11} span={11}>
|
||||
<Form.Item
|
||||
name="email"
|
||||
label={<Text style={{fontSize: "16px", fontWeight: "500"}}>Email</Text>}
|
||||
style={{marginRight: "70px"}}
|
||||
>
|
||||
<Input
|
||||
disabled={user.id}
|
||||
value={formUser.email}
|
||||
style={{color: "#5a5c5a"}}
|
||||
autoComplete="off"/>
|
||||
</Form.Item>
|
||||
</Col>}
|
||||
<Row style={{paddingBottom: "15px"}}>
|
||||
{!user.is_service_user &&
|
||||
<Col xs={24} sm={24} md={11} lg={11} xl={11} xxl={11} span={11}>
|
||||
<Form.Item
|
||||
name="email"
|
||||
label={<Text style={{}}>Email</Text>}
|
||||
style={{marginRight: "70px"}}
|
||||
>
|
||||
<Input
|
||||
disabled={user.id}
|
||||
value={formUser.email}
|
||||
style={{color: "#5a5c5a"}}
|
||||
autoComplete="off"/>
|
||||
</Form.Item>
|
||||
</Col>}
|
||||
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
|
||||
<Form.Item
|
||||
name="role"
|
||||
label={<Text style={{fontSize: "16px", fontWeight: "500"}}>Role</Text>}
|
||||
label={<Text style={{}}>Role</Text>}
|
||||
style={{marginRight: "50px"}}
|
||||
>
|
||||
<Select style={{width: '100%'}}
|
||||
@@ -366,30 +361,45 @@ const UserEdit = () => {
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
{!user.is_service_user && <Row style={{ paddingBottom: "15px"}}>
|
||||
<Col span={9}>
|
||||
<Form.Item
|
||||
name="autoGroupsNames"
|
||||
label={<Text style={{fontSize: "16px", fontWeight: "500"}}>Auto-assigned groups</Text>}
|
||||
tooltip="Every peer enrolled with this user will be automatically added to these groups"
|
||||
rules={[{validator: selectValidator}]}
|
||||
>
|
||||
<Select mode="tags"
|
||||
style={{width: '100%'}}
|
||||
placeholder="Associate groups with the user"
|
||||
tagRender={tagRender}
|
||||
dropdownRender={dropDownRender}
|
||||
disabled={oidcUser && !isUserAdmin(oidcUser.sub)}
|
||||
|
||||
{!user.is_service_user && <Row style={{paddingBottom: "15px"}}>
|
||||
|
||||
<Col xs={24} sm={24} md={11} lg={11} xl={11} xxl={11} span={11}>
|
||||
<Form.Item
|
||||
name="autoGroupsNames"
|
||||
label={<Text style={{}}>Auto-assigned groups</Text>}
|
||||
tooltip="Every peer enrolled with this user will be automatically added to these groups"
|
||||
rules={[{validator: selectValidator}]}
|
||||
style={{marginRight: "70px"}}
|
||||
>
|
||||
{
|
||||
tagGroups.map(m =>
|
||||
<Option key={m}>{optionRender(m)}</Option>
|
||||
)
|
||||
}
|
||||
</Select>
|
||||
<Select mode="tags"
|
||||
placeholder="Associate groups with the user"
|
||||
tagRender={tagRender}
|
||||
dropdownRender={dropDownRender}
|
||||
disabled={oidcUser && !isUserAdmin(oidcUser.sub)}
|
||||
>
|
||||
{
|
||||
tagGroups.map(m =>
|
||||
<Option key={m}>{optionRender(m)}</Option>
|
||||
)
|
||||
}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
||||
{!user.is_current && isAdmin && (
|
||||
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
|
||||
<Form.Item
|
||||
valuePropName="checked"
|
||||
name="is_active"
|
||||
label="Active"
|
||||
style={{marginRight: "50px"}}
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Col>)}
|
||||
</Row>}
|
||||
|
||||
<Space style={{display: 'flex', justifyContent: 'start'}}>
|
||||
<Button disabled={loading} onClick={onCancel}>Cancel</Button>
|
||||
<Button type="primary"
|
||||
@@ -397,16 +407,27 @@ const UserEdit = () => {
|
||||
</Space>
|
||||
</Form>
|
||||
</div>
|
||||
</Container>
|
||||
{user && (user.is_current || user.is_service_user) && <Container style={{backgroundColor: "white", padding: "20px", borderRadius: "4px", boxSizing: "border-box", border: "0.5px solid #D9D9D9"}}>
|
||||
</Card>
|
||||
|
||||
{user && (user.is_current || user.is_service_user) && <Card
|
||||
bordered={true}
|
||||
loading={loading}
|
||||
style={{marginBottom: "7px"}}
|
||||
>
|
||||
<div style={{maxWidth: "800px"}}>
|
||||
<Paragraph style={{textAlign: "left", whiteSpace: "pre-line", fontSize: "1.5em"}}>Access tokens</Paragraph>
|
||||
<Row gutter={21} style={{marginTop: "-22px", marginBottom: "10px"}}>
|
||||
<Paragraph
|
||||
style={{textAlign: "left", whiteSpace: "pre-line", fontSize: "16px", fontWeight: "bold"}}>Access
|
||||
tokens</Paragraph>
|
||||
<Row gutter={21} style={{marginTop: "-16px", marginBottom: "10px"}}>
|
||||
<Col xs={24} sm={24} md={20} lg={20} xl={20} xxl={20} span={20}>
|
||||
<Paragraph type={"secondary"} style={{textAlign: "left", whiteSpace: "pre-line"}}>Access tokens give access to the Netbird API</Paragraph>
|
||||
<Paragraph type={"secondary"}
|
||||
style={{textAlign: "left", whiteSpace: "pre-line"}}>
|
||||
Access tokens give access to NetBird API</Paragraph>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={1} lg={1} xl={1} xxl={1} span={1} style={{marginTop: "-16px"}}>
|
||||
{personalAccessTokens && personalAccessTokens.length > 0 && <Button type="primary" onClick={onClickAddNewPersonalAccessToken}>Create Token</Button>}
|
||||
{personalAccessTokens && personalAccessTokens.length > 0 &&
|
||||
<Button type="primary" onClick={onClickAddNewPersonalAccessToken}>Create
|
||||
token</Button>}
|
||||
</Col>
|
||||
</Row>
|
||||
{personalAccessTokens && personalAccessTokens.length > 0 &&
|
||||
@@ -418,34 +439,62 @@ const UserEdit = () => {
|
||||
pagination={false}
|
||||
loading={tableSpin(loading)}
|
||||
dataSource={tokenTable}>
|
||||
<Column className={"non-highlighted-table-column"} sorter={(a, b) => ((a as TokenDataTable).created_at.localeCompare((b as TokenDataTable).created_at))}
|
||||
<Column className={"non-highlighted-table-column"}
|
||||
sorter={(a, b) => ((a as TokenDataTable).created_at.localeCompare((b as TokenDataTable).created_at))}
|
||||
defaultSortOrder='descend'
|
||||
render={(text, record, index) => {
|
||||
return (<>
|
||||
<Row>
|
||||
<Col>
|
||||
<Badge status={(record as TokenDataTable).status === "valid" ? "success" : "error"} style={{marginTop: "1px", marginRight: "5px", marginLeft: "0px"}}/>
|
||||
<Badge
|
||||
status={(record as TokenDataTable).status === "valid" ? "success" : "error"}
|
||||
style={{
|
||||
marginTop: "1px",
|
||||
marginRight: "5px",
|
||||
marginLeft: "0px"
|
||||
}}/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Paragraph style={{fontSize: "16px", fontWeight: "500", margin: "0px", padding: "0px"}}>{(record as TokenDataTable).name}</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{fontSize: "13px", fontWeight: "400", margin: "0px", marginTop: "-2px", padding: "0px"}}>{"Created" + ((record as TokenDataTable).created_by_email && user.is_service_user ? " by " + (record as TokenDataTable).created_by_email : "") + " on " + fullDate((record as TokenDataTable).created_at)}</Paragraph>
|
||||
<Paragraph style={{
|
||||
margin: "0px",
|
||||
padding: "0px"
|
||||
}}>{(record as TokenDataTable).name}</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{
|
||||
fontSize: "13px",
|
||||
fontWeight: "400",
|
||||
margin: "0px",
|
||||
marginTop: "-2px",
|
||||
padding: "0px"
|
||||
}}>{"Created" + ((record as TokenDataTable).created_by_email && user.is_service_user ? " by " + (record as TokenDataTable).created_by_email : "") + " on " + fullDate((record as TokenDataTable).created_at)}</Paragraph>
|
||||
</Col>
|
||||
</Row>
|
||||
</>)
|
||||
</>)
|
||||
}}/>
|
||||
<Column render={(text, record, index) => {
|
||||
return <>
|
||||
<Paragraph type={"secondary"} style={{textAlign: "left", fontSize: "11px"}}>Expires on</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{textAlign: "left", marginTop: "-10px", marginBottom: "0", fontSize: "15px"}}>{fullDate((record as TokenDataTable).expiration_date)}</Paragraph>
|
||||
</>
|
||||
}}
|
||||
return <>
|
||||
<Paragraph type={"secondary"} style={{textAlign: "left", fontSize: "11px"}}>Expires
|
||||
on</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{
|
||||
textAlign: "left",
|
||||
marginTop: "-10px",
|
||||
marginBottom: "0",
|
||||
fontSize: "15px"
|
||||
}}>{fullDate((record as TokenDataTable).expiration_date)}</Paragraph>
|
||||
</>
|
||||
}}
|
||||
/>
|
||||
<Column render={(text, record, index) => {
|
||||
return <>
|
||||
<Paragraph type={"secondary"} style={{textAlign: "left", fontSize: "11px"}}>Last used</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{textAlign: "left", marginTop: "-10px", marginBottom: "0", fontSize: "15px"}}>{(record as TokenDataTable).last_used ? fullDate((record as TokenDataTable).last_used) : "Never"}</Paragraph>
|
||||
</>
|
||||
}}
|
||||
return <>
|
||||
<Paragraph type={"secondary"} style={{textAlign: "left", fontSize: "11px"}}>Last
|
||||
used</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{
|
||||
textAlign: "left",
|
||||
marginTop: "-10px",
|
||||
marginBottom: "0",
|
||||
fontSize: "15px"
|
||||
}}>{(record as TokenDataTable).last_used ? fullDate((record as TokenDataTable).last_used) : "Never"}</Paragraph>
|
||||
</>
|
||||
}}
|
||||
/>
|
||||
<Column align="right"
|
||||
render={(text, record, index) => {
|
||||
@@ -460,16 +509,23 @@ const UserEdit = () => {
|
||||
/>
|
||||
</Table>}
|
||||
<Divider style={{marginTop: "-12px"}}></Divider>
|
||||
{(personalAccessTokens === null || personalAccessTokens.length === 0) && <Space direction="vertical" size="small" align="start"
|
||||
style={{display: 'flex', padding: '45px 0px', marginTop: "-40px", justifyContent: 'center'}}>
|
||||
<Paragraph
|
||||
style={{textAlign: "start", whiteSpace: "pre-line", fontSize: "16px"}}>
|
||||
You don’t have any access tokens yet.
|
||||
</Paragraph>
|
||||
<Button type="primary" onClick={onClickAddNewPersonalAccessToken}>Create Token</Button>
|
||||
</Space>}
|
||||
{(personalAccessTokens === null || personalAccessTokens.length === 0) &&
|
||||
<Space direction="vertical" size="small" align="start"
|
||||
style={{
|
||||
display: 'flex',
|
||||
padding: '35px 0px',
|
||||
marginTop: "-40px",
|
||||
justifyContent: 'center'
|
||||
}}>
|
||||
<Paragraph
|
||||
style={{textAlign: "start", whiteSpace: "pre-line"}}>
|
||||
You don’t have any access tokens yet
|
||||
</Paragraph>
|
||||
<Button type="primary" onClick={onClickAddNewPersonalAccessToken}>Create token</Button>
|
||||
</Space>}
|
||||
</div>
|
||||
</Container>}
|
||||
|
||||
</Card>}
|
||||
</div>
|
||||
<AddPATPopup/>
|
||||
{confirmModalContextHolder}
|
||||
|
||||
@@ -130,27 +130,25 @@ const AddPATPopup = () => {
|
||||
}
|
||||
width={460}
|
||||
>
|
||||
<Container style={{textAlign: "start"}}>
|
||||
<Container style={{textAlign: "start", marginLeft: "-15px", marginRight: "-15px"}}>
|
||||
<Paragraph
|
||||
style={{textAlign: "start", whiteSpace: "pre-line", fontSize: "22px", fontWeight: "500"}}>
|
||||
{showPlainToken ? "Token created successfully!" : "Create a Personal Access Token"}
|
||||
style={{textAlign: "start", whiteSpace: "pre-line", fontSize: "22px"}}>
|
||||
{showPlainToken ? "Token created successfully!" : "Create token"}
|
||||
</Paragraph>
|
||||
{!showPlainToken && <Paragraph type={"secondary"}
|
||||
style={{
|
||||
textAlign: "start",
|
||||
fontSize: "14px",
|
||||
whiteSpace: "pre-line",
|
||||
marginTop: "-23px",
|
||||
paddingBottom: "25px",
|
||||
}}>
|
||||
{"This token can be used to authenticate against" + "\n" + "NetBird's Public API."}
|
||||
{"Use this token to access NetBird's public API"}
|
||||
</Paragraph>}
|
||||
{showPlainToken && <Paragraph type={"secondary"} style={{
|
||||
textAlign: "start",
|
||||
fontSize: "14px",
|
||||
whiteSpace: "pre-line",
|
||||
marginTop: "40px",
|
||||
}}>{"This token will not be shown again, so be sure to copy it and" + "\n" + "store in a secure location."}</Paragraph>}
|
||||
marginTop: "25px",
|
||||
}}>{"This token won't be shown again, so be sure to copy and store it in a secure location"}</Paragraph>}
|
||||
{!showPlainToken && <Form layout="vertical" hideRequiredMark form={form}
|
||||
initialValues={{
|
||||
expires_in: ExpiresInDefault,
|
||||
@@ -160,14 +158,14 @@ const AddPATPopup = () => {
|
||||
<Col span={24}>
|
||||
<Row align="top">
|
||||
<Col flex="auto">
|
||||
<Paragraph style={{fontSize: "16px", fontWeight: "500", marginTop: "-8px"}}>Token name</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{fontSize: "14px", marginTop: "-18px"}}>Create a name to identify the token easily</Paragraph>
|
||||
<Paragraph style={{fontWeight: "bold", marginTop: "-10px"}}>Name</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{marginTop: "-15px"}}>Set an easily identifiable name for your token</Paragraph>
|
||||
<Form.Item
|
||||
name="name"
|
||||
style={{marginTop: "-10px"}}
|
||||
rules={[{
|
||||
required: true,
|
||||
message: 'Please add a name for this personal access token',
|
||||
message: 'Please add a name for this token',
|
||||
whitespace: true
|
||||
}]}
|
||||
>
|
||||
@@ -179,9 +177,9 @@ const AddPATPopup = () => {
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={24} style={{textAlign: "left"}}>
|
||||
<Paragraph style={{fontSize: "16px", fontWeight: "500"}}>Expires in</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{fontSize: "14px", marginTop: "-18px"}}>Number of days this token is valid for</Paragraph>
|
||||
<Col span={24} style={{textAlign: "left", marginTop: "10px"}}>
|
||||
<Paragraph style={{fontWeight: "bold", marginTop: "-10px"}}>Expires in</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{marginTop: "-15px"}}>Number of days this token will be valid for</Paragraph>
|
||||
<Form.Item
|
||||
name="expires_in"
|
||||
style={{marginTop: "-10px"}}
|
||||
|
||||
@@ -199,10 +199,10 @@ const AddServiceUserPopup = () => {
|
||||
}
|
||||
width={460}
|
||||
>
|
||||
<Container style={{textAlign: "start"}}>
|
||||
<Container style={{textAlign: "start", marginLeft: "-15px", marginRight: "-15px"}}>
|
||||
<Paragraph
|
||||
style={{textAlign: "start", whiteSpace: "pre-line", fontSize: "22px", fontWeight: "500"}}>
|
||||
{"Add Service User"}
|
||||
style={{textAlign: "start", whiteSpace: "pre-line", fontSize: "22px"}}>
|
||||
{"Add service user"}
|
||||
</Paragraph>
|
||||
<Paragraph type={"secondary"}
|
||||
style={{
|
||||
@@ -211,7 +211,7 @@ const AddServiceUserPopup = () => {
|
||||
marginTop: "-23px",
|
||||
paddingBottom: "25px",
|
||||
}}>
|
||||
{"Service Users are non-login users that are not associated with any specific person."}
|
||||
{"Service users are non-login users that are not associated with any specific person."}
|
||||
</Paragraph>
|
||||
<Form layout="vertical" hideRequiredMark form={form}
|
||||
initialValues={{
|
||||
@@ -220,8 +220,8 @@ const AddServiceUserPopup = () => {
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={24}>
|
||||
<Paragraph style={{fontSize: "16px", fontWeight: "500", marginTop: "-10px"}}>Name</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{fontSize: "14px", marginTop: "-18px"}}>Set a name to easily identify the user</Paragraph>
|
||||
<Paragraph style={{fontWeight: "bold", marginTop: "-10px"}}>Name</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{marginTop: "-15px"}}>Set a name to easily identify the user</Paragraph>
|
||||
<Form.Item
|
||||
name="name"
|
||||
rules={[{
|
||||
@@ -232,14 +232,14 @@ const AddServiceUserPopup = () => {
|
||||
style={{marginTop: "-8px"}}
|
||||
>
|
||||
<Input
|
||||
placeholder={'for example "Ansible User"'}
|
||||
placeholder={'for example "Ansible user"'}
|
||||
ref={inputNameRef}
|
||||
autoComplete="off"/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Paragraph style={{fontSize: "16px", fontWeight: "500", marginTop: "0px"}}>Role</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{fontSize: "14px", marginTop: "-18px"}}>Set a role for the user to assign dashboard access permissions</Paragraph>
|
||||
<Paragraph style={{fontWeight: "bold", marginTop: "0px"}}>Role</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{fontSize: "14px", marginTop: "-15px"}}>Set a role for the user to assign access permissions</Paragraph>
|
||||
<Form.Item
|
||||
name="role"
|
||||
rules={[{
|
||||
@@ -256,8 +256,8 @@ const AddServiceUserPopup = () => {
|
||||
</Form.Item>
|
||||
</Col>
|
||||
{/*<Col span={24}>*/}
|
||||
{/* <Paragraph style={{fontSize: "16px", fontWeight: "500", marginTop: "0px"}}>Auto-assigned groups</Paragraph>*/}
|
||||
{/* <Paragraph type={"secondary"} style={{fontSize: "14px", marginTop: "-18px"}}>Add groups, that will be assigned to peers added by this user</Paragraph>*/}
|
||||
{/* <Paragraph style={{fontWeight: "bold", marginTop: "0px"}}>Auto-assigned groups</Paragraph>*/}
|
||||
{/* <Paragraph type={"secondary"} style={{fontSize: "14px", marginTop: "-15px"}}>Add groups, that will be assigned to peers added by this user</Paragraph>*/}
|
||||
{/* <Form.Item*/}
|
||||
{/* name="autoGroupsNames"*/}
|
||||
{/* label="Auto-assigned groups"*/}
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
Typography
|
||||
} from "antd";
|
||||
import {Container} from "../Container";
|
||||
import {CloseOutlined} from "@ant-design/icons";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {RootState} from "typesafe-actions";
|
||||
@@ -20,7 +19,6 @@ import {useGetTokenSilently} from "../../utils/token";
|
||||
import {actions as userActions} from "../../store/user";
|
||||
import {actions as groupActions} from "../../store/group";
|
||||
import {User, UserToSave} from "../../store/user/types";
|
||||
import {Header} from "antd/es/layout/layout";
|
||||
import {RuleObject} from "antd/lib/form";
|
||||
import {CustomTagProps} from "rc-select/lib/BaseSelect";
|
||||
|
||||
@@ -200,10 +198,10 @@ const InviteUserPopup = () => {
|
||||
}
|
||||
width={460}
|
||||
>
|
||||
<Container style={{textAlign: "start"}}>
|
||||
<Container style={{textAlign: "start", marginLeft: "-15px", marginRight: "-15px"}}>
|
||||
<Paragraph
|
||||
style={{textAlign: "start", whiteSpace: "pre-line", fontSize: "22px", fontWeight: "500"}}>
|
||||
{"Invite User"}
|
||||
{"Invite user"}
|
||||
</Paragraph>
|
||||
<Paragraph type={"secondary"}
|
||||
style={{
|
||||
@@ -222,8 +220,8 @@ const InviteUserPopup = () => {
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={24}>
|
||||
<Paragraph style={{fontSize: "16px", fontWeight: "500", marginTop: "-10px"}}>Name</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{fontSize: "14px", marginTop: "-18px"}}>Set a name to easily identify the user</Paragraph>
|
||||
<Paragraph style={{fontWeight: "bold", marginTop: "-10px"}}>Name</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{marginTop: "-15px"}}>Set a name to easily identify the user</Paragraph>
|
||||
<Form.Item
|
||||
name="name"
|
||||
rules={[{
|
||||
@@ -240,8 +238,8 @@ const InviteUserPopup = () => {
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Paragraph style={{fontSize: "16px", fontWeight: "500", marginTop: "0px"}}>Email</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{fontSize: "14px", marginTop: "-18px"}}>Provide the email address of the user</Paragraph>
|
||||
<Paragraph style={{ fontWeight: "bold", marginTop: "0px"}}>Email</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{marginTop: "-15px"}}>Provide the email address of the user</Paragraph>
|
||||
<Form.Item
|
||||
name="email"
|
||||
rules={[{
|
||||
@@ -259,8 +257,8 @@ const InviteUserPopup = () => {
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Paragraph style={{fontSize: "16px", fontWeight: "500", marginTop: "0px"}}>Role</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{fontSize: "14px", marginTop: "-18px"}}>Set a role for the user to assign dashboard access permissions</Paragraph>
|
||||
<Paragraph style={{ fontWeight: "bold", marginTop: "0px"}}>Role</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{marginTop: "-15px"}}>Set a role for the user to assign access permissions</Paragraph>
|
||||
<Form.Item
|
||||
name="role"
|
||||
rules={[{
|
||||
@@ -277,8 +275,8 @@ const InviteUserPopup = () => {
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Paragraph style={{fontSize: "16px", fontWeight: "500", marginTop: "0px"}}>Auto-assigned groups</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{fontSize: "14px", marginTop: "-18px"}}>Add groups, that will be assigned to peers added by this user</Paragraph>
|
||||
<Paragraph style={{fontWeight: "bold", marginTop: "0px"}}>Auto-assigned groups</Paragraph>
|
||||
<Paragraph type={"secondary"} style={{marginTop: "-15px"}}>Add groups, that will be assigned to peers added by this user</Paragraph>
|
||||
<Form.Item
|
||||
name="autoGroupsNames"
|
||||
tooltip="Every peer enrolled with this user will be automatically added to these groups"
|
||||
|
||||
+2
-12
@@ -5,8 +5,6 @@ import service from './service';
|
||||
import actions from './actions';
|
||||
import serviceGroup from "../group/service";
|
||||
import {Group} from "../group/types";
|
||||
import {actions as groupActions} from "../group";
|
||||
import {PersonalAccessToken} from "../personal-access-token/types";
|
||||
|
||||
export function* getUsers(action: ReturnType<typeof actions.getUsers.request>): Generator {
|
||||
try {
|
||||
@@ -74,7 +72,8 @@ export function* saveUser(action: ReturnType<typeof actions.saveUser.request>):
|
||||
email: userToSave.email,
|
||||
role: userToSave.role,
|
||||
auto_groups: newGroups,
|
||||
is_service_user: userToSave.is_service_user
|
||||
is_service_user: userToSave.is_service_user,
|
||||
is_blocked: userToSave.is_blocked
|
||||
} as UserToSave
|
||||
|
||||
let effect
|
||||
@@ -99,15 +98,6 @@ export function* saveUser(action: ReturnType<typeof actions.saveUser.request>):
|
||||
error: null,
|
||||
data: response.body
|
||||
} as CreateResponse<User | null>));
|
||||
|
||||
yield put(groupActions.getGroups.request({
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: null
|
||||
}));
|
||||
yield put(actions.getUsers.request({
|
||||
getAccessTokenSilently: action.payload.getAccessTokenSilently,
|
||||
payload: null
|
||||
}));
|
||||
} catch (err) {
|
||||
yield put(actions.saveUser.failure({
|
||||
loading: false,
|
||||
|
||||
@@ -7,10 +7,12 @@ export interface User {
|
||||
auto_groups: string[];
|
||||
is_current?: boolean;
|
||||
is_service_user?: boolean;
|
||||
is_blocked?: boolean;
|
||||
}
|
||||
|
||||
export interface FormUser extends User {
|
||||
autoGroupsNames: string[]
|
||||
is_active?: boolean
|
||||
}
|
||||
|
||||
export interface UserToSave extends User {
|
||||
|
||||
@@ -27,7 +27,7 @@ import {Rule} from "../store/rule/types";
|
||||
import {actions as ruleActions} from "../store/rule";
|
||||
import {actions as groupActions} from "../store/group";
|
||||
import {filter, sortBy} from "lodash";
|
||||
import {CloseOutlined, ExclamationCircleOutlined} from "@ant-design/icons";
|
||||
import {CloseOutlined, EllipsisOutlined, ExclamationCircleOutlined} from "@ant-design/icons";
|
||||
import bidirect from '../assets/direct_bi.svg';
|
||||
import inbound from '../assets/direct_in.svg';
|
||||
import outbound from '../assets/direct_out.svg';
|
||||
@@ -37,6 +37,7 @@ import AccessControlModalGroups from "../components/AccessControlModalGroups";
|
||||
import tableSpin from "../components/Spin";
|
||||
import {useGetTokenSilently} from "../utils/token";
|
||||
import {usePageSizeHelpers} from "../utils/pageSize";
|
||||
import {PeerDataTable} from "../store/peer/types";
|
||||
|
||||
const {Title, Paragraph, Text} = Typography;
|
||||
const {Column} = Table;
|
||||
@@ -449,11 +450,17 @@ export const AccessControl = () => {
|
||||
<Column title="" align="center"
|
||||
render={(text, record, index) => {
|
||||
if (deletedRule.loading || savedRule.loading) return <></>
|
||||
return <Dropdown.Button type="text" overlay={actionsMenu}
|
||||
trigger={["click"]}
|
||||
onOpenChange={visible => {
|
||||
if (visible) setRuleToAction(record as RuleDataTable)
|
||||
}}></Dropdown.Button>
|
||||
return (
|
||||
<Dropdown trigger={["click"]} overlay={actionsMenu} onOpenChange={visible => {
|
||||
if (visible) setRuleToAction(record as RuleDataTable)
|
||||
}}>
|
||||
<Button type="text">
|
||||
<Space>
|
||||
<EllipsisOutlined />
|
||||
</Space>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
@@ -31,7 +31,7 @@ import {Group} from "../store/group/types";
|
||||
import {TooltipPlacement} from "antd/es/tooltip";
|
||||
import {NameServer, NameServerGroup} from "../store/nameservers/types";
|
||||
import NameServerGroupUpdate from "../components/NameServerGroupUpdate";
|
||||
import {ExclamationCircleOutlined} from "@ant-design/icons";
|
||||
import {EllipsisOutlined, ExclamationCircleOutlined} from "@ant-design/icons";
|
||||
import {useGetGroupTagHelpers} from "../utils/groups";
|
||||
import {usePageSizeHelpers} from "../utils/pageSize";
|
||||
|
||||
@@ -441,11 +441,16 @@ export const Nameservers = () => {
|
||||
<Column title="" align="center" width="30px"
|
||||
render={(text, record) => {
|
||||
return (
|
||||
<Dropdown.Button type="text" overlay={actionsMenu}
|
||||
trigger={["click"]}
|
||||
onOpenChange={visible => {
|
||||
if (visible) setNsGroupToAction(record as NameserverGroupDataTable)
|
||||
}}></Dropdown.Button>)
|
||||
<Dropdown trigger={["click"]} overlay={actionsMenu} onOpenChange={visible => {
|
||||
if (visible) setNsGroupToAction(record as NameserverGroupDataTable)
|
||||
}}>
|
||||
<Button type="text">
|
||||
<Space>
|
||||
<EllipsisOutlined />
|
||||
</Space>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
+12
-6
@@ -6,7 +6,7 @@ import {actions as peerActions} from '../store/peer';
|
||||
import {actions as groupActions} from '../store/group';
|
||||
import {actions as routeActions} from '../store/route';
|
||||
import {Container} from "../components/Container";
|
||||
import {ExclamationCircleOutlined, ReloadOutlined,} from '@ant-design/icons';
|
||||
import {EllipsisOutlined, ExclamationCircleOutlined, ReloadOutlined,} from '@ant-design/icons';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
@@ -611,11 +611,17 @@ export const Peers = () => {
|
||||
<Column title="Version" dataIndex="version"/>
|
||||
<Column title="" align="center"
|
||||
render={(text, record, index) => {
|
||||
return <Dropdown.Button type="text" overlay={actionsMenu}
|
||||
trigger={["click"]}
|
||||
onOpenChange={visible => {
|
||||
if (visible) setPeerToAction(record as PeerDataTable)
|
||||
}}></Dropdown.Button>
|
||||
return (
|
||||
<Dropdown trigger={["click"]} overlay={actionsMenu} onOpenChange={visible => {
|
||||
if (visible) setPeerToAction(record as PeerDataTable)
|
||||
}}>
|
||||
<Button type="text">
|
||||
<Space>
|
||||
<EllipsisOutlined />
|
||||
</Space>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Table>)}
|
||||
|
||||
+105
-40
@@ -1,4 +1,4 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {RootState} from "typesafe-actions";
|
||||
import {actions as userActions} from '../store/user';
|
||||
@@ -8,29 +8,33 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Dropdown,
|
||||
Dropdown, Empty,
|
||||
Input,
|
||||
Menu,
|
||||
message, Modal,
|
||||
Popover,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Space, Switch,
|
||||
Table,
|
||||
Tag,
|
||||
Tag, Tooltip,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import {User} from "../store/user/types";
|
||||
import {User, UserToSave} from "../store/user/types";
|
||||
import {filter} from "lodash";
|
||||
import tableSpin from "../components/Spin";
|
||||
import {useGetTokenSilently} from "../utils/token";
|
||||
import {actions as groupActions} from "../store/group";
|
||||
import {Group} from "../store/group/types";
|
||||
import {TooltipPlacement} from "antd/es/tooltip";
|
||||
import {isLocalDev, isNetBirdHosted} from "../utils/common";
|
||||
import {capitalize, isLocalDev, isNetBirdHosted} from "../utils/common";
|
||||
import {usePageSizeHelpers} from "../utils/pageSize";
|
||||
import AddServiceUserPopup from "../components/popups/AddServiceUserPopup";
|
||||
import InviteUserPopup from "../components/popups/InviteUserPopup";
|
||||
import {Peer, PeerDataTable} from "../store/peer/types";
|
||||
import {ExclamationCircleOutlined, MinusOutlined} from "@ant-design/icons";
|
||||
import {actions as peerActions} from "../store/peer";
|
||||
import {useOidcUser} from "@axa-fr/react-oidc";
|
||||
|
||||
const {Title, Paragraph, Text} = Typography;
|
||||
const {Column} = Table;
|
||||
@@ -42,10 +46,12 @@ interface UserDataTable extends User {
|
||||
const styleNotification = {marginTop: 85}
|
||||
|
||||
export const RegularUsers = () => {
|
||||
const {onChangePageSize,pageSizeOptions,pageSize} = usePageSizeHelpers()
|
||||
const {onChangePageSize, pageSizeOptions, pageSize} = usePageSizeHelpers()
|
||||
const {getTokenSilently} = useGetTokenSilently()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
||||
const groups = useSelector((state: RootState) => state.group.data)
|
||||
const users = useSelector((state: RootState) => state.user.regularUsers);
|
||||
const failed = useSelector((state: RootState) => state.user.failed);
|
||||
@@ -114,6 +120,7 @@ export const RegularUsers = () => {
|
||||
auto_groups: userToAction?.auto_groups ? userToAction?.auto_groups : [],
|
||||
name: userToAction?.name,
|
||||
role: userToAction?.role,
|
||||
is_blocked: userToAction?.is_blocked,
|
||||
} as User));
|
||||
}
|
||||
|
||||
@@ -122,6 +129,15 @@ export const RegularUsers = () => {
|
||||
dispatch(userActions.setUser(null as unknown as User));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(users) {
|
||||
let currentUser = users.find((user) => user.is_current)
|
||||
if(currentUser) {
|
||||
setIsAdmin(currentUser.role === "admin");
|
||||
}
|
||||
}
|
||||
}, [users])
|
||||
|
||||
const renderPopoverGroups = (label: string, rowGroups: string[] | string[] | null, userToAction: UserDataTable) => {
|
||||
|
||||
let groupsMap = new Map<string, Group>();
|
||||
@@ -196,10 +212,10 @@ export const RegularUsers = () => {
|
||||
let errorMsg = "Failed to update user"
|
||||
switch (savedUser.error.statusCode) {
|
||||
case 412:
|
||||
errorMsg = savedUser.error.data
|
||||
break
|
||||
case 403:
|
||||
errorMsg = "Failed to update user. You might not have enough permissions."
|
||||
if (savedUser.error.data) {
|
||||
errorMsg = capitalize(savedUser.error.data.message)
|
||||
}
|
||||
break
|
||||
}
|
||||
message.error({
|
||||
@@ -243,16 +259,59 @@ export const RegularUsers = () => {
|
||||
name: user.name,
|
||||
is_current: user.is_current,
|
||||
is_service_user: user.is_service_user,
|
||||
is_blocked: user.is_blocked
|
||||
} as User));
|
||||
dispatch(userActions.setEditUserPopupVisible(true));
|
||||
}
|
||||
|
||||
const handleBlockUser = (block: boolean, user: UserDataTable) => {
|
||||
if (block) {
|
||||
confirmModal.confirm({
|
||||
icon: <ExclamationCircleOutlined/>,
|
||||
title: "Are you sure you want to deactivate \"" + user.name + "\"?",
|
||||
width: 600,
|
||||
content: <Space direction="vertical" size="small">
|
||||
<Paragraph>Disabling this user will disconnect their devices and block dashboard access.</Paragraph>
|
||||
</Space>,
|
||||
onOk() {
|
||||
let userToSave = createUserToSave(user, block)
|
||||
dispatch(userActions.saveUser.request({
|
||||
getAccessTokenSilently: getTokenSilently,
|
||||
payload: userToSave
|
||||
}));
|
||||
},
|
||||
onCancel() {
|
||||
// noop
|
||||
},
|
||||
});
|
||||
} else {
|
||||
let userToSave = createUserToSave(user, block)
|
||||
dispatch(userActions.saveUser.request({
|
||||
getAccessTokenSilently: getTokenSilently,
|
||||
payload: userToSave
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
const createUserToSave = (values: UserDataTable, block: boolean): UserToSave => {
|
||||
return {
|
||||
id: values.id,
|
||||
role: values.role,
|
||||
name: values.name,
|
||||
groupsToCreate: Array.of(),
|
||||
auto_groups: values.auto_groups,
|
||||
is_service_user: values.is_service_user,
|
||||
is_blocked: block
|
||||
} as UserToSave
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container style={{padding: "0px"}}>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<Paragraph>Manage users and their permissions.{(window.location.hostname == "app.netbird.io") ? "Same-domain email users are added automatically on first sign-in." : ""}</Paragraph>
|
||||
<Paragraph>Manage users and their
|
||||
permissions.{(window.location.hostname == "app.netbird.io") ? "Same-domain email users are added automatically on first sign-in." : ""}</Paragraph>
|
||||
<Space direction="vertical" size="large" style={{display: 'flex'}}>
|
||||
<Row gutter={[16, 24]}>
|
||||
<Col xs={24} sm={24} md={8} lg={8} xl={8} xxl={8} span={8}>
|
||||
@@ -272,15 +331,16 @@ export const RegularUsers = () => {
|
||||
xl={5}
|
||||
xxl={5} span={5}>
|
||||
{(isNetBirdHosted() || isLocalDev()) &&
|
||||
<Row justify="end">
|
||||
<Col>
|
||||
<Button type="primary" onClick={onClickInviteUser}>Invite User</Button>
|
||||
</Col>
|
||||
</Row>}
|
||||
<Row justify="end">
|
||||
<Col>
|
||||
<Button type="primary" onClick={onClickInviteUser}>Invite user</Button>
|
||||
</Col>
|
||||
</Row>}
|
||||
</Col>
|
||||
</Row>
|
||||
{failed &&
|
||||
<Alert message={failed.message} description={failed.data ? failed.data.message : " "} type="error" showIcon
|
||||
<Alert message={failed.message} description={failed.data ? failed.data.message : " "}
|
||||
type="error" showIcon
|
||||
closable/>
|
||||
}
|
||||
<Card bodyStyle={{padding: 0}}>
|
||||
@@ -305,6 +365,7 @@ export const RegularUsers = () => {
|
||||
className="tooltip-label">
|
||||
<Text
|
||||
strong>{(text && text.trim() !== "") ? text : (record as User).id}</Text>
|
||||
|
||||
</Button>
|
||||
|
||||
if ((record as User).is_current) {
|
||||
@@ -313,25 +374,18 @@ export const RegularUsers = () => {
|
||||
</div>
|
||||
}
|
||||
|
||||
if ((record as User).status === "invited") {
|
||||
return <div>{btn}
|
||||
<Tag color="gold">invited</Tag>
|
||||
</div>
|
||||
}
|
||||
|
||||
return btn
|
||||
}}
|
||||
/>
|
||||
<Column title="Name" dataIndex="name"
|
||||
onFilter={(value: string | number | boolean, record) => (record as any).name.includes(value)}
|
||||
sorter={(a, b) => ((a as any).name.localeCompare((b as any).name))}/>
|
||||
<Column title="Status" dataIndex="status"
|
||||
align="center"
|
||||
onFilter={(value: string | number | boolean, record) => (record as any).status.includes(value)}
|
||||
sorter={(a, b) => ((a as any).status.localeCompare((b as any).status))}
|
||||
render={(text, record, index) => {
|
||||
if (text == "active") {
|
||||
return <Tag color="green">{text}</Tag>
|
||||
} else if (text === "invited"){
|
||||
return <Tag color="gold">{text}</Tag>
|
||||
}
|
||||
return <Tag color="red">{text}</Tag>
|
||||
}}
|
||||
/>
|
||||
<Column title="Groups" dataIndex="groupsCount" align="center"
|
||||
render={(text, record: UserDataTable, index) => {
|
||||
return renderPopoverGroups(text, record.auto_groups, record)
|
||||
@@ -340,16 +394,27 @@ export const RegularUsers = () => {
|
||||
<Column title="Role" dataIndex="role"
|
||||
onFilter={(value: string | number | boolean, record) => (record as any).role.includes(value)}
|
||||
sorter={(a, b) => ((a as any).role.localeCompare((b as any).role))}/>
|
||||
{/*<Column title="" align="center" width="30px"*/}
|
||||
{/* render={(text, record, index) => {*/}
|
||||
{/* return (*/}
|
||||
{/* <Dropdown.Button type="text" overlay={actionsMenu}*/}
|
||||
{/* trigger={["click"]}*/}
|
||||
{/* onVisibleChange={visible => {*/}
|
||||
{/* if (visible) setUserToAction(record as UserDataTable)*/}
|
||||
{/* }}></Dropdown.Button>)*/}
|
||||
{/* }}*/}
|
||||
{/*/>*/}
|
||||
{isAdmin && (
|
||||
<Column title="Active" align="center" width="150px" dataIndex="is_blocked"
|
||||
render={(e, record: UserDataTable, index) => {
|
||||
let witch = <Switch size={"small"} checked={!e}
|
||||
disabled={record.is_current}
|
||||
onClick={(active: boolean) => {
|
||||
handleBlockUser(!active, record)
|
||||
}}
|
||||
/>
|
||||
|
||||
if (record.is_current) {
|
||||
return <Tooltip
|
||||
title="You can't block or unblock yourself">
|
||||
<Empty image={""} description={""} style={{height: "1px", width: "auto"}}/>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
return witch
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Table>
|
||||
</Card>
|
||||
</Space>
|
||||
|
||||
+16
-8
@@ -28,7 +28,7 @@ import {Route, RouteToSave} from "../store/route/types";
|
||||
import {actions as routeActions} from "../store/route";
|
||||
import {actions as peerActions} from "../store/peer";
|
||||
import {filter, sortBy} from "lodash";
|
||||
import {ExclamationCircleOutlined, QuestionCircleOutlined} from "@ant-design/icons";
|
||||
import {EllipsisOutlined, ExclamationCircleOutlined, QuestionCircleOutlined} from "@ant-design/icons";
|
||||
import RouteUpdate from "../components/RouteUpdate";
|
||||
import tableSpin from "../components/Spin";
|
||||
import {
|
||||
@@ -243,17 +243,18 @@ export const Routes = () => {
|
||||
|
||||
const onClickViewRoute = () => {
|
||||
dispatch(routeActions.setSetupNewRouteHA(false));
|
||||
dispatch(routeActions.setSetupNewRouteVisible(true));
|
||||
dispatch(routeActions.setRoute({
|
||||
id: routeToAction?.id || null,
|
||||
network: routeToAction?.network,
|
||||
network_id: routeToAction?.network_id,
|
||||
description: routeToAction?.description,
|
||||
peer: peerToPeerIP(routeToAction!.peer, peerNameToIP[routeToAction!.peer]),
|
||||
peer: peerToPeerIP(routeToAction!.peer_name, routeToAction!.peer_ip),
|
||||
metric: routeToAction?.metric,
|
||||
masquerade: routeToAction?.masquerade,
|
||||
enabled: routeToAction?.enabled
|
||||
enabled: routeToAction?.enabled,
|
||||
groups: routeToAction?.groups
|
||||
} as Route))
|
||||
dispatch(routeActions.setSetupNewRouteVisible(true));
|
||||
}
|
||||
|
||||
const setRouteAndView = (route: RouteDataTable) => {
|
||||
@@ -409,10 +410,17 @@ export const Routes = () => {
|
||||
<Column title="" align="center"
|
||||
render={(text, record) => {
|
||||
if (deletedRoute.loading || savedRoute.loading) return <></>
|
||||
return <Dropdown.Button type="text" overlay={actionsMenu} trigger={["click"]}
|
||||
onOpenChange={visible => {
|
||||
if (visible) setRouteToAction(record as RouteDataTable)
|
||||
}}></Dropdown.Button>
|
||||
return (
|
||||
<Dropdown trigger={["click"]} overlay={actionsMenu} onOpenChange={visible => {
|
||||
if (visible) setRouteToAction(record as RouteDataTable)
|
||||
}}>
|
||||
<Button type="text">
|
||||
<Space>
|
||||
<EllipsisOutlined />
|
||||
</Space>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Table>
|
||||
|
||||
@@ -22,7 +22,7 @@ import {filter} from "lodash";
|
||||
import tableSpin from "../components/Spin";
|
||||
import {useGetTokenSilently} from "../utils/token";
|
||||
import {actions as groupActions} from "../store/group";
|
||||
import {isLocalDev, isNetBirdHosted} from "../utils/common";
|
||||
import {capitalize, isLocalDev, isNetBirdHosted} from "../utils/common";
|
||||
import {usePageSizeHelpers} from "../utils/pageSize";
|
||||
import AddServiceUserPopup from "../components/popups/AddServiceUserPopup";
|
||||
import {ExclamationCircleOutlined} from "@ant-design/icons";
|
||||
@@ -111,10 +111,10 @@ export const ServiceUsers = () => {
|
||||
let errorMsg = "Failed to update user"
|
||||
switch (savedUser.error.statusCode) {
|
||||
case 412:
|
||||
errorMsg = savedUser.error.data
|
||||
break
|
||||
case 403:
|
||||
errorMsg = "Failed to update user. You might not have enough permissions."
|
||||
if (savedUser.error.data) {
|
||||
errorMsg = capitalize(savedUser.error.data.message)
|
||||
}
|
||||
break
|
||||
}
|
||||
message.error({
|
||||
@@ -187,12 +187,11 @@ export const ServiceUsers = () => {
|
||||
lg={5}
|
||||
xl={5}
|
||||
xxl={5} span={5}>
|
||||
{(isNetBirdHosted() || isLocalDev()) &&
|
||||
<Row justify="end">
|
||||
<Col>
|
||||
<Button type="primary" onClick={onClickCreateServiceUser}>Create Service User</Button>
|
||||
</Col>
|
||||
</Row>}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
{failed &&
|
||||
|
||||
+420
-296
File diff suppressed because it is too large.
Load diff
@@ -2,7 +2,6 @@ import React, {useEffect, useState} from 'react';
|
||||
|
||||
import {Col, Row, Tabs, TabsProps} from "antd";
|
||||
import {Container} from "../components/Container";
|
||||
import {useOidcUser} from "@axa-fr/react-oidc";
|
||||
import {actions as userActions} from "../store/user";
|
||||
import {useGetTokenSilently} from "../utils/token";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
|
||||
Reference in new issue
Block a user