Compare commits

..
Author SHA1 Message Date
Pascal Fischer 1b103ba0f1 change bidirectional tag 2023-05-17 13:50:31 +02:00
Pascal Fischer 9fa1b4d568 gold tag 2023-05-15 17:52:32 +02:00
Pascal Fischer 0695ebd080 Merge branch 'main' into feature/darkmode 2023-05-15 17:44:30 +02:00
Misha Bragin b595e0d6a8 Switch to activate/deactivate a user (#179)
This PR adds a switch to deactivate (or block) a user.
Only admins can block/unblock users.
Users can't block themselves.
2023-05-15 16:57:35 +02:00
Pascal Fischer ca822d428e more rules 2023-05-15 16:20:57 +02:00
Pascal Fischer c49e766b92 Merge branch 'main' into feature/darkmode 2023-05-15 15:05:44 +02:00
pascal-fischer 33621cae5d remove href- from breadcrumbs (#180) 2023-05-15 14:28:14 +02:00
Pascal Fischer 07cff534e8 more rules 2023-05-15 13:44:08 +02:00
Pascal Fischer 71f1745b82 more rules 2023-05-14 14:52:51 +02:00
Pascal Fischer ac428a0044 more rules 2023-05-14 14:34:54 +02:00
Pascal Fischer 1de8538884 more rules 2023-05-14 14:33:01 +02:00
Maycon Santos 360d807008 Enable creating service user for all domains (#178)
Removed unnecessary check from create button.
2023-05-14 11:56:14 +02:00
Pascal Fischer cec15f6471 add more darkmode 2023-05-13 04:26:45 +02:00
Pascal Fischer 87425f80a4 add more darkmode 2023-05-13 04:21:05 +02:00
Pascal Fischer 39c56ab22e Merge branch 'main' into feature/darkmode 2023-05-12 23:36:31 +02:00
Pascal Fischer 571c2a6e64 fix dropdown button in lists to only show ellipsis 2023-05-12 22:07:09 +02:00
Misha Bragin 6f8897ffa5 Make breadcrumbs look like links (#176) 2023-05-10 18:48:11 +02:00
pascal-fischer 75d5f804c5 fix dropdown button in lists to only show ellipsis (#175) 2023-05-10 16:45:47 +02:00
pascal-fischer f7cac02a2d reduce api calls on user edit (#174) 2023-05-10 16:44:43 +02:00
Pascal Fischer 8bb421abcd reduce api calls on user edit 2023-05-10 13:36:00 +02:00
Misha Bragin ec40730cb2 Fix setup key layout (#172) 2023-05-08 17:49:27 +02:00
Misha Bragin 7f3648861b Fix popups layout (#171) 2023-05-08 12:30:53 +02:00
23 changed files with 1734 additions and 281 deletions

No files matched your search

+52
View File
@@ -57,6 +57,7 @@
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.9",
"@types/react-syntax-highlighter": "^15.5.3"
}
},
@@ -3478,6 +3479,21 @@
"url": "https://github.com/sponsors/gregberge"
}
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
"integrity": "sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==",
"dev": true,
"dependencies": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/@testing-library/dom": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.18.1.tgz",
@@ -10546,11 +10562,23 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"dev": true
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true
},
"node_modules/lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -19239,6 +19267,18 @@
"loader-utils": "^2.0.0"
}
},
"@tailwindcss/typography": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
"integrity": "sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==",
"dev": true,
"requires": {
"lodash.castarray": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.merge": "^4.6.2",
"postcss-selector-parser": "6.0.10"
}
},
"@testing-library/dom": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.18.1.tgz",
@@ -24518,11 +24558,23 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash.castarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
"dev": true
},
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+1
View File
@@ -78,6 +78,7 @@
]
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.9",
"@types/react-syntax-highlighter": "^15.5.3"
}
}
+3
View File
@@ -1,6 +1,9 @@
module.exports = {
plugins: {
tailwindcss: {},
'postcss-focus-visible': {
replaceWith: '[data-focus-visible-added]',
},
autoprefixer: {},
},
}
+1 -1
View File
@@ -22,7 +22,7 @@ import {SecureLoading} from "./components/Loading";
import DNS from "./views/DNS";
import Activity from "./views/Activity";
import Settings from "./views/Settings";
import './styles/tailwind.css'
const {Header, Content} = Layout;
+54
View File
@@ -0,0 +1,54 @@
function SunIcon(props) {
return (
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
<path d="M12.5 10a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z" />
<path
strokeLinecap="round"
d="M10 5.5v-1M13.182 6.818l.707-.707M14.5 10h1M13.182 13.182l.707.707M10 15.5v-1M6.11 13.889l.708-.707M4.5 10h1M6.11 6.111l.708.707"
/>
</svg>
)
}
function MoonIcon(props) {
return (
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" {...props}>
<path d="M15.224 11.724a5.5 5.5 0 0 1-6.949-6.949 5.5 5.5 0 1 0 6.949 6.949Z" />
</svg>
)
}
export function ModeToggle() {
function disableTransitionsTemporarily() {
document.documentElement.classList.add('[&_*]:!transition-none')
window.setTimeout(() => {
document.documentElement.classList.remove('[&_*]:!transition-none')
}, 0)
}
function toggleMode() {
disableTransitionsTemporarily()
let isDarkMode = document.documentElement.classList.toggle('dark')
if (document.documentElement.getAttribute("data-dark-mode") !== "false") {
delete window.localStorage.isDarkMode
document.documentElement.setAttribute('data-dark-mode', 'false');
} else {
window.localStorage.isDarkMode = isDarkMode
document.documentElement.setAttribute('data-dark-mode', 'true');
}
}
return (
<button
type="button"
className="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5"
aria-label="Toggle dark mode"
onClick={toggleMode}
>
<SunIcon className="h-5 w-5 stroke-zinc-900 hidden dark:block" />
<MoonIcon className="dark:hidden h-5 w-5 stroke-white dark:block" />
</button>
)
}
+2 -2
View File
@@ -314,7 +314,7 @@ const NameServerGroupUpdate = () => {
// @ts-ignore
const renderNSList = (fields: FormListFieldData[], {add, remove}, {errors}) => (
<>
<Row>Nameservers</Row>
<Row><label>Nameservers</label></Row>
{!!fields.length && (
<Row align='middle'>
@@ -384,7 +384,7 @@ const NameServerGroupUpdate = () => {
<Row>
<Space>
<Col>
Match domains
<label>Match domains</label>
</Col>
<Col>
<Tooltip title="Only queries to domains specified here will be resolved by these nameservers."
+4
View File
@@ -13,6 +13,7 @@ import {RootState} from "typesafe-actions";
import {actions as userActions} from "../store/user";
import {useGetTokenSilently} from "../utils/token";
import {actions as personalAccessTokenActions} from "../store/personal-access-token";
import {ModeToggle} from "./ModeToggle";
const {useBreakpoint} = Grid;
@@ -170,6 +171,9 @@ const Navbar = () => {
defaultSelectedKeys={[location.pathname]} items={menuItems}/>
</div>
</Col>
<Col style={{width: "3%"}}>
<ModeToggle />
</Col>
{hideMenuUser &&
<Col>
<Dropdown overlay={menuUser} placement="bottomRight" trigger={['click']}>
+55 -79
View File
@@ -292,7 +292,7 @@ const SetupKeyNew = () => {
textAlign: "start",
whiteSpace: "pre-line",
marginTop: "-23px",
paddingBottom: "25px",
paddingBottom: "15px",
}}>
{"Use this key to register new machines in your network"}
</Paragraph>
@@ -323,7 +323,7 @@ const SetupKeyNew = () => {
<Col span={24}>
<Form.Item
style={{marginBottom: "0px", marginTop: "-10px"}}
style={{marginBottom: "0px", marginTop: "10px"}}
name="name"
rules={[{required: true, message: "Please enter key name."}]}
>
@@ -382,9 +382,9 @@ const SetupKeyNew = () => {
>
Reusable
</Paragraph>
<Text type={"secondary"}>
<Paragraph type={"secondary"} style={{whiteSpace: "pre-line", margin: 0}}>
Use this type to enroll multiple peers
</Text>
</Paragraph>
</Col>
<Col span={6}>
<Row justify={"end"}>
@@ -410,10 +410,10 @@ const SetupKeyNew = () => {
<Paragraph style={{whiteSpace: "pre-line", margin: 0, fontWeight: "bold"}}>
{formSetupKey.type === "one-off" ? "One-off" : "Reusable"}
</Paragraph>
<Text type={"secondary"} style={{marginLeft: "5px"}}>type</Text>
<Text type={"secondary"} style={{marginLeft: "5px"}}>key</Text>
</Row>)}
<Row style={{marginTop: "10px"}}>
<Row style={{marginTop: isEditMode? "20px" : "10px"}}>
<Col span={24}>
<Paragraph
style={{whiteSpace: "pre-line", margin: 0, fontWeight: "bold"}}
@@ -421,40 +421,67 @@ const SetupKeyNew = () => {
{isEditMode ? "Available uses" : "Usage limit"}
</Paragraph>
</Col>
<Col>
{isEditMode ? (
{isEditMode && (
<Col>
<Input
disabled
value={formSetupKey.usage_limit}
value={formSetupKey.usage_limit - formSetupKey.used_times}
suffix={<LockOutlined style={{color: "#BFBFBF"}}/>}
style={{width: "104px"}}
style={{width: "104px", marginTop: "5px"}}
/>
) : (
</Col>
)}
{!isEditMode && (
<Col>
<Form.Item
style={{marginTop: "5px", width: "112px", marginBottom: "5px"}}
name="usage_limit"
>
<InputNumber
type={"number"}
style={{marginTop: "5px", width: "112px"}}
disabled={setupKey.id || formSetupKey.type !== "reusable"}
controls={false}
min={0}
/>
</Form.Item>
)}
</Col>
{isEditMode ? (
<></>
) : (
<Col span={24}>
<Text type={"secondary"}>
<Paragraph type={"secondary"} style={{marginTop: "-18px", marginBottom: 0}}>
For example, set to 30 if you want to enroll 30 peers
</Text>
</Paragraph>
</Col>
)}
</Row>
<Row style={{marginTop: "15px"}}>
{isEditMode ? (
{!isEditMode && (
<Row style={{marginTop: "20px"}}>
<Col span={24}>
<Paragraph
style={{whiteSpace: "pre-line", margin: 0, fontWeight: "bold"}}
>
Expires in
</Paragraph>
</Col>
<Col>
<Form.Item
name="expiresIn"
rules={[{required: true, message: "Please enter expiration date"}]}
>
<InputNumber defaultValue={7} placeholder={`2`} type="number" addonAfter=" Days"
style={{width: "160px", marginTop: "5px"}}/>
</Form.Item>
<Paragraph type={"secondary"} style={{marginTop: "-18px", marginBottom: 0}}>
Should be between 1 and 180 days
</Paragraph>
</Col>
</Row>)}
{isEditMode && (
<Row style={{marginTop: "20px"}}>
<Container style={{width: "100%", padding: 0, margin: 0}}>
<Row>
<Col span={11}>
@@ -463,6 +490,7 @@ const SetupKeyNew = () => {
</Paragraph>
<Row>
<Input
style={{marginTop: "5px"}}
disabled
suffix={<LockOutlined style={{color: "#BFBFBF"}}/>}
value={customExpiresFormat(new Date(formSetupKey.expires))!}
@@ -476,6 +504,7 @@ const SetupKeyNew = () => {
<Row>
<Input
disabled
style={{marginTop: "5px"}}
suffix={<LockOutlined style={{color: "#BFBFBF"}}/>}
value={customLastUsedFormat(new Date(formSetupKey.last_used))!}
/>
@@ -483,62 +512,8 @@ const SetupKeyNew = () => {
</Col>
</Row>
</Container>
) : (
<>
<Col span={24}>
<Paragraph
style={{
whiteSpace: "pre-line",
margin: 0,
fontWeight: "bold",
}}
>
Expires in
</Paragraph>
</Col>
<Col>
<Form.Item
style={{
marginTop: "5px",
width: "112px",
marginBottom: "0px",
}}
name="expiresIn"
rules={[{required: true, message: "Please enter expiration date."}]}
>
<Col
style={{
display: "flex",
flexDirection: "row",
}}
>
<InputNumber
type="number"
controls={false}
placeholder={`0`}
min={0}
max={180}
/>
<Col
style={{
marginLeft: "7px",
padding: "4px 12px",
border: "1px solid #D9D9D9",
borderRadius: "2px",
}}
>
Days
</Col>
</Col>
</Form.Item>
</Col>
<Col span={24} style={{marginTop: "5px"}}>
<Text type={"secondary"}>Should be between 1 and 180 days</Text>
</Col>
</>
)}
</Row>
</Row>)}
<Row style={{marginTop: "20px"}}>
<Col span={24}>
@@ -589,7 +564,8 @@ const SetupKeyNew = () => {
</Form>
</Container>
</Modal>
);
)
;
};
export default SetupKeyNew;
export default SetupKeyNew;
+54 -54
View File
@@ -10,7 +10,7 @@ import {
Row,
Select,
Skeleton,
Space, Table,
Space, Switch, Table,
Tag, Typography
} from "antd";
import {useDispatch, useSelector} from "react-redux";
@@ -63,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();
@@ -87,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) => {
@@ -102,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)
@@ -286,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"
@@ -320,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 },
},
{
@@ -344,6 +327,8 @@ const UserEdit = () => {
name: formUser.name,
role: formUser.role,
email: formUser.email,
is_blocked: formUser.is_blocked,
is_active: !formUser.is_blocked,
autoGroupsNames: formUser.autoGroupsNames,
}}
>
@@ -376,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={{}}>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)}
<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"
+914 -50
View File
File diff suppressed because it is too large. Load diff
+2 -12
View File
@@ -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,
+2
View File
@@ -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 {
+32
View File
@@ -0,0 +1,32 @@
:root {
--shiki-color-text: theme('colors.white');
--shiki-token-constant: theme('colors.orange.300');
--shiki-token-string: theme('colors.orange.300');
--shiki-token-comment: theme('colors.zinc.500');
--shiki-token-keyword: theme('colors.sky.300');
--shiki-token-parameter: theme('colors.pink.300');
--shiki-token-function: theme('colors.violet.300');
--shiki-token-string-expression: theme('colors.orange.300');
--shiki-token-punctuation: theme('colors.zinc.200');
}
@tailwind base;
@tailwind components;
@tailwind utilities;
.videowrapper {
float: none;
clear: both;
width: 100%;
position: relative;
padding-bottom: 56.25%;
padding-top: 25px;
height: 0;
}
.videowrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
+15 -8
View File
@@ -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;
@@ -429,9 +430,9 @@ export const AccessControl = () => {
/>
<Column title="Direction" dataIndex="flow"
render={(text, record: RuleDataTable, index) => {
const s = {minWidth: 50, textAlign: "center"} as React.CSSProperties
const s = {minWidth: 50, textAlign: "center", fontSize: "20pt", paddingBottom: "23px"} as React.CSSProperties
if (text === "bidirect")
return <Tag color="processing" style={s}><img src={bidirect}/></Tag>
return <Tag color="processing" style={s}>&harr;</Tag>
else if (text === "srcToDest") {
return <Tag color="green" style={s}><img src={outbound}/></Tag>
} else if (text === "destToSrc") {
@@ -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>
+11 -6
View File
@@ -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
View File
@@ -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>)}
+106 -41
View File
@@ -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,18 +259,61 @@ 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]}>
<Row gutter={[16, 24]} style={{paddingBottom: "0px"}}>
<Col xs={24} sm={24} md={8} lg={8} xl={8} xxl={8} span={8}>
<Input allowClear value={textToSearch} onPressEnter={searchDataTable}
placeholder="Search..." onChange={onChangeTextToSearch}/>
@@ -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>
+12 -5
View File
@@ -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 {
@@ -410,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>
+5 -6
View File
@@ -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 &&
+4 -4
View File
@@ -329,9 +329,9 @@ export const SetupKeys = () => {
<Col span={24}>
<Title level={4}>Setup Keys</Title>
<Paragraph
style={{
color: dataTable.length ? "black" : "#818183",
}}
// style={{
// color: dataTable.length ? "black" : "#818183",
// }}
>
A list of all the setup keys in your account including their name, state, type and
expiration.
@@ -592,4 +592,4 @@ export const SetupKeys = () => {
);
};
export default SetupKeys;
export default SetupKeys;
Loaded 20 of 23 files, more files were not shown because too many files have changed in this diff. Show more