From 99844ec47009dea9141c75ccba77f22cb93b87a8 Mon Sep 17 00:00:00 2001 From: Radoslaw Szwajkowski Date: Fri, 2 Oct 2020 18:22:43 +0200 Subject: [PATCH 1/3] Extend CounterAlert and SelectBox components CounterAlert: 1. onDismiss prop - callback fired when alert is closed 2. children prop - rendered below title --- src/components/CounterAlert.js | 11 ++++++++--- src/components/SelectBox.js | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/CounterAlert.js b/src/components/CounterAlert.js index 31a830e13..b589084de 100644 --- a/src/components/CounterAlert.js +++ b/src/components/CounterAlert.js @@ -39,30 +39,35 @@ class CounterAlert extends React.Component { this.timer = null } this.setState({ showAlert: false }) + this.props.onDismiss() } render () { - const { title, type } = this.props + const { title, type, children } = this.props return this.state.showAlert && {title} + {children} } } CounterAlert.propTypes = { - title: PropTypes.string.isRequired, - + title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired, + children: PropTypes.node, /** * A timeout of <= 0 will force a manual dismiss of the alert. */ timeout: PropTypes.number, type: PropTypes.string, + + onDismiss: PropTypes.func, } CounterAlert.defaultProps = { type: 'success', + onDismiss: () => {}, } export default CounterAlert diff --git a/src/components/SelectBox.js b/src/components/SelectBox.js index 41c343e57..44f92f2cd 100644 --- a/src/components/SelectBox.js +++ b/src/components/SelectBox.js @@ -96,9 +96,9 @@ class SelectBox extends React.Component { SelectBox.propTypes = { /* eslint-disable react/no-unused-prop-types */ - selected: PropTypes.string, // id of a selected item, false-ish for the first item + selected: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), // id of a selected item, false-ish for the first item items: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.string, + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), value: PropTypes.string, })).isRequired, // Array<{ id: string, value: string }>, order matters if sort is false-ish sort: PropTypes.bool, // sorted alphabetically by current locale with { numeric: true } if true From 2d49d1c245dd4fba67db5fd62e99a2d0f3e11799 Mon Sep 17 00:00:00 2001 From: Radoslaw Szwajkowski Date: Fri, 2 Oct 2020 18:59:47 +0200 Subject: [PATCH 2/3] 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 | 251 ++++++++++++++++++ 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, 1118 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 cbdcdfe4a..b2b0a3276 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 dc5121527..5f921a54b 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 da4ae91ed..cfa4cd553 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 000000000..9b863d3e2 --- /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 318852942..000000000 --- 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 c7b52ef24..000000000 --- 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 beef43975..000000000 --- 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 = ( -
-
- -