From ba6f2a17c364b16c967a9f548ed718163179b58b Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Tue, 23 Jan 2024 21:31:13 +0100 Subject: [PATCH 1/7] feat: add in scheduling settings to settings app [DHIS2-15765] (#1295) --- i18n/en.pot | 121 ++++++++++++-------------------------- src/settingsCategories.js | 12 ++++ src/settingsKeyMapping.js | 44 +++++++++++++- 3 files changed, 94 insertions(+), 83 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index f3760541..b2c7d062 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2023-08-24T10:48:48.806Z\n" -"PO-Revision-Date: 2023-08-24T10:48:48.806Z\n" +"POT-Creation-Date: 2024-01-19T12:30:16.705Z\n" +"PO-Revision-Date: 2024-01-19T12:30:16.705Z\n" msgid "Failed to load: {{error}}" msgstr "Failed to load: {{error}}" @@ -89,84 +89,6 @@ msgstr "Don't sync metadata if DHIS versions differ" msgid "Metadata Versioning" msgstr "Metadata Versioning" -msgid "This client ID is already taken" -msgstr "This client ID is already taken" - -msgid "Name" -msgstr "Name" - -msgid "Required" -msgstr "Required" - -msgid "Client ID" -msgstr "Client ID" - -msgid "Client Secret" -msgstr "Client Secret" - -msgid "Grant Types" -msgstr "Grant Types" - -msgid "Password" -msgstr "Password" - -msgid "Refresh token" -msgstr "Refresh token" - -msgid "Authorization code" -msgstr "Authorization code" - -msgid "One URL per line" -msgstr "One URL per line" - -msgid "Redirect URIs" -msgstr "Redirect URIs" - -msgid "This field should contain a list of URLs" -msgstr "This field should contain a list of URLs" - -msgid "Create new OAuth2 Client" -msgstr "Create new OAuth2 Client" - -msgid "Edit OAuth2 Client" -msgstr "Edit OAuth2 Client" - -msgid "Save" -msgstr "Save" - -msgid "Cancel" -msgstr "Cancel" - -msgid "There are currently no OAuth2 clients registered" -msgstr "There are currently no OAuth2 clients registered" - -msgid "Edit" -msgstr "Edit" - -msgid "Delete" -msgstr "Delete" - -msgid "OAuth2 client saved" -msgstr "OAuth2 client saved" - -msgid "Failed to save OAuth2 client" -msgstr "Failed to save OAuth2 client" - -msgid "Add OAuth2 client" -msgstr "Add OAuth2 client" - -msgid "Yes" -msgstr "Yes" - -msgid "No" -msgstr "No" - -msgid "OAuth2 client deleted" -msgstr "OAuth2 client deleted" - -msgid "Failed to delete OAuth2 client" -msgstr "Failed to delete OAuth2 client" - msgid "Settings updated" msgstr "Settings updated" @@ -224,8 +146,8 @@ msgstr "Synchronization" msgid "Synchronization settings" msgstr "Synchronization settings" -msgid "OAuth2 Clients" -msgstr "OAuth2 Clients" +msgid "Scheduled jobs" +msgstr "Scheduled jobs" msgid "This field is required" msgstr "This field is required" @@ -233,6 +155,9 @@ msgstr "This field is required" msgid "This field should be a URL" msgstr "This field should be a URL" +msgid "This field should contain a list of URLs" +msgstr "This field should contain a list of URLs" + msgid "This field should be a number" msgstr "This field should be a number" @@ -632,6 +557,9 @@ msgstr "Database language" msgid "Property to display in analysis modules" msgstr "Property to display in analysis modules" +msgid "Name" +msgstr "Name" + msgid "Short name" msgstr "Short name" @@ -674,6 +602,9 @@ msgstr "Port" msgid "Username" msgstr "Username" +msgid "Password" +msgstr "Password" + msgid "TLS" msgstr "TLS" @@ -737,6 +668,9 @@ msgstr "Minimum characters in password" msgid "CORS whitelist" msgstr "CORS whitelist" +msgid "One URL per line" +msgstr "One URL per line" + msgid "reCAPTCHA Site Key" msgstr "reCAPTCHA Site Key" @@ -782,6 +716,9 @@ msgstr "" "system unusable. If you have entered data, it is strongly recommended that " "you do not change your calendar setting." +msgid "Cancel" +msgstr "Cancel" + msgid "Yes, change calendar" msgstr "Yes, change calendar" @@ -817,3 +754,23 @@ msgstr "Remote server username" msgid "Remote server password" msgstr "Remote server password" + +msgid "Number of minutes after which a stale job is reset to scheduled state (1-60)" +msgstr "Number of minutes after which a stale job is reset to scheduled state (1-60)" + +msgid "Number of minutes after which a completed ad-hoc job is deleted (1+)" +msgstr "Number of minutes after which a completed ad-hoc job is deleted (1+)" + +msgid "" +"Maximum number of hours a job may trigger after its intended time if job " +"has not yet run (1-24)" +msgstr "" +"Maximum number of hours a job may trigger after its intended time if job " +"has not yet run (1-24)" + +msgid "" +"Job execution interval (seconds) below which a job will be logged at debug " +"(rather than info) level (20+)" +msgstr "" +"Job execution interval (seconds) below which a job will be logged at debug " +"(rather than info) level (20+)" diff --git a/src/settingsCategories.js b/src/settingsCategories.js index 63a43349..a9f3e9c1 100644 --- a/src/settingsCategories.js +++ b/src/settingsCategories.js @@ -10,6 +10,7 @@ export const categoryOrder = [ 'calendar', 'import', 'sync', + 'scheduledJobs', ] export const categories = { @@ -161,4 +162,15 @@ export const categories = { 'keyMetadataDataVersioning', ], }, + scheduledJobs: { + label: i18n.t('Scheduled jobs'), + icon: 'schedule', + pageLabel: i18n.t('Scheduled jobs'), + settings: [ + 'jobsRescheduleAfterMinutes', + 'jobsCleanupAfterMinutes', + 'jobsMaxCronDelayHours', + 'jobsLogDebugBelowSeconds', + ], + }, } diff --git a/src/settingsKeyMapping.js b/src/settingsKeyMapping.js index 3c27dabb..6b5d98e2 100644 --- a/src/settingsKeyMapping.js +++ b/src/settingsKeyMapping.js @@ -689,7 +689,49 @@ const settingsKeyMapping = { label: i18n.t('Metadata Versioning'), type: 'metadataSettings', }, - + /* ============================================================================================================ */ + /* Category: Scheduled jobs */ + /* ============================================================================================================ */ + jobsRescheduleAfterMinutes: { + label: i18n.t( + 'Number of minutes after which a stale job is reset to scheduled state (1-60)' + ), + type: 'textfield', + inputType: 'number', + minValue: 1, + maxValue: 60, + validators: ['positive_number'], + }, + jobsCleanupAfterMinutes: { + label: i18n.t( + 'Number of minutes after which a completed ad-hoc job is deleted (1+)' + ), + type: 'textfield', + inputType: 'number', + minValue: 1, + maxValue: 2147483647, + validators: ['positive_number'], + }, + jobsMaxCronDelayHours: { + label: i18n.t( + 'Maximum number of hours a job may trigger after its intended time if job has not yet run (1-24)' + ), + type: 'textfield', + inputType: 'number', + minValue: 1, + maxValue: 24, + validators: ['positive_number'], + }, + jobsLogDebugBelowSeconds: { + label: i18n.t( + 'Job execution interval (seconds) below which a job will be logged at debug (rather than info) level (20+)' + ), + type: 'textfield', + inputType: 'number', + minValue: 20, + maxValue: 2147483647, + validators: ['positive_number'], + }, /* ============================================================================================================ */ // The following keys are present in the demo database but are not managed by dhis-web-maintenance-settings // From 339907c6e4964d0d01474fb431c131d18d9fe87a Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Tue, 23 Jan 2024 20:37:58 +0000 Subject: [PATCH 2/7] chore(release): cut 29.16.0 [skip ci] # [29.16.0](https://github.com/dhis2/settings-app/compare/v29.15.9...v29.16.0) (2024-01-23) ### Features * add in scheduling settings to settings app [DHIS2-15765] ([#1295](https://github.com/dhis2/settings-app/issues/1295)) ([ba6f2a1](https://github.com/dhis2/settings-app/commit/ba6f2a17c364b16c967a9f548ed718163179b58b)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8afbd280..e5a23fb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [29.16.0](https://github.com/dhis2/settings-app/compare/v29.15.9...v29.16.0) (2024-01-23) + + +### Features + +* add in scheduling settings to settings app [DHIS2-15765] ([#1295](https://github.com/dhis2/settings-app/issues/1295)) ([ba6f2a1](https://github.com/dhis2/settings-app/commit/ba6f2a17c364b16c967a9f548ed718163179b58b)) + ## [29.15.9](https://github.com/dhis2/settings-app/compare/v29.15.8...v29.15.9) (2023-12-14) diff --git a/package.json b/package.json index b666cf64..658e85ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "settings-app", - "version": "29.15.9", + "version": "29.16.0", "description": "", "license": "BSD-3-Clause", "private": true, From d9fdbc8a502c0636353df368f26ebb9459e32e00 Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Wed, 24 Jan 2024 13:09:53 +0100 Subject: [PATCH 3/7] fix: remove keyAnalyticsMaintenanceMode [DHIS2-16534] (#1296) --- i18n/en.pot | 7 ++----- src/settingsCategories.js | 1 - src/settingsKeyMapping.js | 4 ---- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index b2c7d062..c334d475 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-01-19T12:30:16.705Z\n" -"PO-Revision-Date: 2024-01-19T12:30:16.705Z\n" +"POT-Creation-Date: 2024-01-23T20:33:57.618Z\n" +"PO-Revision-Date: 2024-01-23T20:33:57.618Z\n" msgid "Failed to load: {{error}}" msgstr "Failed to load: {{error}}" @@ -464,9 +464,6 @@ msgstr "Last 9 years" msgid "Respect category option start and end date in analytics table export" msgstr "Respect category option start and end date in analytics table export" -msgid "Put analytics in maintenance mode" -msgstr "Put analytics in maintenance mode" - msgid "Include zero data values in analytics tables" msgstr "Include zero data values in analytics tables" diff --git a/src/settingsCategories.js b/src/settingsCategories.js index a9f3e9c1..39047eb7 100644 --- a/src/settingsCategories.js +++ b/src/settingsCategories.js @@ -55,7 +55,6 @@ export const categories = { 'keyAnalyticsCacheProgressiveTtlFactor', 'keyIgnoreAnalyticsApprovalYearThreshold', 'keyRespectMetaDataStartEndDatesInAnalyticsTableExport', - 'keyAnalyticsMaintenanceMode', 'keyIncludeZeroValuesInAnalytics', 'keyDashboardContextMenuItemSwitchViewType', 'keyDashboardContextMenuItemOpenInRelevantApp', diff --git a/src/settingsKeyMapping.js b/src/settingsKeyMapping.js index 6b5d98e2..a8a139c0 100644 --- a/src/settingsKeyMapping.js +++ b/src/settingsKeyMapping.js @@ -248,10 +248,6 @@ const settingsKeyMapping = { ), type: 'checkbox', }, - keyAnalyticsMaintenanceMode: { - label: i18n.t('Put analytics in maintenance mode'), - type: 'checkbox', - }, keyIncludeZeroValuesInAnalytics: { label: i18n.t('Include zero data values in analytics tables'), type: 'checkbox', From b63056876a98cd94bdea4a7b8ec2b23128c08529 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 24 Jan 2024 12:14:46 +0000 Subject: [PATCH 4/7] chore(release): cut 29.16.1 [skip ci] ## [29.16.1](https://github.com/dhis2/settings-app/compare/v29.16.0...v29.16.1) (2024-01-24) ### Bug Fixes * remove keyAnalyticsMaintenanceMode [DHIS2-16534] ([#1296](https://github.com/dhis2/settings-app/issues/1296)) ([d9fdbc8](https://github.com/dhis2/settings-app/commit/d9fdbc8a502c0636353df368f26ebb9459e32e00)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a23fb0..159a4f23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [29.16.1](https://github.com/dhis2/settings-app/compare/v29.16.0...v29.16.1) (2024-01-24) + + +### Bug Fixes + +* remove keyAnalyticsMaintenanceMode [DHIS2-16534] ([#1296](https://github.com/dhis2/settings-app/issues/1296)) ([d9fdbc8](https://github.com/dhis2/settings-app/commit/d9fdbc8a502c0636353df368f26ebb9459e32e00)) + # [29.16.0](https://github.com/dhis2/settings-app/compare/v29.15.9...v29.16.0) (2024-01-23) diff --git a/package.json b/package.json index 658e85ad..6bec935f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "settings-app", - "version": "29.16.0", + "version": "29.16.1", "description": "", "license": "BSD-3-Clause", "private": true, From 520b1c76fd8705eb311813c1bea0a9277aef8759 Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Mon, 12 Feb 2024 14:57:52 +0100 Subject: [PATCH 5/7] fix: add back OAUTH2 [DHIS2-15326] (#1300) --- cypress/integration/oauth2.feature | 18 ++ i18n/en.pot | 100 +++++++++-- src/oauth2-client-editor/ClientForm.js | 162 ++++++++++++++++++ .../ClientForm.module.css | 3 + src/oauth2-client-editor/ClientsList.js | 78 +++++++++ .../ClientsList.module.css | 3 + .../OAuth2ClientEditor.component.js | 149 ++++++++++++++++ .../OAuth2ClientEditor.module.css | 7 + .../oauth2Client.actions.js | 56 ++++++ .../oauth2Client.store.js | 3 + src/settingsCategories.js | 8 + src/settingsFields.component.js | 6 + src/settingsKeyMapping.js | 12 ++ 13 files changed, 588 insertions(+), 17 deletions(-) create mode 100644 cypress/integration/oauth2.feature create mode 100644 src/oauth2-client-editor/ClientForm.js create mode 100644 src/oauth2-client-editor/ClientForm.module.css create mode 100644 src/oauth2-client-editor/ClientsList.js create mode 100644 src/oauth2-client-editor/ClientsList.module.css create mode 100644 src/oauth2-client-editor/OAuth2ClientEditor.component.js create mode 100644 src/oauth2-client-editor/OAuth2ClientEditor.module.css create mode 100644 src/oauth2-client-editor/oauth2Client.actions.js create mode 100644 src/oauth2-client-editor/oauth2Client.store.js diff --git a/cypress/integration/oauth2.feature b/cypress/integration/oauth2.feature new file mode 100644 index 00000000..4dd974d7 --- /dev/null +++ b/cypress/integration/oauth2.feature @@ -0,0 +1,18 @@ +Feature: Users should be able to add OAuth2 clients + + Scenario: User adds OAuth2 client + Given the user visits the 'OAuth2 Clients' page + And the user clicks on the 'Add OAuth2 client' button + And the user enters the relevant details in the form that appears + Then a snackbar message should appear telling the user that the client was saved + And the page should show the new client in the table of clients + + Scenario: No OAuth2 clients are present + Given the user visits the 'OAuth2 Clients' page + And there are no OAuth2 clients + Then the message 'There are currently no OAuth2 clients registered' should be shown + + Scenario: Some OAuth2 clients are present + Given the user visits the 'OAuth2 Clients' page + And there are some OAuth2 clients + Then a table showing all clients should be present diff --git a/i18n/en.pot b/i18n/en.pot index c334d475..70e8ef84 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2024-01-23T20:33:57.618Z\n" -"PO-Revision-Date: 2024-01-23T20:33:57.618Z\n" +"POT-Creation-Date: 2024-02-09T16:13:02.251Z\n" +"PO-Revision-Date: 2024-02-09T16:13:02.251Z\n" msgid "Failed to load: {{error}}" msgstr "Failed to load: {{error}}" @@ -89,6 +89,84 @@ msgstr "Don't sync metadata if DHIS versions differ" msgid "Metadata Versioning" msgstr "Metadata Versioning" +msgid "This client ID is already taken" +msgstr "This client ID is already taken" + +msgid "Name" +msgstr "Name" + +msgid "Required" +msgstr "Required" + +msgid "Client ID" +msgstr "Client ID" + +msgid "Client Secret" +msgstr "Client Secret" + +msgid "Grant Types" +msgstr "Grant Types" + +msgid "Password" +msgstr "Password" + +msgid "Refresh token" +msgstr "Refresh token" + +msgid "Authorization code" +msgstr "Authorization code" + +msgid "One URL per line" +msgstr "One URL per line" + +msgid "Redirect URIs" +msgstr "Redirect URIs" + +msgid "This field should contain a list of URLs" +msgstr "This field should contain a list of URLs" + +msgid "Create new OAuth2 Client" +msgstr "Create new OAuth2 Client" + +msgid "Edit OAuth2 Client" +msgstr "Edit OAuth2 Client" + +msgid "Save" +msgstr "Save" + +msgid "Cancel" +msgstr "Cancel" + +msgid "There are currently no OAuth2 clients registered" +msgstr "There are currently no OAuth2 clients registered" + +msgid "Edit" +msgstr "Edit" + +msgid "Delete" +msgstr "Delete" + +msgid "OAuth2 client saved" +msgstr "OAuth2 client saved" + +msgid "Failed to save OAuth2 client" +msgstr "Failed to save OAuth2 client" + +msgid "Add OAuth2 client" +msgstr "Add OAuth2 client" + +msgid "Yes" +msgstr "Yes" + +msgid "No" +msgstr "No" + +msgid "OAuth2 client deleted" +msgstr "OAuth2 client deleted" + +msgid "Failed to delete OAuth2 client" +msgstr "Failed to delete OAuth2 client" + msgid "Settings updated" msgstr "Settings updated" @@ -146,6 +224,9 @@ msgstr "Synchronization" msgid "Synchronization settings" msgstr "Synchronization settings" +msgid "OAuth2 Clients" +msgstr "OAuth2 Clients" + msgid "Scheduled jobs" msgstr "Scheduled jobs" @@ -155,9 +236,6 @@ msgstr "This field is required" msgid "This field should be a URL" msgstr "This field should be a URL" -msgid "This field should contain a list of URLs" -msgstr "This field should contain a list of URLs" - msgid "This field should be a number" msgstr "This field should be a number" @@ -554,9 +632,6 @@ msgstr "Database language" msgid "Property to display in analysis modules" msgstr "Property to display in analysis modules" -msgid "Name" -msgstr "Name" - msgid "Short name" msgstr "Short name" @@ -599,9 +674,6 @@ msgstr "Port" msgid "Username" msgstr "Username" -msgid "Password" -msgstr "Password" - msgid "TLS" msgstr "TLS" @@ -665,9 +737,6 @@ msgstr "Minimum characters in password" msgid "CORS whitelist" msgstr "CORS whitelist" -msgid "One URL per line" -msgstr "One URL per line" - msgid "reCAPTCHA Site Key" msgstr "reCAPTCHA Site Key" @@ -713,9 +782,6 @@ msgstr "" "system unusable. If you have entered data, it is strongly recommended that " "you do not change your calendar setting." -msgid "Cancel" -msgstr "Cancel" - msgid "Yes, change calendar" msgstr "Yes, change calendar" diff --git a/src/oauth2-client-editor/ClientForm.js b/src/oauth2-client-editor/ClientForm.js new file mode 100644 index 00000000..99791af1 --- /dev/null +++ b/src/oauth2-client-editor/ClientForm.js @@ -0,0 +1,162 @@ +import i18n from '@dhis2/d2-i18n' +import { Button, Modal, ModalTitle, ModalContent } from '@dhis2/ui' +import { getInstance as getD2 } from 'd2' +import FormBuilder from 'd2-ui/lib/forms/FormBuilder.component.js' +import { isUrlArray, isRequired } from 'd2-ui/lib/forms/Validators.js' +import PropTypes from 'prop-types' +import React from 'react' +import MultiToggle from '../form-fields/multi-toggle.js' +import TextField from '../form-fields/text-field.js' +import styles from './ClientForm.module.css' + +const formFieldStyle = { + width: '100%', +} + +const validateClientID = async (v) => { + const d2 = await getD2() + const list = await d2.models.oAuth2Clients.list({ + paging: false, + filter: [`cid:eq:${v}`], + }) + if (list.size > 0) { + throw i18n.t('This client ID is already taken') + } +} + +const ClientForm = ({ clientModel, onUpdate, onSave, onCancel }) => { + const grantTypes = ((clientModel && clientModel.grantTypes) || []).reduce( + (curr, prev) => { + curr[prev] = true + return curr + }, + {} + ) + + const fields = [ + { + name: 'name', + value: clientModel.name, + component: TextField, + props: { + floatingLabelText: i18n.t('Name'), + style: formFieldStyle, + changeEvent: 'onBlur', + }, + validators: [ + { + validator: isRequired, + message: i18n.t('Required'), + }, + ], + }, + { + name: 'cid', + value: clientModel.cid, + component: TextField, + props: { + floatingLabelText: i18n.t('Client ID'), + style: formFieldStyle, + changeEvent: 'onBlur', + }, + validators: [ + { + validator: isRequired, + message: i18n.t('Required'), + }, + { + validator: (v) => v.toString().trim().length > 0, + message: i18n.t('Required'), + }, + ], + asyncValidators: [validateClientID], + }, + { + name: 'secret', + value: clientModel && clientModel.secret, + component: TextField, + props: { + floatingLabelText: i18n.t('Client Secret'), + disabled: true, + style: formFieldStyle, + }, + }, + { + name: 'grantTypes', + component: MultiToggle, + style: formFieldStyle, + props: { + label: i18n.t('Grant Types'), + items: [ + { + name: 'password', + text: i18n.t('Password'), + value: grantTypes.password, + }, + { + name: 'refresh_token', + text: i18n.t('Refresh token'), + value: grantTypes.refresh_token, + }, + { + name: 'authorization_code', + text: i18n.t('Authorization code'), + value: grantTypes.authorization_code, + }, + ], + }, + }, + { + name: 'redirectUris', + value: (clientModel.redirectUris || []).join('\n'), + component: TextField, + props: { + hintText: i18n.t('One URL per line'), + floatingLabelText: i18n.t('Redirect URIs'), + multiLine: true, + style: formFieldStyle, + changeEvent: 'onBlur', + }, + validators: [ + { + validator: isUrlArray, + message: i18n.t('This field should contain a list of URLs'), + }, + ], + }, + ] + + const headerText = + clientModel.id === undefined + ? i18n.t('Create new OAuth2 Client') + : i18n.t('Edit OAuth2 Client') + return ( + + {headerText} + + +
+ + +
+
+
+ ) +} + +ClientForm.propTypes = { + clientModel: PropTypes.object.isRequired, + onCancel: PropTypes.func.isRequired, + onSave: PropTypes.func.isRequired, + onUpdate: PropTypes.func.isRequired, +} + +export default ClientForm diff --git a/src/oauth2-client-editor/ClientForm.module.css b/src/oauth2-client-editor/ClientForm.module.css new file mode 100644 index 00000000..8a4219fb --- /dev/null +++ b/src/oauth2-client-editor/ClientForm.module.css @@ -0,0 +1,3 @@ +.cancelBtn { + float: right; +} diff --git a/src/oauth2-client-editor/ClientsList.js b/src/oauth2-client-editor/ClientsList.js new file mode 100644 index 00000000..b76a3148 --- /dev/null +++ b/src/oauth2-client-editor/ClientsList.js @@ -0,0 +1,78 @@ +import i18n from '@dhis2/d2-i18n' +import { + CenteredContent, + Table, + TableBody, + TableCell, + TableCellHead, + TableHead, + TableRow, + TableRowHead, + Button, +} from '@dhis2/ui' +import PropTypes from 'prop-types' +import React from 'react' +import styles from './ClientsList.module.css' + +const ClientsList = ({ clients, onClientEdit, onClientDelete }) => { + if (clients.length === 0) { + return ( + +

+ {i18n.t('There are currently no OAuth2 clients registered')} +

+
+ ) + } + + return ( + + + + {i18n.t('Name')} + {i18n.t('Password')} + {i18n.t('Refresh token')} + + {i18n.t('Authorization code')} + + {/* Buttons column */} + + + + {clients.map((client) => ( + + {client.name} + {client.password} + {client.refresh_token} + {client.authorization_code} + + + + + + ))} + +
+ ) +} + +ClientsList.propTypes = { + clients: PropTypes.array.isRequired, + onClientDelete: PropTypes.func.isRequired, + onClientEdit: PropTypes.func.isRequired, +} + +export default ClientsList diff --git a/src/oauth2-client-editor/ClientsList.module.css b/src/oauth2-client-editor/ClientsList.module.css new file mode 100644 index 00000000..2269cda4 --- /dev/null +++ b/src/oauth2-client-editor/ClientsList.module.css @@ -0,0 +1,3 @@ +.editBtn { + margin-right: var(--spacers-dp16); +} diff --git a/src/oauth2-client-editor/OAuth2ClientEditor.component.js b/src/oauth2-client-editor/OAuth2ClientEditor.component.js new file mode 100644 index 00000000..ce431a04 --- /dev/null +++ b/src/oauth2-client-editor/OAuth2ClientEditor.component.js @@ -0,0 +1,149 @@ +import i18n from '@dhis2/d2-i18n' +import { CircularLoader, CenteredContent, Button } from '@dhis2/ui' +import { getInstance as getD2 } from 'd2' +import React, { Component } from 'react' +import settingsActions from '../settingsActions.js' +import ClientForm from './ClientForm.js' +import ClientsList from './ClientsList.js' +import oa2Actions from './oauth2Client.actions.js' +import oa2ClientStore from './oauth2Client.store.js' +import styles from './OAuth2ClientEditor.module.css' + +function generateSecret() { + const alphabet = '0123456789abcdef' + let uid = '' + for (let i = 0; i < 32; i++) { + uid += alphabet.charAt(Math.random() * alphabet.length) + if (i === 8 || i === 12 || i === 16 || i === 20) { + uid += '-' + } + } + return uid +} + +class OAuth2ClientEditor extends Component { + state = { + showForm: false, + saving: false, + } + + componentDidMount() { + this.subscriptions = [] + this.subscriptions.push( + oa2ClientStore.subscribe(() => { + this.forceUpdate() + }) + ) + + this.subscriptions.push( + oa2Actions.delete.subscribe(() => { + this.setState({ saving: false }) + }) + ) + + oa2Actions.load() + } + + componentWillUnmount() { + this.subscriptions.forEach((sub) => { + sub.unsubscribe() + }) + } + + cancelAction = () => { + this.clientModel = undefined + oa2Actions.load() + this.setState({ showForm: false }) + } + + newAction = () => { + getD2().then((d2) => { + this.clientModel = d2.models.oAuth2Client.create() + this.clientModel.secret = generateSecret() + this.setState({ showForm: true }) + }) + } + + editAction = (model) => { + this.clientModel = model + this.setState({ showForm: true }) + } + + deleteAction = (model) => { + this.setState({ showForm: false, saving: true }) + oa2Actions.delete(model.id ? model : this.clientModel) + this.clientModel = undefined + } + + saveAction = () => { + this.clientModel.name = this.clientModel.name || '' + this.clientModel.cid = this.clientModel.cid || '' + this.setState({ saving: true }) + this.clientModel + .save() + .then((importReport) => { + if (importReport.status !== 'OK') { + throw new Error(importReport) + } + + settingsActions.showSnackbarMessage( + i18n.t('OAuth2 client saved') + ) + oa2Actions.load() + this.setState({ showForm: false, saving: false }) + }) + .catch(() => { + settingsActions.showSnackbarMessage( + i18n.t('Failed to save OAuth2 client') + ) + this.setState({ saving: false }) + }) + } + + formUpdateAction = (field, v) => { + let value = v + if (field === 'redirectUris') { + value = v.split('\n').filter((a) => a.trim().length > 0) + } + this.clientModel[field] = value + this.forceUpdate() + } + + render() { + const clients = oa2ClientStore.state + if (!clients || this.state.saving) { + return ( + + + + ) + } + + return ( +
+ + + {this.state.showForm && ( + + )} +
+ ) + } +} + +export default OAuth2ClientEditor diff --git a/src/oauth2-client-editor/OAuth2ClientEditor.module.css b/src/oauth2-client-editor/OAuth2ClientEditor.module.css new file mode 100644 index 00000000..20d8fce6 --- /dev/null +++ b/src/oauth2-client-editor/OAuth2ClientEditor.module.css @@ -0,0 +1,7 @@ +.wrapper { + margin: var(--spacers-dp24) 0; +} + +.addClientBtn { + margin-top: var(--spacers-dp24); +} diff --git a/src/oauth2-client-editor/oauth2Client.actions.js b/src/oauth2-client-editor/oauth2Client.actions.js new file mode 100644 index 00000000..b7f28471 --- /dev/null +++ b/src/oauth2-client-editor/oauth2Client.actions.js @@ -0,0 +1,56 @@ +import i18n from '@dhis2/d2-i18n' +import { getInstance as getD2 } from 'd2' +import Action from 'd2-ui/lib/action/Action.js' +import settingsActions from '../settingsActions.js' +import oa2Store from './oauth2Client.store.js' + +const oa2Actions = Action.createActionsFromNames(['load', 'delete']) + +oa2Actions.load.subscribe(() => { + getD2().then((d2) => { + d2.models.oAuth2Client + .list({ paging: false, fields: ':all', order: 'displayName' }) + .then((oa2ClientCollection) => { + const yes = i18n.t('Yes') + const no = i18n.t('No') + // Map grant types to object props in order to display them in the data table + oa2Store.setState( + oa2ClientCollection.toArray().map((oa2c) => + Object.assign(oa2c, { + password: + oa2c.grantTypes.indexOf('password') !== -1 + ? yes + : no, + refresh_token: + oa2c.grantTypes.indexOf('refresh_token') !== -1 + ? yes + : no, + authorization_code: + oa2c.grantTypes.indexOf( + 'authorization_code' + ) !== -1 + ? yes + : no, + }) + ) + ) + }) + }) +}) + +oa2Actions.delete.subscribe((e) => { + e.data + .delete() + .then(() => { + oa2Actions.load() + settingsActions.showSnackbarMessage(i18n.t('OAuth2 client deleted')) + }) + .catch((err) => { + console.error('Error when deleting OAuth2 client:', err) + settingsActions.showSnackbarMessage( + i18n.t('Failed to delete OAuth2 client') + ) + }) +}) + +export default oa2Actions diff --git a/src/oauth2-client-editor/oauth2Client.store.js b/src/oauth2-client-editor/oauth2Client.store.js new file mode 100644 index 00000000..140efaf4 --- /dev/null +++ b/src/oauth2-client-editor/oauth2Client.store.js @@ -0,0 +1,3 @@ +import Store from 'd2-ui/lib/store/Store.js' + +export default Store.create() diff --git a/src/settingsCategories.js b/src/settingsCategories.js index 39047eb7..ddf48c36 100644 --- a/src/settingsCategories.js +++ b/src/settingsCategories.js @@ -11,6 +11,7 @@ export const categoryOrder = [ 'import', 'sync', 'scheduledJobs', + 'oauth2', ] export const categories = { @@ -172,4 +173,11 @@ export const categories = { 'jobsLogDebugBelowSeconds', ], }, + oauth2: { + label: i18n.t('OAuth2 Clients'), + icon: 'vpn_lock', + pageLabel: i18n.t('OAuth2 Clients'), + authority: 'F_OAUTH2_CLIENT_MANAGE', + settings: ['oauth2clients'], + }, } diff --git a/src/settingsFields.component.js b/src/settingsFields.component.js index c33057ea..a5a1629f 100644 --- a/src/settingsFields.component.js +++ b/src/settingsFields.component.js @@ -18,6 +18,7 @@ import FileUpload from './form-fields/file-upload.js' import TextField from './form-fields/text-field.js' import LocalizedAppearance from './localized-text/LocalizedAppearanceEditor.component.js' import metadataSettings from './metadata-settings/metadataSettings.component.js' +import Oauth2ClientEditor from './oauth2-client-editor/OAuth2ClientEditor.component.js' import settingsActions from './settingsActions.js' import { categories } from './settingsCategories.js' import classes from './SettingsFields.module.css' @@ -243,6 +244,11 @@ class SettingsFields extends React.Component { }, }) + case 'oauth2clients': + return Object.assign({}, fieldBase, { + component: Oauth2ClientEditor, + }) + case 'localizedAppearance': return Object.assign({}, fieldBase, { component: LocalizedAppearance, diff --git a/src/settingsKeyMapping.js b/src/settingsKeyMapping.js index a8a139c0..6c29feb0 100644 --- a/src/settingsKeyMapping.js +++ b/src/settingsKeyMapping.js @@ -686,6 +686,18 @@ const settingsKeyMapping = { type: 'metadataSettings', }, /* ============================================================================================================ */ + /* Category: oAuth2 clients */ + /* ============================================================================================================ */ + oauth2clients: { + type: 'oauth2clients', + searchLabels: [ + 'oauth2_clients', + 'password', + 'refresh_token', + 'authorization_code', + ], + }, + /* ============================================================================================================ */ /* Category: Scheduled jobs */ /* ============================================================================================================ */ jobsRescheduleAfterMinutes: { From 943a4850d5b6f7923241c1f6aca168fa61b11248 Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Mon, 12 Feb 2024 14:03:13 +0000 Subject: [PATCH 6/7] chore(release): cut 29.16.2 [skip ci] ## [29.16.2](https://github.com/dhis2/settings-app/compare/v29.16.1...v29.16.2) (2024-02-12) ### Bug Fixes * add back OAUTH2 [DHIS2-15326] ([#1300](https://github.com/dhis2/settings-app/issues/1300)) ([520b1c7](https://github.com/dhis2/settings-app/commit/520b1c76fd8705eb311813c1bea0a9277aef8759)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 159a4f23..4ed8f906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [29.16.2](https://github.com/dhis2/settings-app/compare/v29.16.1...v29.16.2) (2024-02-12) + + +### Bug Fixes + +* add back OAUTH2 [DHIS2-15326] ([#1300](https://github.com/dhis2/settings-app/issues/1300)) ([520b1c7](https://github.com/dhis2/settings-app/commit/520b1c76fd8705eb311813c1bea0a9277aef8759)) + ## [29.16.1](https://github.com/dhis2/settings-app/compare/v29.16.0...v29.16.1) (2024-01-24) diff --git a/package.json b/package.json index 6bec935f..9e2bbd02 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "settings-app", - "version": "29.16.1", + "version": "29.16.2", "description": "", "license": "BSD-3-Clause", "private": true, From 854a9727b64d8d13187d49309c9b9db886d14a55 Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Wed, 14 Feb 2024 10:20:22 +0100 Subject: [PATCH 7/7] chore: set minDHIS2Version (#1301) --- d2.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/d2.config.js b/d2.config.js index 95fd5979..142b21ef 100644 --- a/d2.config.js +++ b/d2.config.js @@ -3,6 +3,7 @@ const config = { name: 'settings', title: 'Settings', coreApp: true, + minDHIS2Version: '2.41', entryPoints: { app: './src/App.js',