1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-08 21:17:43 +03:00

F #5422: Reformat logic for apps in beta (#1492)

This commit is contained in:
Sergio Betanzos 2021-09-28 18:47:27 +02:00 committed by GitHub
parent 4083ed1d9b
commit 5313df52c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 221 additions and 135 deletions

View File

@ -19,17 +19,17 @@ import Router from 'client/router'
import { ENDPOINTS, PATH } from 'client/apps/provision/routes'
import { ENDPOINTS as DEV_ENDPOINTS } from 'client/router/dev'
import { useGeneralApi } from 'client/features/General'
import { useGeneral, useGeneralApi } from 'client/features/General'
import { useAuth, useAuthApi } from 'client/features/Auth'
import { useProvisionTemplate, useProvisionApi } from 'client/features/One'
import Sidebar from 'client/components/Sidebar'
import Notifier from 'client/components/Notifier'
import LoadingScreen from 'client/components/LoadingScreen'
import { _APPS } from 'client/constants'
import { isDevelopment } from 'client/utils'
import { _APPS } from 'client/constants'
const APP_NAME = _APPS.provision.name
export const APP_NAME = _APPS.provision.name
/**
* Provision App component.
@ -42,13 +42,18 @@ const ProvisionApp = () => {
const provisionTemplate = useProvisionTemplate()
const { getProvisionsTemplates } = useProvisionApi()
const { appTitle } = useGeneral()
const { changeAppTitle } = useGeneralApi()
useEffect(() => {
appTitle !== APP_NAME && changeAppTitle(APP_NAME)
}, [])
useEffect(() => {
(async () => {
try {
if (jwt) {
changeAppTitle(APP_NAME)
getAuthUser()
!providerConfig && await getProviderConfig()
!provisionTemplate?.length && await getProvisionsTemplates()

View File

@ -25,11 +25,9 @@ import MuiProvider from 'client/providers/muiProvider'
import NotistackProvider from 'client/providers/notistackProvider'
import { TranslateProvider } from 'client/components/HOC'
import App from 'client/apps/provision/_app'
import App, { APP_NAME as ProvisionAppName } from 'client/apps/provision/_app'
import theme from 'client/apps/provision/theme'
import { _APPS, APP_URL } from 'client/constants'
const APP_NAME = _APPS.provision.name
import { APP_URL } from 'client/constants'
/**
* @param {object} props - Props
@ -51,7 +49,7 @@ const Provision = ({ store = {}, location = '', context = {} }) => (
</StaticRouter>
) : (
// browser build
<BrowserRouter basename={`${APP_URL}/${APP_NAME}`}>
<BrowserRouter basename={`${APP_URL}/${ProvisionAppName}`}>
<App />
</BrowserRouter>
)}
@ -70,4 +68,6 @@ Provision.propTypes = {
Provision.displayName = 'ProvisionApp'
export { ProvisionAppName }
export default Provision

View File

@ -14,23 +14,22 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { useEffect, useMemo, JSXElementConstructor } from 'react'
import { Chip } from '@material-ui/core'
import Router from 'client/router'
import { ENDPOINTS, PATH, getEndpointsByView } from 'client/apps/sunstone/routes'
import { ENDPOINTS as ONE_ENDPOINTS } from 'client/apps/sunstone/routesOne'
import { ENDPOINTS as DEV_ENDPOINTS } from 'client/router/dev'
import { useGeneralApi } from 'client/features/General'
import { useGeneral, useGeneralApi } from 'client/features/General'
import { useAuth, useAuthApi } from 'client/features/Auth'
import Sidebar from 'client/components/Sidebar'
import Notifier from 'client/components/Notifier'
import LoadingScreen from 'client/components/LoadingScreen'
import { _APPS } from 'client/constants'
import { isDevelopment } from 'client/utils'
import { _APPS } from 'client/constants'
const APP_NAME = _APPS.sunstone.name
export const APP_NAME = _APPS.sunstone.name
/**
* Sunstone App component.
@ -38,23 +37,23 @@ const APP_NAME = _APPS.sunstone.name
* @returns {JSXElementConstructor} App rendered.
*/
const SunstoneApp = () => {
const { isLogged, jwt, firstRender, view, views } = useAuth()
const { isLogged, jwt, firstRender, view, views, config } = useAuth()
const { getAuthUser, logout, getSunstoneViews, getSunstoneConfig } = useAuthApi()
const { appTitle } = useGeneral()
const { changeAppTitle } = useGeneralApi()
useEffect(() => {
appTitle !== APP_NAME && changeAppTitle(APP_NAME)
}, [])
useEffect(() => {
(async () => {
try {
if (jwt) {
changeAppTitle(
<>
{APP_NAME}
<Chip size='small' label='BETA' color='primary' />
</>
)
getAuthUser()
await getSunstoneViews()
await getSunstoneConfig()
!view && await getSunstoneViews()
!config && await getSunstoneConfig()
}
} catch {
logout()

View File

@ -24,11 +24,9 @@ import MuiProvider from 'client/providers/muiProvider'
import NotistackProvider from 'client/providers/notistackProvider'
import { TranslateProvider } from 'client/components/HOC'
import App from 'client/apps/sunstone/_app'
import App, { APP_NAME as SunstoneAppName } from 'client/apps/sunstone/_app'
import theme from 'client/apps/sunstone/theme'
import { _APPS, APP_URL } from 'client/constants'
const APP_NAME = _APPS.sunstone.name
import { APP_URL } from 'client/constants'
/**
* @param {object} props - Props
@ -49,7 +47,7 @@ const Sunstone = ({ store = {}, location = '', context = {} }) => (
</StaticRouter>
) : (
// browser build
<BrowserRouter basename={`${APP_URL}/${APP_NAME}`}>
<BrowserRouter basename={`${APP_URL}/${SunstoneAppName}`}>
<App />
</BrowserRouter>
)}
@ -67,4 +65,6 @@ Sunstone.propTypes = {
Sunstone.displayName = 'SunstoneApp'
export { SunstoneAppName }
export default Sunstone

View File

@ -16,7 +16,7 @@
/* eslint-disable jsdoc/require-jsdoc */
import PropTypes from 'prop-types'
import { AppBar, Box, Toolbar, Typography, IconButton, useMediaQuery } from '@material-ui/core'
import { AppBar, Box, Toolbar, Typography, IconButton, Chip, useMediaQuery } from '@material-ui/core'
import { Menu as MenuIcon } from 'iconoir-react'
import { useAuth } from 'client/features/Auth'
@ -30,7 +30,7 @@ import headerStyles from 'client/components/Header/styles'
const Header = () => {
const { isOneAdmin } = useAuth()
const { appTitle, title } = useGeneral()
const { appTitle, title, isBeta } = useGeneral()
const { fixMenu } = useGeneralApi()
const isUpLg = useMediaQuery(theme => theme.breakpoints.up('lg'))
@ -58,7 +58,10 @@ const Header = () => {
data-cy='header-app-title'
>
{'One'}
<span className={classes.app}>{appTitle}</span>
<span className={classes.app}>
{appTitle}
{isBeta && <Chip size='small' label='BETA' color='primary' />}
</span>
</Typography>
)}
<Typography

View File

@ -17,10 +17,12 @@ import { memo } from 'react'
import { number, string, bool, oneOfType } from 'prop-types'
import { useTheme } from '@material-ui/core'
import { useGeneral } from 'client/features/General'
import { SCHEMES } from 'client/constants'
const OpenNebulaLogo = memo(
({ width, height, spinner, withText, withBeta, viewBox, ...props }) => {
({ width, height, spinner, withText, viewBox, disabledBetaText, ...props }) => {
const { isBeta } = useGeneral()
const { palette: { type } } = useTheme()
const isDarkMode = type === SCHEMES.DARK
@ -142,7 +144,7 @@ const OpenNebulaLogo = memo(
/>
</g>
)}
{withBeta && (
{!disabledBetaText && isBeta && (
<g id="beta">
<path
fill={textColor.beta}
@ -186,7 +188,7 @@ OpenNebulaLogo.propTypes = {
viewBox: string,
spinner: bool,
withText: bool,
withBeta: bool
disabledBetaText: bool
}
OpenNebulaLogo.defaultProps = {
@ -195,7 +197,7 @@ OpenNebulaLogo.defaultProps = {
viewBox: '0 0 120 45',
spinner: false,
withText: false,
withBeta: false
disabledBetaText: false
}
OpenNebulaLogo.displayName = 'OpenNebulaLogo'

View File

@ -46,7 +46,6 @@ const LoadingScreen = () => {
height={360}
spinner
withText
withBeta
/>
</Box>
)

View File

@ -76,6 +76,7 @@ const Sidebar = ({ endpoints }) => {
height={50}
withText
className={classes.svg}
disabledBetaText
/>
<IconButton onClick={handleSwapMenu}>
{isUpLg ? <MenuIcon /> : <CloseIcon />}

View File

@ -65,7 +65,6 @@ const ActionItem = memo(({ item, selectedRows }) => {
label,
color = 'secondary',
icon: Icon,
dialogProps: { title, children, ...dialogProps } = {},
options,
action,
disabled
@ -85,16 +84,21 @@ const ActionItem = memo(({ item, selectedRows }) => {
) : (
<ButtonToTriggerForm
buttonProps={buttonProps}
options={options?.map(({ form, onSubmit, ...option }) => ({
dialogProps: {
...dialogProps,
title: typeof title === 'function' ? title(selectedRows) : title,
children: typeof children === 'function' ? children(selectedRows) : children
},
form: form ? () => form(selectedRows) : undefined,
onSubmit: data => onSubmit(data, selectedRows),
...option
}))}
options={options?.map(option => {
const { form, onSubmit, dialogProps } = option ?? {}
const { title, children } = dialogProps ?? {}
return {
...option,
dialogProps: {
...dialogProps,
title: typeof title === 'function' ? title(selectedRows) : title,
children: typeof children === 'function' ? children(selectedRows) : children
},
form: form ? () => form(selectedRows) : undefined,
onSubmit: data => onSubmit(data, selectedRows)
}
})}
/>
)
}, (prev, next) => prev.selectedRows?.length === next.selectedRows?.length)
@ -115,14 +119,14 @@ export const ActionPropTypes = PropTypes.shape({
]),
action: PropTypes.func,
isConfirmDialog: PropTypes.bool,
dialogProps: PropTypes.shape(DialogPropTypes),
options: PropTypes.arrayOf(
PropTypes.shape({
cy: PropTypes.string,
name: PropTypes.string,
icon: PropTypes.any,
form: PropTypes.func,
onSubmit: PropTypes.func
onSubmit: PropTypes.func,
dialogProps: PropTypes.shape(DialogPropTypes)
})
)
})

View File

@ -29,11 +29,30 @@ import {
import { useAuth } from 'client/features/Auth'
import { useVmTemplateApi } from 'client/features/One'
import { Tr, Translate } from 'client/components/HOC'
import { CloneForm } from 'client/components/Forms/VmTemplate'
import { createActions } from 'client/components/Tables/Enhanced/Utils'
import { PATH } from 'client/apps/sunstone/routesOne'
import { VM_TEMPLATE_ACTIONS, MARKETPLACE_APP_ACTIONS } from 'client/constants'
import { T, VM_TEMPLATE_ACTIONS, MARKETPLACE_APP_ACTIONS } from 'client/constants'
const MessageToConfirmAction = rows => {
const names = rows?.map?.(({ original }) => original?.NAME)
return (
<>
<p>
<Translate word={T.VMTemplates} />
{`: ${names.join(', ')}`}
</p>
<p>
<Translate word={T.DoYouWantProceed} />
</p>
</>
)
}
MessageToConfirmAction.displayName = 'MessageToConfirmAction'
const Actions = () => {
const history = useHistory()
@ -52,7 +71,7 @@ const Actions = () => {
actions: [
{
accessor: VM_TEMPLATE_ACTIONS.REFRESH,
tooltip: 'Refresh',
tooltip: Tr(T.Refresh),
icon: RefreshDouble,
action: async () => {
await getVmTemplates()
@ -60,7 +79,7 @@ const Actions = () => {
},
{
accessor: VM_TEMPLATE_ACTIONS.CREATE_DIALOG,
tooltip: 'Create',
tooltip: Tr(T.Create),
icon: AddSquare,
disabled: true,
action: rows => {
@ -73,7 +92,7 @@ const Actions = () => {
},
{
accessor: VM_TEMPLATE_ACTIONS.IMPORT_DIALOG,
tooltip: 'Import',
tooltip: Tr(T.Import),
icon: Import,
selected: { max: 1 },
disabled: true,
@ -83,8 +102,7 @@ const Actions = () => {
},
{
accessor: VM_TEMPLATE_ACTIONS.INSTANTIATE_DIALOG,
label: 'Instantiate',
tooltip: 'Instantiate',
tooltip: Tr(T.Instantiate),
icon: PlayOutline,
selected: { max: 1 },
action: rows => {
@ -96,8 +114,8 @@ const Actions = () => {
},
{
accessor: VM_TEMPLATE_ACTIONS.UPDATE_DIALOG,
label: 'Update',
tooltip: 'Update',
label: Tr(T.Update),
tooltip: Tr(T.Update),
selected: { max: 1 },
disabled: true,
action: rows => {
@ -109,8 +127,8 @@ const Actions = () => {
},
{
accessor: VM_TEMPLATE_ACTIONS.CLONE,
label: 'Clone',
tooltip: 'Clone',
label: Tr(T.Clone),
tooltip: Tr(T.Clone),
selected: true,
options: [{
dialogProps: {
@ -119,7 +137,7 @@ const Actions = () => {
const { ID, NAME } = rows?.[0]?.original
return [
isMultiple ? 'Clone several Templates' : 'Clone Template',
Tr(isMultiple ? T.CloneSeveralTemplates : T.CloneTemplate),
!isMultiple && `#${ID} ${NAME}`
].filter(Boolean).join(' - ')
}
@ -150,49 +168,46 @@ const Actions = () => {
}]
},
{
tooltip: 'Change ownership',
tooltip: Tr(T.Ownership),
icon: Group,
selected: true,
options: [{
cy: `action.${VM_TEMPLATE_ACTIONS.CHANGE_OWNER}`,
name: 'Change owner',
name: T.ChangeOwner,
disabled: true,
isConfirmDialog: true,
onSubmit: () => undefined
}, {
cy: `action.${VM_TEMPLATE_ACTIONS.CHANGE_GROUP}`,
name: 'Change group',
name: T.ChangeGroup,
disabled: true,
isConfirmDialog: true,
onSubmit: () => undefined
}, {
cy: `action.${VM_TEMPLATE_ACTIONS.SHARE}`,
disabled: true,
name: 'Share',
name: T.Share,
isConfirmDialog: true,
onSubmit: () => undefined
}, {
cy: `action.${VM_TEMPLATE_ACTIONS.UNSHARE}`,
disabled: true,
name: 'Unshare',
name: T.Unshare,
isConfirmDialog: true,
onSubmit: () => undefined
}]
},
{
tooltip: 'Lock/Unlock',
tooltip: `${Tr(T.Lock)}/${Tr(T.Unlock)}`,
icon: Lock,
selected: true,
options: [{
cy: `action.${VM_TEMPLATE_ACTIONS.LOCK}`,
name: 'Lock',
name: T.Lock,
isConfirmDialog: true,
dialogProps: {
title: 'Lock',
children: rows => {
const templates = rows?.map?.(({ original }) => original?.NAME)
return 'Templates: ' + templates.join(', ')
}
title: T.Lock,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
@ -201,14 +216,11 @@ const Actions = () => {
}
}, {
cy: `action.${VM_TEMPLATE_ACTIONS.UNLOCK}`,
name: 'Unlock',
name: T.Unlock,
isConfirmDialog: true,
dialogProps: {
title: 'Unlock',
children: rows => {
const templates = rows?.map?.(({ original }) => original?.NAME)
return 'Templates: ' + templates.join(', ')
}
title: T.Unlock,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
@ -219,17 +231,14 @@ const Actions = () => {
},
{
accessor: VM_TEMPLATE_ACTIONS.DELETE,
tooltip: 'Delete',
tooltip: T.Delete,
icon: Trash,
selected: true,
options: [{
isConfirmDialog: true,
dialogProps: {
title: 'Delete',
children: rows => {
const templates = rows?.map?.(({ original }) => original?.NAME)
return 'Templates: ' + templates.join(', ')
}
title: T.Delete,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
@ -246,7 +255,7 @@ const Actions = () => {
actions: [
{
accessor: MARKETPLACE_APP_ACTIONS.CREATE_DIALOG,
tooltip: 'Create Marketplace App',
tooltip: T.CreateMarketApp,
icon: Cart,
selected: { max: 1 },
disabled: true,

View File

@ -31,13 +31,31 @@ import {
import { useAuth } from 'client/features/Auth'
import { useVmApi } from 'client/features/One'
import { Tr } from 'client/components/HOC'
import { Tr, Translate } from 'client/components/HOC'
// import { } from 'client/components/Forms/Vm'
import { createActions } from 'client/components/Tables/Enhanced/Utils'
import { PATH } from 'client/apps/sunstone/routesOne'
import { T, VM_ACTIONS, MARKETPLACE_APP_ACTIONS } from 'client/constants'
const MessageToConfirmAction = rows => {
const names = rows?.map?.(({ original }) => original?.NAME)
return (
<>
<p>
<Translate word={T.VMs} />
{`: ${names.join(', ')}`}
</p>
<p>
<Translate word={T.DoYouWantProceed} />
</p>
</>
)
}
MessageToConfirmAction.displayName = 'MessageToConfirmAction'
const Actions = () => {
const history = useHistory()
const { view, getResourceView } = useAuth()
@ -68,7 +86,7 @@ const Actions = () => {
actions: [
{
accessor: VM_ACTIONS.REFRESH,
tooltip: T.Refresh,
tooltip: Tr(T.Refresh),
icon: RefreshDouble,
action: async () => {
await getVms({ state: -1 })
@ -76,7 +94,7 @@ const Actions = () => {
},
{
accessor: VM_ACTIONS.CREATE_DIALOG,
tooltip: T.Create,
tooltip: Tr(T.Create),
icon: AddSquare,
action: () => {
const path = PATH.TEMPLATE.VMS.INSTANTIATE
@ -86,7 +104,7 @@ const Actions = () => {
},
{
accessor: VM_ACTIONS.RESUME,
tooltip: T.Resume,
tooltip: Tr(T.Resume),
selected: true,
icon: PlayOutline,
action: async rows => {
@ -97,20 +115,24 @@ const Actions = () => {
},
{
accessor: VM_ACTIONS.SAVE_AS_TEMPLATE,
tooltip: T.SaveAsTemplate,
tooltip: Tr(T.SaveAsTemplate),
selected: { max: 1 },
disabled: true,
icon: SaveFloppyDisk,
action: () => {}
},
{
tooltip: T.Manage,
tooltip: Tr(T.Manage),
icon: SystemShut,
selected: true,
options: [{
cy: `action.${VM_ACTIONS.SUSPEND}`,
name: T.Suspend,
isConfirmDialog: true,
dialogProps: {
title: T.Suspend,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map(id => suspend(id)))
@ -120,6 +142,10 @@ const Actions = () => {
cy: `action.${VM_ACTIONS.STOP}`,
name: T.Stop,
isConfirmDialog: true,
dialogProps: {
title: T.Stop,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map(id => stop(id)))
@ -129,6 +155,10 @@ const Actions = () => {
cy: `action.${VM_ACTIONS.POWEROFF}`,
name: T.Poweroff,
isConfirmDialog: true,
dialogProps: {
title: T.Poweroff,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map(id => poweroff(id)))
@ -138,6 +168,10 @@ const Actions = () => {
cy: `action.${VM_ACTIONS.POWEROFF_HARD}`,
name: T.PoweroffHard,
isConfirmDialog: true,
dialogProps: {
title: T.PoweroffHard,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map(id => poweroffHard(id)))
@ -147,6 +181,10 @@ const Actions = () => {
cy: `action.${VM_ACTIONS.REBOOT}`,
name: T.Reboot,
isConfirmDialog: true,
dialogProps: {
title: T.Reboot,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map(id => reboot(id)))
@ -156,6 +194,10 @@ const Actions = () => {
cy: `action.${VM_ACTIONS.REBOOT_HARD}`,
name: T.RebootHard,
isConfirmDialog: true,
dialogProps: {
title: T.RebootHard,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map(id => rebootHard(id)))
@ -165,6 +207,10 @@ const Actions = () => {
cy: `action.${VM_ACTIONS.UNDEPLOY}`,
name: T.Undeploy,
isConfirmDialog: true,
dialogProps: {
title: T.Undeploy,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map(id => undeploy(id)))
@ -174,6 +220,10 @@ const Actions = () => {
cy: `action.${VM_ACTIONS.UNDEPLOY_HARD}`,
name: T.UndeployHard,
isConfirmDialog: true,
dialogProps: {
title: T.UndeployHard,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map(id => undeployHard(id)))
@ -182,7 +232,7 @@ const Actions = () => {
}]
},
{
tooltip: 'Hosting',
tooltip: Tr(T.Host),
icon: TransitionRight,
selected: true,
options: [{
@ -207,6 +257,10 @@ const Actions = () => {
cy: `action.${VM_ACTIONS.HOLD}`,
name: T.Hold,
isConfirmDialog: true,
dialogProps: {
title: T.Hold,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map(id => hold(id)))
@ -216,6 +270,10 @@ const Actions = () => {
cy: `action.${VM_ACTIONS.RELEASE}`,
name: T.Release,
isConfirmDialog: true,
dialogProps: {
title: T.Release,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map(id => release(id)))
@ -225,6 +283,10 @@ const Actions = () => {
cy: `action.${VM_ACTIONS.RESCHED}`,
name: T.Reschedule,
isConfirmDialog: true,
dialogProps: {
title: T.Reschedule,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map(id => resched(id)))
@ -234,6 +296,10 @@ const Actions = () => {
cy: `action.${VM_ACTIONS.UNRESCHED}`,
name: T.UnReschedule,
isConfirmDialog: true,
dialogProps: {
title: T.UnReschedule,
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
await Promise.all(ids.map(id => unresched(id)))
@ -248,7 +314,7 @@ const Actions = () => {
}]
},
{
tooltip: 'Change ownership',
tooltip: Tr(T.Ownership),
icon: Group,
selected: true,
options: [{
@ -275,10 +341,7 @@ const Actions = () => {
isConfirmDialog: true,
dialogProps: {
title: T.Lock,
children: rows => {
const templates = rows?.map?.(({ original }) => original?.NAME)
return 'VMs: ' + templates.join(', ')
}
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
@ -291,10 +354,7 @@ const Actions = () => {
isConfirmDialog: true,
dialogProps: {
title: T.Unlock,
children: rows => {
const templates = rows?.map?.(({ original }) => original?.NAME)
return 'VMs: ' + templates.join(', ')
}
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
@ -304,7 +364,7 @@ const Actions = () => {
}]
},
{
tooltip: T.Terminate,
tooltip: Tr(T.Terminate),
icon: Trash,
selected: true,
options: [{
@ -313,10 +373,7 @@ const Actions = () => {
isConfirmDialog: true,
dialogProps: {
title: T.Terminate,
children: rows => {
const templates = rows?.map?.(({ original }) => original?.NAME)
return 'VMs: ' + templates.join(', ')
}
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
@ -329,10 +386,7 @@ const Actions = () => {
isConfirmDialog: true,
dialogProps: {
title: T.TerminateHard,
children: rows => {
const templates = rows?.map?.(({ original }) => original?.NAME)
return 'VMs: ' + templates.join(', ')
}
children: MessageToConfirmAction
},
onSubmit: async (_, rows) => {
const ids = rows?.map?.(({ original }) => original?.ID)
@ -349,7 +403,7 @@ const Actions = () => {
actions: [
{
accessor: MARKETPLACE_APP_ACTIONS.CREATE_DIALOG,
tooltip: 'Create Marketplace App',
tooltip: Tr(T.CreateMarketApp),
icon: Cart,
selected: { max: 1 },
disabled: true,

View File

@ -119,7 +119,7 @@ const StorageItem = ({ disk, actions = [] }) => {
)}
</div>
)}
{SNAPSHOTS && (
{SNAPSHOTS?.length > 0 && (
<div style={{ flexBasis: '100%' }}>
{SNAPSHOTS?.map(snapshot => (
<StorageSubItem

View File

@ -26,6 +26,7 @@ export const BY = {
export const TIME_HIDE_LOGO = 1500
export const _APPS = defaultApps
export const APPS = Object.keys(defaultApps)
export const APPS_IN_BETA = [_APPS.sunstone.name]
export const APP_URL = defaultAppName ? `/${defaultAppName}` : ''
export const WEBSOCKET_URL = `${APP_URL}/websockets`
export const STATIC_FILES_URL = `${APP_URL}/client/assets`

View File

@ -31,16 +31,19 @@ module.exports = {
Attach: 'Attach',
AttachDisk: 'Attach disk',
AttachImage: 'Attach image disk',
AttachVolatile: 'Attach volatile disk',
AttachNic: 'Attach NIC',
AttachVolatile: 'Attach volatile disk',
BackToList: 'Back to %s list',
Cancel: 'Cancel',
Change: 'Change',
ChangeGroup: 'Change group',
ChangeOwner: 'Change owner',
Clone: 'Clone',
CloneSeveralTemplates: 'Clone several Templates',
CloneTemplate: 'Clone Template',
Configuration: 'Configuration',
Create: 'Create',
CreateMarketApp: 'Create Marketplace App',
Delete: 'Delete',
DeleteScheduledAction: 'Delete scheduled action: %s',
DeleteSomething: 'Delete: %s',
@ -52,6 +55,7 @@ module.exports = {
EditSomething: 'Edit: %s',
Finish: 'Finish',
Hold: 'Hold',
Import: 'Import',
Info: 'Info',
Instantiate: 'Instantiate',
Lock: 'Lock',
@ -69,13 +73,11 @@ module.exports = {
RenameSomething: 'Rename: %s',
Reschedule: 'Reschedule',
Resize: 'Resize',
ResizeSomething: 'Resize: %s',
ResizeCapacity: 'Resize capacity',
ResizeSomething: 'Resize: %s',
Resume: 'Resume',
Revert: 'Revert',
RevertSomething: 'Revert: %s',
Terminate: 'Terminate',
TerminateHard: 'Terminate hard',
Save: 'Save',
SaveAs: 'Save as',
SaveAsImage: 'Save as Image',
@ -85,6 +87,7 @@ module.exports = {
SelectGroup: 'Select a group',
SelectRequest: 'Select request',
SelectVmTemplate: 'Select a VM Template',
Share: 'Share',
Show: 'Show',
ShowAll: 'Show all',
SignIn: 'Sign In',
@ -94,12 +97,15 @@ module.exports = {
Suspend: 'Suspend',
Take: 'Take',
TakeSnapshot: 'Take snapshot',
TakeSnapshotSomething: 'Take snapshot: %s',
TakeSnapshotOf: 'Take snapshot: %s',
TakeSnapshotSomething: 'Take snapshot: %s',
Terminate: 'Terminate',
TerminateHard: 'Terminate hard',
Undeploy: 'Undeploy',
UndeployHard: 'Undeploy hard',
Unlock: 'Unlock',
UnReschedule: 'Un-Reschedule',
Unshare: 'Unshare',
Update: 'Update',
UpdateScheduledAction: 'Update scheduled action: %s',

View File

@ -83,7 +83,6 @@ function Login () {
height={100}
width='100%'
withText
withBeta
/>
), [])}
<Box className={classes.wrapperForm}>

View File

@ -17,6 +17,7 @@ import { JSXElementConstructor } from 'react'
import SunstoneApp from 'client/apps/sunstone'
import ProvisionApp from 'client/apps/provision'
import LoadingScreen from 'client/components/LoadingScreen'
import { isDevelopment, isBackend } from 'client/utils'
import { _APPS, APPS } from 'client/constants'
@ -31,21 +32,16 @@ const DevelopmentApp = props => {
let appName = ''
if (isDevelopment() && !isBackend()) {
const parseUrl = window.location.pathname
appName = window.location.pathname
.split(/\//gi)
.filter(sub => sub?.length > 0)
parseUrl.forEach(resource => {
if (resource && APPS.includes(resource)) {
appName = resource
}
})
.find(resource => APPS.includes(resource))
}
return {
[_APPS.provision.name]: <ProvisionApp {...props} />,
[_APPS.sunstone.name]: <SunstoneApp {...props} />
}[appName]
}[appName] ?? <LoadingScreen />
}
DevelopmentApp.displayName = 'DevelopmentApp'

View File

@ -18,6 +18,6 @@ import { render } from 'react-dom'
import { createStore } from 'client/store'
import App from 'client/dev/_app'
const { store } = createStore({ initState: window.REDUX_DATA })
const { store } = createStore({ initState: window.__PRELOADED_STATE__ })
render(<App store={store} />, document.getElementById('root'))

View File

@ -14,14 +14,14 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useDispatch, useSelector } from 'react-redux'
import { useDispatch, useSelector, shallowEqual } from 'react-redux'
import * as actions from 'client/features/General/actions'
import { name } from 'client/features/General/slice'
import { name as generalSlice } from 'client/features/General/slice'
import { generateKey } from 'client/utils'
export const useGeneral = () => (
useSelector(state => state[name])
useSelector(state => state[generalSlice], shallowEqual)
)
export const useGeneralApi = () => {

View File

@ -18,11 +18,13 @@ import { createSlice } from '@reduxjs/toolkit'
import { logout } from 'client/features/Auth/actions'
import * as actions from 'client/features/General/actions'
import { generateKey } from 'client/utils'
import { APPS_IN_BETA } from 'client/constants'
const initial = {
zone: 0,
title: null,
appTitle: null,
isBeta: false,
isLoading: false,
isFixMenu: false,
@ -45,7 +47,9 @@ const { name, reducer } = createSlice({
return { ...state, title: payload }
})
.addCase(actions.changeAppTitle, (state, { payload }) => {
return { ...state, appTitle: payload }
const isBeta = APPS_IN_BETA?.includes(String(payload).toLowerCase())
return { ...state, appTitle: payload, isBeta }
})
.addCase(actions.changeZone, (state, { payload }) => {
return { ...state, zone: payload }

View File

@ -18,7 +18,7 @@ import { hydrate, render } from 'react-dom'
import { createStore } from 'client/store'
import App from 'client/apps/provision'
const { store } = createStore({ initState: window.REDUX_DATA })
const { store } = createStore({ initState: window.__PRELOADED_STATE__ })
const rootHTML = document.getElementById('root')?.innerHTML
const renderMethod = rootHTML !== '' ? hydrate : render

View File

@ -13,14 +13,18 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import { configureStore, getDefaultMiddleware, EnhancedStore } from '@reduxjs/toolkit'
import thunkMiddleware from 'redux-thunk'
import rootReducer from 'client/store/reducers'
import { isDevelopment } from 'client/utils'
/**
* @param {object} props - Props
* @param {object} props.initState - Initial state
* @param {*} props.services - Services
* @returns {{ store: EnhancedStore }} Configured Redux Store
*/
export const createStore = ({ initState = {}, services }) => {
const middleware = getDefaultMiddleware({
immutableCheck: true,

View File

@ -18,7 +18,7 @@ import { hydrate, render } from 'react-dom'
import { createStore } from 'client/store'
import App from 'client/apps/sunstone'
const { store } = createStore({ initState: window.REDUX_DATA })
const { store } = createStore({ initState: window.__PRELOADED_STATE__ })
const rootHTML = document.getElementById('root')?.innerHTML
const renderMethod = rootHTML !== '' ? hydrate : render