From 7abd8860f9130bf96db4d7523ac80c841d809c10 Mon Sep 17 00:00:00 2001 From: Radoslaw Szwajkowski Date: Fri, 2 Oct 2020 18:59:47 +0200 Subject: [PATCH] Add Account Settings Summary: 1. Remove existing Options dialog a) move SSH Key to Account Settings b) continue using existing REST endpoint: users/${id}/sshpublickeys 2. support following (local) settings via Redux-store: a) showNotifications (do not disturb) b) notificationSnoozeDuration (do not disturb for) 3. add typing for user settings related entities 4. in Settings use data-driven 3-way diff for change detection. Change is detected by comparing current content of Redux store with user changes(draft values) and base values(store content before at the beginning of user settings updates) --- packaging/ovirt-web-ui.war/WEB-INF/web.xml | 2 + src/actions/index.js | 17 +- src/actions/options.js | 107 +++++++- src/actions/types.js | 24 ++ src/components/OptionsDialog/actions.js | 35 --- src/components/OptionsDialog/constants.js | 4 - src/components/OptionsDialog/index.js | 122 --------- src/components/OptionsDialog/reducer.js | 15 -- src/components/OptionsDialog/sagas.js | 27 -- src/components/Pages/index.js | 6 + src/components/Settings/Settings.js | 140 ++++++++++ src/components/Settings/SettingsBase.js | 83 ++++++ src/components/Settings/SettingsToolbar.js | 53 ++++ src/components/Settings/index.js | 2 + src/components/Settings/style.css | 63 +++++ src/components/Toolbar/index.js | 3 + src/components/UserSettings/GlobalSettings.js | 255 ++++++++++++++++++ src/components/UserSettings/index.js | 1 + src/components/UserSettings/style.css | 3 + src/components/VmsPageHeader/UserMenu.js | 4 - src/components/VmsPageHeader/index.js | 12 + src/config.js | 1 + src/constants/index.js | 12 + src/constants/pages.js | 1 + src/intl/index.js | 7 +- src/intl/localeWithFullName.json | 13 + src/intl/messages.js | 33 ++- src/intl/translated-messages.json | 8 - src/ovirtapi/index.js | 38 ++- src/ovirtapi/types.js | 32 ++- src/reducers/config.js | 4 + src/reducers/index.js | 2 - src/reducers/options.js | 81 ++++-- src/routes.js | 37 ++- src/sagas/background-refresh.js | 5 + src/sagas/index.js | 4 +- src/sagas/login.js | 6 +- src/sagas/options.js | 118 ++++++++ 38 files changed, 1122 insertions(+), 258 deletions(-) create mode 100644 src/actions/types.js delete mode 100644 src/components/OptionsDialog/actions.js delete mode 100644 src/components/OptionsDialog/constants.js delete mode 100644 src/components/OptionsDialog/index.js delete mode 100644 src/components/OptionsDialog/reducer.js delete mode 100644 src/components/OptionsDialog/sagas.js create mode 100644 src/components/Settings/Settings.js create mode 100644 src/components/Settings/SettingsBase.js create mode 100644 src/components/Settings/SettingsToolbar.js create mode 100644 src/components/Settings/index.js create mode 100644 src/components/Settings/style.css create mode 100644 src/components/UserSettings/GlobalSettings.js create mode 100644 src/components/UserSettings/index.js create mode 100644 src/components/UserSettings/style.css create mode 100644 src/intl/localeWithFullName.json create mode 100644 src/sagas/options.js diff --git a/packaging/ovirt-web-ui.war/WEB-INF/web.xml b/packaging/ovirt-web-ui.war/WEB-INF/web.xml index cbdcdfe4a7..b2b0a32765 100644 --- a/packaging/ovirt-web-ui.war/WEB-INF/web.xml +++ b/packaging/ovirt-web-ui.war/WEB-INF/web.xml @@ -83,6 +83,7 @@ logout org.ovirt.engine.core.aaa.servlet.SsoLogoutServlet + logout /sso/logout @@ -124,6 +125,7 @@ index /vm/* /pool/* + /settings/* diff --git a/src/actions/index.js b/src/actions/index.js index dc51215272..5f921a54bc 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -1,10 +1,11 @@ -import AppConfiguration from '../config' +import AppConfiguration from '_/config' import { APP_CONFIGURED, CHANGE_PAGE, CHECK_TOKEN_EXPIRED, GET_BY_PAGE, GET_OPTION, + GET_USER, GET_USER_GROUPS, MANUAL_REFRESH, SET_ADMINISTRATOR, @@ -13,6 +14,7 @@ import { SET_DEFAULT_TIMEZONE, SET_USB_AUTOSHARE, SET_USB_FILTER, + SET_USER, SET_USER_FILTER_PERMISSION, SET_USER_GROUPS, SET_USER_SESSION_TIMEOUT_INTERVAL, @@ -194,6 +196,19 @@ export function getUserGroups () { return { type: GET_USER_GROUPS } } +export function setUser ({ user }) { + return { + type: SET_USER, + payload: { + user, + }, + } +} + +export function getUser () { + return { type: GET_USER } +} + export function setCpuTopologyOptions ({ maxNumberOfSockets, maxNumberOfCores, diff --git a/src/actions/options.js b/src/actions/options.js index da4ae91ed3..cfa4cd553e 100644 --- a/src/actions/options.js +++ b/src/actions/options.js @@ -1,10 +1,24 @@ +// @flow + +import type { UserOptionsType, SshKeyType } from '_/ovirtapi/types' +import type { LoadUserOptionsActionType, SaveGlobalOptionsActionType } from '_/actions/types' + import { GET_CONSOLE_OPTIONS, SAVE_CONSOLE_OPTIONS, SET_CONSOLE_OPTIONS, + GET_SSH_KEY, + SAVE_GLOBAL_OPTIONS, + SAVE_SSH_KEY, + SET_SSH_KEY, + SET_OPTION, + LOAD_USER_OPTIONS, + LOAD_USER_OPTIONS_IN_PROGRESS, + LOAD_USER_OPTIONS_FINISHED, + PERSIST_OPTIONS, } from '_/constants' -export function setConsoleOptions ({ vmId, options }) { +export function setConsoleOptions ({ vmId, options }: Object): Object { return { type: SET_CONSOLE_OPTIONS, payload: { @@ -14,7 +28,7 @@ export function setConsoleOptions ({ vmId, options }) { } } -export function getConsoleOptions ({ vmId }) { +export function getConsoleOptions ({ vmId }: Object): Object { return { type: GET_CONSOLE_OPTIONS, payload: { @@ -23,7 +37,7 @@ export function getConsoleOptions ({ vmId }) { } } -export function saveConsoleOptions ({ vmId, options }) { +export function saveConsoleOptions ({ vmId, options }: Object): Object { return { type: SAVE_CONSOLE_OPTIONS, payload: { @@ -32,3 +46,90 @@ export function saveConsoleOptions ({ vmId, options }) { }, } } + +export function getSSHKey ({ userId }: Object): Object { + return { + type: GET_SSH_KEY, + payload: { + userId, + }, + } +} + +export function setSSHKey ({ key, id }: SshKeyType): Object { + return { + type: SET_SSH_KEY, + payload: { + key, + id, + }, + } +} + +export function setOption ({ key, value }: Object): Object { + return { + type: SET_OPTION, + payload: { + key, + value, + }, + } +} + +export function loadUserOptions (userOptions: UserOptionsType): LoadUserOptionsActionType { + return { + type: LOAD_USER_OPTIONS, + payload: { + userOptions, + }, + } +} + +export function loadingUserOptionsInProgress (): Object { + return { + type: LOAD_USER_OPTIONS_IN_PROGRESS, + } +} + +export function loadingUserOptionsFinished (): Object { + return { + type: LOAD_USER_OPTIONS_FINISHED, + } +} + +export function saveGlobalOptions ({ values: { sshKey, language, showNotifications, notificationSnoozeDuration, updateRate } = {} }: Object, { transactionId }: Object): SaveGlobalOptionsActionType { + return { + type: SAVE_GLOBAL_OPTIONS, + payload: { + sshKey, + language, + showNotifications, + notificationSnoozeDuration, + updateRate, + }, + meta: { + transactionId, + }, + } +} + +export function saveSSHKey ({ key, userId, sshId }: Object): Object { + return { + type: SAVE_SSH_KEY, + payload: { + key, + userId, + sshId, + }, + } +} + +export function persistUserOptions ({ options, userId }: Object): Object { + return { + type: PERSIST_OPTIONS, + payload: { + options, + userId, + }, + } +} diff --git a/src/actions/types.js b/src/actions/types.js new file mode 100644 index 0000000000..9b863d3e29 --- /dev/null +++ b/src/actions/types.js @@ -0,0 +1,24 @@ +// @flow +import * as C from '_/constants' +import type { UserOptionsType } from '_/ovirtapi/types' + +export type LoadUserOptionsActionType = { + type: C.LOAD_USER_OPTIONS, + payload: { + userOptions: UserOptionsType + } +} + +export type SaveGlobalOptionsActionType = { + type: C.SAVE_GLOBAL_OPTIONS, + payload: {| + updateRate?: number, + language?: string, + showNotifications?: boolean, + notificationSnoozeDuration?: number, + sshKey?: string + |}, + meta: {| + transactionId: string + |} +} diff --git a/src/components/OptionsDialog/actions.js b/src/components/OptionsDialog/actions.js deleted file mode 100644 index 318852942e..0000000000 --- a/src/components/OptionsDialog/actions.js +++ /dev/null @@ -1,35 +0,0 @@ -import { SET_SSH_KEY, GET_SSH_KEY, SAVE_SSH_KEY, SET_UNLOADED } from './constants' - -export function setSSHKey ({ key, id }) { - return { - type: SET_SSH_KEY, - payload: { - key, - id, - }, - } -} - -export function saveSSHKey ({ key, userId, sshId }) { - return { - type: SAVE_SSH_KEY, - payload: { - key, - userId, - sshId, - }, - } -} - -export function getSSHKey ({ userId }) { - return { - type: GET_SSH_KEY, - payload: { - userId, - }, - } -} - -export function setUnloaded () { - return { type: SET_UNLOADED } -} diff --git a/src/components/OptionsDialog/constants.js b/src/components/OptionsDialog/constants.js deleted file mode 100644 index c7b52ef247..0000000000 --- a/src/components/OptionsDialog/constants.js +++ /dev/null @@ -1,4 +0,0 @@ -export const GET_SSH_KEY = 'OPTIONS_DIALOG_HEADER_GET_SSH_KEY' -export const SAVE_SSH_KEY = 'OPTIONS_DIALOG_HEADER_SAVE_SSH_KEY' -export const SET_SSH_KEY = 'OPTIONS_DIALOG_HEADER_SET_SSH_KEY' -export const SET_UNLOADED = 'OPTIONS_DIALOG_HEADER_SET_UNLOADED' diff --git a/src/components/OptionsDialog/index.js b/src/components/OptionsDialog/index.js deleted file mode 100644 index beef439759..0000000000 --- a/src/components/OptionsDialog/index.js +++ /dev/null @@ -1,122 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' - -import { Modal } from 'patternfly-react' - -import FieldHelp from '../FieldHelp/index' - -import { - getSSHKey, - saveSSHKey, -} from './actions' - -import { msg } from '_/intl' - -class OptionsDialog extends React.Component { - constructor (props) { - super(props) - this.state = { - sshKey: props.optionsDialog.get('sshKey') || '', - openModal: false, - } - this.onSSHKeyChange = this.onSSHKeyChange.bind(this) - this.onSaveClick = this.onSaveClick.bind(this) - this.handleModalOpen = this.handleModalOpen.bind(this) - } - - handleModalOpen () { - if (this.props.userId) { - this.props.getSSH() - } - this.setState({ 'sshKey': 'Loading...', openModal: true }) - } - - componentWillReceiveProps (nextProps) { - if (nextProps.optionsDialog.get('loaded')) { - this.setState({ 'sshKey': nextProps.optionsDialog.get('sshKey') }) - } - } - - onSSHKeyChange (event) { - this.setState({ sshKey: event.target.value }) - } - - onSaveClick () { - this.props.onSave({ key: this.state.sshKey, sshId: this.props.optionsDialog.get('sshId') }) - this.setState({ openModal: false }) - } - - render () { - const { oVirtApiVersion } = this.props - const idPrefix = `optionsdialog` - - let content = ( -
-
- -