1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-16 22:50:10 +03:00

F OpenNebula/one#5422: Add vm detail tab forms (#1422)

This commit is contained in:
Sergio Betanzos 2021-08-31 14:46:19 +02:00 committed by GitHub
parent 2d196caeca
commit ada6fb630b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
300 changed files with 4739 additions and 2447 deletions

View File

@ -1,4 +1,5 @@
{
"sourceType": "unambiguous",
"presets": [
[
"@babel/preset-env",
@ -12,7 +13,9 @@
}
}
],
"@babel/preset-react"
["@babel/preset-react", {
"runtime": "automatic"
}]
],
"plugins": [
["module-resolver", {

View File

@ -21,6 +21,8 @@
"import/no-extraneous-dependencies": ["error", {
"devDependencies": ["*.js"]
}],
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
"default-case": 0,
"jsdoc/require-jsdoc": ["error", {
"publicOnly": true,

View File

@ -130,6 +130,7 @@ info-tabs:
enabled: true
not_on:
- vcenter
- firecracker
network:
enabled: true

View File

@ -0,0 +1,14 @@
# this display button and clock icon in table of vm
:leases:
suspend:
time: "+1209600"
color: "#000000"
warning:
time: "-86400"
color: "#085aef"
terminate:
time: "+1209600"
color: "#e1ef08"
warning:
time: "-86400"
color: "#ef2808"

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@
"vms"
],
"devDependencies": {
"@pmmmwh/react-refresh-webpack-plugin": "0.5.0-rc.3",
"cross-env": "7.0.2",
"eslint": "7.11.0",
"eslint-config-prettier": "6.11.0",
@ -46,7 +47,7 @@
"eslint-plugin-standard": "4.0.1",
"opennebula-generatepotfile": "1.0.0",
"opennebula-potojson": "1.0.0",
"react-hot-loader": "4.13.0",
"react-refresh": "0.10.0",
"webpack-dev-middleware": "5.0.0",
"webpack-hot-middleware": "2.25.0"
},
@ -60,13 +61,13 @@
"@babel/plugin-proposal-optional-chaining": "7.12.13",
"@babel/preset-env": "7.12.13",
"@babel/preset-react": "7.12.13",
"@hookform/resolvers": "0.1.1",
"@hookform/resolvers": "1.3.7",
"@loadable/babel-plugin": "5.13.2",
"@loadable/component": "5.14.1",
"@loadable/server": "5.14.2",
"@loadable/webpack-plugin": "5.15.0",
"@material-ui/core": "4.11.0",
"@material-ui/lab": "4.0.0-alpha.58",
"@loadable/component": "5.15.0",
"@loadable/server": "5.15.1",
"@loadable/webpack-plugin": "5.15.1",
"@material-ui/core": "4.12.3",
"@material-ui/lab": "4.0.0-alpha.60",
"@reduxjs/toolkit": "1.5.1",
"ace-builds": "1.4.12",
"atob": "2.1.2",
@ -74,7 +75,6 @@
"babel-eslint": "10.1.0",
"babel-loader": "8.2.1",
"babel-plugin-module-resolver": "4.0.0",
"babel-preset-react-hmre": "1.1.1",
"btoa": "1.2.1",
"clsx": "1.1.1",
"compression": "1.7.4",
@ -101,20 +101,20 @@
"luxon": "1.25.0",
"marked": "2.0.0",
"morgan": "1.10.0",
"notistack": "1.0.9",
"notistack": "1.0.10",
"opennebula-guacamole": "1.0.0",
"path": "0.12.7",
"process": "0.11.10",
"prop-types": "15.7.2",
"qrcode": "1.4.4",
"react": "16.14.0",
"react-ace": "9.1.4",
"react-dom": "16.13.1",
"react-flow-renderer": "5.11.1",
"react-hook-form": "6.8.6",
"react": "17.0.2",
"react-ace": "9.2.1",
"react-dom": "17.0.2",
"react-flow-renderer": "9.6.0",
"react-hook-form": "6.12.0",
"react-json-pretty": "2.2.0",
"react-minimal-pie-chart": "8.2.0",
"react-redux": "7.2.1",
"react-redux": "7.2.4",
"react-router": "5.2.0",
"react-router-dom": "5.2.0",
"react-table": "7.7.0",

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { useEffect, useMemo, JSXElementConstructor } from 'react'
import Router from 'client/router'
import { ENDPOINTS, PATH } from 'client/apps/provision/routes'
@ -34,7 +34,7 @@ const APP_NAME = _APPS.provision.name
/**
* Provision App component.
*
* @returns {React.JSXElementConstructor} App rendered.
* @returns {JSXElementConstructor} App rendered.
*/
const ProvisionApp = () => {
const { isLogged, jwt, firstRender } = useAuth()
@ -44,7 +44,7 @@ const ProvisionApp = () => {
const { getProvisionsTemplates } = useProvisionApi()
const { changeTitle } = useGeneralApi()
React.useEffect(() => {
useEffect(() => {
(async () => {
try {
if (jwt) {
@ -58,7 +58,7 @@ const ProvisionApp = () => {
})()
}, [jwt])
const endpoints = React.useMemo(() => [
const endpoints = useMemo(() => [
...ENDPOINTS,
...(isDevelopment() ? DEV_ENDPOINTS : [])
], [])

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { JSXElementConstructor } from 'react'
import PropTypes from 'prop-types'
import { StaticRouter, BrowserRouter } from 'react-router-dom'
@ -36,9 +36,9 @@ const APP_NAME = _APPS.provision.name
* @param {Store} props.store - Redux store
* @param {string|object} props.location - The URL the server received
* @param {object} props.context - Context object contains the results of the render
* @returns {React.JSXElementConstructor} Provision App
* @returns {JSXElementConstructor} Provision App
*/
const Provision = ({ store, location, context }) => (
const Provision = ({ store = {}, location = '', context = {} }) => (
<ReduxProvider store={store}>
<SocketProvider>
<TranslateProvider>
@ -64,14 +64,8 @@ const Provision = ({ store, location, context }) => (
Provision.propTypes = {
location: PropTypes.string,
context: PropTypes.shape({}),
store: PropTypes.shape({})
}
Provision.defaultProps = {
location: '',
context: {},
store: {}
context: PropTypes.object,
store: PropTypes.object
}
Provision.displayName = 'ProvisionApp'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { useEffect, useMemo, JSXElementConstructor } from 'react'
import Router from 'client/router'
import { ENDPOINTS, PATH, getEndpointsByView } from 'client/apps/sunstone/routes'
@ -34,20 +34,21 @@ const APP_NAME = _APPS.sunstone.name
/**
* Sunstone App component.
*
* @returns {React.JSXElementConstructor} App rendered.
* @returns {JSXElementConstructor} App rendered.
*/
const SunstoneApp = () => {
const { isLogged, jwt, firstRender, view, views } = useAuth()
const { getAuthUser, logout, getSunstoneViews } = useAuthApi()
const { getAuthUser, logout, getSunstoneViews, getSunstoneConfig } = useAuthApi()
const { changeTitle } = useGeneralApi()
React.useEffect(() => {
useEffect(() => {
(async () => {
try {
if (jwt) {
changeTitle(APP_NAME)
getAuthUser()
await getSunstoneViews()
await getSunstoneConfig()
}
} catch {
logout()
@ -55,7 +56,7 @@ const SunstoneApp = () => {
})()
}, [jwt])
const endpoints = React.useMemo(() => [
const endpoints = useMemo(() => [
...ENDPOINTS,
...(view ? getEndpointsByView(views?.[view], ONE_ENDPOINTS) : []),
...(isDevelopment() ? DEV_ENDPOINTS : [])

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { JSXElementConstructor } from 'react'
import PropTypes from 'prop-types'
import { StaticRouter, BrowserRouter } from 'react-router-dom'
@ -35,9 +35,9 @@ const APP_NAME = _APPS.sunstone.name
* @param {Store} props.store - Redux store
* @param {string|object} props.location - The URL the server received
* @param {object} props.context - Context object contains the results of the render
* @returns {React.JSXElementConstructor} Sunstone App
* @returns {JSXElementConstructor} Sunstone App
*/
const Provision = ({ store, location, context }) => (
const Sunstone = ({ store = {}, location = '', context = {} }) => (
<ReduxProvider store={store}>
<TranslateProvider>
<MuiProvider theme={theme}>
@ -59,18 +59,12 @@ const Provision = ({ store, location, context }) => (
</ReduxProvider>
)
Provision.propTypes = {
Sunstone.propTypes = {
location: PropTypes.string,
context: PropTypes.shape({}),
store: PropTypes.shape({})
}
Provision.defaultProps = {
location: '',
context: {},
store: {}
}
Sunstone.displayName = 'SunstoneApp'
Provision.displayName = 'SunstoneApp'
export default Provision
export default Sunstone

View File

@ -14,7 +14,6 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import PropTypes from 'prop-types'
import { Box } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo, useEffect, useRef, useState } from 'react'
import { memo, useEffect, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Chip, Slide } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import {

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { Button, CardActions } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import {

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Badge, Box, CardContent } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Typography } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { Card, CardHeader, Fade, makeStyles } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Typography } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { NetworkAlt as NetworkIcon } from 'iconoir-react'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Card, CardContent, Button, CardActions } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo, useMemo } from 'react'
import { memo, useMemo } from 'react'
import PropTypes from 'prop-types'
import { Db as ProviderIcon, Cloud as ProvisionIcon } from 'iconoir-react'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo, useMemo } from 'react'
import { memo, useMemo } from 'react'
import PropTypes from 'prop-types'
import { Db as ProviderIcon, SettingsCloud as ProvisionIcon } from 'iconoir-react'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import useFetch from 'client/hooks/useFetch'
@ -22,7 +22,6 @@ import { SubmitButton } from 'client/components/FormControl'
const Action = memo(({
cy,
handleClick,
icon,
stopPropagation,
...props
}) => {
@ -33,9 +32,7 @@ const Action = memo(({
return (
<SubmitButton
data-cy={cy}
icon={!!icon}
isSubmitting={loading}
label={icon}
onClick={evt => {
stopPropagation && evt?.stopPropagation?.()
fetchRequest()
@ -49,7 +46,7 @@ const Action = memo(({
Action.propTypes = {
cy: PropTypes.string,
handleClick: PropTypes.func.isRequired,
icon: PropTypes.node.isRequired,
icon: PropTypes.node,
stopPropagation: PropTypes.bool
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { Button, CardActions, Badge } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { ViewGrid as VmIcon } from 'iconoir-react'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
@ -76,7 +76,7 @@ const useStyles = makeStyles(theme => {
}
})
const WavesCard = React.memo(({ text, value, bgColor, icon: Icon }) => {
const WavesCard = memo(({ text, value, bgColor, icon: Icon }) => {
const classes = useStyles({ bgColor })
return (

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { memo, useState, useEffect, JSXElementConstructor } from 'react'
import PropTypes from 'prop-types'
import { Box, CircularProgress, Typography } from '@material-ui/core'
@ -24,12 +24,12 @@ import NumberEasing from 'client/components/NumberEasing'
*
* @param {object} props - Props
* @param {string} props.color - Color of component: primary, secondary or inherit
* @returns {React.JSXElementConstructor} Circular progress bar component
* @returns {JSXElementConstructor} Circular progress bar component
*/
const Circle = React.memo(({ color = 'secondary' }) => {
const [progress, setProgress] = React.useState(0)
const Circle = memo(({ color = 'secondary' }) => {
const [progress, setProgress] = useState(0)
React.useEffect(() => {
useEffect(() => {
const timer = setInterval(() => {
setProgress(prevProgress => {
const nextProgress = prevProgress + 2
@ -65,9 +65,9 @@ Circle.displayName = 'Circle'
* @param {object} props - Props
* @param {string} props.label - Text in the middle
* @param {object} props.labelProps - Props of text
* @returns {React.JSXElementConstructor} Circular chart component
* @returns {JSXElementConstructor} Circular chart component
*/
const CircleChart = React.memo(({ label, labelProps }) => (
const CircleChart = memo(({ label, labelProps }) => (
<Box position='relative' display='inline-flex' width={1}>
<Box display='flex' flexDirection='column' alignItems='center' width={1}>
<Circle />

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { JSXElementConstructor } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Tooltip } from '@material-ui/core'
@ -48,9 +48,9 @@ const useStyles = makeStyles(theme => ({
* @param {{ name: string, color: string }[]} props.legend - Legend
* @param {number[]} props.data - Chart data
* @param {number} props.total - Total value of chart, equals to 100% of bar
* @returns {React.JSXElementConstructor} Chart bar component
* @returns {JSXElementConstructor} Chart bar component
*/
const SingleBar = ({ legend, data, total }) => {
const SingleBar = ({ legend, data, total = 0 }) => {
const fragments = data.map(data => Math.floor(data * 10 / (total || 1)))
const classes = useStyles({ fragments })
@ -101,12 +101,6 @@ SingleBar.propTypes = {
total: PropTypes.number
}
SingleBar.defaultProps = {
legend: undefined,
data: undefined,
total: 0
}
SingleBar.displayName = 'SingleBar'
export default SingleBar

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Paper, Divider } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { useEffect, useState, memo } from 'react'
import { useEffect, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo, useState } from 'react'
import { memo, useState } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'

View File

@ -14,7 +14,6 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import PropTypes from 'prop-types'
import Message from 'client/components/DebugLog/message'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import {
@ -71,8 +71,14 @@ const DialogConfirmation = memo(
>
<DialogTitle disableTypography className={classes.title}>
<div className={classes.titleText}>
<Typography variant='h6'>{Tr(title)}</Typography>
{subheader && <Typography variant='subtitle1'>{Tr(subheader)}</Typography>}
<Typography variant='h6'>
{typeof title === 'string' ? Tr(title) : title}
</Typography>
{subheader && (
<Typography variant='subtitle1'>
{typeof subheader === 'string' ? Tr(subheader) : subheader}
</Typography>
)}
</div>
{handleCancel && (
<IconButton
@ -85,16 +91,18 @@ const DialogConfirmation = memo(
</IconButton>
)}
</DialogTitle>
<DialogContent dividers {...contentProps}>
{children}
</DialogContent>
{children && (
<DialogContent dividers {...contentProps}>
{children}
</DialogContent>
)}
{handleAccept && (
<DialogActions>
<Action
aria-label='accept'
color='secondary'
data-cy='dg-accept-button'
handleClick={handleAccept}
icon={false}
label={T.Accept}
{...acceptButtonProps}
/>
@ -106,19 +114,19 @@ const DialogConfirmation = memo(
)
export const DialogPropTypes = {
open: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired,
subheader: PropTypes.string,
contentProps: PropTypes.objectOf(PropTypes.any),
handleAccept: PropTypes.func,
acceptButtonProps: PropTypes.objectOf(PropTypes.any),
handleCancel: PropTypes.func,
cancelButtonProps: PropTypes.objectOf(PropTypes.any),
handleEntering: PropTypes.func,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
open: PropTypes.bool,
title: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
])
]).isRequired,
subheader: PropTypes.string,
contentProps: PropTypes.object,
handleAccept: PropTypes.func,
acceptButtonProps: PropTypes.object,
handleCancel: PropTypes.func,
cancelButtonProps: PropTypes.object,
handleEntering: PropTypes.func,
children: PropTypes.any
}
DialogConfirmation.propTypes = DialogPropTypes

View File

@ -14,13 +14,12 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
import { makeStyles } from '@material-ui/core'
import { useForm, FormProvider } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers'
import { yupResolver } from '@hookform/resolvers/yup'
import DialogConfirmation, { DialogPropTypes } from 'client/components/Dialogs/DialogConfirmation'
@ -63,6 +62,9 @@ const DialogForm = ({ values, resolver, handleSubmit, dialogProps, children }) =
acceptButtonProps={{
isSubmitting: methods.formState.isSubmitting
}}
cancelButtonProps={{
disabled: methods.formState.isSubmitting
}}
{...dialogProps}
>
<FormProvider {...methods}>
@ -79,7 +81,7 @@ DialogForm.propTypes = {
]),
resolver: PropTypes.func.isRequired,
handleSubmit: PropTypes.func,
dialogProps: DialogPropTypes,
dialogProps: PropTypes.shape(DialogPropTypes),
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React, { useEffect } from 'react'
import { useEffect } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
@ -29,7 +29,7 @@ const useStyles = makeStyles(theme => ({
}
}))
const FloatingActionButton = React.memo(
const FloatingActionButton = memo(
({ icon, className, ...props }) => {
const classes = useStyles()

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import { Box, Link, Typography } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { TextField, Chip } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import {

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import { string } from 'prop-types'
import { Box, makeStyles, Typography } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo, useState, useRef, useEffect } from 'react'
import { memo, useState, useRef, useEffect, ChangeEvent } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
@ -78,7 +78,7 @@ const FileController = memo(
/**
* Handle change to validate the files.
*
* @param {React.ChangeEvent} event - Change event object
* @param {ChangeEvent} event - Change event object
*/
const handleChange = async event => {
try {

View File

@ -14,53 +14,80 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import { Box } from '@material-ui/core'
import AceEditor from 'react-ace'
import { useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import 'ace-builds/src-noconflict/mode-json'
import 'ace-builds/src-noconflict/theme-github'
import loadable from '@loadable/component'
const InputCode = ({ code, language, ...props }) => {
const handleChange = newValue => {
console.log('change', newValue)
}
const Ace = loadable.lib(() => import('react-ace'), { ssr: false })
return (
<Box height="100%" minHeight={200}>
<AceEditor
style={{ border: '1px solid lightgray' }}
wrapEnabled
value={code}
fontSize={16}
mode="json"
theme="github"
width="100%"
height="100%"
// maxLines={Infinity}
minLines={10}
onChange={handleChange}
name="form-control-code"
showPrintMargin={false}
editorProps={{ $blockScrolling: true }}
setOptions={{
useWorker: false,
tabSize: 2
}}
{...props}
/>
</Box>
)
const WrapperToLoadMode = ({ children, mode }) => {
const [loading, setLoading] = useState(true)
useEffect(() => {
const load = async () => {
await import(`ace-builds/src-noconflict/mode-${mode}`)
await import('ace-builds/src-noconflict/theme-github')
setLoading(false)
}
load()
return () => {
// remove all styles when component will be unmounted
document
.querySelectorAll('[id^=ace]')
.forEach(child => child.parentNode.removeChild(child))
}
}, [])
return loading ? null : children
}
const InputCode = ({ code, mode, ...props }) => (
<Ace>
{({ default: Editor }) => (
<WrapperToLoadMode mode={mode}>
<Editor
style={{ border: '1px solid lightgray' }}
wrapEnabled
value={code}
fontSize={16}
mode={mode}
theme="github"
width="100%"
height="100%"
// maxLines={Infinity}
minLines={10}
name="form-control-code"
showPrintMargin={false}
editorProps={{ $blockScrolling: true }}
setOptions={{
useWorker: false,
tabSize: 2
}}
{...props}
/>
</WrapperToLoadMode>
)}
</Ace>
)
InputCode.propTypes = {
code: PropTypes.string,
language: PropTypes.string
mode: PropTypes.oneOf([
'json',
'apache_conf',
'css',
'dockerfile',
'markdown',
'xml'
])
}
InputCode.defaultProps = {
code: '',
language: 'json'
mode: 'json'
}
export default InputCode

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo, useState, useCallback } from 'react'
import { memo, useState, useCallback } from 'react'
import PropTypes from 'prop-types'
import { InputAdornment, IconButton } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { TextField } from '@material-ui/core'
@ -32,7 +32,22 @@ const SelectController = memo(
<TextField
value={optionSelected ?? defaultValue}
onBlur={onBlur}
onChange={onChange}
onChange={
multiple
? event => {
const { options } = event.target
const newValue = []
for (let i = 0, l = options.length; i < l; i += 1) {
if (options[i].selected) {
newValue.push(options[i].value)
}
}
onChange(newValue)
}
: onChange
}
color='secondary'
select
fullWidth
@ -55,6 +70,7 @@ const SelectController = memo(
)}
name={name}
control={control}
multiple={multiple}
/>
)
},

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, TextField, Slider, FormHelperText, Grid } from '@material-ui/core'

View File

@ -13,13 +13,20 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { forwardRef, memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, CircularProgress, Button, IconButton } from '@material-ui/core'
import clsx from 'clsx'
import { Tr } from 'client/components/HOC'
import {
makeStyles,
CircularProgress,
Button,
IconButton,
Tooltip,
Typography
} from '@material-ui/core'
import { Tr, ConditionalWrap } from 'client/components/HOC'
import { T } from 'client/constants'
const useStyles = makeStyles(theme => ({
@ -34,37 +41,60 @@ const useStyles = makeStyles(theme => ({
}
}))
const ButtonComponent = ({ icon, children, ...props }) => icon ? (
<IconButton {...props}>{children}</IconButton>
) : (
<Button type='submit' variant='contained' {...props}>
{children}
</Button>
)
ButtonComponent.propTypes = {
icon: PropTypes.bool,
children: PropTypes.any
}
const SubmitButton = React.memo(
({ isSubmitting, disabled, label, icon, color, size, className, ...props }) => {
const classes = useStyles()
return (
<ButtonComponent
className={clsx(classes.root, className, {
[classes.disabled]: disabled
})}
color={color}
disabled={disabled || isSubmitting}
icon={icon}
const ButtonComponent = forwardRef(
({ icon, endicon, children, size = 'small', ...props }, ref) =>
icon ? (
<IconButton ref={ref} {...props}>{children}</IconButton>
) : (
<Button ref={ref}
type='submit'
endIcon={endicon}
variant='contained'
size={size}
{...props}
>
{isSubmitting && <CircularProgress color='secondary' size={24} />}
{!isSubmitting && (label ?? Tr(T.Submit))}
</ButtonComponent>
{children}
</Button>
)
)
const TooltipComponent = ({ tooltip, tooltipProps, children }) => (
<ConditionalWrap
condition={tooltip && tooltip !== ''}
wrap={wrapperChildren => (
<Tooltip
arrow
placement='bottom'
title={<Typography variant='subtitle2'>{tooltip}</Typography>}
{...tooltipProps}
>{wrapperChildren}</Tooltip>
)}
>
{children}
</ConditionalWrap>
)
const SubmitButton = memo(
({ isSubmitting, disabled, label, icon, className, ...props }) => {
const classes = useStyles()
return (
<TooltipComponent {...props}>
<ButtonComponent
className={clsx(
classes.root,
className,
{ [classes.disabled]: disabled }
)}
disabled={disabled || isSubmitting}
icon={icon}
aria-label={label ?? T.Submit}
{...props}
>
{isSubmitting && <CircularProgress color='secondary' size={24} />}
{!isSubmitting && (icon ?? label ?? Tr(T.Submit))}
</ButtonComponent>
</TooltipComponent>
)
},
(prev, next) =>
@ -74,9 +104,13 @@ const SubmitButton = React.memo(
prev.onClick === next.onClick
)
SubmitButton.propTypes = {
icon: PropTypes.bool,
export const SubmitButtonPropTypes = {
children: PropTypes.any,
icon: PropTypes.node,
endicon: PropTypes.node,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
tooltip: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
tooltipProps: PropTypes.object,
isSubmitting: PropTypes.bool,
disabled: PropTypes.bool,
className: PropTypes.string,
@ -84,13 +118,11 @@ SubmitButton.propTypes = {
size: PropTypes.oneOf(['large', 'medium', 'small'])
}
SubmitButton.defaultProps = {
icon: false,
label: undefined,
isSubmitting: false,
disabled: false,
className: undefined,
color: 'default'
}
TooltipComponent.propTypes = SubmitButtonPropTypes
SubmitButton.propTypes = SubmitButtonPropTypes
ButtonComponent.propTypes = SubmitButtonPropTypes
ButtonComponent.displayName = 'SubmitButtonComponent'
SubmitButton.displayName = 'SubmitButton'
export default SubmitButton

View File

@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { TextField } from '@material-ui/core'
import { Controller } from 'react-hook-form'
import { Tr } from 'client/components/HOC'
import { ErrorHelper } from 'client/components/FormControl'
import { Tr } from 'client/components/HOC'
const TextController = memo(
({ control, cy, type, multiline, name, label, error, fieldProps }) => (
@ -35,7 +35,7 @@ const TextController = memo(
variant='outlined'
margin='dense'
{...(label && { label: Tr(label) })}
inputProps={{ 'data-cy': cy }}
inputProps={{ 'data-cy': cy, ...fieldProps }}
error={Boolean(error)}
helperText={Boolean(error) && <ErrorHelper label={error?.message} />}
FormHelperTextProps={{ 'data-cy': `${cy}-error` }}
@ -48,7 +48,9 @@ const TextController = memo(
/>
),
(prevProps, nextProps) =>
prevProps.error === nextProps.error && prevProps.type === nextProps.type
prevProps.error === nextProps.error &&
prevProps.type === nextProps.type &&
prevProps.label === nextProps.label
)
TextController.propTypes = {
@ -62,7 +64,14 @@ TextController.propTypes = {
PropTypes.bool,
PropTypes.objectOf(PropTypes.any)
]),
fieldProps: PropTypes.object
fieldProps: PropTypes.object,
formContext: PropTypes.shape({
setValue: PropTypes.func,
setError: PropTypes.func,
clearErrors: PropTypes.func,
watch: PropTypes.func,
register: PropTypes.func
})
}
TextController.defaultProps = {

View File

@ -0,0 +1,87 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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 { memo } from 'react'
import PropTypes from 'prop-types'
import { TextField } from '@material-ui/core'
import { Controller } from 'react-hook-form'
import { Tr } from 'client/components/HOC'
import { ErrorHelper } from 'client/components/FormControl'
const TimeController = memo(
({ control, cy, name, type, label, error, fieldProps }) => (
<Controller
render={({ value, ...props }) =>
<TextField
{...props}
fullWidth
color='secondary'
value={value}
variant='outlined'
margin='dense'
{...(label && { label: Tr(label) })}
type={type}
inputProps={{ 'data-cy': cy, ...fieldProps }}
InputLabelProps={{ shrink: true }}
error={Boolean(error)}
helperText={Boolean(error) && <ErrorHelper label={error?.message} />}
FormHelperTextProps={{ 'data-cy': `${cy}-error` }}
/>
}
name={name}
control={control}
/>
),
(prevProps, nextProps) =>
prevProps.error === nextProps.error &&
prevProps.label === nextProps.label
)
TimeController.propTypes = {
control: PropTypes.object,
cy: PropTypes.string,
multiline: PropTypes.bool,
name: PropTypes.string.isRequired,
label: PropTypes.string,
type: PropTypes.string,
error: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.objectOf(PropTypes.any)
]),
fieldProps: PropTypes.object,
formContext: PropTypes.shape({
setValue: PropTypes.func,
setError: PropTypes.func,
clearErrors: PropTypes.func,
watch: PropTypes.func,
register: PropTypes.func
})
}
TimeController.defaultProps = {
control: {},
cy: 'cy',
name: '',
label: '',
type: 'datetime-local',
error: false,
fieldProps: undefined
}
TimeController.displayName = 'TimeController'
export default TimeController

View File

@ -13,28 +13,31 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import TextController from 'client/components/FormControl/TextController'
import AutocompleteController from 'client/components/FormControl/AutocompleteController'
import CheckboxController from 'client/components/FormControl/CheckboxController'
import FileController from 'client/components/FormControl/FileController'
import PasswordController from 'client/components/FormControl/PasswordController'
import SelectController from 'client/components/FormControl/SelectController'
import SliderController from 'client/components/FormControl/SliderController'
import CheckboxController from 'client/components/FormControl/CheckboxController'
import AutocompleteController from 'client/components/FormControl/AutocompleteController'
import FileController from 'client/components/FormControl/FileController'
import TextController from 'client/components/FormControl/TextController'
import TimeController from 'client/components/FormControl/TimeController'
import SubmitButton from 'client/components/FormControl/SubmitButton'
import SubmitButton, { SubmitButtonPropTypes } from 'client/components/FormControl/SubmitButton'
import InputCode from 'client/components/FormControl/InputCode'
import ErrorHelper from 'client/components/FormControl/ErrorHelper'
export {
TextController,
AutocompleteController,
CheckboxController,
FileController,
PasswordController,
SelectController,
SliderController,
CheckboxController,
AutocompleteController,
FileController,
TextController,
TimeController,
SubmitButton,
SubmitButtonPropTypes,
InputCode,
ErrorHelper
}

View File

@ -14,11 +14,10 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import PropTypes from 'prop-types'
import { Button, MobileStepper, Typography, Box } from '@material-ui/core'
import { makeStyles, fade } from '@material-ui/core/styles'
import { makeStyles, alpha } from '@material-ui/core/styles'
import {
NavArrowLeft as PreviousIcon,
NavArrowRight as NextIcon
@ -31,7 +30,7 @@ const useStyles = makeStyles(theme => ({
root: {
position: 'sticky',
top: -15,
background: fade(theme.palette.primary.light, 0.65),
background: alpha(theme.palette.primary.light, 0.65),
zIndex: theme.zIndex.mobileStepper,
margin: theme.spacing(2, 0)
},

View File

@ -14,7 +14,6 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import PropTypes from 'prop-types'
import {
@ -26,7 +25,7 @@ import {
Typography,
StepButton
} from '@material-ui/core'
import { makeStyles, fade } from '@material-ui/core/styles'
import { makeStyles, alpha } from '@material-ui/core/styles'
import { SubmitButton } from 'client/components/FormControl'
import { Tr } from 'client/components/HOC'
@ -37,7 +36,7 @@ const useStyles = makeStyles(theme => ({
position: 'sticky',
top: -15,
minHeight: 100,
background: fade(theme.palette.background.paper, 0.95),
background: alpha(theme.palette.background.paper, 0.95),
zIndex: theme.zIndex.mobileStepper
},
icon: {

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React, { useState, useMemo, useCallback, useEffect } from 'react'
import { useState, useMemo, useCallback, useEffect } from 'react'
import PropTypes from 'prop-types'
import { useFormContext } from 'react-hook-form'

View File

@ -14,11 +14,10 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import { useState } from 'react'
import PropTypes from 'prop-types'
import {
Button,
ClickAwayListener,
Grow,
Paper,
@ -29,27 +28,43 @@ import {
import { NavArrowDown } from 'iconoir-react'
import { useDialog } from 'client/hooks'
import { DialogForm } from 'client/components/Dialogs'
import { FormWithSchema } from 'client/components/Forms'
import { DialogConfirmation, DialogForm, DialogPropTypes } from 'client/components/Dialogs'
import { SubmitButton, SubmitButtonPropTypes } from 'client/components/FormControl'
import FormWithSchema from 'client/components/Forms/FormWithSchema'
import FormStepper from 'client/components/FormStepper'
import { Translate } from 'client/components/HOC'
const ButtonToTriggerForm = ({ buttonProps = {}, title, options = [] }) => {
const ButtonToTriggerForm = ({
buttonProps = {},
isConfirmDialog = false,
dialogProps = {},
options = []
}) => {
const buttonId = buttonProps['data-cy'] ?? 'main-button-form'
const isGroupButton = options.length > 1
const [anchorEl, setAnchorEl] = React.useState(null)
const [anchorEl, setAnchorEl] = useState(null)
const open = Boolean(anchorEl)
const { display, show, hide, values: Form } = useDialog()
const { steps, defaultValues, resolver, fields, onSubmit: handleSubmit } = Form ?? {}
const {
steps,
defaultValues,
resolver,
fields,
onSubmit: handleSubmit
} = Form ?? {}
const handleTriggerSubmit = async formData => {
await handleSubmit?.(formData)
hide()
try {
await handleSubmit?.(formData)
} finally {
hide()
}
}
const openDialogForm = ({ form = {}, onSubmit }) => {
show({ ...form, onSubmit })
const openDialogForm = ({ form = {}, ...rest }) => {
show({ ...form, ...rest })
handleClose()
}
@ -58,29 +73,24 @@ const ButtonToTriggerForm = ({ buttonProps = {}, title, options = [] }) => {
return (
<>
<Button
color='secondary'
size='small'
variant='contained'
aria-describedby={buttonProps.cy ?? 'main-button-form'}
<SubmitButton
aria-describedby={buttonId}
disabled={!options.length}
endIcon={isGroupButton && <NavArrowDown />}
endicon={isGroupButton ? <NavArrowDown /> : undefined}
onClick={evt => !isGroupButton
? openDialogForm(options[0])
: handleToggle(evt)
}
{...buttonProps}
>
<Translate word={title} />
</Button>
/>
{isGroupButton && (
<Popper
open={open}
anchorEl={anchorEl}
id={buttonProps.cy ?? 'main-button-form'}
transition
disablePortal
id={buttonId}
open={open}
transition
>
{({ TransitionProps }) => (
<Grow {...TransitionProps}>
@ -105,26 +115,39 @@ const ButtonToTriggerForm = ({ buttonProps = {}, title, options = [] }) => {
)}
{display && (
<DialogForm
resolver={resolver}
values={defaultValues}
handleSubmit={!steps ? handleTriggerSubmit : undefined}
dialogProps={{ title, handleCancel: hide }}
>
{steps ? (
<FormStepper steps={steps} schema={resolver} onSubmit={handleTriggerSubmit}/>
) : (
<FormWithSchema cy='form-dg' fields={fields} />
)}
</DialogForm>
isConfirmDialog ? (
<DialogConfirmation
handleAccept={handleTriggerSubmit}
handleCancel={hide}
{...dialogProps}
/>
) : (
<DialogForm
resolver={resolver}
values={defaultValues}
handleSubmit={!steps ? handleTriggerSubmit : undefined}
dialogProps={{ handleCancel: hide, ...dialogProps }}
>
{steps ? (
<FormStepper
steps={steps}
schema={resolver}
onSubmit={handleTriggerSubmit}
/>
) : (
<FormWithSchema cy='form-dg' fields={fields} />
)}
</DialogForm>
)
)}
</>
)
}
ButtonToTriggerForm.propTypes = {
buttonProps: PropTypes.object,
title: PropTypes.string,
buttonProps: PropTypes.shape(SubmitButtonPropTypes),
dialogProps: PropTypes.shape(DialogPropTypes),
isConfirmDialog: PropTypes.bool,
options: PropTypes.arrayOf(
PropTypes.shape({
cy: PropTypes.string,
@ -135,6 +158,6 @@ ButtonToTriggerForm.propTypes = {
handleSubmit: PropTypes.func
}
ButtonToTriggerForm.displayName = 'VmStorageTab'
ButtonToTriggerForm.displayName = 'ButtonToTriggerForm'
export default ButtonToTriggerForm

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import { createElement } from 'react'
import PropTypes from 'prop-types'
import { Box, Grid } from '@material-ui/core'
@ -31,11 +31,12 @@ const InputController = {
[INPUT_TYPES.SLIDER]: FC.SliderController,
[INPUT_TYPES.CHECKBOX]: FC.CheckboxController,
[INPUT_TYPES.AUTOCOMPLETE]: FC.AutocompleteController,
[INPUT_TYPES.FILE]: FC.FileController
[INPUT_TYPES.FILE]: FC.FileController,
[INPUT_TYPES.TIME]: FC.TimeController
}
const HiddenInput = ({ isHidden, children }) =>
isHidden ? <Box display="none">{children}</Box> : children
isHidden ? <Box display='none'>{children}</Box> : children
const FormWithSchema = ({ id, cy, fields }) => {
const { control, errors, ...formContext } = useFormContext()
@ -43,37 +44,44 @@ const FormWithSchema = ({ id, cy, fields }) => {
return (
<Grid container spacing={1}>
{fields?.map?.(
({ name, type, htmlType, values, dependOf, grid, ...restOfProps }) => {
({ dependOf, ...props }) => {
let valueOfDependField = null
if (dependOf) {
const nameOfDependField = id
? Array.isArray(dependOf) ? dependOf.map(d => `${id}.${d}`) : `${id}.${dependOf}`
: dependOf
valueOfDependField = useWatch({ control, name: nameOfDependField })
}
const { name, type, htmlType, grid, ...fieldProps } = Object
.entries(props)
.reduce((field, property) => {
const [key, value] = property
const finalValue = typeof value === 'function' ? value(valueOfDependField) : value
return { ...field, [key]: finalValue }
}, {})
const dataCy = `${cy}-${name}`
const inputName = id ? `${id}.${name}` : name
const inputError = get(errors, inputName) ?? false
const dependValue = dependOf
? useWatch({ control, name: id ? `${id}.${dependOf}` : dependOf })
: null
const htmlTypeValue = typeof htmlType === 'function'
? htmlType(dependValue)
: htmlType
const isHidden = htmlTypeValue === INPUT_TYPES.HIDDEN
const isHidden = htmlType === INPUT_TYPES.HIDDEN
return (
InputController[type] && (
<HiddenInput key={`${cy}-${name}`} isHidden={isHidden}>
<Grid item xs={12} md={6} {...grid}>
{React.createElement(InputController[type], {
{createElement(InputController[type], {
control,
cy: dataCy,
error: inputError,
formContext,
name: inputName,
type: htmlTypeValue,
values: typeof values === 'function'
? values(dependValue)
: values,
...restOfProps
type: htmlType,
...fieldProps
})}
</Grid>
</HiddenInput>

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React, { useCallback } from 'react'
import { useCallback } from 'react'
import FormWithSchema from 'client/components/Forms/FormWithSchema'

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React, { useCallback } from 'react'
import { useCallback } from 'react'
import { useListForm } from 'client/hooks'
import { ImagesTable } from 'client/components/Tables'

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React, { useCallback } from 'react'
import { useCallback } from 'react'
import FormWithSchema from 'client/components/Forms/FormWithSchema'

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React, { useCallback } from 'react'
import { useCallback } from 'react'
import FormWithSchema from 'client/components/Forms/FormWithSchema'

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React, { useCallback } from 'react'
import { useCallback } from 'react'
import FormWithSchema from 'client/components/Forms/FormWithSchema'

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React, { useCallback } from 'react'
import { useCallback } from 'react'
import { useListForm } from 'client/hooks'
import { VNetworksTable } from 'client/components/Tables'

View File

@ -0,0 +1,27 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { SCHEMA, FIELDS } from 'client/components/Forms/Vm/CreateDiskSnapshotForm/schema'
const CreateDiskSnapshotForm = ({ snapshot } = {}) => {
return {
resolver: () => SCHEMA,
defaultValues: SCHEMA.cast(snapshot, { stripUnknown: true }),
fields: FIELDS
}
}
export default CreateDiskSnapshotForm

View File

@ -0,0 +1,37 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as yup from 'yup'
import { INPUT_TYPES } from 'client/constants'
import { getValidationFromFields } from 'client/utils'
const NAME = {
name: 'NAME',
label: 'Name',
type: INPUT_TYPES.TEXT,
tooltip: 'Name for the snapshot.',
validation: yup
.string()
.trim()
.notRequired()
.default('')
}
export const FIELDS = [
NAME
]
export const SCHEMA = yup.object(getValidationFromFields(FIELDS))

View File

@ -0,0 +1,186 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as yup from 'yup'
import { INPUT_TYPES, VM_ACTIONS, VM_ACTIONS_WITH_SCHEDULE } from 'client/constants'
import { getValidationFromFields, capitalize, clearString } from 'client/utils'
import { getSnapshotList, getDisks } from 'client/models/VirtualMachine'
const ARGS_TYPES = {
DISK_ID: 'DISK_ID',
NAME: 'NAME',
SNAPSHOT_ID: 'SNAPSHOT_ID'
}
const SCHED_ACTION_OPTIONS = VM_ACTIONS_WITH_SCHEDULE
.map(action => ({
text: capitalize(clearString(action)),
value: action
}))
const ARGS_BY_ACTION = action => {
const { DISK_ID, NAME, SNAPSHOT_ID } = ARGS_TYPES
return {
[VM_ACTIONS.SNAPSHOT_DISK_CREATE]: [DISK_ID, NAME],
[VM_ACTIONS.SNAPSHOT_DISK_REVERT]: [DISK_ID, SNAPSHOT_ID],
[VM_ACTIONS.SNAPSHOT_DISK_DELETE]: [DISK_ID, SNAPSHOT_ID],
[VM_ACTIONS.SNAPSHOT_CREATE]: [NAME],
[VM_ACTIONS.SNAPSHOT_REVERT]: [SNAPSHOT_ID],
[VM_ACTIONS.SNAPSHOT_DELETE]: [SNAPSHOT_ID]
}[action]
}
export const ACTION_FIELD = {
name: 'ACTION',
label: 'Action',
type: INPUT_TYPES.SELECT,
values: SCHED_ACTION_OPTIONS,
validation: yup
.string()
.trim()
.required('Action field is required')
.default(SCHED_ACTION_OPTIONS[0]?.value),
grid: { xs: 12 }
}
export const ARGS_DISK_ID_FIELD = vm => {
const diskOptions = getDisks(vm)
.map(({ DISK_ID, IMAGE }) => ({ text: IMAGE, value: DISK_ID }))
return {
name: ARGS_TYPES.DISK_ID,
label: 'Disk',
type: INPUT_TYPES.SELECT,
dependOf: ACTION_FIELD.name,
htmlType: action => ARGS_BY_ACTION(action)?.includes(ARGS_TYPES.DISK_ID)
? undefined
: INPUT_TYPES.HIDDEN,
values: diskOptions,
validation: yup
.string()
.trim()
.when(
ACTION_FIELD.name,
(action, schema) => ARGS_BY_ACTION(action)?.includes(ARGS_TYPES.DISK_ID)
? schema
: schema.strip()
)
.notRequired()
.default(() => diskOptions[0]?.value)
}
}
export const ARGS_NAME_FIELD = () => ({
name: ARGS_TYPES.NAME,
label: 'Snapshot name',
type: INPUT_TYPES.TEXT,
dependOf: ACTION_FIELD.name,
htmlType: action => ARGS_BY_ACTION(action)?.includes(ARGS_TYPES.NAME)
? undefined
: INPUT_TYPES.HIDDEN,
validation: yup
.string()
.trim()
.when(
ACTION_FIELD.name,
(action, schema) => ARGS_BY_ACTION(action)?.includes(ARGS_TYPES.NAME)
? schema
: schema.strip()
)
.notRequired()
.default(undefined)
})
export const ARGS_SNAPSHOT_ID_FIELD = vm => {
const snapshotOptions = getSnapshotList(vm)
.map(({ SNAPSHOT_ID, NAME }) => ({ text: NAME, value: SNAPSHOT_ID }))
return {
name: ARGS_TYPES.SNAPSHOT_ID,
label: 'Snapshot',
type: INPUT_TYPES.SELECT,
dependOf: ACTION_FIELD.name,
htmlType: action => ARGS_BY_ACTION(action)?.includes(ARGS_TYPES.SNAPSHOT_ID)
? undefined
: INPUT_TYPES.HIDDEN,
values: snapshotOptions,
validation: yup
.string()
.trim()
.when(
ACTION_FIELD.name,
(action, schema) => ARGS_BY_ACTION(action)?.includes(ARGS_TYPES.SNAPSHOT_ID)
? schema
: schema.strip()
)
.notRequired()
.default(() => snapshotOptions[0]?.value)
}
}
export const COMMON_FIELDS = vm => [
ACTION_FIELD,
ARGS_DISK_ID_FIELD,
ARGS_NAME_FIELD,
ARGS_SNAPSHOT_ID_FIELD
].map(field => typeof field === 'function' ? field(vm) : field)
export const COMMON_SCHEMA = vm => yup
.object(getValidationFromFields(COMMON_FIELDS(vm)))
.transform(value => {
const {
ARGS,
[ACTION_FIELD.name]: ACTION,
[ARGS_TYPES.NAME]: NAME,
[ARGS_TYPES.SNAPSHOT_ID]: SNAPSHOT_ID,
[ARGS_TYPES.DISK_ID]: DISK_ID,
...rest
} = value
let argsValues = {}
if (ARGS) {
// IMPORTANT - String data from ARGS has strict order: DISK_ID,NAME,SNAPSHOT_ID
const splittedArgs = ARGS.split(',')
argsValues = {
[VM_ACTIONS.SNAPSHOT_DISK_CREATE]:
{ DISK_ID: splittedArgs[0], NAME: splittedArgs[1] },
[VM_ACTIONS.SNAPSHOT_DISK_REVERT]:
{ DISK_ID: splittedArgs[0], SNAPSHOT_ID: splittedArgs[1] },
[VM_ACTIONS.SNAPSHOT_DISK_DELETE]:
{ DISK_ID: splittedArgs[0], SNAPSHOT_ID: splittedArgs[1] },
[VM_ACTIONS.SNAPSHOT_CREATE]:
{ NAME: splittedArgs[0] },
[VM_ACTIONS.SNAPSHOT_REVERT]:
{ SNAPSHOT_ID: splittedArgs[0] },
[VM_ACTIONS.SNAPSHOT_DELETE]:
{ SNAPSHOT_ID: splittedArgs[0] }
}[ACTION]
} else {
// Transform to send form data
const argsAsString = Object.entries({ DISK_ID, NAME, SNAPSHOT_ID })
.map(([name, val]) => ARGS_BY_ACTION(ACTION)?.includes(name) && val)
.filter(Boolean)
.join(',')
argsValues = { ARGS: argsAsString }
}
return { ...rest, ACTION, ...argsValues }
})

View File

@ -0,0 +1,27 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { SCHEMA, FIELDS } from 'client/components/Forms/Vm/CreateSchedActionForm/PunctualForm/schema'
const PunctualForm = ({ vm, schedule } = {}) => ({
resolver: () => SCHEMA(vm),
defaultValues: schedule
? SCHEMA(vm).cast(schedule, { stripUnknown: true })
: SCHEMA(vm).default(),
fields: FIELDS(vm)
})
export default PunctualForm

View File

@ -0,0 +1,254 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as yup from 'yup'
import { DateTime } from 'luxon'
import { T, INPUT_TYPES } from 'client/constants'
import { getValidationFromFields, capitalize } from 'client/utils'
import { timeFromMilliseconds } from 'client/models/Helper'
import { COMMON_FIELDS, COMMON_SCHEMA } from 'client/components/Forms/Vm/CreateSchedActionForm/CommonSchema'
const ISO_FORMAT = "yyyy-MM-dd'T'HH:mm"
const ISO_REG = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/
const MONTH_DAYS_REG = /^(3[01]|[12][0-9]|[1-9])(,(3[01]|[12][0-9]|[1-9]))*$/
const YEAR_DAYS_REG = /^(36[0-5]|3[0-5]\d|[12]\d{2}|[0-9]\d?)(,(36[0-5]|3[0-5]\d|[12]\d{2}|[1-9]\d?))*$/
const HOURS_REG = /^(16[0-8]|1[01][0-9]|[1-9]?[0-9])$/
const REPEAT_VALUES = {
WEEKLY: '0',
MONTHLY: '1',
YEARLY: '2',
HOURLY: '3'
}
const END_TYPE_VALUES = {
NEVER: '0',
REPETITION: '1',
DATE: '2'
}
const REPEAT_OPTIONS = Object.entries(REPEAT_VALUES)
.map(([text, value]) => ({ text: capitalize(text), value }))
const END_TYPE_OPTIONS = Object.entries(END_TYPE_VALUES)
.map(([text, value]) => ({ text: capitalize(text), value }))
const isoDateValidation = nameInput => yup
.string()
.trim()
.default(() => DateTime.local().toFormat(ISO_FORMAT))
.matches(
ISO_REG,
{ message: `${nameInput} should be a date with ISO format: yyyy-MM-ddTHH:mm` }
)
.transform((value, originalValue) => {
if (value.length < 10 || (isNaN(value) && value.match(ISO_REG) === null)) {
return value
}
const newValue = isNaN(originalValue)
? DateTime.fromISO(originalValue)
: timeFromMilliseconds(originalValue)
return newValue.isValid ? newValue.toFormat(ISO_FORMAT) : originalValue
})
const PERIODIC_FIELD = {
name: 'PERIODIC',
label: 'Periodic',
type: INPUT_TYPES.CHECKBOX,
validation: yup
.boolean()
.default(false),
grid: { md: 12 }
}
const TIME_FIELD = {
name: 'TIME',
label: 'Time',
type: INPUT_TYPES.TIME,
validation: yup
.string()
.required('Time field is required')
.concat(isoDateValidation('Time'))
}
const REPEAT_FIELD = {
name: 'REPEAT',
label: 'Periodicity',
type: INPUT_TYPES.SELECT,
dependOf: PERIODIC_FIELD.name,
htmlType: isPeriodic => !isPeriodic ? INPUT_TYPES.HIDDEN : undefined,
values: REPEAT_OPTIONS,
validation: yup
.string()
.trim()
.when(
PERIODIC_FIELD.name,
(isPeriodic, schema) => isPeriodic ? schema : schema.strip()
)
.notRequired()
.default(REPEAT_OPTIONS[0].value)
}
const DAYS_FIELD = {
name: 'DAYS',
dependOf: [PERIODIC_FIELD.name, REPEAT_FIELD.name],
multiple: (dependValues = {}) => {
const { [REPEAT_FIELD.name]: repeat } = dependValues
return REPEAT_VALUES.WEEKLY === repeat
},
type: (dependValues = {}) => {
const { [REPEAT_FIELD.name]: repeat } = dependValues
return REPEAT_VALUES.WEEKLY === repeat ? INPUT_TYPES.SELECT : INPUT_TYPES.TEXT
},
label: (dependValues = {}) => {
const { [REPEAT_FIELD.name]: repeat } = dependValues
return {
[REPEAT_VALUES.WEEKLY]: 'Days of week',
[REPEAT_VALUES.MONTHLY]: 'Days of month',
[REPEAT_VALUES.YEARLY]: 'Days of year',
[REPEAT_VALUES.HOURLY]: "Each 'x' hours"
}[repeat]
},
values: [
{ text: T.Sunday, value: '0' },
{ text: T.Monday, value: '1' },
{ text: T.Tuesday, value: '2' },
{ text: T.Wednesday, value: '3' },
{ text: T.Thursday, value: '4' },
{ text: T.Friday, value: '5' },
{ text: T.Saturday, value: '6' }
],
htmlType: (dependValues = {}) => {
const { [PERIODIC_FIELD.name]: periodic, [REPEAT_FIELD.name]: repeat } = dependValues
if (!periodic) return INPUT_TYPES.HIDDEN
return REPEAT_VALUES.HOURLY === repeat ? 'number' : undefined
},
validation: yup
.string()
.default(undefined)
.when(
REPEAT_FIELD.name,
(repeatType, schema) => ({
[REPEAT_VALUES.WEEKLY]: schema
.transform(value => Array.isArray(value) ? value.join(',') : value)
.required('Days field is required: between 0 (Sunday) and 6 (Saturday)'),
[REPEAT_VALUES.MONTHLY]: schema
.trim()
.matches(MONTH_DAYS_REG, { message: 'Days should be between 1 and 31' })
.required('Days field is required: between 1 and 31'),
[REPEAT_VALUES.YEARLY]: schema
.trim()
.matches(YEAR_DAYS_REG, { message: 'Days should be between 0 and 365' })
.required('Days field is required: between 0 and 365'),
[REPEAT_VALUES.HOURLY]: schema
.trim()
.matches(HOURS_REG, { message: 'Hours should be between 0 and 168' })
.required('Hours field is required: between 0 and 168')
}[repeatType])
),
fieldProps: { min: 0, max: 168, step: 1 }
}
const END_TYPE_FIELD = {
name: 'END_TYPE',
label: 'End type',
type: INPUT_TYPES.SELECT,
dependOf: PERIODIC_FIELD.name,
htmlType: isPeriodic => !isPeriodic ? INPUT_TYPES.HIDDEN : undefined,
values: END_TYPE_OPTIONS,
validation: yup
.string()
.trim()
.when(
PERIODIC_FIELD.name,
(isPeriodic, schema) => isPeriodic ? schema : schema.strip()
)
.notRequired()
.default(END_TYPE_OPTIONS[0].value)
}
const END_VALUE_FIELD = {
name: 'END_VALUE',
label: 'When you want that the action finishes',
dependOf: [PERIODIC_FIELD.name, END_TYPE_FIELD.name],
type: (dependValues = {}) => {
const { [END_TYPE_FIELD.name]: endType } = dependValues
return endType === END_TYPE_VALUES.DATE ? INPUT_TYPES.TIME : INPUT_TYPES.TEXT
},
htmlType: (dependValues = {}) => {
const { [PERIODIC_FIELD.name]: isPeriodic, [END_TYPE_FIELD.name]: endType } = dependValues
if (!isPeriodic) return INPUT_TYPES.HIDDEN
return {
[END_TYPE_VALUES.NEVER]: INPUT_TYPES.HIDDEN,
[END_TYPE_VALUES.REPETITION]: 'number',
[END_TYPE_VALUES.DATE]: 'datetime-local'
}[endType]
},
validation: yup
.string()
.trim()
.default(undefined)
.when(
PERIODIC_FIELD.name,
(isPeriodic, schema) => isPeriodic ? schema : schema.strip()
)
.when(
END_TYPE_FIELD.name,
(endType, schema) => ({
[END_TYPE_VALUES.NEVER]: schema.strip(),
[END_TYPE_VALUES.REPETITION]: schema
.required('Number of repetitions is required'),
[END_TYPE_VALUES.DATE]: schema
.concat(isoDateValidation('Date'))
.required('Date to finish the action is required')
}[endType])
)
}
export const FIELDS = vm => [
...COMMON_FIELDS(vm),
TIME_FIELD,
PERIODIC_FIELD,
REPEAT_FIELD,
DAYS_FIELD,
END_TYPE_FIELD,
END_VALUE_FIELD
]
export const SCHEMA = vm => yup
.object(getValidationFromFields(FIELDS(vm)))
.concat(COMMON_SCHEMA(vm))
.transform(value => {
const { [DAYS_FIELD.name]: DAYS, [REPEAT_FIELD.name]: REPEAT, ...rest } = value
return {
...rest,
[DAYS_FIELD.name]: DAYS,
[REPEAT_FIELD.name]: REPEAT,
[PERIODIC_FIELD.name]: !!(DAYS || REPEAT)
}
})

View File

@ -0,0 +1,27 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { SCHEMA, FIELDS } from 'client/components/Forms/Vm/CreateSchedActionForm/RelativeForm/schema'
const RelativeForm = ({ vm, schedule } = {}) => ({
resolver: () => SCHEMA(vm),
defaultValues: schedule
? SCHEMA(vm).cast(schedule, { stripUnknown: true })
: SCHEMA(vm).default(),
fields: FIELDS(vm)
})
export default RelativeForm

View File

@ -0,0 +1,97 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as yup from 'yup'
import { INPUT_TYPES } from 'client/constants'
import { getValidationFromFields, capitalize } from 'client/utils'
import { COMMON_FIELDS, COMMON_SCHEMA } from 'client/components/Forms/Vm/CreateSchedActionForm/CommonSchema'
const PERIOD_TYPES = {
YEARS: 'years',
MONTHS: 'months',
WEEKS: 'weeks',
DAYS: 'days',
HOURS: 'hours',
MINUTES: 'minutes'
}
const PERIOD_OPTIONS = Object.entries(PERIOD_TYPES)
.map(([text, value]) => ({ text: capitalize(text), value }))
const TIME_FIELD = {
name: 'TIME',
label: 'Time after the VM is instantiated',
type: INPUT_TYPES.TEXT,
htmlType: 'number',
validation: yup
.number()
.typeError('Time value must be a number')
.required('Time field is required')
.positive()
.default(undefined)
}
const PERIOD_FIELD = {
name: 'PERIOD',
label: 'Period type',
type: INPUT_TYPES.SELECT,
values: PERIOD_OPTIONS,
validation: yup
.string()
.trim()
.notRequired()
.default(PERIOD_OPTIONS[0].value)
}
export const FIELDS = vm => [
...COMMON_FIELDS(vm),
TIME_FIELD,
PERIOD_FIELD
]
export const SCHEMA = vm => yup
.object(getValidationFromFields(FIELDS(vm)))
.concat(COMMON_SCHEMA(vm))
.transform(value => {
const { [PERIOD_FIELD.name]: PERIOD, [TIME_FIELD.name]: TIME, ...rest } = value
if (String(TIME).includes('+')) {
const allPeriods = {
[PERIOD_TYPES.YEARS]: TIME / 365 / 24 / 3600,
[PERIOD_TYPES.MONTHS]: TIME / 30 / 24 / 3600,
[PERIOD_TYPES.WEEKS]: TIME / 7 / 24 / 3600,
[PERIOD_TYPES.DAYS]: TIME / 24 / 3600,
[PERIOD_TYPES.HOURS]: TIME / 3600,
[PERIOD_TYPES.MINUTES]: TIME / 60
}
const [period, time] = Object.entries(allPeriods).find(([_, time]) => time >= 1)
return { ...rest, [PERIOD_FIELD.name]: period, [TIME_FIELD.name]: time }
}
const timeInMilliseconds = {
[PERIOD_TYPES.YEARS]: TIME * 365 * 24 * 3600,
[PERIOD_TYPES.MONTHS]: TIME * 30 * 24 * 3600,
[PERIOD_TYPES.WEEKS]: TIME * 7 * 24 * 3600,
[PERIOD_TYPES.DAYS]: TIME * 24 * 3600,
[PERIOD_TYPES.HOURS]: TIME * 3600,
[PERIOD_TYPES.MINUTES]: TIME * 60
}[PERIOD]
return { ...rest, [TIME_FIELD.name]: timeInMilliseconds }
})

View File

@ -0,0 +1,22 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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 PunctualForm from 'client/components/Forms/Vm/CreateSchedActionForm/PunctualForm'
import RelativeForm from 'client/components/Forms/Vm/CreateSchedActionForm/RelativeForm'
export {
PunctualForm,
RelativeForm
}

View File

@ -0,0 +1,25 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { SCHEMA, FIELDS } from 'client/components/Forms/Vm/CreateSnapshotForm/schema'
const CreateSnapshotForm = () => ({
resolver: () => SCHEMA,
defaultValues: SCHEMA.default(),
fields: FIELDS
})
export default CreateSnapshotForm

View File

@ -0,0 +1,37 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as yup from 'yup'
import { INPUT_TYPES } from 'client/constants'
import { getValidationFromFields } from 'client/utils'
const NAME = {
name: 'NAME',
label: 'Snapshot name',
type: INPUT_TYPES.TEXT,
tooltip: 'The new snapshot name. It can be empty.',
validation: yup
.string()
.trim()
.notRequired()
.default('')
}
export const FIELDS = [
NAME
]
export const SCHEMA = yup.object(getValidationFromFields(FIELDS))

View File

@ -14,7 +14,6 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import PropTypes from 'prop-types'
import { SCHEMA, FIELDS } from 'client/components/Forms/Vm/ResizeCapacityForm/schema'
const ResizeCapacityForm = ({ vm } = {}) => {
@ -27,10 +26,4 @@ const ResizeCapacityForm = ({ vm } = {}) => {
}
}
ResizeCapacityForm.propTypes = {
vm: PropTypes.object
}
ResizeCapacityForm.displayName = 'ResizeCapacityForm'
export default ResizeCapacityForm

View File

@ -0,0 +1,25 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { SCHEMA, FIELDS } from 'client/components/Forms/Vm/ResizeDiskForm/schema'
const ResizeDiskForm = ({ disk } = {}) => ({
resolver: () => SCHEMA,
defaultValues: SCHEMA.cast(disk, { stripUnknown: true }),
fields: FIELDS
})
export default ResizeDiskForm

View File

@ -0,0 +1,39 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as yup from 'yup'
import { INPUT_TYPES } from 'client/constants'
import { getValidationFromFields } from 'client/utils'
const SIZE = {
name: 'SIZE',
label: 'New size',
type: INPUT_TYPES.TEXT,
htmlType: 'number',
tooltip: 'The new size string',
validation: yup
.number()
.typeError('Size value must be a number')
.required('Size field is required')
.positive()
.default(undefined)
}
export const FIELDS = [
SIZE
]
export const SCHEMA = yup.object(getValidationFromFields(FIELDS))

View File

@ -0,0 +1,25 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { SCHEMA, FIELDS } from 'client/components/Forms/Vm/SaveAsDiskForm/schema'
const SaveAsDiskForm = () => ({
resolver: () => SCHEMA,
defaultValues: SCHEMA.default(),
fields: FIELDS
})
export default SaveAsDiskForm

View File

@ -0,0 +1,37 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as yup from 'yup'
import { INPUT_TYPES } from 'client/constants'
import { getValidationFromFields } from 'client/utils'
const NAME = {
name: 'NAME',
label: 'New Image name',
type: INPUT_TYPES.TEXT,
tooltip: 'Name for the new Image where the disk will be saved.',
validation: yup
.string()
.trim()
.required('Name field is required')
.default('')
}
export const FIELDS = [
NAME
]
export const SCHEMA = yup.object(getValidationFromFields(FIELDS))

View File

@ -13,11 +13,20 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import ResizeCapacityForm from 'client/components/Forms/Vm/ResizeCapacityForm'
import AttachNicForm from 'client/components/Forms/Vm/AttachNicForm'
import CreateDiskSnapshotForm from 'client/components/Forms/Vm/CreateDiskSnapshotForm'
import CreateSnapshotForm from 'client/components/Forms/Vm/CreateSnapshotForm'
import ResizeCapacityForm from 'client/components/Forms/Vm/ResizeCapacityForm'
import ResizeDiskForm from 'client/components/Forms/Vm/ResizeDiskForm'
import SaveAsDiskForm from 'client/components/Forms/Vm/SaveAsDiskForm'
export * from 'client/components/Forms/Vm/AttachDiskForm'
export * from 'client/components/Forms/Vm/CreateSchedActionForm'
export {
AttachNicForm,
ResizeCapacityForm
CreateDiskSnapshotForm,
CreateSnapshotForm,
ResizeCapacityForm,
ResizeDiskForm,
SaveAsDiskForm
}

View File

@ -13,6 +13,10 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm'
import FormWithSchema from 'client/components/Forms/FormWithSchema'
export { FormWithSchema }
export {
ButtonToTriggerForm,
FormWithSchema
}

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import { useRef } from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
@ -28,7 +28,7 @@ import internalStyles from 'client/components/HOC/InternalLayout/styles'
const InternalLayout = ({ children }) => {
const classes = internalStyles()
const container = React.useRef()
const container = useRef()
const { isFixMenu } = useGeneral()
return (

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React, { useContext, useState, useEffect, createContext } from 'react'
import { useContext, useState, useEffect, createContext } from 'react'
import PropTypes from 'prop-types'
import root from 'window-or-global'
import { sprintf } from 'sprintf-js'

View File

@ -14,8 +14,6 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import { Button } from '@material-ui/core'
import { Group as GroupIcon, VerifiedBadge as SelectIcon } from 'iconoir-react'
@ -33,10 +31,6 @@ const Group = () => {
const { user, groups, filterPool } = useAuth()
const { changeGroup } = useAuthApi()
const handleChangeGroup = group => {
group && changeGroup({ id: user.ID, group })
}
const renderResult = ({ ID, NAME }, handleClose) => {
const isSelected =
(filterPool === ALL_RESOURCES && ALL_RESOURCES === ID) ||
@ -44,11 +38,11 @@ const Group = () => {
return (
<Button
key={`term-${ID}`}
key={`switcher-group-${ID}`}
fullWidth
className={classes.groupButton}
onClick={() => {
handleChangeGroup(ID)
ID && changeGroup({ id: user.ID, group: ID })
handleClose()
}}
>
@ -73,10 +67,10 @@ const Group = () => {
return (
<HeaderPopover
id="group-list"
id='group-list'
icon={<GroupIcon />}
buttonProps={{ 'data-cy': 'header-group-button' }}
headerTitle="Switch group"
headerTitle='Switch group'
>
{({ handleClose }) => (
<Search

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import { memo, useState } from 'react'
import PropTypes from 'prop-types'
import {
@ -31,7 +31,7 @@ import clsx from 'clsx'
import { Tr } from 'client/components/HOC'
import headerStyles from 'client/components/Header/styles'
const HeaderPopover = React.memo(({
const HeaderPopover = memo(({
id,
icon,
buttonLabel,
@ -44,7 +44,7 @@ const HeaderPopover = React.memo(({
const classes = headerStyles()
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'))
const [anchorEl, setAnchorEl] = React.useState(null)
const [anchorEl, setAnchorEl] = useState(null)
const handleOpen = event => setAnchorEl(event.currentTarget)
const handleClose = () => setAnchorEl(null)

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { memo } from 'react'
import { MenuItem, MenuList, Link } from '@material-ui/core'
import { ProfileCircled as UserIcon } from 'iconoir-react'
@ -25,7 +25,7 @@ import { Tr } from 'client/components/HOC'
import { isDevelopment } from 'client/utils'
import { T, APPS, APP_URL } from 'client/constants'
const User = React.memo(() => {
const User = memo(() => {
const { user } = useAuth()
const { logout } = useAuthApi()
@ -45,7 +45,11 @@ const User = React.memo(() => {
{isDevelopment() &&
APPS?.map(appName => (
<MenuItem key={appName}>
<Link color='secondary' href={`${APP_URL}/${appName}`} style={{ width: '100%' }}>
<Link
color='secondary'
href={`${APP_URL}/${appName}`}
style={{ width: '100%' }}
>
<DevTypography label={appName} />
</Link>
</MenuItem>

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import { useMemo } from 'react'
import { Button } from '@material-ui/core'
import { ViewGrid as ViewIcon, VerifiedBadge as SelectIcon } from 'iconoir-react'
@ -30,26 +30,22 @@ const View = () => {
const { view, views = {} } = useAuth()
const { changeView } = useAuthApi()
const handleChangeView = newView => {
newView && newView !== view && changeView(newView)
}
const renderResult = (viewName, handleClose) => (
const renderResult = (newView, handleClose) => (
<Button
key={`view-${viewName}`}
key={`view-${newView}`}
fullWidth
className={classes.groupButton}
onClick={() => {
handleChangeView(viewName)
newView && newView !== view && changeView(newView)
handleClose()
}}
>
{viewName}
{viewName === view && <SelectIcon size='1em' />}
{newView}
{newView === view && <SelectIcon size='1em' />}
</Button>
)
const viewNames = React.useMemo(() => Object.keys(views), [view])
const viewNames = useMemo(() => Object.keys(views), [view])
return (
<HeaderPopover

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import { MenuItem, MenuList } from '@material-ui/core'
import { Language as ZoneIcon } from 'iconoir-react'

View File

@ -14,7 +14,6 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import PropTypes from 'prop-types'
import {

View File

@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { memo } from 'react'
import { number, string, oneOfType } from 'prop-types'
const DockerLogo = React.memo(({ viewBox, width, height, color, ...props }) => {
const DockerLogo = memo(({ viewBox, width, height, color, ...props }) => {
return (
<svg viewBox={viewBox} width={width} height={height} {...props} {...props}>
<path

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import { number, string, bool, oneOfType } from 'prop-types'
import { useTheme } from '@material-ui/core'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { useState, memo } from 'react'
import { useState, memo, JSXElementConstructor } from 'react'
import PropTypes from 'prop-types'
import { DEFAULT_IMAGE, IMAGE_FORMATS } from 'client/constants'
@ -27,7 +27,7 @@ const INITIAL_STATE = { fail: false, retries: 0 }
* @param {string} props.src - Image source
* @param {boolean} props.withSources - Add image formats to picture: webp, png and jpg
* @param {string} props.imgProps - Properties to image element
* @returns {React.JSXElementConstructor} Picture with all images format
* @returns {JSXElementConstructor} Picture with all images format
*/
const Image = memo(({ src, imageInError, withSources, imgProps }) => {
const [error, setError] = useState(INITIAL_STATE)

View File

@ -14,7 +14,6 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React from 'react'
import PropTypes from 'prop-types'
import { CSSTransition, TransitionGroup } from 'react-transition-group'
@ -74,7 +73,7 @@ const ListCards = ({
return (
<CSSTransition
// use key to render transition (default: id or ID)
// use key to render transition (default: id or ID)
key={`card-${key.replace(/\s/g, '')}`}
classNames={classes.item}
timeout={400}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import React, { memo } from 'react'
import { memo } from 'react'
import PropTypes from 'prop-types'
import { Box, Typography, InputBase } from '@material-ui/core'
@ -44,7 +44,7 @@ const ListHeader = memo(({
<Box className={classes.root}>
<Box className={classes.title}>
{!!(hasReloadButton || reloadButtonProps) && (
<SubmitButton icon label={<RefreshIcon />} {...reloadButtonProps} />
<SubmitButton icon={<RefreshIcon />} {...reloadButtonProps} />
)}
{title && (
<Typography variant='h5' className={classes.titleText}>
@ -54,7 +54,7 @@ const ListHeader = memo(({
</Box>
<Box className={classes.actions}>
{!!(hasAddButton || addButtonProps) && (
<SubmitButton color='secondary' icon label={<AddIcon />} {...addButtonProps} />
<SubmitButton color='secondary' icon={<AddIcon />} {...addButtonProps} />
)}
{!!(hasSearch || searchProps) && (
<Box className={classes.search}>

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { makeStyles, fade } from '@material-ui/core'
import { makeStyles, alpha } from '@material-ui/core'
export default makeStyles(theme => ({
root: {
@ -54,9 +54,9 @@ export default makeStyles(theme => ({
search: {
position: 'relative',
borderRadius: theme.shape.borderRadius,
backgroundColor: fade(theme.palette.primary.dark, 0.15),
backgroundColor: alpha(theme.palette.primary.dark, 0.15),
'&:hover': {
backgroundColor: fade(theme.palette.primary.dark, 0.25)
backgroundColor: alpha(theme.palette.primary.dark, 0.25)
},
margin: theme.spacing(1, 0),
width: '100%',

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React, { useRef, useEffect, useCallback, createRef } from 'react'
import { useRef, useEffect, useCallback, createRef } from 'react'
import PropTypes from 'prop-types'
import { debounce, LinearProgress } from '@material-ui/core'

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import * as React from 'react'
import { useRef, useCallback, useEffect } from 'react'
import PropTypes from 'prop-types'
import { useVirtual } from 'react-virtual'
@ -59,7 +59,7 @@ const ListVirtualized = ({
const classes = useStyles()
// OBSERVER
const loaderRef = React.useRef()
const loaderRef = useRef()
const { isNearScreen } = useNearScreen({
distance: '100px',
externalRef: isLoading ? null : loaderRef,
@ -67,18 +67,18 @@ const ListVirtualized = ({
})
// VIRTUALIZER
const parentRef = React.useRef()
const parentRef = useRef()
const rowVirtualizer = useVirtual({
size: data.length,
parentRef,
overscan: 20,
estimateSize: React.useCallback(() => 40, []),
estimateSize: useCallback(() => 40, []),
keyExtractor: index => data[index]?.id
})
const debounceHandleNextPage = React.useCallback(debounce(fetchMore, 200), [])
const debounceHandleNextPage = useCallback(debounce(fetchMore, 200), [])
React.useEffect(() => {
useEffect(() => {
if (isNearScreen && !canFetchMore) debounceHandleNextPage()
}, [isNearScreen, canFetchMore, debounceHandleNextPage])

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { JSXElementConstructor } from 'react'
import { makeStyles, Box } from '@material-ui/core'
import { OpenNebulaLogo } from 'client/components/Icons'
@ -34,7 +34,7 @@ const useStyles = makeStyles(theme => ({
/**
* Component with OpenNebula logo as spinner in full width and height.
*
* @returns {React.JSXElementConstructor} Container with logo inside
* @returns {JSXElementConstructor} Container with logo inside
*/
const LoadingScreen = () => {
const classes = useStyles()

View File

@ -14,7 +14,7 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import React, { useEffect } from 'react'
import { useEffect } from 'react'
import PropTypes from 'prop-types'
import { useSnackbar } from 'notistack'

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
/**
@ -25,9 +25,9 @@ import PropTypes from 'prop-types'
* @returns {string} Returns a count number
*/
const NumberEasing = ({ value = '0', speed = 200 }) => {
const [count, setCount] = React.useState('0')
const [count, setCount] = useState('0')
React.useEffect(() => {
useEffect(() => {
let start = 0
const end = parseInt(String(value).substring(0, 3))

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { JSXElementConstructor } from 'react'
import PropTypes from 'prop-types'
import { Redirect, Route } from 'react-router-dom'
@ -23,7 +23,7 @@ import { useAuth } from 'client/features/Auth'
* Public route.
*
* @param {object} props - Route props
* @param {React.JSXElementConstructor} props.redirectWhenAuth
* @param {JSXElementConstructor} props.redirectWhenAuth
* - Route to redirect in case of user is authenticated
* @returns {Redirect|Route}
* - If current user is authenticated, then redirect to private route

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import * as React from 'react'
import { useEffect } from 'react'
import { Redirect, Route } from 'react-router-dom'
import { useAuth, useAuthApi } from 'client/features/Auth'
@ -29,7 +29,7 @@ const ProtectedRoute = props => {
const { isLogged, jwt } = useAuth()
const { getAuthUser } = useAuthApi()
React.useEffect(() => {
useEffect(() => {
jwt && getAuthUser()
}, [])

Some files were not shown because too many files have changed in this diff Show More