@strapi/admin 4.4.0-alpha.0 → 4.4.0-beta.1

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 (138) hide show
  1. package/admin/src/StrapiApp.js +12 -4
  2. package/admin/src/components/Providers/index.js +10 -14
  3. package/admin/src/content-manager/components/DynamicTable/CellContent/RelationSingle/index.js +1 -1
  4. package/admin/src/content-manager/components/FieldTypeIcon/index.js +1 -31
  5. package/admin/src/content-manager/components/Inputs/index.js +10 -30
  6. package/admin/src/content-manager/components/SelectMany/index.js +3 -0
  7. package/admin/src/content-manager/components/SelectOne/SingleValue.js +2 -2
  8. package/admin/src/content-manager/components/SelectOne/index.js +3 -0
  9. package/admin/src/content-manager/components/SelectWrapper/Option.js +2 -2
  10. package/admin/src/content-manager/components/SelectWrapper/index.js +6 -0
  11. package/admin/src/content-manager/pages/EditSettingsView/components/FormModal.js +2 -7
  12. package/admin/src/content-manager/pages/EditSettingsView/index.js +1 -2
  13. package/admin/src/content-manager/pages/EditView/index.js +84 -91
  14. package/admin/src/content-manager/pages/ListSettingsView/index.js +1 -1
  15. package/admin/src/contexts/ApiTokenPermissions/index.js +24 -0
  16. package/admin/src/core/apis/index.js +0 -1
  17. package/admin/src/hooks/index.js +1 -0
  18. package/admin/src/hooks/useRegenerate/index.js +34 -0
  19. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ActionBoundRoutes/index.js +56 -0
  20. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/getMethodColor.js +41 -0
  21. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/index.js +72 -0
  22. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/CheckBoxWrapper.js +30 -0
  23. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/index.js +150 -0
  24. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ContenTypesSection/index.js +37 -0
  25. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Permissions/index.js +40 -0
  26. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +68 -0
  27. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +452 -180
  28. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/init.js +13 -0
  29. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/reducer.js +55 -0
  30. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/getDateOfExpiration.js +16 -0
  31. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/index.js +5 -0
  32. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/schema.js +2 -1
  33. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/transformPermissionsData.js +36 -0
  34. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DefaultButton/index.js +63 -0
  35. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js +1 -0
  36. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/ReadButton/index.js +19 -0
  37. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js +3 -36
  38. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +13 -11
  39. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/index.js +4 -3
  40. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/utils/tableHeaders.js +8 -8
  41. package/admin/src/pages/SettingsPage/pages/ApiTokens/ProtectedEditView/index.js +1 -1
  42. package/admin/src/pages/SettingsPage/pages/Roles/ListPage/index.js +1 -1
  43. package/admin/src/pages/SettingsPage/pages/Users/ListPage/index.js +1 -1
  44. package/admin/src/pages/SettingsPage/pages/Webhooks/EditView/components/EventInput/index.js +10 -10
  45. package/admin/src/pages/SettingsPage/pages/Webhooks/ListView/index.js +1 -1
  46. package/admin/src/permissions/defaultPermissions.js +2 -6
  47. package/admin/src/translations/en.json +18 -0
  48. package/admin/src/translations/es.json +1 -1
  49. package/admin/src/translations/fr.json +33 -0
  50. package/build/1669.d1b29c28.chunk.js +1 -0
  51. package/build/{1856.d3da2fcd.chunk.js → 1856.47226450.chunk.js} +6 -6
  52. package/build/{2077.b25a0b57.chunk.js → 2077.61cebc93.chunk.js} +4 -4
  53. package/build/{2912.da8a70aa.chunk.js → 2912.a015078a.chunk.js} +10 -10
  54. package/build/4235.982b5799.chunk.js +30 -0
  55. package/build/{4715.3787be19.chunk.js → 4715.3f6cac0a.chunk.js} +30 -30
  56. package/build/{4800.d3ebc81d.chunk.js → 4800.d09f1225.chunk.js} +1 -1
  57. package/build/{4982.a4e36c9a.chunk.js → 4982.c6f88c5d.chunk.js} +8 -8
  58. package/build/611.a91aff91.chunk.js +158 -0
  59. package/build/7379.d246dd38.chunk.js +1 -0
  60. package/build/{7841.922b96eb.chunk.js → 7841.91f793dc.chunk.js} +9 -9
  61. package/build/{7866.22e3c9f8.chunk.js → 7866.c793a31d.chunk.js} +23 -23
  62. package/build/{8380.ab3939f3.chunk.js → 8380.8789ff76.chunk.js} +8 -8
  63. package/build/{8549.0e30f86d.chunk.js → 8549.133c4473.chunk.js} +5 -5
  64. package/build/{8773.4e36117f.chunk.js → 8773.eccaa5f3.chunk.js} +9 -9
  65. package/build/{9066.2847fdff.chunk.js → 9066.08049eb1.chunk.js} +4 -4
  66. package/build/{9166.280c7521.chunk.js → 9166.037339e0.chunk.js} +2 -2
  67. package/build/{9420.ee1ccff7.chunk.js → 9420.43a86e7c.chunk.js} +30 -30
  68. package/build/{Admin-authenticatedApp.aaa66872.chunk.js → Admin-authenticatedApp.3a31a087.chunk.js} +3 -3
  69. package/build/{Admin_homePage.118926e0.chunk.js → Admin_homePage.6d5e3236.chunk.js} +1 -1
  70. package/build/{Admin_marketplace.2d181ad7.chunk.js → Admin_marketplace.82c0570b.chunk.js} +1 -1
  71. package/build/{Admin_profilePage.8617313a.chunk.js → Admin_profilePage.83991a6c.chunk.js} +1 -1
  72. package/build/{Admin_settingsPage.e58753c8.chunk.js → Admin_settingsPage.fc9c607a.chunk.js} +16 -16
  73. package/build/admin-app.41b6472c.chunk.js +112 -0
  74. package/build/admin-edit-roles-page.4dd6bcb9.chunk.js +1 -0
  75. package/build/{admin-users.1d0aa7a0.chunk.js → admin-users.dccd5f4c.chunk.js} +2 -2
  76. package/build/api-tokens-create-page.29cc87b6.chunk.js +1 -0
  77. package/build/api-tokens-edit-page.c294a88f.chunk.js +1 -0
  78. package/build/api-tokens-list-page.bb36535f.chunk.js +16 -0
  79. package/build/content-manager.fb5ee865.chunk.js +1178 -0
  80. package/build/content-type-builder-list-view.8cc534e0.chunk.js +194 -0
  81. package/build/content-type-builder-translation-en-json.201bfb78.chunk.js +1 -0
  82. package/build/content-type-builder.42cecba9.chunk.js +142 -0
  83. package/build/{email-settings-page.818761d5.chunk.js → email-settings-page.64037147.chunk.js} +5 -5
  84. package/build/en-json.a9918c93.chunk.js +1 -0
  85. package/build/{es-json.bb1fc425.chunk.js → es-json.3a9c7c09.chunk.js} +1 -1
  86. package/build/fr-json.4ed1fc2c.chunk.js +1 -0
  87. package/build/{i18n-settings-page.bf1304b0.chunk.js → i18n-settings-page.0b73785d.chunk.js} +5 -5
  88. package/build/index.html +1 -1
  89. package/build/{main.7db3414f.js → main.cdfda31e.js} +1235 -1235
  90. package/build/{runtime~main.c1c5510b.js → runtime~main.fa8f8898.js} +1 -1
  91. package/build/sso-settings-page.9ceb0140.chunk.js +1 -0
  92. package/build/{upload-settings.5dfe0fe2.chunk.js → upload-settings.80ff0974.chunk.js} +7 -7
  93. package/build/upload-translation-ca-json.db8ed7ba.chunk.js +1 -0
  94. package/build/{users-advanced-settings-page.f11c8af4.chunk.js → users-advanced-settings-page.a02f4806.chunk.js} +5 -5
  95. package/build/{users-roles-settings-page.cafb4fe5.chunk.js → users-roles-settings-page.b33ec5e5.chunk.js} +1 -1
  96. package/build/{webhook-edit-page.9aba79b2.chunk.js → webhook-edit-page.9e46fc3f.chunk.js} +3 -3
  97. package/build/{webhook-list-page.912becb8.chunk.js → webhook-list-page.2775a683.chunk.js} +5 -5
  98. package/ee/admin/pages/SettingsPage/pages/Roles/ListPage/index.js +1 -1
  99. package/package.json +14 -13
  100. package/scripts/build.js +2 -4
  101. package/server/bootstrap.js +19 -1
  102. package/server/config/admin-actions.js +20 -0
  103. package/server/content-types/api-token-permission.js +36 -0
  104. package/server/content-types/api-token.js +25 -1
  105. package/server/content-types/index.js +1 -0
  106. package/server/controllers/api-token.js +24 -1
  107. package/server/controllers/content-api.js +15 -0
  108. package/server/controllers/index.js +1 -0
  109. package/server/routes/api-tokens.js +11 -0
  110. package/server/routes/content-api.js +20 -0
  111. package/server/routes/index.js +2 -0
  112. package/server/services/api-token.js +310 -29
  113. package/server/services/constants.js +10 -0
  114. package/server/services/permission/engine.js +36 -226
  115. package/server/services/permission/permissions-manager/query-builers.js +3 -2
  116. package/server/services/permission/queries.js +1 -1
  117. package/server/services/permission.js +4 -1
  118. package/server/strategies/admin.js +7 -1
  119. package/server/strategies/api-token.js +71 -11
  120. package/server/validation/api-tokens.js +12 -2
  121. package/server/validation/common-functions/check-fields-are-correctly-nested.js +1 -1
  122. package/admin/src/core/apis/CustomFields.js +0 -80
  123. package/build/1669.4ce92b2f.chunk.js +0 -1
  124. package/build/524.2437fb56.chunk.js +0 -644
  125. package/build/admin-app.1f9e13f8.chunk.js +0 -112
  126. package/build/admin-edit-roles-page.554ba3fa.chunk.js +0 -1
  127. package/build/api-tokens-create-page.b4a9987d.chunk.js +0 -1
  128. package/build/api-tokens-edit-page.6f5b4e26.chunk.js +0 -1
  129. package/build/api-tokens-list-page.06938769.chunk.js +0 -15
  130. package/build/content-manager.86f7594d.chunk.js +0 -1178
  131. package/build/content-type-builder-list-view.9b874fd4.chunk.js +0 -194
  132. package/build/content-type-builder-translation-en-json.f985c9c4.chunk.js +0 -1
  133. package/build/content-type-builder.47ab07ad.chunk.js +0 -145
  134. package/build/en-json.1bf20384.chunk.js +0 -1
  135. package/build/fr-json.a3cf2e0b.chunk.js +0 -1
  136. package/build/sso-settings-page.445184e0.chunk.js +0 -1
  137. package/build/upload-translation-ca-json.00dc1f33.chunk.js +0 -1
  138. 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,122 @@ 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
+ value.forEach((action) => {
247
+ dispatch({
248
+ type: 'ON_CHANGE',
249
+ value: action.actionId,
250
+ });
251
+ });
252
+ };
253
+
254
+ const handleChangeSelectApiTokenType = ({ target: { value } }) => {
255
+ setHasChangedPermissions(false);
256
+
257
+ if (value === 'full-access') {
258
+ dispatch({
259
+ type: 'SELECT_ALL_ACTIONS',
260
+ });
261
+ }
262
+ if (value === 'read-only') {
263
+ dispatch({
264
+ type: 'ON_CHANGE_READ_ONLY',
117
265
  });
118
266
  }
267
+ };
268
+
269
+ const setSelectedAction = ({ target: { value } }) => {
270
+ dispatch({
271
+ type: 'SET_SELECTED_ACTION',
272
+ value,
273
+ });
274
+ };
275
+
276
+ const handleRegenerate = (newKey) => {
277
+ setApiToken({
278
+ ...apiToken,
279
+ accessKey: newKey,
280
+ });
281
+ };
119
282
 
120
- unlockApp();
283
+ const providerValue = {
284
+ ...state,
285
+ onChange: handleChangeCheckbox,
286
+ onChangeSelectAll: handleChangeSelectAllCheckbox,
287
+ setSelectedAction,
121
288
  };
122
289
 
290
+ const canEditInputs = (canUpdate && !isCreating) || (canCreate && isCreating);
123
291
  const isLoading = !isCreating && !apiToken && status !== 'success';
124
292
 
125
293
  if (isLoading) {
@@ -127,162 +295,266 @@ const ApiTokenCreateView = () => {
127
295
  }
128
296
 
129
297
  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
298
+ <ApiTokenPermissionsContextProvider value={providerValue}>
299
+ <Main>
300
+ <SettingsPageTitle name="API Tokens" />
301
+ <Formik
302
+ validationSchema={schema}
303
+ validateOnChange={false}
304
+ initialValues={{
305
+ name: apiToken?.name || '',
306
+ description: apiToken?.description || '',
307
+ type: apiToken?.type,
308
+ lifespan: apiToken?.lifespan ? apiToken.lifespan.toString() : apiToken?.lifespan,
309
+ }}
310
+ enableReinitialize
311
+ onSubmit={(body, actions) => handleSubmit(body, actions)}
312
+ >
313
+ {({ errors, handleChange, isSubmitting, values, setFieldValue }) => {
314
+ if (hasChangedPermissions && values?.type !== 'custom') {
315
+ setFieldValue('type', 'custom');
316
+ }
317
+
318
+ return (
319
+ <Form>
320
+ <HeaderLayout
321
+ title={
322
+ apiToken?.name ||
323
+ formatMessage({
324
+ id: 'Settings.apiTokens.createPage.title',
325
+ defaultMessage: 'Create API Token',
326
+ })
327
+ }
328
+ primaryAction={
329
+ canEditInputs ? (
330
+ <Stack horizontal spacing={2}>
331
+ {canRegenerate && apiToken?.id && (
332
+ <Regenerate
333
+ onRegenerate={handleRegenerate}
334
+ idToRegenerate={apiToken?.id}
215
335
  />
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',
336
+ )}
337
+ <Button
338
+ disabled={isSubmitting}
339
+ loading={isSubmitting}
340
+ startIcon={<Check />}
341
+ type="submit"
342
+ size="S"
343
+ >
344
+ {formatMessage({
345
+ id: 'global.save',
346
+ defaultMessage: 'Save',
347
+ })}
348
+ </Button>
349
+ </Stack>
350
+ ) : (
351
+ canRegenerate &&
352
+ apiToken?.id && (
353
+ <Regenerate onRegenerate={handleRegenerate} idToRegenerate={apiToken?.id} />
354
+ )
355
+ )
356
+ }
357
+ navigationAction={
358
+ <Link startIcon={<ArrowLeft />} to="/settings/api-tokens">
359
+ {formatMessage({
360
+ id: 'global.back',
361
+ defaultMessage: 'Back',
362
+ })}
363
+ </Link>
364
+ }
365
+ />
366
+ <ContentLayout>
367
+ <Stack spacing={6}>
368
+ {Boolean(apiToken?.name) && <HeaderContentBox apiToken={apiToken.accessKey} />}
369
+ <Box
370
+ background="neutral0"
371
+ hasRadius
372
+ shadow="filterShadow"
373
+ paddingTop={6}
374
+ paddingBottom={6}
375
+ paddingLeft={7}
376
+ paddingRight={7}
377
+ >
378
+ <Stack spacing={4}>
379
+ <Typography variant="delta" as="h2">
380
+ {formatMessage({
381
+ id: 'global.details',
382
+ defaultMessage: 'Details',
383
+ })}
384
+ </Typography>
385
+ <Grid gap={5}>
386
+ <GridItem key="name" col={6} xs={12}>
387
+ <TextInput
388
+ name="name"
389
+ error={
390
+ errors.name
391
+ ? formatMessage(
392
+ errors.name?.id
393
+ ? errors.name
394
+ : { id: errors.name, defaultMessage: errors.name }
395
+ )
396
+ : null
397
+ }
398
+ label={formatMessage({
399
+ id: 'Settings.apiTokens.form.name',
400
+ defaultMessage: 'Name',
401
+ })}
402
+ onChange={handleChange}
403
+ value={values.name}
404
+ disabled={!canEditInputs}
405
+ required
406
+ />
407
+ </GridItem>
408
+ <GridItem key="description" col={6} xs={12}>
409
+ <Textarea
410
+ label={formatMessage({
411
+ id: 'Settings.apiTokens.form.description',
412
+ defaultMessage: 'Description',
266
413
  })}
267
- </Option>
268
- <Option value="full-access">
269
- {formatMessage({
270
- id: 'Settings.apiTokens.types.full-access',
271
- defaultMessage: 'Full access',
414
+ name="description"
415
+ error={
416
+ errors.description
417
+ ? formatMessage(
418
+ errors.description?.id
419
+ ? errors.description
420
+ : {
421
+ id: errors.description,
422
+ defaultMessage: errors.description,
423
+ }
424
+ )
425
+ : null
426
+ }
427
+ onChange={handleChange}
428
+ disabled={!canEditInputs}
429
+ >
430
+ {values.description}
431
+ </Textarea>
432
+ </GridItem>
433
+ <GridItem key="lifespan" col={6} xs={12}>
434
+ <Select
435
+ name="lifespan"
436
+ label={formatMessage({
437
+ id: 'Settings.apiTokens.form.duration',
438
+ defaultMessage: 'Token duration',
439
+ })}
440
+ value={values.lifespan}
441
+ error={
442
+ errors.lifespan
443
+ ? formatMessage(
444
+ errors.lifespan?.id
445
+ ? errors.lifespan
446
+ : { id: errors.lifespan, defaultMessage: errors.lifespan }
447
+ )
448
+ : null
449
+ }
450
+ onChange={(value) => {
451
+ handleChange({ target: { name: 'lifespan', value } });
452
+ }}
453
+ required
454
+ disabled={!isCreating}
455
+ placeholder="Select"
456
+ >
457
+ <Option value="604800000">
458
+ {formatMessage({
459
+ id: 'Settings.apiTokens.duration.7-days',
460
+ defaultMessage: '7 days',
461
+ })}
462
+ </Option>
463
+ <Option value="2592000000">
464
+ {formatMessage({
465
+ id: 'Settings.apiTokens.duration.30-days',
466
+ defaultMessage: '30 days',
467
+ })}
468
+ </Option>
469
+ <Option value="7776000000">
470
+ {formatMessage({
471
+ id: 'Settings.apiTokens.duration.90-days',
472
+ defaultMessage: '90 days',
473
+ })}
474
+ </Option>
475
+ <Option value={null}>
476
+ {formatMessage({
477
+ id: 'Settings.apiTokens.duration.unlimited',
478
+ defaultMessage: 'Unlimited',
479
+ })}
480
+ </Option>
481
+ </Select>
482
+ <Typography variant="pi" textColor="neutral600">
483
+ {!isCreating &&
484
+ `${formatMessage({
485
+ id: 'Settings.apiTokens.duration.expiration-date',
486
+ defaultMessage: 'Expiration date',
487
+ })}: ${getDateOfExpiration(
488
+ apiToken?.createdAt,
489
+ parseInt(values.lifespan, 10),
490
+ lang
491
+ )}`}
492
+ </Typography>
493
+ </GridItem>
494
+
495
+ <GridItem key="type" col={6} xs={12}>
496
+ <Select
497
+ name="type"
498
+ label={formatMessage({
499
+ id: 'Settings.apiTokens.form.type',
500
+ defaultMessage: 'Token type',
272
501
  })}
273
- </Option>
274
- </Select>
275
- </GridItem>
276
- </Grid>
277
- </Stack>
278
- </Box>
279
- </Stack>
280
- </ContentLayout>
281
- </Form>
282
- );
283
- }}
284
- </Formik>
285
- </Main>
502
+ value={values?.type}
503
+ error={
504
+ errors.type
505
+ ? formatMessage(
506
+ errors.type?.id
507
+ ? errors.type
508
+ : { id: errors.type, defaultMessage: errors.type }
509
+ )
510
+ : null
511
+ }
512
+ onChange={(value) => {
513
+ handleChangeSelectApiTokenType({ target: { value } });
514
+ handleChange({ target: { name: 'type', value } });
515
+ }}
516
+ placeholder="Select"
517
+ required
518
+ disabled={!canEditInputs}
519
+ >
520
+ <Option value="read-only">
521
+ {formatMessage({
522
+ id: 'Settings.apiTokens.types.read-only',
523
+ defaultMessage: 'Read-only',
524
+ })}
525
+ </Option>
526
+ <Option value="full-access">
527
+ {formatMessage({
528
+ id: 'Settings.apiTokens.types.full-access',
529
+ defaultMessage: 'Full access',
530
+ })}
531
+ </Option>
532
+ <Option value="custom">
533
+ {formatMessage({
534
+ id: 'Settings.apiTokens.types.custom',
535
+ defaultMessage: 'Custom',
536
+ })}
537
+ </Option>
538
+ </Select>
539
+ </GridItem>
540
+ </Grid>
541
+ </Stack>
542
+ </Box>
543
+ <Permissions
544
+ disabled={
545
+ !canEditInputs ||
546
+ values?.type === 'read-only' ||
547
+ values?.type === 'full-access'
548
+ }
549
+ />
550
+ </Stack>
551
+ </ContentLayout>
552
+ </Form>
553
+ );
554
+ }}
555
+ </Formik>
556
+ </Main>
557
+ </ApiTokenPermissionsContextProvider>
286
558
  );
287
559
  };
288
560