diff --git a/src/fireedge/src/client/components/Forms/FormWithSchema.js b/src/fireedge/src/client/components/Forms/FormWithSchema.js
index b797284cb6..3d14ea6818 100644
--- a/src/fireedge/src/client/components/Forms/FormWithSchema.js
+++ b/src/fireedge/src/client/components/Forms/FormWithSchema.js
@@ -57,7 +57,7 @@ const FormWithSchema = ({
const formContext = useFormContext()
const { control, watch } = formContext
- const { sx: sxRoot, restOfRootProps } = rootProps ?? {}
+ const { sx: sxRoot, ...restOfRootProps } = rootProps ?? {}
const getFields = useMemo(
() => (typeof fields === 'function' ? fields() : fields),
diff --git a/src/fireedge/src/client/components/Forms/Legend.js b/src/fireedge/src/client/components/Forms/Legend.js
index 51e300ce8c..bf1d5bdbf0 100644
--- a/src/fireedge/src/client/components/Forms/Legend.js
+++ b/src/fireedge/src/client/components/Forms/Legend.js
@@ -18,7 +18,7 @@ import PropTypes from 'prop-types'
import { styled, Typography } from '@mui/material'
import AdornmentWithTooltip from 'client/components/FormControl/Tooltip'
-import { Tr, labelCanBeTranslated } from 'client/components/HOC'
+import { Translate, labelCanBeTranslated } from 'client/components/HOC'
const StyledLegend = styled((props) => (
@@ -35,7 +35,7 @@ const StyledLegend = styled((props) => (
const Legend = memo(
({ title, tooltip }) => (
- {labelCanBeTranslated(title) ? Tr(title) : title}
+ {labelCanBeTranslated(title) ? : title}
{!!tooltip && }
),
diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js
index 9df54d1de7..b10e45cbc9 100644
--- a/src/fireedge/src/client/constants/translates.js
+++ b/src/fireedge/src/client/constants/translates.js
@@ -86,6 +86,7 @@ module.exports = {
Pin: 'Pin',
Poweroff: 'Poweroff',
PoweroffHard: 'Poweroff hard',
+ PressEscapeToCancel: 'Press Escape to cancel',
Reboot: 'Reboot',
RebootHard: 'Reboot hard',
Recover: 'Recover',
@@ -269,6 +270,9 @@ module.exports = {
Language: 'Language',
DisableDashboardAnimations: 'Disable dashboard animations',
ConfigurationUI: 'Configuration UI',
+ Authentication: 'Authentication',
+ SshPrivateKey: 'SSH private key',
+ SshPassphraseKey: 'SSH private key passphrase',
/* sections - system */
User: 'User',
diff --git a/src/fireedge/src/client/containers/Settings/Authentication/index.js b/src/fireedge/src/client/containers/Settings/Authentication/index.js
new file mode 100644
index 0000000000..681a62ea27
--- /dev/null
+++ b/src/fireedge/src/client/containers/Settings/Authentication/index.js
@@ -0,0 +1,157 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may *
+ * not use this file except in compliance with the License. You may obtain *
+ * a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ * ------------------------------------------------------------------------- */
+import { ReactElement, useMemo, memo, useState } from 'react'
+import PropTypes from 'prop-types'
+import {
+ Paper,
+ Stack,
+ IconButton,
+ Typography,
+ Skeleton,
+ TextField,
+} from '@mui/material'
+import { Edit } from 'iconoir-react'
+import { useForm } from 'react-hook-form'
+import { yupResolver } from '@hookform/resolvers/yup'
+
+import { useAuth } from 'client/features/Auth'
+import { useUpdateUserMutation } from 'client/features/OneApi/user'
+import { useGeneralApi } from 'client/features/General'
+
+import {
+ FIELDS,
+ SCHEMA,
+} from 'client/containers/Settings/Authentication/schema'
+import { Translate } from 'client/components/HOC'
+import { Legend } from 'client/components/Forms'
+import { jsonToXml } from 'client/models/Helper'
+import { sanitize } from 'client/utils'
+import { T } from 'client/constants'
+
+/** @returns {ReactElement} Settings authentication */
+const Settings = () => (
+
+
+ {FIELDS.map((field) => (
+
+ ))}
+
+
+)
+
+const FieldComponent = memo(({ field }) => {
+ const [isEnabled, setIsEnabled] = useState(false)
+ const { name, label, tooltip } = field
+
+ const { user, settings } = useAuth()
+ const [updateUser, { isLoading }] = useUpdateUserMutation()
+ const { enqueueError } = useGeneralApi()
+
+ const defaultValues = useMemo(() => SCHEMA.cast(settings), [settings])
+
+ const { watch, register, reset } = useForm({
+ reValidateMode: 'onSubmit',
+ defaultValues,
+ resolver: yupResolver(SCHEMA),
+ })
+
+ const sanitizedValue = useMemo(() => sanitize`${watch(name)}`, [isEnabled])
+
+ const handleUpdateUser = async () => {
+ try {
+ if (isLoading || !isEnabled) return
+
+ const castedData = SCHEMA.cast(watch(), { isSubmit: true })
+ const template = jsonToXml(castedData)
+
+ updateUser({ id: user.ID, template })
+ setIsEnabled(false)
+ } catch {
+ enqueueError(T.SomethingWrong)
+ }
+ }
+
+ const handleBlur = () => {
+ handleUpdateUser()
+ }
+
+ const handleKeyDown = (evt) => {
+ if (evt.key === 'Escape') {
+ reset(defaultValues)
+ setIsEnabled(false)
+ evt.stopPropagation()
+ }
+
+ if (evt.key === 'Enter') {
+ handleUpdateUser()
+ }
+ }
+
+ return (
+
+
+ {isEnabled ? (
+ <>
+ }
+ {...register(field.name, { onBlur: handleBlur })}
+ />
+ >
+ ) : (
+
+ {isLoading ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+
+ {sanitizedValue}
+
+ setIsEnabled(true)}>
+
+
+ >
+ )}
+
+ )}
+
+ )
+})
+
+FieldComponent.propTypes = {
+ field: PropTypes.object.isRequired,
+}
+
+FieldComponent.displayName = 'FieldComponent'
+
+export default Settings
diff --git a/src/fireedge/src/client/containers/Settings/Authentication/schema.js b/src/fireedge/src/client/containers/Settings/Authentication/schema.js
new file mode 100644
index 0000000000..defae5823e
--- /dev/null
+++ b/src/fireedge/src/client/containers/Settings/Authentication/schema.js
@@ -0,0 +1,53 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may *
+ * not use this file except in compliance with the License. You may obtain *
+ * a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ * ------------------------------------------------------------------------- */
+import { object, string } from 'yup'
+import { getValidationFromFields } from 'client/utils'
+import { T, INPUT_TYPES } from 'client/constants'
+
+const PUBLIC_KEY_FIELD = {
+ name: 'SSH_PUBLIC_KEY',
+ label: T.SshPublicKey,
+ tooltip: T.AddUserSshPublicKey,
+ type: INPUT_TYPES.TEXT,
+ validation: string()
+ .trim()
+ .required()
+ .default(() => undefined),
+}
+
+const PRIVATE_KEY_FIELD = {
+ name: 'SSH_PRIVATE_KEY',
+ label: T.SshPrivateKey,
+ type: INPUT_TYPES.TEXT,
+ validation: string()
+ .trim()
+ .required()
+ .default(() => undefined),
+}
+
+const PASSPHRASE_FIELD = {
+ name: 'SSH_PASSPHRASE',
+ label: T.SshPassphraseKey,
+ type: INPUT_TYPES.TEXT,
+ validation: string()
+ .trim()
+ .required()
+ .default(() => undefined),
+}
+
+export const FIELDS = [PUBLIC_KEY_FIELD, PRIVATE_KEY_FIELD, PASSPHRASE_FIELD]
+
+export const SCHEMA = object(getValidationFromFields(FIELDS))
diff --git a/src/fireedge/src/client/containers/Settings/ConfigurationUI/index.js b/src/fireedge/src/client/containers/Settings/ConfigurationUI/index.js
new file mode 100644
index 0000000000..bbf68ad185
--- /dev/null
+++ b/src/fireedge/src/client/containers/Settings/ConfigurationUI/index.js
@@ -0,0 +1,88 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
+ * *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may *
+ * not use this file except in compliance with the License. You may obtain *
+ * a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
+ * See the License for the specific language governing permissions and *
+ * limitations under the License. *
+ * ------------------------------------------------------------------------- */
+import { ReactElement, useEffect, useMemo, useRef } from 'react'
+import { Paper, Stack, CircularProgress } from '@mui/material'
+import { useForm, FormProvider } from 'react-hook-form'
+import { yupResolver } from '@hookform/resolvers/yup'
+
+import { useAuth } from 'client/features/Auth'
+import { useUpdateUserMutation } from 'client/features/OneApi/user'
+import { useGeneralApi } from 'client/features/General'
+
+import {
+ FIELDS,
+ SCHEMA,
+} from 'client/containers/Settings/ConfigurationUI/schema'
+import { FormWithSchema } from 'client/components/Forms'
+import { Translate } from 'client/components/HOC'
+import { jsonToXml } from 'client/models/Helper'
+import { T } from 'client/constants'
+
+/** @returns {ReactElement} Settings configuration UI */
+const Settings = () => {
+ const fieldsetRef = useRef([])
+ const { user, settings } = useAuth()
+ const [updateUser, { isLoading }] = useUpdateUserMutation()
+ const { enqueueError } = useGeneralApi()
+
+ const { watch, ...methods } = useForm({
+ reValidateMode: 'onSubmit',
+ defaultValues: useMemo(() => SCHEMA.cast(settings), [settings]),
+ resolver: yupResolver(SCHEMA),
+ })
+
+ useEffect(() => {
+ watch((formData) => {
+ try {
+ if (isLoading) return
+
+ const castedData = SCHEMA.cast(formData, { isSubmit: true })
+ const template = jsonToXml(castedData)
+
+ updateUser({ id: user.ID, template })
+ } catch {
+ enqueueError(T.SomethingWrong)
+ }
+ })
+ }, [watch])
+
+ useEffect(() => {
+ fieldsetRef.current.disabled = isLoading
+ }, [isLoading])
+
+ return (
+
+
+
+
+ {isLoading && }
+
+ }
+ />
+
+
+ )
+}
+
+export default Settings
diff --git a/src/fireedge/src/client/containers/Settings/schema.js b/src/fireedge/src/client/containers/Settings/ConfigurationUI/schema.js
similarity index 86%
rename from src/fireedge/src/client/containers/Settings/schema.js
rename to src/fireedge/src/client/containers/Settings/ConfigurationUI/schema.js
index 76fb85f03f..7e60a0aed9 100644
--- a/src/fireedge/src/client/containers/Settings/schema.js
+++ b/src/fireedge/src/client/containers/Settings/ConfigurationUI/schema.js
@@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { object, boolean, string } from 'yup'
-import { arrayToOptions, getValidationFromFields } from 'client/utils'
+import { boolean, string } from 'yup'
+import { arrayToOptions, getObjectSchemaFromFields } from 'client/utils'
import {
T,
INPUT_TYPES,
@@ -25,7 +25,7 @@ import {
} from 'client/constants'
const SCHEME_FIELD = {
- name: 'SCHEME',
+ name: 'FIREEDGE.SCHEME',
label: T.Schema,
type: INPUT_TYPES.SELECT,
values: [
@@ -41,7 +41,7 @@ const SCHEME_FIELD = {
}
const LANG_FIELD = {
- name: 'LANG',
+ name: 'FIREEDGE.LANG',
label: T.Language,
type: INPUT_TYPES.SELECT,
values: () =>
@@ -58,7 +58,7 @@ const LANG_FIELD = {
}
const DISABLE_ANIMATIONS_FIELD = {
- name: 'DISABLE_ANIMATIONS',
+ name: 'FIREEDGE.DISABLE_ANIMATIONS',
label: T.DisableDashboardAnimations,
type: INPUT_TYPES.CHECKBOX,
validation: boolean()
@@ -67,6 +67,6 @@ const DISABLE_ANIMATIONS_FIELD = {
grid: { md: 12 },
}
-export const FORM_FIELDS = [SCHEME_FIELD, LANG_FIELD, DISABLE_ANIMATIONS_FIELD]
+export const FIELDS = [SCHEME_FIELD, LANG_FIELD, DISABLE_ANIMATIONS_FIELD]
-export const FORM_SCHEMA = object(getValidationFromFields(FORM_FIELDS))
+export const SCHEMA = getObjectSchemaFromFields(FIELDS)
diff --git a/src/fireedge/src/client/containers/Settings/index.js b/src/fireedge/src/client/containers/Settings/index.js
index 7e033f2a2b..0663030d6b 100644
--- a/src/fireedge/src/client/containers/Settings/index.js
+++ b/src/fireedge/src/client/containers/Settings/index.js
@@ -13,88 +13,29 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { ReactElement, useMemo } from 'react'
-import { Container, Paper, Box, Typography, Divider } from '@mui/material'
-import { useForm, FormProvider } from 'react-hook-form'
-import { yupResolver } from '@hookform/resolvers/yup'
+import { ReactElement } from 'react'
+import { Container, Typography, Divider, Stack } from '@mui/material'
-import FormWithSchema from 'client/components/Forms/FormWithSchema'
-import SubmitButton from 'client/components/FormControl/SubmitButton'
-
-import { useAuth } from 'client/features/Auth'
-import { useLazyGetAuthUserQuery } from 'client/features/AuthApi'
-import { useUpdateUserMutation } from 'client/features/OneApi/user'
-import { useGeneralApi } from 'client/features/General'
-import { Translate, Tr } from 'client/components/HOC'
+import { Translate } from 'client/components/HOC'
import { T } from 'client/constants'
-import { FORM_FIELDS, FORM_SCHEMA } from 'client/containers/Settings/schema'
-import * as Helper from 'client/models/Helper'
+import ConfigurationUISection from 'client/containers/Settings/ConfigurationUI'
+import AuthenticationSection from 'client/containers/Settings/Authentication'
/** @returns {ReactElement} Settings container */
-const Settings = () => {
- const { user, settings } = useAuth()
- const [getAuthUser] = useLazyGetAuthUserQuery()
- const [updateUser] = useUpdateUserMutation()
- const { enqueueError } = useGeneralApi()
+const Settings = () => (
+
+
+
+
- const { handleSubmit, reset, formState, ...methods } = useForm({
- reValidateMode: 'onSubmit',
- defaultValues: useMemo(() => FORM_SCHEMA.cast(settings), [settings]),
- resolver: yupResolver(FORM_SCHEMA),
- })
+
- const onSubmit = async (formData) => {
- try {
- const data = FORM_SCHEMA.cast(formData, { isSubmit: true })
- const template = Helper.jsonToXml({ FIREEDGE: data })
- await updateUser({ id: user.ID, template })
- await getAuthUser()
-
- // Reset either the entire form state or part of the form state
- reset(formData)
- } catch {
- enqueueError(T.SomethingWrong)
- }
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
+
+
+
+
+
+)
export default Settings
diff --git a/src/fireedge/src/client/features/Auth/hooks.js b/src/fireedge/src/client/features/Auth/hooks.js
index 082f642295..5037787c80 100644
--- a/src/fireedge/src/client/features/Auth/hooks.js
+++ b/src/fireedge/src/client/features/Auth/hooks.js
@@ -72,6 +72,7 @@ export const useAuth = () => {
SCHEME: DEFAULT_SCHEME,
LANG: DEFAULT_LANGUAGE,
DISABLE_ANIMATIONS: 'NO',
+ ...(user?.TEMPLATE ?? {}),
...(user?.TEMPLATE?.FIREEDGE ?? {}),
},
isLogged:
diff --git a/src/fireedge/src/client/features/OneApi/user.js b/src/fireedge/src/client/features/OneApi/user.js
index dcdcdb01e4..297c60932d 100644
--- a/src/fireedge/src/client/features/OneApi/user.js
+++ b/src/fireedge/src/client/features/OneApi/user.js
@@ -19,6 +19,7 @@ import {
ONE_RESOURCES,
ONE_RESOURCES_POOL,
} from 'client/features/OneApi'
+import { authApi } from 'client/features/AuthApi'
import { User } from 'client/constants'
const { USER } = ONE_RESOURCES
@@ -109,6 +110,19 @@ const userApi = oneApi.injectEndpoints({
return { params, command }
},
invalidatesTags: (_, __, { id }) => [{ type: USER, id }],
+ async onQueryStarted({ id }, { queryFulfilled, dispatch, getState }) {
+ try {
+ await queryFulfilled
+
+ if (+id === +getState().auth.user.ID) {
+ dispatch(
+ authApi.endpoints.getAuthUser.initiate(undefined, {
+ forceRefetch: true,
+ })
+ )
+ }
+ } catch {}
+ },
}),
removeUser: builder.mutation({
/**