@strapi/admin 4.4.0-beta.3 → 4.4.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/admin/src/content-manager/components/DynamicTable/index.js +2 -2
  2. package/admin/src/contexts/ApiTokenPermissions/index.js +24 -0
  3. package/admin/src/hooks/index.js +1 -0
  4. package/admin/src/hooks/useRegenerate/index.js +34 -0
  5. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ActionBoundRoutes/index.js +56 -0
  6. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/getMethodColor.js +41 -0
  7. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/index.js +72 -0
  8. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/CheckBoxWrapper.js +30 -0
  9. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/index.js +150 -0
  10. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ContenTypesSection/index.js +37 -0
  11. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Permissions/index.js +40 -0
  12. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +68 -0
  13. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +450 -180
  14. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/init.js +13 -0
  15. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/reducer.js +72 -0
  16. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/getDateOfExpiration.js +16 -0
  17. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/index.js +5 -0
  18. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/schema.js +2 -1
  19. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/transformPermissionsData.js +36 -0
  20. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DefaultButton/index.js +63 -0
  21. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js +1 -0
  22. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/ReadButton/index.js +19 -0
  23. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js +3 -36
  24. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +13 -11
  25. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/index.js +3 -2
  26. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/utils/tableHeaders.js +8 -8
  27. package/admin/src/pages/SettingsPage/pages/ApiTokens/ProtectedEditView/index.js +1 -1
  28. package/admin/src/permissions/defaultPermissions.js +2 -6
  29. package/admin/src/translations/en.json +17 -0
  30. package/build/4235.c44d8565.chunk.js +30 -0
  31. package/build/7379.d246dd38.chunk.js +1 -0
  32. package/build/{Admin-authenticatedApp.50e41ff2.chunk.js → Admin-authenticatedApp.6ad28580.chunk.js} +1 -1
  33. package/build/{Admin_homePage.118926e0.chunk.js → Admin_homePage.6d5e3236.chunk.js} +1 -1
  34. package/build/{Admin_profilePage.9d50ac44.chunk.js → Admin_profilePage.da32abbc.chunk.js} +1 -1
  35. package/build/{Admin_settingsPage.98a711e5.chunk.js → Admin_settingsPage.fc9c607a.chunk.js} +16 -16
  36. package/build/admin-app.7b7f9463.chunk.js +112 -0
  37. package/build/admin-edit-roles-page.4dd6bcb9.chunk.js +1 -0
  38. package/build/api-tokens-create-page.29cc87b6.chunk.js +1 -0
  39. package/build/api-tokens-edit-page.c294a88f.chunk.js +1 -0
  40. package/build/api-tokens-list-page.bb36535f.chunk.js +16 -0
  41. package/build/{content-manager.2a6f876d.chunk.js → content-manager.5ac9916a.chunk.js} +1 -1
  42. package/build/en-json.a9918c93.chunk.js +1 -0
  43. package/build/index.html +1 -1
  44. package/build/{main.fdc482f3.js → main.c04d580d.js} +1 -1
  45. package/build/{runtime~main.29105d25.js → runtime~main.3bd4f055.js} +2 -2
  46. package/build/sso-settings-page.9ceb0140.chunk.js +1 -0
  47. package/build/{webhook-edit-page.d2ea3351.chunk.js → webhook-edit-page.9e46fc3f.chunk.js} +1 -1
  48. package/package.json +8 -7
  49. package/server/bootstrap.js +19 -1
  50. package/server/config/admin-actions.js +20 -0
  51. package/server/content-types/api-token-permission.js +36 -0
  52. package/server/content-types/api-token.js +25 -1
  53. package/server/content-types/index.js +1 -0
  54. package/server/controllers/api-token.js +24 -1
  55. package/server/controllers/content-api.js +15 -0
  56. package/server/controllers/index.js +1 -0
  57. package/server/routes/api-tokens.js +11 -0
  58. package/server/routes/content-api.js +20 -0
  59. package/server/routes/index.js +2 -0
  60. package/server/services/api-token.js +310 -29
  61. package/server/services/constants.js +10 -0
  62. package/server/services/permission/engine.js +36 -226
  63. package/server/services/permission.js +4 -1
  64. package/server/strategies/admin.js +7 -1
  65. package/server/strategies/api-token.js +71 -11
  66. package/server/validation/api-tokens.js +12 -2
  67. package/build/admin-app.8bc3e80f.chunk.js +0 -112
  68. package/build/admin-edit-roles-page.554ba3fa.chunk.js +0 -1
  69. package/build/api-tokens-create-page.4c262d6e.chunk.js +0 -1
  70. package/build/api-tokens-edit-page.10a9d368.chunk.js +0 -1
  71. package/build/api-tokens-list-page.442c9f3c.chunk.js +0 -15
  72. package/build/en-json.12bc5a14.chunk.js +0 -1
  73. package/build/sso-settings-page.445184e0.chunk.js +0 -1
  74. package/server/services/permission/engine-hooks.js +0 -82
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useRef } from 'react';
1
+ import React, { useEffect, useState, useRef, useReducer } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import {
4
4
  SettingsPageTitle,
@@ -9,6 +9,8 @@ import {
9
9
  useTracking,
10
10
  useGuidedTour,
11
11
  Link,
12
+ usePersistentState,
13
+ useRBAC,
12
14
  } from '@strapi/helper-plugin';
13
15
  import { HeaderLayout, ContentLayout } from '@strapi/design-system/Layout';
14
16
  import { Main } from '@strapi/design-system/Main';
@@ -23,47 +25,132 @@ import { Grid, GridItem } from '@strapi/design-system/Grid';
23
25
  import { TextInput } from '@strapi/design-system/TextInput';
24
26
  import { Textarea } from '@strapi/design-system/Textarea';
25
27
  import { Select, Option } from '@strapi/design-system/Select';
26
- import get from 'lodash/get';
28
+ import { get } from 'lodash';
27
29
  import { useRouteMatch, useHistory } from 'react-router-dom';
28
30
  import { useQuery } from 'react-query';
29
31
  import { formatAPIErrors } from '../../../../../utils';
30
32
  import { axiosInstance } from '../../../../../core/utils';
31
- import schema from './utils/schema';
33
+ import { getDateOfExpiration, schema } from './utils';
32
34
  import LoadingView from './components/LoadingView';
33
35
  import HeaderContentBox from './components/ContentBox';
36
+ import Permissions from './components/Permissions';
37
+ import Regenerate from './components/Regenerate';
38
+ import adminPermissions from '../../../../../permissions';
39
+ import { ApiTokenPermissionsContextProvider } from '../../../../../contexts/ApiTokenPermissions';
40
+ import init from './init';
41
+ import reducer, { initialState } from './reducer';
42
+
43
+ const MSG_ERROR_NAME_TAKEN = 'Name already taken';
34
44
 
35
45
  const ApiTokenCreateView = () => {
36
- let apiToken;
37
46
  useFocusWhenNavigate();
38
47
  const { formatMessage } = useIntl();
39
48
  const { lockApp, unlockApp } = useOverlayBlocker();
40
49
  const toggleNotification = useNotification();
41
50
  const history = useHistory();
51
+ const [apiToken, setApiToken] = useState(
52
+ history.location.state?.apiToken.accessKey
53
+ ? {
54
+ ...history.location.state.apiToken,
55
+ }
56
+ : null
57
+ );
42
58
  const { trackUsage } = useTracking();
43
59
  const trackUsageRef = useRef(trackUsage);
44
60
  const { setCurrentStep } = useGuidedTour();
45
-
61
+ const {
62
+ allowedActions: { canCreate, canUpdate, canRegenerate },
63
+ } = useRBAC(adminPermissions.settings['api-tokens']);
64
+ const [lang] = usePersistentState('strapi-admin-language', 'en');
65
+ const [state, dispatch] = useReducer(reducer, initialState, (state) => init(state, {}));
46
66
  const {
47
67
  params: { id },
48
68
  } = useRouteMatch('/settings/api-tokens/:id');
49
69
 
50
70
  const isCreating = id === 'create';
51
71
 
72
+ useQuery(
73
+ 'content-api-permissions',
74
+ async () => {
75
+ const [permissions, routes] = await Promise.all(
76
+ ['/admin/content-api/permissions', '/admin/content-api/routes'].map(async (url) => {
77
+ const { data } = await axiosInstance.get(url);
78
+
79
+ return data.data;
80
+ })
81
+ );
82
+
83
+ dispatch({
84
+ type: 'UPDATE_PERMISSIONS_LAYOUT',
85
+ value: permissions,
86
+ });
87
+
88
+ dispatch({
89
+ type: 'UPDATE_ROUTES',
90
+ value: routes,
91
+ });
92
+
93
+ if (apiToken) {
94
+ if (apiToken?.type === 'read-only') {
95
+ dispatch({
96
+ type: 'ON_CHANGE_READ_ONLY',
97
+ });
98
+ }
99
+ if (apiToken?.type === 'full-access') {
100
+ dispatch({
101
+ type: 'SELECT_ALL_ACTIONS',
102
+ });
103
+ }
104
+ if (apiToken?.type === 'custom') {
105
+ dispatch({
106
+ type: 'UPDATE_PERMISSIONS',
107
+ value: apiToken?.permissions,
108
+ });
109
+ }
110
+ }
111
+ },
112
+ {
113
+ onError() {
114
+ toggleNotification({
115
+ type: 'warning',
116
+ message: { id: 'notification.error', defaultMessage: 'An error occured' },
117
+ });
118
+ },
119
+ }
120
+ );
121
+
52
122
  useEffect(() => {
53
123
  trackUsageRef.current(isCreating ? 'didAddTokenFromList' : 'didEditTokenFromList');
54
124
  }, [isCreating]);
55
125
 
56
- if (history.location.state?.apiToken.accessKey) {
57
- apiToken = history.location.state.apiToken;
58
- }
59
-
60
- const { status, data } = useQuery(
126
+ const { status } = useQuery(
61
127
  ['api-token', id],
62
128
  async () => {
63
129
  const {
64
130
  data: { data },
65
131
  } = await axiosInstance.get(`/admin/api-tokens/${id}`);
66
132
 
133
+ setApiToken({
134
+ ...data,
135
+ });
136
+
137
+ if (data?.type === 'read-only') {
138
+ dispatch({
139
+ type: 'ON_CHANGE_READ_ONLY',
140
+ });
141
+ }
142
+ if (data?.type === 'full-access') {
143
+ dispatch({
144
+ type: 'SELECT_ALL_ACTIONS',
145
+ });
146
+ }
147
+ if (data?.type === 'custom') {
148
+ dispatch({
149
+ type: 'UPDATE_PERMISSIONS',
150
+ value: data?.permissions,
151
+ });
152
+ }
153
+
67
154
  return data;
68
155
  },
69
156
  {
@@ -77,10 +164,6 @@ const ApiTokenCreateView = () => {
77
164
  }
78
165
  );
79
166
 
80
- if (data) {
81
- apiToken = data;
82
- }
83
-
84
167
  const handleSubmit = async (body, actions) => {
85
168
  trackUsageRef.current(isCreating ? 'willCreateToken' : 'willEditToken');
86
169
  lockApp();
@@ -89,37 +172,120 @@ const ApiTokenCreateView = () => {
89
172
  const {
90
173
  data: { data: response },
91
174
  } = isCreating
92
- ? await axiosInstance.post(`/admin/api-tokens`, body)
93
- : await axiosInstance.put(`/admin/api-tokens/${id}`, body);
175
+ ? await axiosInstance.post(`/admin/api-tokens`, {
176
+ ...body,
177
+ lifespan:
178
+ body.lifespan && parseInt(body.lifespan, 10)
179
+ ? parseInt(body.lifespan, 10)
180
+ : body.lifespan,
181
+ permissions: body.type === 'custom' ? state.selectedActions : null,
182
+ })
183
+ : await axiosInstance.put(`/admin/api-tokens/${id}`, {
184
+ name: body.name,
185
+ description: body.description,
186
+ type: body.type,
187
+ permissions: body.type === 'custom' ? state.selectedActions : null,
188
+ });
94
189
 
95
- apiToken = response;
190
+ if (isCreating) {
191
+ history.replace(`/settings/api-tokens/${response.id}`, { apiToken: response });
192
+ setCurrentStep('apiTokens.success');
193
+ }
194
+ unlockApp();
195
+ setApiToken({
196
+ ...response,
197
+ });
96
198
 
97
199
  toggleNotification({
98
200
  type: 'success',
99
- message: formatMessage({ id: 'notification.success.saved', defaultMessage: 'Saved' }),
201
+ message: isCreating
202
+ ? formatMessage({
203
+ id: 'notification.success.tokencreated',
204
+ defaultMessage: 'API Token successfully created',
205
+ })
206
+ : formatMessage({
207
+ id: 'notification.success.tokenedited',
208
+ defaultMessage: 'API Token successfully edited',
209
+ }),
100
210
  });
101
211
 
102
212
  trackUsageRef.current(isCreating ? 'didCreateToken' : 'didEditToken', {
103
213
  type: apiToken.type,
104
214
  });
105
-
106
- if (isCreating) {
107
- history.replace(`/settings/api-tokens/${response.id}`, { apiToken: response });
108
- setCurrentStep('apiTokens.success');
109
- }
110
215
  } catch (err) {
111
216
  const errors = formatAPIErrors(err.response.data);
112
217
  actions.setErrors(errors);
113
218
 
114
- toggleNotification({
115
- type: 'warning',
116
- message: get(err, 'response.data.message', 'notification.error'),
219
+ if (err?.response?.data?.error?.message === MSG_ERROR_NAME_TAKEN) {
220
+ toggleNotification({
221
+ type: 'warning',
222
+ message: get(err, 'response.data.message', 'notification.error.tokennamenotunique'),
223
+ });
224
+ } else {
225
+ toggleNotification({
226
+ type: 'warning',
227
+ message: get(err, 'response.data.message', 'notification.error'),
228
+ });
229
+ }
230
+ unlockApp();
231
+ }
232
+ };
233
+
234
+ const [hasChangedPermissions, setHasChangedPermissions] = useState(false);
235
+
236
+ const handleChangeCheckbox = ({ target: { value } }) => {
237
+ setHasChangedPermissions(true);
238
+ dispatch({
239
+ type: 'ON_CHANGE',
240
+ value,
241
+ });
242
+ };
243
+
244
+ const handleChangeSelectAllCheckbox = ({ target: { value } }) => {
245
+ setHasChangedPermissions(true);
246
+ dispatch({
247
+ type: 'SELECT_ALL_IN_PERMISSION',
248
+ value,
249
+ });
250
+ };
251
+
252
+ const handleChangeSelectApiTokenType = ({ target: { value } }) => {
253
+ setHasChangedPermissions(false);
254
+
255
+ if (value === 'full-access') {
256
+ dispatch({
257
+ type: 'SELECT_ALL_ACTIONS',
258
+ });
259
+ }
260
+ if (value === 'read-only') {
261
+ dispatch({
262
+ type: 'ON_CHANGE_READ_ONLY',
117
263
  });
118
264
  }
265
+ };
266
+
267
+ const setSelectedAction = ({ target: { value } }) => {
268
+ dispatch({
269
+ type: 'SET_SELECTED_ACTION',
270
+ value,
271
+ });
272
+ };
273
+
274
+ const handleRegenerate = (newKey) => {
275
+ setApiToken({
276
+ ...apiToken,
277
+ accessKey: newKey,
278
+ });
279
+ };
119
280
 
120
- unlockApp();
281
+ const providerValue = {
282
+ ...state,
283
+ onChange: handleChangeCheckbox,
284
+ onChangeSelectAll: handleChangeSelectAllCheckbox,
285
+ setSelectedAction,
121
286
  };
122
287
 
288
+ const canEditInputs = (canUpdate && !isCreating) || (canCreate && isCreating);
123
289
  const isLoading = !isCreating && !apiToken && status !== 'success';
124
290
 
125
291
  if (isLoading) {
@@ -127,162 +293,266 @@ const ApiTokenCreateView = () => {
127
293
  }
128
294
 
129
295
  return (
130
- <Main>
131
- <SettingsPageTitle name="API Tokens" />
132
- <Formik
133
- validationSchema={schema}
134
- validateOnChange={false}
135
- initialValues={{
136
- name: apiToken?.name || '',
137
- description: apiToken?.description || '',
138
- type: apiToken?.type || 'read-only',
139
- }}
140
- onSubmit={handleSubmit}
141
- >
142
- {({ errors, handleChange, isSubmitting, values }) => {
143
- return (
144
- <Form>
145
- <HeaderLayout
146
- title={
147
- apiToken?.name ||
148
- formatMessage({
149
- id: 'Settings.apiTokens.createPage.title',
150
- defaultMessage: 'Create API Token',
151
- })
152
- }
153
- primaryAction={
154
- <Button
155
- disabled={isSubmitting}
156
- loading={isSubmitting}
157
- startIcon={<Check />}
158
- type="submit"
159
- size="L"
160
- >
161
- {formatMessage({
162
- id: 'global.save',
163
- defaultMessage: 'Save',
164
- })}
165
- </Button>
166
- }
167
- navigationAction={
168
- <Link startIcon={<ArrowLeft />} to="/settings/api-tokens">
169
- {formatMessage({
170
- id: 'global.back',
171
- defaultMessage: 'Back',
172
- })}
173
- </Link>
174
- }
175
- />
176
- <ContentLayout>
177
- <Stack spacing={6}>
178
- {Boolean(apiToken?.name) && <HeaderContentBox apiToken={apiToken.accessKey} />}
179
- <Box
180
- background="neutral0"
181
- hasRadius
182
- shadow="filterShadow"
183
- paddingTop={6}
184
- paddingBottom={6}
185
- paddingLeft={7}
186
- paddingRight={7}
187
- >
188
- <Stack spacing={4}>
189
- <Typography variant="delta" as="h2">
190
- {formatMessage({
191
- id: 'global.details',
192
- defaultMessage: 'Details',
193
- })}
194
- </Typography>
195
- <Grid gap={5}>
196
- <GridItem key="name" col={6} xs={12}>
197
- <TextInput
198
- name="name"
199
- error={
200
- errors.name
201
- ? formatMessage(
202
- errors.name?.id
203
- ? errors.name
204
- : { id: errors.name, defaultMessage: errors.name }
205
- )
206
- : null
207
- }
208
- label={formatMessage({
209
- id: 'Settings.apiTokens.form.name',
210
- defaultMessage: 'Name',
211
- })}
212
- onChange={handleChange}
213
- value={values.name}
214
- required
296
+ <ApiTokenPermissionsContextProvider value={providerValue}>
297
+ <Main>
298
+ <SettingsPageTitle name="API Tokens" />
299
+ <Formik
300
+ validationSchema={schema}
301
+ validateOnChange={false}
302
+ initialValues={{
303
+ name: apiToken?.name || '',
304
+ description: apiToken?.description || '',
305
+ type: apiToken?.type,
306
+ lifespan: apiToken?.lifespan ? apiToken.lifespan.toString() : apiToken?.lifespan,
307
+ }}
308
+ enableReinitialize
309
+ onSubmit={(body, actions) => handleSubmit(body, actions)}
310
+ >
311
+ {({ errors, handleChange, isSubmitting, values, setFieldValue }) => {
312
+ if (hasChangedPermissions && values?.type !== 'custom') {
313
+ setFieldValue('type', 'custom');
314
+ }
315
+
316
+ return (
317
+ <Form>
318
+ <HeaderLayout
319
+ title={
320
+ apiToken?.name ||
321
+ formatMessage({
322
+ id: 'Settings.apiTokens.createPage.title',
323
+ defaultMessage: 'Create API Token',
324
+ })
325
+ }
326
+ primaryAction={
327
+ canEditInputs ? (
328
+ <Stack horizontal spacing={2}>
329
+ {canRegenerate && apiToken?.id && (
330
+ <Regenerate
331
+ onRegenerate={handleRegenerate}
332
+ idToRegenerate={apiToken?.id}
215
333
  />
216
- </GridItem>
217
- <GridItem key="description" col={6} xs={12}>
218
- <Textarea
219
- label={formatMessage({
220
- id: 'Settings.apiTokens.form.description',
221
- defaultMessage: 'Description',
222
- })}
223
- name="description"
224
- error={
225
- errors.description
226
- ? formatMessage(
227
- errors.description?.id
228
- ? errors.description
229
- : {
230
- id: errors.description,
231
- defaultMessage: errors.description,
232
- }
233
- )
234
- : null
235
- }
236
- onChange={handleChange}
237
- >
238
- {values.description}
239
- </Textarea>
240
- </GridItem>
241
- <GridItem key="type" col={6} xs={12}>
242
- <Select
243
- name="type"
244
- label={formatMessage({
245
- id: 'Settings.apiTokens.form.type',
246
- defaultMessage: 'Token type',
247
- })}
248
- value={values.type}
249
- error={
250
- errors.type
251
- ? formatMessage(
252
- errors.type?.id
253
- ? errors.type
254
- : { id: errors.type, defaultMessage: errors.type }
255
- )
256
- : null
257
- }
258
- onChange={(value) => {
259
- handleChange({ target: { name: 'type', value } });
260
- }}
261
- >
262
- <Option value="read-only">
263
- {formatMessage({
264
- id: 'Settings.apiTokens.types.read-only',
265
- defaultMessage: 'Read-only',
334
+ )}
335
+ <Button
336
+ disabled={isSubmitting}
337
+ loading={isSubmitting}
338
+ startIcon={<Check />}
339
+ type="submit"
340
+ size="S"
341
+ >
342
+ {formatMessage({
343
+ id: 'global.save',
344
+ defaultMessage: 'Save',
345
+ })}
346
+ </Button>
347
+ </Stack>
348
+ ) : (
349
+ canRegenerate &&
350
+ apiToken?.id && (
351
+ <Regenerate onRegenerate={handleRegenerate} idToRegenerate={apiToken?.id} />
352
+ )
353
+ )
354
+ }
355
+ navigationAction={
356
+ <Link startIcon={<ArrowLeft />} to="/settings/api-tokens">
357
+ {formatMessage({
358
+ id: 'global.back',
359
+ defaultMessage: 'Back',
360
+ })}
361
+ </Link>
362
+ }
363
+ />
364
+ <ContentLayout>
365
+ <Stack spacing={6}>
366
+ {Boolean(apiToken?.name) && <HeaderContentBox apiToken={apiToken.accessKey} />}
367
+ <Box
368
+ background="neutral0"
369
+ hasRadius
370
+ shadow="filterShadow"
371
+ paddingTop={6}
372
+ paddingBottom={6}
373
+ paddingLeft={7}
374
+ paddingRight={7}
375
+ >
376
+ <Stack spacing={4}>
377
+ <Typography variant="delta" as="h2">
378
+ {formatMessage({
379
+ id: 'global.details',
380
+ defaultMessage: 'Details',
381
+ })}
382
+ </Typography>
383
+ <Grid gap={5}>
384
+ <GridItem key="name" col={6} xs={12}>
385
+ <TextInput
386
+ name="name"
387
+ error={
388
+ errors.name
389
+ ? formatMessage(
390
+ errors.name?.id
391
+ ? errors.name
392
+ : { id: errors.name, defaultMessage: errors.name }
393
+ )
394
+ : null
395
+ }
396
+ label={formatMessage({
397
+ id: 'Settings.apiTokens.form.name',
398
+ defaultMessage: 'Name',
399
+ })}
400
+ onChange={handleChange}
401
+ value={values.name}
402
+ disabled={!canEditInputs}
403
+ required
404
+ />
405
+ </GridItem>
406
+ <GridItem key="description" col={6} xs={12}>
407
+ <Textarea
408
+ label={formatMessage({
409
+ id: 'Settings.apiTokens.form.description',
410
+ defaultMessage: 'Description',
266
411
  })}
267
- </Option>
268
- <Option value="full-access">
269
- {formatMessage({
270
- id: 'Settings.apiTokens.types.full-access',
271
- defaultMessage: 'Full access',
412
+ name="description"
413
+ error={
414
+ errors.description
415
+ ? formatMessage(
416
+ errors.description?.id
417
+ ? errors.description
418
+ : {
419
+ id: errors.description,
420
+ defaultMessage: errors.description,
421
+ }
422
+ )
423
+ : null
424
+ }
425
+ onChange={handleChange}
426
+ disabled={!canEditInputs}
427
+ >
428
+ {values.description}
429
+ </Textarea>
430
+ </GridItem>
431
+ <GridItem key="lifespan" col={6} xs={12}>
432
+ <Select
433
+ name="lifespan"
434
+ label={formatMessage({
435
+ id: 'Settings.apiTokens.form.duration',
436
+ defaultMessage: 'Token duration',
437
+ })}
438
+ value={values.lifespan}
439
+ error={
440
+ errors.lifespan
441
+ ? formatMessage(
442
+ errors.lifespan?.id
443
+ ? errors.lifespan
444
+ : { id: errors.lifespan, defaultMessage: errors.lifespan }
445
+ )
446
+ : null
447
+ }
448
+ onChange={(value) => {
449
+ handleChange({ target: { name: 'lifespan', value } });
450
+ }}
451
+ required
452
+ disabled={!isCreating}
453
+ placeholder="Select"
454
+ >
455
+ <Option value="604800000">
456
+ {formatMessage({
457
+ id: 'Settings.apiTokens.duration.7-days',
458
+ defaultMessage: '7 days',
459
+ })}
460
+ </Option>
461
+ <Option value="2592000000">
462
+ {formatMessage({
463
+ id: 'Settings.apiTokens.duration.30-days',
464
+ defaultMessage: '30 days',
465
+ })}
466
+ </Option>
467
+ <Option value="7776000000">
468
+ {formatMessage({
469
+ id: 'Settings.apiTokens.duration.90-days',
470
+ defaultMessage: '90 days',
471
+ })}
472
+ </Option>
473
+ <Option value={null}>
474
+ {formatMessage({
475
+ id: 'Settings.apiTokens.duration.unlimited',
476
+ defaultMessage: 'Unlimited',
477
+ })}
478
+ </Option>
479
+ </Select>
480
+ <Typography variant="pi" textColor="neutral600">
481
+ {!isCreating &&
482
+ `${formatMessage({
483
+ id: 'Settings.apiTokens.duration.expiration-date',
484
+ defaultMessage: 'Expiration date',
485
+ })}: ${getDateOfExpiration(
486
+ apiToken?.createdAt,
487
+ parseInt(values.lifespan, 10),
488
+ lang
489
+ )}`}
490
+ </Typography>
491
+ </GridItem>
492
+
493
+ <GridItem key="type" col={6} xs={12}>
494
+ <Select
495
+ name="type"
496
+ label={formatMessage({
497
+ id: 'Settings.apiTokens.form.type',
498
+ defaultMessage: 'Token type',
272
499
  })}
273
- </Option>
274
- </Select>
275
- </GridItem>
276
- </Grid>
277
- </Stack>
278
- </Box>
279
- </Stack>
280
- </ContentLayout>
281
- </Form>
282
- );
283
- }}
284
- </Formik>
285
- </Main>
500
+ value={values?.type}
501
+ error={
502
+ errors.type
503
+ ? formatMessage(
504
+ errors.type?.id
505
+ ? errors.type
506
+ : { id: errors.type, defaultMessage: errors.type }
507
+ )
508
+ : null
509
+ }
510
+ onChange={(value) => {
511
+ handleChangeSelectApiTokenType({ target: { value } });
512
+ handleChange({ target: { name: 'type', value } });
513
+ }}
514
+ placeholder="Select"
515
+ required
516
+ disabled={!canEditInputs}
517
+ >
518
+ <Option value="read-only">
519
+ {formatMessage({
520
+ id: 'Settings.apiTokens.types.read-only',
521
+ defaultMessage: 'Read-only',
522
+ })}
523
+ </Option>
524
+ <Option value="full-access">
525
+ {formatMessage({
526
+ id: 'Settings.apiTokens.types.full-access',
527
+ defaultMessage: 'Full access',
528
+ })}
529
+ </Option>
530
+ <Option value="custom">
531
+ {formatMessage({
532
+ id: 'Settings.apiTokens.types.custom',
533
+ defaultMessage: 'Custom',
534
+ })}
535
+ </Option>
536
+ </Select>
537
+ </GridItem>
538
+ </Grid>
539
+ </Stack>
540
+ </Box>
541
+ <Permissions
542
+ disabled={
543
+ !canEditInputs ||
544
+ values?.type === 'read-only' ||
545
+ values?.type === 'full-access'
546
+ }
547
+ />
548
+ </Stack>
549
+ </ContentLayout>
550
+ </Form>
551
+ );
552
+ }}
553
+ </Formik>
554
+ </Main>
555
+ </ApiTokenPermissionsContextProvider>
286
556
  );
287
557
  };
288
558