Skip to content

Commit 31a2d21

Browse files
authored
Merge pull request #102 from KSJaay/0.9.3
0.9.3 - Adds support for users to create API Tokens
2 parents 506b214 + 184cbe9 commit 31a2d21

File tree

38 files changed

+985
-135
lines changed

38 files changed

+985
-135
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
// import dependencies
2+
import { useState } from 'react';
3+
import { toast } from 'react-toastify';
4+
import { Input } from '@lunalytics/ui';
5+
6+
// import local files
7+
import Modal from '../../../ui/modal';
8+
import SwitchWithText from '../../../ui/switch';
9+
import useTokensContext from '../../../../context/tokens';
10+
import { createPostRequest } from '../../../../services/axios';
11+
import { PermissionsBits } from '../../../../../shared/permissions/bitFlags';
12+
import TokenValidator from '../../../../../shared/validators/token';
13+
14+
const permissionsWithDescription = [
15+
{
16+
permission: PermissionsBits.VIEW_MONITORS,
17+
title: 'View Monitors',
18+
description:
19+
'Tokens with this permission will be able to view all monitors.',
20+
},
21+
{
22+
permission: PermissionsBits.MANAGE_MONITORS,
23+
title: 'Manage Monitors',
24+
description:
25+
'Tokens with this permission will be able to create, edit and delete monitors.',
26+
},
27+
{
28+
permission: PermissionsBits.VIEW_NOTIFICATIONS,
29+
title: 'View Notifications',
30+
description:
31+
'Tokens with this permission will be able to view all notifications.',
32+
},
33+
{
34+
permission: PermissionsBits.MANAGE_NOTIFICATIONS,
35+
title: 'Manage Notifications',
36+
description:
37+
'Tokens with this permission will be able to create, edit and delete notifications.',
38+
},
39+
{
40+
permission: PermissionsBits.VIEW_STATUS_PAGES,
41+
title: 'View Status Pages',
42+
description:
43+
'Tokens with this permission will be able to view all status pages.',
44+
},
45+
{
46+
permission: PermissionsBits.MANAGE_STATUS_PAGES,
47+
title: 'Manage Status Pages',
48+
description:
49+
'Tokens with this permission will be able to create, edit and delete status pages.',
50+
},
51+
{
52+
permission: PermissionsBits.VIEW_INCIDENTS,
53+
title: 'View Incidents',
54+
description:
55+
'Tokens with this permission will be able to view all incidents.',
56+
},
57+
{
58+
permission: PermissionsBits.MANAGE_INCIDENTS,
59+
title: 'Manage Incidents',
60+
description:
61+
'Tokens with this permission will be able to create, edit and delete incidents.',
62+
},
63+
{
64+
permission: PermissionsBits.MANAGE_TEAM,
65+
title: 'Manage Team',
66+
description:
67+
'Tokens with this permission will be able to manage the team members.',
68+
},
69+
{
70+
permission: PermissionsBits.ADMINISTRATOR,
71+
title: 'Administrator',
72+
description:
73+
'Tokens with this permission will have every permission and will be able to bypass any restrictions.',
74+
},
75+
];
76+
77+
const SettingsApiConfigureModal = ({
78+
closeModal,
79+
tokenId = '',
80+
tokenName = '',
81+
tokenPermissions = 0,
82+
isEdit = false,
83+
}) => {
84+
const { addToken, updateTokenPermission } = useTokensContext();
85+
86+
const [name, setName] = useState(tokenName);
87+
const [perms, setPermission] = useState(tokenPermissions);
88+
89+
const changePermission = (isChecked, permission) => {
90+
if (isChecked) {
91+
setPermission(perms | permission);
92+
} else {
93+
setPermission(perms & ~permission);
94+
}
95+
};
96+
97+
const handleEditOrCreateToken = async () => {
98+
try {
99+
const path = isEdit ? '/api/tokens/update' : '/api/tokens/create';
100+
101+
const isInvalid = TokenValidator({
102+
name,
103+
token: tokenId,
104+
permission: perms,
105+
isEdit,
106+
});
107+
108+
if (isInvalid) {
109+
return toast.error(isInvalid);
110+
}
111+
112+
const response = await createPostRequest(path, {
113+
name,
114+
token: tokenId,
115+
permission: perms,
116+
});
117+
118+
const data = response?.data || {};
119+
120+
if (isEdit) {
121+
updateTokenPermission(data.token, data.permission, data.name);
122+
} else {
123+
addToken(data);
124+
}
125+
126+
const message = isEdit
127+
? 'Token has been updated successfully'
128+
: 'Token has been created successfully';
129+
130+
toast.success(message);
131+
132+
closeModal();
133+
} catch (error) {
134+
if (error.response?.status === 401) {
135+
closeModal();
136+
return;
137+
}
138+
139+
toast.error(
140+
`Error occured while ${isEdit ? 'editing' : 'creating'} token`
141+
);
142+
closeModal();
143+
}
144+
};
145+
146+
return (
147+
<Modal.Container contentProps={{ style: { width: '850px' } }}>
148+
<Modal.Title>{isEdit ? 'Update' : 'Create'} API Token</Modal.Title>
149+
<Modal.Message>
150+
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
151+
<Input
152+
title="Token Name"
153+
id="name"
154+
type="text"
155+
placeholder="Lunalytics"
156+
onChange={(event) => setName(event.target.value)}
157+
value={name}
158+
subtitle="This will be automatically generated if one is not provided."
159+
/>
160+
161+
<div>
162+
<div className="input-label">Token Permissions</div>
163+
<div className="input-short-description">
164+
Permissions are used to restrict what the token can access.
165+
</div>
166+
<div
167+
style={{
168+
gap: '10px',
169+
display: 'flex',
170+
flexDirection: 'column',
171+
padding: '15px 0 20px 0',
172+
}}
173+
>
174+
{permissionsWithDescription.map((permission) => (
175+
<div
176+
key={title}
177+
style={{
178+
borderBottom: '1px solid var(--accent-700)',
179+
paddingBottom: '10px',
180+
}}
181+
>
182+
<SwitchWithText
183+
key={permission.title}
184+
label={permission.title}
185+
shortDescription={permission.description}
186+
onChange={(event) =>
187+
changePermission(
188+
event.target.checked,
189+
permission.permission
190+
)
191+
}
192+
checked={
193+
perms & permission.permission ||
194+
perms === PermissionsBits.ADMINISTRATOR ||
195+
perms & PermissionsBits.ADMINISTRATOR
196+
}
197+
/>
198+
</div>
199+
))}
200+
</div>
201+
</div>
202+
</div>
203+
</Modal.Message>
204+
<Modal.Actions>
205+
<Modal.Button id="manage-close-button" onClick={closeModal}>
206+
Close
207+
</Modal.Button>
208+
<Modal.Button
209+
id="manage-create-button"
210+
color="green"
211+
onClick={handleEditOrCreateToken}
212+
>
213+
{isEdit ? 'Update' : 'Create'}
214+
</Modal.Button>
215+
</Modal.Actions>
216+
</Modal.Container>
217+
);
218+
};
219+
220+
SettingsApiConfigureModal.displayName = 'SettingsApiConfigureModal';
221+
222+
export default SettingsApiConfigureModal;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { toast } from 'react-toastify';
2+
import { observer } from 'mobx-react-lite';
3+
4+
import Modal from '../../../ui/modal';
5+
import useContextStore from '../../../../context';
6+
import useTokensContext from '../../../../context/tokens';
7+
import { createPostRequest } from '../../../../services/axios';
8+
9+
const SettingsApiCloseModal = ({ tokenId = '', tokenName = '' }) => {
10+
const { removeToken } = useTokensContext();
11+
const {
12+
modalStore: { closeModal },
13+
} = useContextStore();
14+
15+
const handleDelete = async () => {
16+
try {
17+
await createPostRequest(`/api/tokens/delete`, { token: tokenId });
18+
19+
removeToken(tokenId);
20+
21+
closeModal();
22+
} catch (error) {
23+
if (error.response.status === 401) {
24+
closeModal();
25+
return;
26+
}
27+
28+
toast.error('Error occured while deleting token');
29+
}
30+
};
31+
32+
return (
33+
<Modal.Container>
34+
<Modal.Title>Delete API Token</Modal.Title>
35+
<Modal.Message>
36+
Are you sure you want to delete <b>{tokenName}</b> token?
37+
<br />
38+
This is an irreversible action.
39+
</Modal.Message>
40+
<Modal.Actions>
41+
<Modal.Button id="manage-close-button" onClick={closeModal}>
42+
Close
43+
</Modal.Button>
44+
<Modal.Button
45+
id="manage-create-button"
46+
color="green"
47+
onClick={handleDelete}
48+
>
49+
Delete
50+
</Modal.Button>
51+
</Modal.Actions>
52+
</Modal.Container>
53+
);
54+
};
55+
56+
SettingsApiCloseModal.displayName = 'SettingsApiCloseModal';
57+
58+
export default observer(SettingsApiCloseModal);
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import './style.scss';
2+
3+
// import dependencies
4+
import { useEffect } from 'react';
5+
import { toast } from 'react-toastify';
6+
import { observer } from 'mobx-react-lite';
7+
import { Button } from '@lunalytics/ui';
8+
import { IoKey } from 'react-icons/io5';
9+
10+
// import local files
11+
import { createGetRequest } from '../../../services/axios';
12+
import useTokensContext from '../../../context/tokens';
13+
import SettingsApiCreateModal from '../../modal/settings/api/createOrEdit';
14+
import ManageApiToken from './token';
15+
import useContextStore from '../../../context';
16+
17+
const ManageApiTokens = () => {
18+
const { allTokens, setTokens } = useTokensContext();
19+
const {
20+
modalStore: { openModal, closeModal },
21+
} = useContextStore();
22+
23+
useEffect(() => {
24+
const fetchTokens = async () => {
25+
try {
26+
const query = await createGetRequest('/api/tokens');
27+
28+
const tokens = query?.data?.tokens || [];
29+
setTokens(tokens);
30+
} catch {
31+
toast.error("Couldn't fetch api tokens");
32+
}
33+
};
34+
35+
fetchTokens();
36+
}, []);
37+
38+
return (
39+
<div
40+
style={{ overflow: 'auto', overflowX: 'hidden' }}
41+
className="settings-account-container"
42+
id="manage"
43+
>
44+
<div className="sat-header">
45+
<div style={{ flex: 1 }}>
46+
<div className="settings-subtitle" style={{ margin: '0px' }}>
47+
API Tokens
48+
</div>
49+
<div className="sat-subheader">
50+
API tokens can be used to authenticate your requests to Lunalytics.
51+
</div>
52+
</div>
53+
<div className="sat-create-btn">
54+
<Button
55+
variant="flat"
56+
color="primary"
57+
onClick={() =>
58+
openModal(<SettingsApiCreateModal closeModal={closeModal} />)
59+
}
60+
>
61+
Create Token
62+
</Button>
63+
</div>
64+
</div>
65+
66+
{!allTokens?.length ? (
67+
<div className="notification-empty">
68+
<div className="notification-empty-icon">
69+
<IoKey style={{ width: '64px', height: '64px' }} />
70+
</div>
71+
<div className="notification-empty-text">No tokens found</div>
72+
</div>
73+
) : null}
74+
75+
{allTokens?.length
76+
? allTokens.map((token) => (
77+
<ManageApiToken
78+
key={token.token}
79+
tokenId={token.token}
80+
tokenName={token.name}
81+
tokenPermissions={token.permission}
82+
/>
83+
))
84+
: null}
85+
</div>
86+
);
87+
};
88+
89+
ManageApiTokens.displayName = 'ManageApiTokens';
90+
91+
export default observer(ManageApiTokens);

0 commit comments

Comments
 (0)